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