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:
Jon Koops 2021-10-14 23:08:42 +02:00 committed by GitHub
parent 0b4cb21134
commit 405ae4875a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 81 deletions

View file

@ -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>,

View file

@ -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)

View file

@ -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}
/>
);
};

View file

@ -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"