Do not save realm when testing e-mail connection (#1325)
* Do not save realm when testing e-mail connection * Register email before testing connection
This commit is contained in:
parent
0b4cb21134
commit
405ae4875a
4 changed files with 73 additions and 81 deletions
|
@ -11,36 +11,44 @@ import {
|
||||||
ValidatedOptions,
|
ValidatedOptions,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useForm, UseFormMethods } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
|
||||||
import { emailRegexPattern } from "../util";
|
import { emailRegexPattern } from "../util";
|
||||||
|
import { useAdminClient } from "../context/auth/AdminClient";
|
||||||
|
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||||
|
import type { EmailRegistrationCallback } from "./EmailTab";
|
||||||
|
|
||||||
type AddUserEmailModalProps = {
|
type AddUserEmailModalProps = {
|
||||||
id?: string;
|
callback: EmailRegistrationCallback;
|
||||||
form: UseFormMethods<UserRepresentation>;
|
|
||||||
rename?: string;
|
|
||||||
handleModalToggle: () => void;
|
|
||||||
testConnection: () => void;
|
|
||||||
user: UserRepresentation;
|
|
||||||
save: (user?: UserRepresentation) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddUserEmailModal = ({
|
type AddUserEmailForm = {
|
||||||
handleModalToggle,
|
email: string;
|
||||||
save,
|
};
|
||||||
}: AddUserEmailModalProps) => {
|
|
||||||
|
export const AddUserEmailModal = ({ callback }: AddUserEmailModalProps) => {
|
||||||
const { t } = useTranslation("groups");
|
const { t } = useTranslation("groups");
|
||||||
const { register, errors, handleSubmit, watch } = useForm();
|
const adminClient = useAdminClient();
|
||||||
|
const { whoAmI } = useWhoAmI();
|
||||||
|
const { register, errors, handleSubmit, watch } = useForm<AddUserEmailForm>({
|
||||||
|
defaultValues: { email: "" },
|
||||||
|
});
|
||||||
|
|
||||||
const watchEmailInput = watch("email", "");
|
const watchEmailInput = watch("email", "");
|
||||||
|
const cancel = () => callback(false);
|
||||||
|
const proceed = () => callback(true);
|
||||||
|
|
||||||
|
const save = async (formData: AddUserEmailForm) => {
|
||||||
|
await adminClient.users.update({ id: whoAmI.getUserId() }, formData);
|
||||||
|
proceed();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
variant={ModalVariant.small}
|
variant={ModalVariant.small}
|
||||||
title={t("realm-settings:provideEmailTitle")}
|
title={t("realm-settings:provideEmailTitle")}
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
onClose={handleModalToggle}
|
onClose={cancel}
|
||||||
actions={[
|
actions={[
|
||||||
<Button
|
<Button
|
||||||
data-testid="modal-test-connection-button"
|
data-testid="modal-test-connection-button"
|
||||||
|
@ -56,9 +64,7 @@ export const AddUserEmailModal = ({
|
||||||
id="modal-cancel"
|
id="modal-cancel"
|
||||||
key="cancel"
|
key="cancel"
|
||||||
variant={ButtonVariant.link}
|
variant={ButtonVariant.link}
|
||||||
onClick={() => {
|
onClick={cancel}
|
||||||
handleModalToggle();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t("common:cancel")}
|
{t("common:cancel")}
|
||||||
</Button>,
|
</Button>,
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
@ -26,12 +25,12 @@ import "./RealmSettingsSection.css";
|
||||||
|
|
||||||
type RealmSettingsEmailTabProps = {
|
type RealmSettingsEmailTabProps = {
|
||||||
realm: RealmRepresentation;
|
realm: RealmRepresentation;
|
||||||
user: UserRepresentation;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type EmailRegistrationCallback = (registered: boolean) => void;
|
||||||
|
|
||||||
export const RealmSettingsEmailTab = ({
|
export const RealmSettingsEmailTab = ({
|
||||||
realm: initialRealm,
|
realm: initialRealm,
|
||||||
user,
|
|
||||||
}: RealmSettingsEmailTabProps) => {
|
}: RealmSettingsEmailTabProps) => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const adminClient = useAdminClient();
|
const adminClient = useAdminClient();
|
||||||
|
@ -40,20 +39,18 @@ export const RealmSettingsEmailTab = ({
|
||||||
const { whoAmI } = useWhoAmI();
|
const { whoAmI } = useWhoAmI();
|
||||||
|
|
||||||
const [realm, setRealm] = useState(initialRealm);
|
const [realm, setRealm] = useState(initialRealm);
|
||||||
const [userEmailModalOpen, setUserEmailModalOpen] = useState(false);
|
const [callback, setCallback] = useState<EmailRegistrationCallback>();
|
||||||
const [currentUser, setCurrentUser] = useState<UserRepresentation>(user);
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
errors,
|
errors,
|
||||||
watch,
|
watch,
|
||||||
setValue,
|
|
||||||
reset: resetForm,
|
reset: resetForm,
|
||||||
getValues,
|
getValues,
|
||||||
} = useForm<RealmRepresentation>({ defaultValues: realm });
|
} = useForm<RealmRepresentation>({ defaultValues: realm });
|
||||||
|
|
||||||
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
const reset = () => resetForm(realm);
|
||||||
const watchFromValue = watch("smtpServer.from", "");
|
const watchFromValue = watch("smtpServer.from", "");
|
||||||
const watchHostValue = watch("smtpServer.host", "");
|
const watchHostValue = watch("smtpServer.host", "");
|
||||||
|
|
||||||
|
@ -63,12 +60,14 @@ export const RealmSettingsEmailTab = ({
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleModalToggle = () => {
|
|
||||||
setUserEmailModalOpen(!userEmailModalOpen);
|
|
||||||
};
|
|
||||||
|
|
||||||
const save = async (form: RealmRepresentation) => {
|
const save = async (form: RealmRepresentation) => {
|
||||||
try {
|
try {
|
||||||
|
const registered = await registerEmailIfNeeded();
|
||||||
|
|
||||||
|
if (!registered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const savedRealm = { ...realm, ...form };
|
const savedRealm = { ...realm, ...form };
|
||||||
await adminClient.realms.update({ realm: realmName }, savedRealm);
|
await adminClient.realms.update({ realm: realmName }, savedRealm);
|
||||||
setRealm(savedRealm);
|
setRealm(savedRealm);
|
||||||
|
@ -78,31 +77,6 @@ export const RealmSettingsEmailTab = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAndTestEmail = async (email?: UserRepresentation) => {
|
|
||||||
if (email) {
|
|
||||||
await adminClient.users.update({ id: whoAmI.getUserId() }, email);
|
|
||||||
setCurrentUser(email);
|
|
||||||
|
|
||||||
await save(getValues());
|
|
||||||
testConnection();
|
|
||||||
} else {
|
|
||||||
const user = await adminClient.users.findOne({ id: whoAmI.getUserId() });
|
|
||||||
if (user && !user.email) {
|
|
||||||
handleModalToggle();
|
|
||||||
} else {
|
|
||||||
await save(getValues());
|
|
||||||
testConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const reset = () => {
|
|
||||||
if (realm) {
|
|
||||||
resetForm(realm);
|
|
||||||
Object.entries(realm).map((entry) => setValue(entry[0], entry[1]));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const testConnection = async () => {
|
const testConnection = async () => {
|
||||||
const serverSettings = { ...getValues()["smtpServer"] };
|
const serverSettings = { ...getValues()["smtpServer"] };
|
||||||
|
|
||||||
|
@ -126,6 +100,12 @@ export const RealmSettingsEmailTab = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const registered = await registerEmailIfNeeded();
|
||||||
|
|
||||||
|
if (!registered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await adminClient.realms.testSMTPConnection(
|
await adminClient.realms.testSMTPConnection(
|
||||||
{ realm: realm.realm! },
|
{ realm: realm.realm! },
|
||||||
serverSettings
|
serverSettings
|
||||||
|
@ -136,20 +116,36 @@ export const RealmSettingsEmailTab = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{userEmailModalOpen && (
|
{callback && <AddUserEmailModal callback={callback} />}
|
||||||
<AddUserEmailModal
|
|
||||||
handleModalToggle={handleModalToggle}
|
|
||||||
testConnection={testConnection}
|
|
||||||
save={(email) => {
|
|
||||||
saveAndTestEmail(email!);
|
|
||||||
handleModalToggle();
|
|
||||||
}}
|
|
||||||
form={userForm}
|
|
||||||
user={currentUser!}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
<FormPanel title={t("template")} className="kc-email-template">
|
<FormPanel title={t("template")} className="kc-email-template">
|
||||||
<FormAccess
|
<FormAccess
|
||||||
|
@ -405,7 +401,7 @@ export const RealmSettingsEmailTab = ({
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={() => saveAndTestEmail()}
|
onClick={() => testConnection()}
|
||||||
data-testid="test-connection-button"
|
data-testid="test-connection-button"
|
||||||
isDisabled={
|
isDisabled={
|
||||||
!(emailRegexPattern.test(watchFromValue) && watchHostValue)
|
!(emailRegexPattern.test(watchFromValue) && watchHostValue)
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { Breadcrumb, BreadcrumbItem, Spinner } from "@patternfly/react-core";
|
import { Breadcrumb, BreadcrumbItem, Spinner } from "@patternfly/react-core";
|
||||||
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
import { useRealm } from "../context/realm-context/RealmContext";
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
|
||||||
import { KEY_PROVIDER_TYPE } from "../util";
|
import { KEY_PROVIDER_TYPE } from "../util";
|
||||||
import { toRealmSettings } from "./routes/RealmSettings";
|
import { toRealmSettings } from "./routes/RealmSettings";
|
||||||
import { RealmSettingsTabs } from "./RealmSettingsTabs";
|
import { RealmSettingsTabs } from "./RealmSettingsTabs";
|
||||||
|
@ -50,8 +48,6 @@ export const RealmSettingsSection = () => {
|
||||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||||
const [realmComponents, setRealmComponents] =
|
const [realmComponents, setRealmComponents] =
|
||||||
useState<ComponentRepresentation[]>();
|
useState<ComponentRepresentation[]>();
|
||||||
const [currentUser, setCurrentUser] = useState<UserRepresentation>();
|
|
||||||
const { whoAmI } = useWhoAmI();
|
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
|
@ -65,19 +61,17 @@ export const RealmSettingsSection = () => {
|
||||||
type: KEY_PROVIDER_TYPE,
|
type: KEY_PROVIDER_TYPE,
|
||||||
realm: realmName,
|
realm: realmName,
|
||||||
});
|
});
|
||||||
const user = await adminClient.users.findOne({ id: whoAmI.getUserId() });
|
|
||||||
|
|
||||||
return { user, realm, realmComponents };
|
return { realm, realmComponents };
|
||||||
},
|
},
|
||||||
({ user, realm, realmComponents }) => {
|
({ realm, realmComponents }) => {
|
||||||
setRealmComponents(sortByPriority(realmComponents));
|
setRealmComponents(sortByPriority(realmComponents));
|
||||||
setCurrentUser(user);
|
|
||||||
setRealm(realm);
|
setRealm(realm);
|
||||||
},
|
},
|
||||||
[key]
|
[key]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!realm || !realmComponents || !currentUser) {
|
if (!realm || !realmComponents) {
|
||||||
return (
|
return (
|
||||||
<div className="pf-u-text-align-center">
|
<div className="pf-u-text-align-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
@ -89,7 +83,6 @@ export const RealmSettingsSection = () => {
|
||||||
realm={realm}
|
realm={realm}
|
||||||
refresh={refresh}
|
refresh={refresh}
|
||||||
realmComponents={realmComponents}
|
realmComponents={realmComponents}
|
||||||
currentUser={currentUser}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
|
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
import type ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation";
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
|
||||||
|
|
||||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||||
|
@ -141,13 +140,11 @@ type RealmSettingsTabsProps = {
|
||||||
realm: RealmRepresentation;
|
realm: RealmRepresentation;
|
||||||
refresh: () => void;
|
refresh: () => void;
|
||||||
realmComponents: ComponentRepresentation[];
|
realmComponents: ComponentRepresentation[];
|
||||||
currentUser: UserRepresentation;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RealmSettingsTabs = ({
|
export const RealmSettingsTabs = ({
|
||||||
realm,
|
realm,
|
||||||
realmComponents,
|
realmComponents,
|
||||||
currentUser,
|
|
||||||
refresh,
|
refresh,
|
||||||
}: RealmSettingsTabsProps) => {
|
}: RealmSettingsTabsProps) => {
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
|
@ -260,7 +257,7 @@ export const RealmSettingsTabs = ({
|
||||||
data-testid="rs-email-tab"
|
data-testid="rs-email-tab"
|
||||||
aria-label="email-tab"
|
aria-label="email-tab"
|
||||||
>
|
>
|
||||||
<RealmSettingsEmailTab user={currentUser} realm={realm} />
|
<RealmSettingsEmailTab realm={realm} />
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
eventKey="themes"
|
eventKey="themes"
|
||||||
|
|
Loading…
Reference in a new issue