2021-05-03 20:00:12 +00:00
|
|
|
import {
|
|
|
|
ActionGroup,
|
2021-05-27 07:01:55 +00:00
|
|
|
AlertVariant,
|
2021-05-03 20:00:12 +00:00
|
|
|
Button,
|
|
|
|
Checkbox,
|
|
|
|
FormGroup,
|
|
|
|
PageSection,
|
|
|
|
Switch,
|
|
|
|
TextInput,
|
|
|
|
} from "@patternfly/react-core";
|
2021-08-26 08:39:35 +00:00
|
|
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
2021-09-02 20:14:43 +00:00
|
|
|
import React, { useState } from "react";
|
2021-07-21 09:30:18 +00:00
|
|
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import { useAlerts } from "../components/alert/Alerts";
|
2021-05-03 20:00:12 +00:00
|
|
|
import { FormAccess } from "../components/form-access/FormAccess";
|
|
|
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
|
|
|
import { FormPanel } from "../components/scroll-form/FormPanel";
|
2021-05-27 07:01:55 +00:00
|
|
|
import { useAdminClient } from "../context/auth/AdminClient";
|
|
|
|
import { useRealm } from "../context/realm-context/RealmContext";
|
2021-07-21 09:30:18 +00:00
|
|
|
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
|
|
|
import { emailRegexPattern } from "../util";
|
2021-06-10 20:00:14 +00:00
|
|
|
import { AddUserEmailModal } from "./AddUserEmailModal";
|
2022-02-16 11:39:08 +00:00
|
|
|
import "./realm-settings-section.css";
|
2021-05-03 20:00:12 +00:00
|
|
|
|
|
|
|
type RealmSettingsEmailTabProps = {
|
2021-05-27 07:01:55 +00:00
|
|
|
realm: RealmRepresentation;
|
2021-05-03 20:00:12 +00:00
|
|
|
};
|
|
|
|
|
2021-10-14 21:08:42 +00:00
|
|
|
export type EmailRegistrationCallback = (registered: boolean) => void;
|
|
|
|
|
2021-05-03 20:00:12 +00:00
|
|
|
export const RealmSettingsEmailTab = ({
|
2021-05-27 07:01:55 +00:00
|
|
|
realm: initialRealm,
|
2021-05-03 20:00:12 +00:00
|
|
|
}: RealmSettingsEmailTabProps) => {
|
|
|
|
const { t } = useTranslation("realm-settings");
|
2021-05-27 07:01:55 +00:00
|
|
|
const adminClient = useAdminClient();
|
|
|
|
const { realm: realmName } = useRealm();
|
2021-07-28 12:01:42 +00:00
|
|
|
const { addAlert, addError } = useAlerts();
|
2021-07-21 09:30:18 +00:00
|
|
|
const { whoAmI } = useWhoAmI();
|
2021-05-27 07:01:55 +00:00
|
|
|
|
|
|
|
const [realm, setRealm] = useState(initialRealm);
|
2021-10-14 21:08:42 +00:00
|
|
|
const [callback, setCallback] = useState<EmailRegistrationCallback>();
|
2021-05-27 07:01:55 +00:00
|
|
|
const {
|
|
|
|
register,
|
|
|
|
control,
|
|
|
|
handleSubmit,
|
|
|
|
errors,
|
2021-06-22 14:17:45 +00:00
|
|
|
watch,
|
2021-05-27 07:01:55 +00:00
|
|
|
reset: resetForm,
|
2021-06-21 14:46:05 +00:00
|
|
|
getValues,
|
2021-09-02 20:14:43 +00:00
|
|
|
} = useForm<RealmRepresentation>({ defaultValues: realm });
|
2021-05-27 07:01:55 +00:00
|
|
|
|
2021-10-14 21:08:42 +00:00
|
|
|
const reset = () => resetForm(realm);
|
2021-06-22 14:17:45 +00:00
|
|
|
const watchFromValue = watch("smtpServer.from", "");
|
|
|
|
const watchHostValue = watch("smtpServer.host", "");
|
2021-06-10 20:00:14 +00:00
|
|
|
|
2021-06-22 15:19:13 +00:00
|
|
|
const authenticationEnabled = useWatch({
|
|
|
|
control,
|
|
|
|
name: "smtpServer.authentication",
|
2021-10-06 20:54:33 +00:00
|
|
|
defaultValue: "",
|
2021-06-22 15:19:13 +00:00
|
|
|
});
|
|
|
|
|
2021-05-27 07:01:55 +00:00
|
|
|
const save = async (form: RealmRepresentation) => {
|
|
|
|
try {
|
2021-10-14 21:08:42 +00:00
|
|
|
const registered = await registerEmailIfNeeded();
|
|
|
|
|
|
|
|
if (!registered) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-05-27 07:01:55 +00:00
|
|
|
const savedRealm = { ...realm, ...form };
|
|
|
|
await adminClient.realms.update({ realm: realmName }, savedRealm);
|
|
|
|
setRealm(savedRealm);
|
|
|
|
addAlert(t("saveSuccess"), AlertVariant.success);
|
|
|
|
} catch (error) {
|
2021-07-28 12:01:42 +00:00
|
|
|
addError("realm-settings:saveError", error);
|
2021-05-27 07:01:55 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-10 20:00:14 +00:00
|
|
|
const testConnection = async () => {
|
2021-10-06 20:54:33 +00:00
|
|
|
const serverSettings = { ...getValues()["smtpServer"] };
|
|
|
|
|
|
|
|
// Code below uses defensive coding as the server configuration uses an ambiguous record type.
|
|
|
|
if (typeof serverSettings.port === "string") {
|
|
|
|
serverSettings.port = Number(serverSettings.port);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof serverSettings.ssl === "string") {
|
|
|
|
serverSettings.ssl = serverSettings.ssl === true.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof serverSettings.starttls === "string") {
|
|
|
|
serverSettings.starttls = serverSettings.starttls === true.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
// For some reason the API wants a duplicate field for the authentication status.
|
|
|
|
// Somebody thought this was a good idea, so here we are.
|
|
|
|
if (serverSettings.authentication === true.toString()) {
|
|
|
|
serverSettings.auth = true;
|
|
|
|
}
|
|
|
|
|
2021-06-28 06:02:35 +00:00
|
|
|
try {
|
2021-10-14 21:08:42 +00:00
|
|
|
const registered = await registerEmailIfNeeded();
|
|
|
|
|
|
|
|
if (!registered) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-28 06:02:35 +00:00
|
|
|
await adminClient.realms.testSMTPConnection(
|
|
|
|
{ realm: realm.realm! },
|
2021-10-06 20:54:33 +00:00
|
|
|
serverSettings
|
2021-06-28 06:02:35 +00:00
|
|
|
);
|
|
|
|
addAlert(t("testConnectionSuccess"), AlertVariant.success);
|
|
|
|
} catch (error) {
|
2021-07-28 12:01:42 +00:00
|
|
|
addError("realm-settings:testConnectionError", error);
|
2021-06-28 06:02:35 +00:00
|
|
|
}
|
2021-06-10 20:00:14 +00:00
|
|
|
};
|
|
|
|
|
2021-10-14 21:08:42 +00:00
|
|
|
/**
|
|
|
|
* Triggers the flow to register the user's email if the user does not yet have one configured, if successful resolves true, otherwise false.
|
|
|
|
*/
|
|
|
|
const registerEmailIfNeeded = async () => {
|
|
|
|
const user = await adminClient.users.findOne({ id: whoAmI.getUserId() });
|
|
|
|
|
|
|
|
// A user should always be found, throw if it is not.
|
|
|
|
if (!user) {
|
|
|
|
throw new Error("Unable to find user.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// User already has an e-mail associated with it, no need to register.
|
|
|
|
if (user.email) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// User needs to register, show modal to do so.
|
|
|
|
return new Promise<boolean>((resolve) => {
|
|
|
|
const callback: EmailRegistrationCallback = (registered) => {
|
|
|
|
setCallback(undefined);
|
|
|
|
resolve(registered);
|
|
|
|
};
|
|
|
|
|
|
|
|
setCallback(() => callback);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-05-03 20:00:12 +00:00
|
|
|
return (
|
|
|
|
<>
|
2021-10-14 21:08:42 +00:00
|
|
|
{callback && <AddUserEmailModal callback={callback} />}
|
2021-05-03 20:00:12 +00:00
|
|
|
<PageSection variant="light">
|
|
|
|
<FormPanel title={t("template")} className="kc-email-template">
|
|
|
|
<FormAccess
|
|
|
|
isHorizontal
|
|
|
|
role="manage-realm"
|
|
|
|
className="pf-u-mt-lg"
|
|
|
|
onSubmit={handleSubmit(save)}
|
|
|
|
>
|
|
|
|
<FormGroup
|
|
|
|
label={t("from")}
|
|
|
|
fieldId="kc-display-name"
|
|
|
|
isRequired
|
2021-05-27 07:01:55 +00:00
|
|
|
validated={errors.smtpServer?.from ? "error" : "default"}
|
2021-05-03 20:00:12 +00:00
|
|
|
helperTextInvalid={t("users:emailInvalid")}
|
|
|
|
>
|
|
|
|
<TextInput
|
|
|
|
type="email"
|
|
|
|
id="kc-sender-email-address"
|
|
|
|
data-testid="sender-email-address"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.from"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register({
|
|
|
|
pattern: emailRegexPattern,
|
|
|
|
required: true,
|
|
|
|
})}
|
|
|
|
placeholder="Sender email address"
|
2021-05-27 07:01:55 +00:00
|
|
|
validated={errors.smtpServer?.from ? "error" : "default"}
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
|
|
label={t("fromDisplayName")}
|
|
|
|
fieldId="kc-from-display-name"
|
|
|
|
labelIcon={
|
|
|
|
<HelpItem
|
|
|
|
helpText="realm-settings-help:fromDisplayName"
|
2021-12-14 14:56:36 +00:00
|
|
|
fieldLabelId="realm-settings:authentication"
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<TextInput
|
|
|
|
type="text"
|
|
|
|
id="kc-from-display-name"
|
|
|
|
data-testid="from-display-name"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.fromDisplayName"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register}
|
|
|
|
placeholder="Display name for Sender email address"
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
|
|
label={t("replyTo")}
|
|
|
|
fieldId="kc-reply-to"
|
2021-05-27 07:01:55 +00:00
|
|
|
validated={errors.smtpServer?.replyTo ? "error" : "default"}
|
2021-05-03 20:00:12 +00:00
|
|
|
helperTextInvalid={t("users:emailInvalid")}
|
|
|
|
>
|
|
|
|
<TextInput
|
|
|
|
type="email"
|
|
|
|
id="kc-reply-to"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.replyTo"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register({
|
|
|
|
pattern: emailRegexPattern,
|
|
|
|
})}
|
|
|
|
placeholder="Reply to email address"
|
2021-05-27 07:01:55 +00:00
|
|
|
validated={errors.smtpServer?.replyTo ? "error" : "default"}
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
|
|
label={t("replyToDisplayName")}
|
|
|
|
fieldId="kc-reply-to-display-name"
|
|
|
|
labelIcon={
|
|
|
|
<HelpItem
|
|
|
|
helpText="realm-settings-help:replyToDisplayName"
|
2021-12-14 14:56:36 +00:00
|
|
|
fieldLabelId="realm-settings:replyToDisplayName"
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<TextInput
|
|
|
|
type="text"
|
|
|
|
id="kc-reply-to-display-name"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.replyToDisplayName"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register}
|
|
|
|
placeholder='Display name for "reply to" email address'
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
|
|
label={t("envelopeFrom")}
|
|
|
|
fieldId="kc-envelope-from"
|
|
|
|
labelIcon={
|
|
|
|
<HelpItem
|
|
|
|
helpText="realm-settings-help:envelopeFrom"
|
2021-12-14 14:56:36 +00:00
|
|
|
fieldLabelId="realm-settings:envelopeFrom"
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<TextInput
|
|
|
|
type="text"
|
|
|
|
id="kc-envelope-from"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.envelopeFrom"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register}
|
|
|
|
placeholder="Sender envelope email address"
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
</FormAccess>
|
|
|
|
</FormPanel>
|
|
|
|
<FormPanel
|
|
|
|
className="kc-email-connection"
|
|
|
|
title={t("connectionAndAuthentication")}
|
|
|
|
>
|
|
|
|
<FormAccess
|
|
|
|
isHorizontal
|
|
|
|
role="manage-realm"
|
|
|
|
className="pf-u-mt-lg"
|
|
|
|
onSubmit={handleSubmit(save)}
|
|
|
|
>
|
2021-05-27 07:01:55 +00:00
|
|
|
<FormGroup
|
|
|
|
label={t("host")}
|
|
|
|
fieldId="kc-host"
|
|
|
|
isRequired
|
|
|
|
validated={errors.smtpServer?.host ? "error" : "default"}
|
|
|
|
helperTextInvalid={t("common:required")}
|
|
|
|
>
|
2021-05-03 20:00:12 +00:00
|
|
|
<TextInput
|
|
|
|
type="text"
|
|
|
|
id="kc-host"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.host"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register({ required: true })}
|
|
|
|
placeholder="SMTP host"
|
2021-05-27 07:01:55 +00:00
|
|
|
validated={errors.smtpServer?.host ? "error" : "default"}
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup label={t("port")} fieldId="kc-port">
|
|
|
|
<TextInput
|
|
|
|
type="text"
|
|
|
|
id="kc-port"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.port"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register}
|
|
|
|
placeholder="SMTP port (defaults to 25)"
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup label={t("encryption")} fieldId="kc-html-display-name">
|
|
|
|
<Controller
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.ssl"
|
2021-05-03 20:00:12 +00:00
|
|
|
control={control}
|
|
|
|
defaultValue="false"
|
|
|
|
render={({ onChange, value }) => (
|
|
|
|
<Checkbox
|
|
|
|
id="kc-enable-ssl"
|
|
|
|
data-testid="enable-ssl"
|
|
|
|
label={t("enableSSL")}
|
|
|
|
ref={register}
|
|
|
|
isChecked={value === "true"}
|
|
|
|
onChange={(value) => onChange("" + value)}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
<Controller
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.starttls"
|
2021-05-03 20:00:12 +00:00
|
|
|
control={control}
|
|
|
|
defaultValue="false"
|
|
|
|
render={({ onChange, value }) => (
|
|
|
|
<Checkbox
|
|
|
|
id="kc-enable-start-tls"
|
|
|
|
data-testid="enable-start-tls"
|
|
|
|
label={t("enableStartTLS")}
|
|
|
|
ref={register}
|
|
|
|
isChecked={value === "true"}
|
|
|
|
onChange={(value) => onChange("" + value)}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
|
|
hasNoPaddingTop
|
|
|
|
label={t("authentication")}
|
|
|
|
fieldId="kc-authentication"
|
|
|
|
>
|
|
|
|
<Controller
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.authentication"
|
2021-05-03 20:00:12 +00:00
|
|
|
control={control}
|
2021-10-06 20:54:33 +00:00
|
|
|
defaultValue=""
|
2021-05-03 20:00:12 +00:00
|
|
|
render={({ onChange, value }) => (
|
|
|
|
<Switch
|
2021-07-12 14:09:14 +00:00
|
|
|
id="kc-authentication-switch"
|
2021-05-03 20:00:12 +00:00
|
|
|
data-testid="email-authentication-switch"
|
|
|
|
label={t("common:enabled")}
|
|
|
|
labelOff={t("common:disabled")}
|
|
|
|
isChecked={value === "true"}
|
|
|
|
onChange={(value) => {
|
|
|
|
onChange("" + value);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
</FormGroup>
|
2021-06-22 15:19:13 +00:00
|
|
|
{authenticationEnabled === "true" && (
|
2021-05-03 20:00:12 +00:00
|
|
|
<>
|
|
|
|
<FormGroup
|
|
|
|
label={t("username")}
|
|
|
|
fieldId="kc-username"
|
2021-05-27 07:01:55 +00:00
|
|
|
isRequired
|
|
|
|
validated={errors.smtpServer?.user ? "error" : "default"}
|
|
|
|
helperTextInvalid={t("common:required")}
|
2021-05-03 20:00:12 +00:00
|
|
|
>
|
|
|
|
<TextInput
|
|
|
|
type="text"
|
|
|
|
id="kc-username"
|
|
|
|
data-testid="username-input"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.user"
|
2021-05-03 20:00:12 +00:00
|
|
|
ref={register({ required: true })}
|
|
|
|
placeholder="Login username"
|
2021-05-27 07:01:55 +00:00
|
|
|
validated={errors.smtpServer?.user ? "error" : "default"}
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
<FormGroup
|
|
|
|
label={t("password")}
|
|
|
|
fieldId="kc-username"
|
2021-05-27 07:01:55 +00:00
|
|
|
isRequired
|
|
|
|
validated={errors.smtpServer?.password ? "error" : "default"}
|
|
|
|
helperTextInvalid={t("common:required")}
|
2021-05-03 20:00:12 +00:00
|
|
|
labelIcon={
|
|
|
|
<HelpItem
|
2021-05-27 07:01:55 +00:00
|
|
|
helpText="realm-settings-help:password"
|
2021-12-14 14:56:36 +00:00
|
|
|
fieldLabelId="realm-settings:password"
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<TextInput
|
|
|
|
type="password"
|
|
|
|
id="kc-password"
|
|
|
|
data-testid="password-input"
|
2021-05-27 07:01:55 +00:00
|
|
|
name="smtpServer.password"
|
|
|
|
ref={register({ required: true })}
|
2021-05-03 20:00:12 +00:00
|
|
|
placeholder="Login password"
|
2021-05-27 07:01:55 +00:00
|
|
|
validated={
|
|
|
|
errors.smtpServer?.password ? "error" : "default"
|
|
|
|
}
|
2021-05-03 20:00:12 +00:00
|
|
|
/>
|
|
|
|
</FormGroup>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<ActionGroup>
|
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
type="submit"
|
|
|
|
data-testid="email-tab-save"
|
|
|
|
>
|
|
|
|
{t("common:save")}
|
|
|
|
</Button>
|
2021-06-10 20:00:14 +00:00
|
|
|
<Button
|
|
|
|
variant="secondary"
|
2021-10-14 21:08:42 +00:00
|
|
|
onClick={() => testConnection()}
|
2021-06-10 20:00:14 +00:00
|
|
|
data-testid="test-connection-button"
|
2021-06-22 14:17:45 +00:00
|
|
|
isDisabled={
|
|
|
|
!(emailRegexPattern.test(watchFromValue) && watchHostValue)
|
|
|
|
}
|
2021-06-10 20:00:14 +00:00
|
|
|
>
|
2021-10-05 10:31:39 +00:00
|
|
|
{t("common:testConnection")}
|
2021-06-10 20:00:14 +00:00
|
|
|
</Button>
|
2021-05-03 20:00:12 +00:00
|
|
|
<Button variant="link" onClick={reset}>
|
|
|
|
{t("common:revert")}
|
|
|
|
</Button>
|
|
|
|
</ActionGroup>
|
|
|
|
</FormAccess>
|
|
|
|
</FormPanel>
|
|
|
|
</PageSection>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|