From 405ae4875a473deafb0c66a0d84b4d2334fd774f Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Thu, 14 Oct 2021 23:08:42 +0200 Subject: [PATCH] Do not save realm when testing e-mail connection (#1325) * Do not save realm when testing e-mail connection * Register email before testing connection --- src/realm-settings/AddUserEmailModal.tsx | 42 +++++---- src/realm-settings/EmailTab.tsx | 94 ++++++++++----------- src/realm-settings/RealmSettingsSection.tsx | 13 +-- src/realm-settings/RealmSettingsTabs.tsx | 5 +- 4 files changed, 73 insertions(+), 81 deletions(-) diff --git a/src/realm-settings/AddUserEmailModal.tsx b/src/realm-settings/AddUserEmailModal.tsx index c10530e811..ef334c4c60 100644 --- a/src/realm-settings/AddUserEmailModal.tsx +++ b/src/realm-settings/AddUserEmailModal.tsx @@ -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; - 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({ + 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 ( { - handleModalToggle(); - }} + onClick={cancel} > {t("common:cancel")} , diff --git a/src/realm-settings/EmailTab.tsx b/src/realm-settings/EmailTab.tsx index 272c44f8d3..0c30be7869 100644 --- a/src/realm-settings/EmailTab.tsx +++ b/src/realm-settings/EmailTab.tsx @@ -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(user); + const [callback, setCallback] = useState(); const { register, control, handleSubmit, errors, watch, - setValue, reset: resetForm, getValues, } = useForm({ defaultValues: realm }); - const userForm = useForm({ 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((resolve) => { + const callback: EmailRegistrationCallback = (registered) => { + setCallback(undefined); + resolve(registered); + }; + + setCallback(() => callback); + }); + }; + return ( <> - {userEmailModalOpen && ( - { - saveAndTestEmail(email!); - handleModalToggle(); - }} - form={userForm} - user={currentUser!} - /> - )} + {callback && }