Merge pull request #696 from jenny-s51/testEmailConnection
Test email connection
This commit is contained in:
commit
125fd1fe3d
11 changed files with 273 additions and 11 deletions
|
@ -56,6 +56,8 @@ describe("Realm roles test", function () {
|
|||
// Delete
|
||||
listingPage.deleteItem(itemId);
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
modalUtils.checkModalTitle("Delete role?").confirmModal();
|
||||
|
||||
masthead.checkNotificationMessage("The role has been deleted");
|
||||
|
|
|
@ -69,6 +69,17 @@ describe("Realm settings", () => {
|
|||
realmSettingsPage.toggleCheck(realmSettingsPage.enableStartTlsCheck);
|
||||
|
||||
realmSettingsPage.save(realmSettingsPage.emailSaveBtn);
|
||||
|
||||
realmSettingsPage.fillHostField("localhost");
|
||||
cy.getId(realmSettingsPage.testConnectionButton).click();
|
||||
|
||||
realmSettingsPage.fillEmailField(
|
||||
"example" + (Math.random() + 1).toString(36).substring(7) + "@example.com"
|
||||
);
|
||||
|
||||
cy.getId(realmSettingsPage.modalTestConnectionButton).click();
|
||||
|
||||
masthead.checkNotificationMessage("Error! Failed to send email.");
|
||||
});
|
||||
|
||||
it("Go to themes tab", () => {
|
||||
|
|
|
@ -11,6 +11,7 @@ export default class RealmSettingsPage {
|
|||
adminThemeList = "#kc-admin-console-theme + ul";
|
||||
selectEmailTheme = "#kc-email-theme";
|
||||
emailThemeList = "#kc-email-theme + ul";
|
||||
hostInput = "#kc-host";
|
||||
selectDefaultLocale = "select-default-locale";
|
||||
defaultLocaleList = "select-default-locale + ul";
|
||||
emailSaveBtn = "email-tab-save";
|
||||
|
@ -35,6 +36,9 @@ export default class RealmSettingsPage {
|
|||
filterSelectMenu = ".kc-filter-type-select";
|
||||
passiveKeysOption = "passive-keys-option";
|
||||
disabledKeysOption = "disabled-keys-option";
|
||||
testConnectionButton = "test-connection-button";
|
||||
modalTestConnectionButton = "modal-test-connection-button";
|
||||
emailAddressInput = "email-address-input";
|
||||
|
||||
selectLoginThemeType(themeType: string) {
|
||||
const themesUrl = "/auth/admin/realms/master/themes";
|
||||
|
@ -64,6 +68,16 @@ export default class RealmSettingsPage {
|
|||
return this;
|
||||
}
|
||||
|
||||
fillEmailField(email: string) {
|
||||
cy.getId(this.emailAddressInput).type(email);
|
||||
return this;
|
||||
}
|
||||
|
||||
fillHostField(host: string) {
|
||||
cy.get(this.hostInput).type(host);
|
||||
return this;
|
||||
}
|
||||
|
||||
setDefaultLocale(locale: string) {
|
||||
cy.get(this.selectDefaultLocale).click();
|
||||
cy.get(this.defaultLocaleList).contains(locale).click();
|
||||
|
|
|
@ -36,6 +36,7 @@ export type ConfirmDialogProps = {
|
|||
titleKey: string;
|
||||
messageKey?: string;
|
||||
noCancelButton?: boolean;
|
||||
confirmButtonDisabled?: boolean;
|
||||
cancelButtonLabel?: string;
|
||||
continueButtonLabel?: string;
|
||||
continueButtonVariant?: ButtonVariant;
|
||||
|
@ -58,6 +59,7 @@ export const ConfirmDialogModal = ({
|
|||
open = true,
|
||||
variant = ModalVariant.small,
|
||||
toggleDialog,
|
||||
confirmButtonDisabled,
|
||||
}: ConfirmDialogModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
|
@ -71,6 +73,7 @@ export const ConfirmDialogModal = ({
|
|||
id="modal-confirm"
|
||||
data-testid="modalConfirm"
|
||||
key="confirm"
|
||||
isDisabled={confirmButtonDisabled}
|
||||
variant={continueButtonVariant || ButtonVariant.primary}
|
||||
onClick={() => {
|
||||
onConfirm();
|
||||
|
|
|
@ -23,6 +23,12 @@ export class WhoAmI {
|
|||
return this.me.displayName;
|
||||
}
|
||||
|
||||
public getUserId(): string {
|
||||
if (this.me === undefined) return "";
|
||||
|
||||
return this.me.userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the realm I am signed in to.
|
||||
*/
|
||||
|
|
96
src/realm-settings/AddUserEmailModal.tsx
Normal file
96
src/realm-settings/AddUserEmailModal.tsx
Normal file
|
@ -0,0 +1,96 @@
|
|||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
ButtonVariant,
|
||||
Form,
|
||||
FormGroup,
|
||||
Modal,
|
||||
ModalVariant,
|
||||
TextContent,
|
||||
TextInput,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm, UseFormMethods } from "react-hook-form";
|
||||
|
||||
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
|
||||
import { emailRegexPattern } from "../util";
|
||||
|
||||
type AddUserEmailModalProps = {
|
||||
id?: string;
|
||||
form: UseFormMethods<UserRepresentation>;
|
||||
rename?: string;
|
||||
handleModalToggle: () => void;
|
||||
testConnection: () => void;
|
||||
user: UserRepresentation;
|
||||
save: (user?: UserRepresentation) => void;
|
||||
};
|
||||
|
||||
export const AddUserEmailModal = ({
|
||||
handleModalToggle,
|
||||
save,
|
||||
}: AddUserEmailModalProps) => {
|
||||
const { t } = useTranslation("groups");
|
||||
const { register, errors, handleSubmit, watch } = useForm();
|
||||
|
||||
const watchEmailInput = watch("email", "");
|
||||
|
||||
return (
|
||||
<Modal
|
||||
variant={ModalVariant.small}
|
||||
title={t("realm-settings:provideEmailTitle")}
|
||||
isOpen={true}
|
||||
onClose={handleModalToggle}
|
||||
actions={[
|
||||
<Button
|
||||
data-testid="modal-test-connection-button"
|
||||
key="confirm"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
form="email-form"
|
||||
isDisabled={!watchEmailInput}
|
||||
>
|
||||
{t("realm-settings:testConnection")}
|
||||
</Button>,
|
||||
<Button
|
||||
id="modal-cancel"
|
||||
key="cancel"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={() => {
|
||||
handleModalToggle();
|
||||
}}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<TextContent className="kc-provide-email-text">
|
||||
{t("realm-settings:provideEmail")}
|
||||
</TextContent>
|
||||
<Form id="email-form" isHorizontal onSubmit={handleSubmit(save)}>
|
||||
<FormGroup
|
||||
className="kc-email-form-group"
|
||||
name="add-email-address"
|
||||
fieldId="email-id"
|
||||
helperTextInvalid={t("users:emailInvalid")}
|
||||
validated={
|
||||
errors.email ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<TextInput
|
||||
data-testid="email-address-input"
|
||||
ref={register({ required: true, pattern: emailRegexPattern })}
|
||||
autoFocus
|
||||
type="text"
|
||||
id="add-email"
|
||||
name="email"
|
||||
validated={
|
||||
errors.email ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
|
@ -20,36 +20,64 @@ import { emailRegexPattern } from "../util";
|
|||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { getBaseUrl } from "../util";
|
||||
|
||||
import "./RealmSettingsSection.css";
|
||||
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
|
||||
import { WhoAmIContext } from "../context/whoami/WhoAmI";
|
||||
import { AddUserEmailModal } from "./AddUserEmailModal";
|
||||
|
||||
type RealmSettingsEmailTabProps = {
|
||||
realm: RealmRepresentation;
|
||||
user: UserRepresentation;
|
||||
};
|
||||
|
||||
export const RealmSettingsEmailTab = ({
|
||||
realm: initialRealm,
|
||||
user,
|
||||
}: RealmSettingsEmailTabProps) => {
|
||||
const { t } = useTranslation("realm-settings");
|
||||
const adminClient = useAdminClient();
|
||||
const { realm: realmName } = useRealm();
|
||||
const { addAlert } = useAlerts();
|
||||
const { whoAmI } = useContext(WhoAmIContext);
|
||||
|
||||
const [isAuthenticationEnabled, setAuthenticationEnabled] = useState("true");
|
||||
const [realm, setRealm] = useState(initialRealm);
|
||||
const [userEmailModalOpen, setUserEmailModalOpen] = useState(false);
|
||||
const [currentUser, setCurrentUser] = useState<UserRepresentation>();
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
errors,
|
||||
watch,
|
||||
setValue,
|
||||
reset: resetForm,
|
||||
getValues,
|
||||
} = useForm<RealmRepresentation>();
|
||||
|
||||
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
||||
const watchFromValue = watch("smtpServer.from", "");
|
||||
const watchHostValue = watch("smtpServer.host", "");
|
||||
|
||||
const authenticationEnabled = useWatch({
|
||||
control,
|
||||
name: "smtpServer.authentication",
|
||||
defaultValue: realm?.smtpServer!.authentication,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [realm]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentUser(user);
|
||||
}, []);
|
||||
|
||||
const handleModalToggle = () => {
|
||||
setUserEmailModalOpen(!userEmailModalOpen);
|
||||
};
|
||||
|
||||
const save = async (form: RealmRepresentation) => {
|
||||
try {
|
||||
const savedRealm = { ...realm, ...form };
|
||||
|
@ -64,6 +92,27 @@ export const RealmSettingsEmailTab = ({
|
|||
}
|
||||
};
|
||||
|
||||
const saveAndTestEmail = async (email?: UserRepresentation) => {
|
||||
if (email) {
|
||||
await adminClient.users.update({ id: whoAmI.getUserId() }, email);
|
||||
const updated = await adminClient.users.findOne({
|
||||
id: whoAmI.getUserId(),
|
||||
});
|
||||
setCurrentUser(updated);
|
||||
|
||||
await save(getValues());
|
||||
testConnection();
|
||||
} else {
|
||||
const user = await adminClient.users.findOne({ id: whoAmI.getUserId() });
|
||||
if (!user.email) {
|
||||
handleModalToggle();
|
||||
} else {
|
||||
await save(getValues());
|
||||
testConnection();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
if (realm) {
|
||||
resetForm(realm);
|
||||
|
@ -71,8 +120,40 @@ export const RealmSettingsEmailTab = ({
|
|||
}
|
||||
};
|
||||
|
||||
const testConnection = async () => {
|
||||
const response = await fetch(
|
||||
`${getBaseUrl(adminClient)}admin/realms/${
|
||||
realm.realm
|
||||
}/testSMTPConnection`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `bearer ${await adminClient.getAccessToken()}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(getValues()["smtpServer"] as BodyInit),
|
||||
}
|
||||
);
|
||||
response.ok
|
||||
? addAlert(t("testConnectionSuccess"), AlertVariant.success)
|
||||
: addAlert(t("testConnectionError"), AlertVariant.danger);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{userEmailModalOpen && (
|
||||
<AddUserEmailModal
|
||||
handleModalToggle={handleModalToggle}
|
||||
testConnection={testConnection}
|
||||
save={(email) => {
|
||||
saveAndTestEmail(email!);
|
||||
handleModalToggle();
|
||||
}}
|
||||
form={userForm}
|
||||
user={currentUser!}
|
||||
/>
|
||||
)}
|
||||
<PageSection variant="light">
|
||||
<FormPanel title={t("template")} className="kc-email-template">
|
||||
<FormAccess
|
||||
|
@ -253,7 +334,7 @@ export const RealmSettingsEmailTab = ({
|
|||
<Controller
|
||||
name="smtpServer.authentication"
|
||||
control={control}
|
||||
defaultValue="true"
|
||||
defaultValue={authenticationEnabled}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
id="kc-authentication"
|
||||
|
@ -263,13 +344,12 @@ export const RealmSettingsEmailTab = ({
|
|||
isChecked={value === "true"}
|
||||
onChange={(value) => {
|
||||
onChange("" + value);
|
||||
setAuthenticationEnabled(String(value));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
{isAuthenticationEnabled === "true" && (
|
||||
{authenticationEnabled === "true" && (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("username")}
|
||||
|
@ -325,6 +405,16 @@ export const RealmSettingsEmailTab = ({
|
|||
>
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => saveAndTestEmail()}
|
||||
data-testid="test-connection-button"
|
||||
isDisabled={
|
||||
!(emailRegexPattern.test(watchFromValue) && watchHostValue)
|
||||
}
|
||||
>
|
||||
{t("realm-settings:testConnection")}
|
||||
</Button>
|
||||
<Button variant="link" onClick={reset}>
|
||||
{t("common:revert")}
|
||||
</Button>
|
||||
|
|
|
@ -239,7 +239,7 @@ export const LocalizationTab = ({
|
|||
</FormAccess>
|
||||
</FormPanel>
|
||||
|
||||
<FormPanel className="kc-login-screen" title="Edit message bundles">
|
||||
<FormPanel className="kc-message-bundles" title="Edit message bundles">
|
||||
<TextContent className="messageBundleDescription">
|
||||
{t("messageBundleDescription")}
|
||||
</TextContent>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
.pf-c-card.pf-m-flat.kc-login-screen,
|
||||
.pf-c-card.pf-m-flat.kc-email-settings,
|
||||
.pf-c-card.pf-m-flat.kc-email-template,
|
||||
.pf-c-card.pf-m-flat.kc-email-connection {
|
||||
.pf-c-card.pf-m-flat.kc-email-connection,
|
||||
.pf-c-card.pf-m-flat.kc-message-bundles {
|
||||
border: none;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
|
@ -86,9 +87,17 @@ button.pf-c-button.pf-m-link.add-provider {
|
|||
padding-bottom: var(--pf-global--spacer--lg);
|
||||
}
|
||||
|
||||
div.tableBorder {
|
||||
.kc-message-bundles > .pf-c-card__body.kc-form-panel__body > div.tableBorder {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: var(--pf-global--BorderColor--100);
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
||||
.pf-c-form__group.kc-email-form-group {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
.pf-c-content.kc-provide-email-text {
|
||||
padding-bottom: var(--pf-global--spacer--md);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useContext } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
|
@ -32,6 +32,8 @@ import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepre
|
|||
import { KeysProviderTab } from "./KeysProvidersTab";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { LocalizationTab } from "./LocalizationTab";
|
||||
import { WhoAmIContext } from "../context/whoami/WhoAmI";
|
||||
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
|
||||
|
||||
type RealmSettingsHeaderProps = {
|
||||
onChange: (value: boolean) => void;
|
||||
|
@ -136,6 +138,8 @@ export const RealmSettingsSection = () => {
|
|||
const [realmComponents, setRealmComponents] = useState<
|
||||
ComponentRepresentation[]
|
||||
>();
|
||||
const [currentUser, setCurrentUser] = useState<UserRepresentation>();
|
||||
const { whoAmI } = useContext(WhoAmIContext);
|
||||
|
||||
const kpComponentTypes = useServerInfo().componentTypes![
|
||||
"org.keycloak.keys.KeyProvider"
|
||||
|
@ -150,6 +154,26 @@ export const RealmSettingsSection = () => {
|
|||
[]
|
||||
);
|
||||
|
||||
useFetch(
|
||||
() => adminClient.users.findOne({ id: whoAmI.getUserId()! }),
|
||||
|
||||
(user) => {
|
||||
setCurrentUser(user);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const update = async () => {
|
||||
const realmComponents = await adminClient.components.find({
|
||||
type: "org.keycloak.keys.KeyProvider",
|
||||
realm: realmName,
|
||||
});
|
||||
setRealmComponents(realmComponents);
|
||||
};
|
||||
setTimeout(update, 100);
|
||||
}, [key]);
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
const realm = await adminClient.realms.findOne({ realm: realmName });
|
||||
|
@ -233,7 +257,9 @@ export const RealmSettingsSection = () => {
|
|||
title={<TabTitleText>{t("email")}</TabTitleText>}
|
||||
data-testid="rs-email-tab"
|
||||
>
|
||||
{realm && <RealmSettingsEmailTab realm={realm} />}
|
||||
{realm && (
|
||||
<RealmSettingsEmailTab user={currentUser!} realm={realm} />
|
||||
)}
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey="themes"
|
||||
|
|
|
@ -92,8 +92,13 @@
|
|||
"loginWithEmailHelpText": "Allow users to log in with their email address.",
|
||||
"duplicateEmails": "Duplicate emails",
|
||||
"duplicateEmailsHelpText": "Allow multiple users to have the same email address. Changing this setting will also clear the user's cache. It is recommended to manually update email constraints of existing users in the database after switching off support for duplicate email addresses.",
|
||||
"provideEmailTitle": "Provide your email address",
|
||||
"provideEmail": "To test connection, you should provide your email address first.",
|
||||
"verifyEmail": "Verify email",
|
||||
"verifyEmailHelpText": "Require user to verify their email address after initial login or after address changes are submitted.",
|
||||
"testConnection": "Test connection",
|
||||
"testConnectionSuccess": "Success! SMTP connection successful. E-mail was sent!",
|
||||
"testConnectionError": "Error! Failed to send email.",
|
||||
"realmId": "Realm ID",
|
||||
"displayName": "Display name",
|
||||
"htmlDisplayName": "HTML Display name",
|
||||
|
|
Loading…
Reference in a new issue