Realm settings(tokens): Add tokens tab (#934)
* wip tokens tab * tokens * add data-testids to cypress doc * uncomment * uncomment * finish tests * remove logs * fix help text * fix form panel title for sessions * removed unused useEffect * wip tokens updates to convert function * fix flattened attributes to match call * interpolate timespan * delete unused variables
This commit is contained in:
parent
c7e72abfc1
commit
45e07266eb
11 changed files with 905 additions and 8 deletions
|
@ -329,4 +329,49 @@ describe("Realm settings", () => {
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("add token data", () => {
|
||||||
|
sidebarPage.goToRealmSettings();
|
||||||
|
|
||||||
|
cy.getId("rs-tokens-tab").click();
|
||||||
|
|
||||||
|
realmSettingsPage.populateTokensPage();
|
||||||
|
realmSettingsPage.save("tokens-tab-save");
|
||||||
|
|
||||||
|
masthead.checkNotificationMessage("Realm successfully updated");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("check that token data was saved", () => {
|
||||||
|
sidebarPage.goToRealmSettings();
|
||||||
|
|
||||||
|
cy.getId("rs-tokens-tab").click();
|
||||||
|
|
||||||
|
cy.getId(realmSettingsPage.accessTokenLifespanInput).should(
|
||||||
|
"have.value",
|
||||||
|
1
|
||||||
|
);
|
||||||
|
cy.getId(realmSettingsPage.accessTokenLifespanImplicitInput).should(
|
||||||
|
"have.value",
|
||||||
|
2
|
||||||
|
);
|
||||||
|
cy.getId(realmSettingsPage.clientLoginTimeoutInput).should("have.value", 3);
|
||||||
|
cy.getId(realmSettingsPage.userInitiatedActionLifespanInput).should(
|
||||||
|
"have.value",
|
||||||
|
4
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getId(realmSettingsPage.defaultAdminInitatedInput).should(
|
||||||
|
"have.value",
|
||||||
|
5
|
||||||
|
);
|
||||||
|
cy.getId(realmSettingsPage.emailVerificationInput).should("have.value", 6);
|
||||||
|
|
||||||
|
cy.getId(realmSettingsPage.idpEmailVerificationInput).should(
|
||||||
|
"have.value",
|
||||||
|
7
|
||||||
|
);
|
||||||
|
cy.getId(realmSettingsPage.forgotPasswordInput).should("have.value", 8);
|
||||||
|
|
||||||
|
cy.getId(realmSettingsPage.executeActionsInput).should("have.value", 9);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -87,6 +87,59 @@ export default class RealmSettingsPage {
|
||||||
offlineSessionMaxSwitch = "offline-session-max-switch";
|
offlineSessionMaxSwitch = "offline-session-max-switch";
|
||||||
loginTimeoutInput = "login-timeout-input";
|
loginTimeoutInput = "login-timeout-input";
|
||||||
loginActionTimeoutInput = "login-action-timeout-input";
|
loginActionTimeoutInput = "login-action-timeout-input";
|
||||||
|
selectDefaultSignatureAlgorithm = "#kc-default-sig-alg";
|
||||||
|
revokeRefreshTokenSwitch = "revoke-refresh-token-switch";
|
||||||
|
accessTokenLifespanInput = "access-token-lifespan-input";
|
||||||
|
accessTokenLifespanImplicitInput = "access-token-lifespan-implicit-input";
|
||||||
|
clientLoginTimeoutInput = "client-login-timeout-input";
|
||||||
|
offlineSessionMaxInput = "offline-session-max-input";
|
||||||
|
userInitiatedActionLifespanInput = "user-initiated-action-lifespan";
|
||||||
|
defaultAdminInitatedInput = "default-admin-initated-input";
|
||||||
|
emailVerificationInput = "email-verification-input";
|
||||||
|
idpEmailVerificationInput = "idp-email-verification-input";
|
||||||
|
forgotPasswordInput = "forgot-pw-input";
|
||||||
|
executeActionsInput = "execute-actions-input";
|
||||||
|
|
||||||
|
accessTokenLifespanSelectMenu = "#kc-access-token-lifespan-select-menu";
|
||||||
|
accessTokenLifespanSelectMenuList =
|
||||||
|
"#kc-access-token-lifespan-select-menu > div > ul";
|
||||||
|
|
||||||
|
accessTokenLifespanImplicitSelectMenu =
|
||||||
|
"#kc-access-token-lifespan-implicit-select-menu";
|
||||||
|
accessTokenLifespanImplicitSelectMenuList =
|
||||||
|
"#kc-access-token-lifespan-implicit-select-menu > div > ul";
|
||||||
|
|
||||||
|
clientLoginTimeoutSelectMenu = "#kc-client-login-timeout-select-menu";
|
||||||
|
clientLoginTimeoutSelectMenuList =
|
||||||
|
"#kc-client-login-timeout-select-menu > div > ul";
|
||||||
|
|
||||||
|
offlineSessionMaxSelectMenu = "#kc-offline-session-max-select-menu";
|
||||||
|
offlineSessionMaxSelectMenuList =
|
||||||
|
"#kc-offline-session-max-select-menu > div > ul";
|
||||||
|
|
||||||
|
userInitiatedActionLifespanSelectMenu =
|
||||||
|
"#kc-user-initiated-action-lifespan-select-menu";
|
||||||
|
userInitiatedActionLifespanSelectMenuList =
|
||||||
|
"#kc-user-initiated-action-lifespan-select-menu > div > ul";
|
||||||
|
|
||||||
|
defaultAdminInitatedInputSelectMenu =
|
||||||
|
"#kc-default-admin-initiated-select-menu";
|
||||||
|
defaultAdminInitatedInputSelectMenuList =
|
||||||
|
"#kc-default-admin-initiated-select-menu";
|
||||||
|
|
||||||
|
emailVerificationSelectMenu = "#kc-email-verification-select-menu";
|
||||||
|
emailVerificationSelectMenuList =
|
||||||
|
"#kc-email-verification-select-menu > div > ul";
|
||||||
|
|
||||||
|
idpEmailVerificationSelectMenu = "#kc-idp-email-verification-select-menu";
|
||||||
|
idpEmailVerificationSelectMenuList =
|
||||||
|
"#kc-idp-email-verification-select-menu > div > ul";
|
||||||
|
|
||||||
|
forgotPasswordSelectMenu = "#kc-forgot-pw-select-menu";
|
||||||
|
forgotPasswordSelectMenuList = "#kc-forgot-pw-select-menu > div > ul";
|
||||||
|
|
||||||
|
executeActionsSelectMenu = "#kc-execute-actions-select-menu";
|
||||||
|
executeActionsSelectMenuList = "#kc-execute-actions-select-menu > div > ul";
|
||||||
|
|
||||||
selectLoginThemeType(themeType: string) {
|
selectLoginThemeType(themeType: string) {
|
||||||
cy.get(this.selectLoginTheme).click();
|
cy.get(this.selectLoginTheme).click();
|
||||||
|
@ -314,6 +367,76 @@ export default class RealmSettingsPage {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
populateTokensPage() {
|
||||||
|
this.toggleSwitch(this.revokeRefreshTokenSwitch);
|
||||||
|
|
||||||
|
cy.getId(this.accessTokenLifespanInput)
|
||||||
|
.focus()
|
||||||
|
.clear({ force: true })
|
||||||
|
.getId(this.accessTokenLifespanInput)
|
||||||
|
.clear()
|
||||||
|
.type("1");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Days",
|
||||||
|
this.accessTokenLifespanSelectMenu,
|
||||||
|
this.accessTokenLifespanSelectMenuList
|
||||||
|
);
|
||||||
|
cy.getId(this.accessTokenLifespanImplicitInput).clear().type("2");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Minutes",
|
||||||
|
this.accessTokenLifespanImplicitSelectMenu,
|
||||||
|
this.accessTokenLifespanImplicitSelectMenuList
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getId(this.clientLoginTimeoutInput).clear().type("3");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Hours",
|
||||||
|
this.clientLoginTimeoutSelectMenu,
|
||||||
|
this.clientLoginTimeoutSelectMenuList
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getId(this.userInitiatedActionLifespanInput).clear().type("4");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Minutes",
|
||||||
|
this.userInitiatedActionLifespanSelectMenu,
|
||||||
|
this.userInitiatedActionLifespanSelectMenuList
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getId(this.defaultAdminInitatedInput).clear().type("5");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Days",
|
||||||
|
this.defaultAdminInitatedInputSelectMenu,
|
||||||
|
this.defaultAdminInitatedInputSelectMenuList
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getId(this.emailVerificationInput).clear().type("6");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Days",
|
||||||
|
this.emailVerificationSelectMenu,
|
||||||
|
this.emailVerificationSelectMenuList
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getId(this.idpEmailVerificationInput).clear().type("7");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Days",
|
||||||
|
this.idpEmailVerificationSelectMenu,
|
||||||
|
this.idpEmailVerificationSelectMenuList
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.getId(this.forgotPasswordInput).clear().type("8");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Days",
|
||||||
|
this.forgotPasswordSelectMenu,
|
||||||
|
this.forgotPasswordSelectMenuList
|
||||||
|
);
|
||||||
|
cy.getId(this.executeActionsInput).clear().type("9");
|
||||||
|
this.changeTimeUnit(
|
||||||
|
"Days",
|
||||||
|
this.executeActionsSelectMenu,
|
||||||
|
this.executeActionsSelectMenuList
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
checkUserEvents(events: string[]) {
|
checkUserEvents(events: string[]) {
|
||||||
cy.get(this.eventTypeColumn).should((event) => {
|
cy.get(this.eventTypeColumn).should((event) => {
|
||||||
for (const user of events) {
|
for (const user of events) {
|
||||||
|
|
|
@ -107,6 +107,7 @@ export default {
|
||||||
minutes: "Minutes",
|
minutes: "Minutes",
|
||||||
hours: "Hours",
|
hours: "Hours",
|
||||||
days: "Days",
|
days: "Days",
|
||||||
|
years: "Years",
|
||||||
},
|
},
|
||||||
|
|
||||||
attributes: "Attributes",
|
attributes: "Attributes",
|
||||||
|
|
|
@ -120,7 +120,17 @@ div#offline-session-max-label > .pf-c-form__group-label {
|
||||||
.kc-client-session-idle-input,
|
.kc-client-session-idle-input,
|
||||||
.kc-client-session-max-input,
|
.kc-client-session-max-input,
|
||||||
.kc-login-timeout-input,
|
.kc-login-timeout-input,
|
||||||
.kc-login-action-timeout-input {
|
.kc-login-action-timeout-input,
|
||||||
|
.kc-access-token-lifespan-input,
|
||||||
|
.kc-user-initiated-action-lifespan-input,
|
||||||
|
.kc-default-admin-initiated-input,
|
||||||
|
.kc-access-token-lifespan-implicit-input,
|
||||||
|
.kc-client-login-timeout-input,
|
||||||
|
.kc-email-verification-input,
|
||||||
|
.kc-idp-email-verification-input,
|
||||||
|
.kc-idp-forgot-password-input,
|
||||||
|
.kc-forgot-pw-input,
|
||||||
|
.kc-execute-actions-input {
|
||||||
width: 170px;
|
width: 170px;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
}
|
}
|
||||||
|
@ -157,3 +167,8 @@ article.pf-c-card.pf-m-flat.kc-login-settings-template
|
||||||
> .pf-c-card__body.kc-form-panel__body {
|
> .pf-c-card__body.kc-form-panel__body {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kc-override-action-tokens-subtitle {
|
||||||
|
font-size: var(--pf-global--FontSize--md);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { PartialImportDialog } from "./PartialImport";
|
||||||
import { SecurityDefences } from "./security-defences/SecurityDefences";
|
import { SecurityDefences } from "./security-defences/SecurityDefences";
|
||||||
import { RealmSettingsSessionsTab } from "./SessionsTab";
|
import { RealmSettingsSessionsTab } from "./SessionsTab";
|
||||||
import { RealmSettingsThemesTab } from "./ThemesTab";
|
import { RealmSettingsThemesTab } from "./ThemesTab";
|
||||||
|
import { RealmSettingsTokensTab } from "./TokensTab";
|
||||||
|
|
||||||
type RealmSettingsHeaderProps = {
|
type RealmSettingsHeaderProps = {
|
||||||
onChange: (value: boolean) => void;
|
onChange: (value: boolean) => void;
|
||||||
|
@ -227,7 +228,7 @@ export const RealmSettingsSection = () => {
|
||||||
>
|
>
|
||||||
<RealmSettingsGeneralTab
|
<RealmSettingsGeneralTab
|
||||||
save={save}
|
save={save}
|
||||||
reset={() => resetForm(realm!)}
|
reset={() => resetForm(realm)}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab
|
<Tab
|
||||||
|
@ -256,7 +257,7 @@ export const RealmSettingsSection = () => {
|
||||||
>
|
>
|
||||||
<RealmSettingsThemesTab
|
<RealmSettingsThemesTab
|
||||||
save={save}
|
save={save}
|
||||||
reset={() => resetForm(realm!)}
|
reset={() => resetForm(realm)}
|
||||||
realm={realm!}
|
realm={realm!}
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
@ -341,6 +342,15 @@ export const RealmSettingsSection = () => {
|
||||||
>
|
>
|
||||||
<RealmSettingsSessionsTab key={key} realm={realm} />
|
<RealmSettingsSessionsTab key={key} realm={realm} />
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
id="tokens"
|
||||||
|
eventKey="tokens"
|
||||||
|
data-testid="rs-tokens-tab"
|
||||||
|
aria-label="tokens-tab"
|
||||||
|
title={<TabTitleText>{t("realm-settings:tokens")}</TabTitleText>}
|
||||||
|
>
|
||||||
|
<RealmSettingsTokensTab reset={() => resetForm(realm)} />
|
||||||
|
</Tab>
|
||||||
</KeycloakTabs>
|
</KeycloakTabs>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</PageSection>
|
</PageSection>
|
||||||
|
|
|
@ -199,7 +199,7 @@ export const RealmSettingsSessionsTab = ({
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
</FormPanel>
|
</FormPanel>
|
||||||
<FormPanel
|
<FormPanel
|
||||||
title={t(".pf-c-data-list__item-draggable-iconclientSessionSettings")}
|
title={t("clientSessionSettings")}
|
||||||
className="kc-client-session-template"
|
className="kc-client-session-template"
|
||||||
>
|
>
|
||||||
<FormAccess
|
<FormAccess
|
||||||
|
@ -331,7 +331,7 @@ export const RealmSettingsSessionsTab = ({
|
||||||
label={t("common:enabled")}
|
label={t("common:enabled")}
|
||||||
labelOff={t("common:disabled")}
|
labelOff={t("common:disabled")}
|
||||||
isChecked={value}
|
isChecked={value}
|
||||||
onChange={onChange}
|
onChange={(value) => onChange(value.toString())}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
569
src/realm-settings/TokensTab.tsx
Normal file
569
src/realm-settings/TokensTab.tsx
Normal file
|
@ -0,0 +1,569 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Controller, useForm, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
ActionGroup,
|
||||||
|
AlertVariant,
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
NumberInput,
|
||||||
|
PageSection,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
SelectVariant,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
TextVariants,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
|
||||||
|
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
|
||||||
|
import { FormAccess } from "../components/form-access/FormAccess";
|
||||||
|
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||||
|
import { FormPanel } from "../components/scroll-form/FormPanel";
|
||||||
|
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import { useRealm } from "../context/realm-context/RealmContext";
|
||||||
|
|
||||||
|
import "./RealmSettingsSection.css";
|
||||||
|
import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
|
||||||
|
import { TimeSelector } from "../components/time-selector/TimeSelector";
|
||||||
|
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||||
|
import {
|
||||||
|
convertToFormValues,
|
||||||
|
forHumans,
|
||||||
|
flatten,
|
||||||
|
convertFormValuesToObject,
|
||||||
|
interpolateTimespan,
|
||||||
|
} from "../util";
|
||||||
|
|
||||||
|
type RealmSettingsSessionsTabProps = {
|
||||||
|
realm?: RealmRepresentation;
|
||||||
|
user?: UserRepresentation;
|
||||||
|
reset?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RealmSettingsTokensTab = ({
|
||||||
|
realm: initialRealm,
|
||||||
|
reset,
|
||||||
|
}: RealmSettingsSessionsTabProps) => {
|
||||||
|
const { t } = useTranslation("realm-settings");
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm: realmName } = useRealm();
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
const serverInfo = useServerInfo();
|
||||||
|
|
||||||
|
const [realm, setRealm] = useState(initialRealm);
|
||||||
|
const [defaultSigAlgDrpdwnIsOpen, setDefaultSigAlgDrpdwnOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const allComponentTypes =
|
||||||
|
serverInfo.componentTypes?.["org.keycloak.keys.KeyProvider"] ?? [];
|
||||||
|
|
||||||
|
const esOptions = ["ES256", "ES384", "ES512"];
|
||||||
|
|
||||||
|
const hmacAlgorithmOptions = allComponentTypes[2].properties[4].options;
|
||||||
|
|
||||||
|
const javaKeystoreAlgOptions = allComponentTypes[3].properties[3].options;
|
||||||
|
|
||||||
|
const defaultSigAlgOptions = esOptions.concat(
|
||||||
|
hmacAlgorithmOptions!,
|
||||||
|
javaKeystoreAlgOptions!
|
||||||
|
);
|
||||||
|
|
||||||
|
const form = useForm<RealmRepresentation>();
|
||||||
|
const { control } = useFormContext();
|
||||||
|
|
||||||
|
const offlineSessionMaxEnabled = useWatch({
|
||||||
|
control,
|
||||||
|
name: "offlineSessionMaxLifespanEnabled",
|
||||||
|
defaultValue: realm?.offlineSessionMaxLifespanEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setupForm = (realm: RealmRepresentation) => {
|
||||||
|
const { ...formValues } = realm;
|
||||||
|
form.reset(formValues);
|
||||||
|
Object.entries(realm).map((entry) => {
|
||||||
|
if (entry[0] === "attributes") {
|
||||||
|
convertToFormValues(entry[1], "attributes", form.setValue);
|
||||||
|
} else {
|
||||||
|
form.setValue(entry[0], entry[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useFetch(
|
||||||
|
() => adminClient.realms.findOne({ realm: realmName }),
|
||||||
|
(realm) => {
|
||||||
|
setRealm(realm);
|
||||||
|
setupForm(realm);
|
||||||
|
},
|
||||||
|
[realmName]
|
||||||
|
);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
const firstInstanceOnly = true;
|
||||||
|
const flattenedAttributes = convertFormValuesToObject(
|
||||||
|
flatten(form.getValues()["attributes"]),
|
||||||
|
firstInstanceOnly
|
||||||
|
);
|
||||||
|
|
||||||
|
const attributes = { ...flattenedAttributes, ...realm?.attributes };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const newRealm: RealmRepresentation = {
|
||||||
|
...realm,
|
||||||
|
...form.getValues(),
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
|
|
||||||
|
await adminClient.realms.update({ realm: realmName }, newRealm);
|
||||||
|
|
||||||
|
setupForm(newRealm);
|
||||||
|
setRealm(newRealm);
|
||||||
|
addAlert(t("saveSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addError("realm-settings:saveError", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PageSection variant="light">
|
||||||
|
<FormPanel
|
||||||
|
title={t("realm-settings:general")}
|
||||||
|
className="kc-sso-session-template"
|
||||||
|
>
|
||||||
|
<FormAccess
|
||||||
|
isHorizontal
|
||||||
|
role="manage-realm"
|
||||||
|
onSubmit={form.handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<FormGroup
|
||||||
|
label={t("defaultSigAlg")}
|
||||||
|
fieldId="kc-default-signature-algorithm"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:defaultSigAlg"
|
||||||
|
forLabel={t("defaultSigAlg")}
|
||||||
|
forID={t("common:helpLabel", { label: t("algorithm") })}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="defaultSignatureAlgorithm"
|
||||||
|
defaultValue={"RS256"}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Select
|
||||||
|
toggleId="kc-default-sig-alg"
|
||||||
|
onToggle={() =>
|
||||||
|
setDefaultSigAlgDrpdwnOpen(!defaultSigAlgDrpdwnIsOpen)
|
||||||
|
}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
onChange(value.toString());
|
||||||
|
setDefaultSigAlgDrpdwnOpen(false);
|
||||||
|
}}
|
||||||
|
selections={[value.toString()]}
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
aria-label={t("defaultSigAlg")}
|
||||||
|
isOpen={defaultSigAlgDrpdwnIsOpen}
|
||||||
|
data-testid="select-default-sig-alg"
|
||||||
|
>
|
||||||
|
{defaultSigAlgOptions!.map((p, idx) => (
|
||||||
|
<SelectOption
|
||||||
|
selected={p === value}
|
||||||
|
key={`default-sig-alg-${idx}`}
|
||||||
|
value={p}
|
||||||
|
></SelectOption>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</FormAccess>
|
||||||
|
</FormPanel>
|
||||||
|
<FormPanel
|
||||||
|
title={t("realm-settings:refreshTokens")}
|
||||||
|
className="kc-client-session-template"
|
||||||
|
>
|
||||||
|
<FormAccess
|
||||||
|
isHorizontal
|
||||||
|
role="manage-realm"
|
||||||
|
className="pf-u-mt-lg"
|
||||||
|
onSubmit={form.handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<FormGroup
|
||||||
|
hasNoPaddingTop
|
||||||
|
label={t("revokeRefreshToken")}
|
||||||
|
fieldId="kc-revoke-refresh-token"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:revokeRefreshToken"
|
||||||
|
forLabel={t("revokeRefreshToken")}
|
||||||
|
forID="revokeRefreshToken"
|
||||||
|
id="revokeRefreshToken"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="revokeRefreshToken"
|
||||||
|
control={form.control}
|
||||||
|
defaultValue={false}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<Switch
|
||||||
|
id="kc-revoke-refresh-token"
|
||||||
|
data-testid="revoke-refresh-token-switch"
|
||||||
|
aria-label="revoke-refresh-token-switch"
|
||||||
|
label={t("common:enabled")}
|
||||||
|
labelOff={t("common:disabled")}
|
||||||
|
isChecked={value}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("refreshTokenMaxReuse")}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:refreshTokenMaxReuse"
|
||||||
|
forLabel={t("refreshTokenMaxReuse")}
|
||||||
|
forID="refreshTokenMaxReuse"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
fieldId="refreshTokenMaxReuse"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="refreshTokenMaxReuse"
|
||||||
|
defaultValue={0}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<NumberInput
|
||||||
|
type="text"
|
||||||
|
id="refreshTokenMaxReuseMs"
|
||||||
|
value={value}
|
||||||
|
onPlus={() => onChange(value + 1)}
|
||||||
|
onMinus={() => onChange(value - 1)}
|
||||||
|
onChange={(event) =>
|
||||||
|
onChange(Number((event.target as HTMLInputElement).value))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</FormAccess>
|
||||||
|
</FormPanel>
|
||||||
|
<FormPanel
|
||||||
|
title={t("realm-settings:accessTokens")}
|
||||||
|
className="kc-offline-session-template"
|
||||||
|
>
|
||||||
|
<FormAccess
|
||||||
|
isHorizontal
|
||||||
|
role="manage-realm"
|
||||||
|
className="pf-u-mt-lg"
|
||||||
|
onSubmit={form.handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<FormGroup
|
||||||
|
label={t("accessTokenLifespan")}
|
||||||
|
fieldId="accessTokenLifespan"
|
||||||
|
helperText={`It is recommended for this value to be shorter than the SSO session idle timeout: ${interpolateTimespan(
|
||||||
|
forHumans(realm?.ssoSessionIdleTimeout!)
|
||||||
|
)}`}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:accessTokenLifespan"
|
||||||
|
forLabel={t("accessTokenLifespan")}
|
||||||
|
forID="accessTokenLifespan"
|
||||||
|
id="accessTokenLifespan"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="accessTokenLifespan"
|
||||||
|
defaultValue=""
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
validated={
|
||||||
|
value > realm?.ssoSessionIdleTimeout!
|
||||||
|
? "warning"
|
||||||
|
: "default"
|
||||||
|
}
|
||||||
|
className="kc-access-token-lifespan"
|
||||||
|
data-testid="access-token-lifespan-input"
|
||||||
|
aria-label="access-token-lifespan"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormGroup
|
||||||
|
label={t("accessTokenLifespanImplicitFlow")}
|
||||||
|
fieldId="accessTokenLifespanImplicitFlow"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:accessTokenLifespanImplicitFlow"
|
||||||
|
forLabel={t("accessTokenLifespanImplicitFlow")}
|
||||||
|
forID="accessTokenLifespanImplicitFlow"
|
||||||
|
id="accessTokenLifespanImplicitFlow"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="accessTokenLifespanForImplicitFlow"
|
||||||
|
defaultValue=""
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-access-token-lifespan-implicit"
|
||||||
|
data-testid="access-token-lifespan-implicit-input"
|
||||||
|
aria-label="access-token-lifespan-implicit"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("clientLoginTimeout")}
|
||||||
|
fieldId="clientLoginTimeout"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:clientLoginTimeout"
|
||||||
|
forLabel={t("clientLoginTimeout")}
|
||||||
|
forID="clientLoginTimeout"
|
||||||
|
id="clientLoginTimeout"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="accessCodeLifespan"
|
||||||
|
defaultValue=""
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-client-login-timeout"
|
||||||
|
data-testid="client-login-timeout-input"
|
||||||
|
aria-label="client-login-timeout"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{offlineSessionMaxEnabled && (
|
||||||
|
<FormGroup
|
||||||
|
label={t("offlineSessionMax")}
|
||||||
|
fieldId="offlineSessionMax"
|
||||||
|
id="offline-session-max-label"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:offlineSessionMax"
|
||||||
|
forLabel={t("offlineSessionMax")}
|
||||||
|
forID="offlineSessionMax"
|
||||||
|
id="offlineSessionMax"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="offlineSessionMaxLifespan"
|
||||||
|
defaultValue=""
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-offline-session-max"
|
||||||
|
data-testid="offline-session-max-input"
|
||||||
|
aria-label="offline-session-max-input"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</FormAccess>
|
||||||
|
</FormPanel>
|
||||||
|
<FormPanel
|
||||||
|
className="kc-login-settings-template"
|
||||||
|
title={t("actionTokens")}
|
||||||
|
>
|
||||||
|
<FormAccess
|
||||||
|
isHorizontal
|
||||||
|
role="manage-realm"
|
||||||
|
className="pf-u-mt-lg"
|
||||||
|
onSubmit={form.handleSubmit(save)}
|
||||||
|
>
|
||||||
|
<FormGroup
|
||||||
|
label={t("userInitiatedActionLifespan")}
|
||||||
|
id="kc-user-initiated-action-lifespan"
|
||||||
|
fieldId="userInitiatedActionLifespan"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:userInitiatedActionLifespan"
|
||||||
|
forLabel={t("userInitiatedActionLifespan")}
|
||||||
|
forID="userInitiatedActionLifespan"
|
||||||
|
id="userInitiatedActionLifespan"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="actionTokenGeneratedByUserLifespan"
|
||||||
|
defaultValue={""}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-user-initiated-action-lifespan"
|
||||||
|
data-testid="user-initiated-action-lifespan"
|
||||||
|
aria-label="user-initiated-action-lifespan"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("defaultAdminInitiated")}
|
||||||
|
fieldId="defaultAdminInitiated"
|
||||||
|
id="default-admin-initiated-label"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="realm-settings-help:defaultAdminInitiatedActionLifespan"
|
||||||
|
forLabel={t("defaultAdminInitiated")}
|
||||||
|
forID="defaultAdminInitiated"
|
||||||
|
id="defaultAdminInitiated"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="actionTokenGeneratedByAdminLifespan"
|
||||||
|
defaultValue={""}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-default-admin-initiated"
|
||||||
|
data-testid="default-admin-initated-input"
|
||||||
|
aria-label="default-admin-initated-input"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<Text
|
||||||
|
className="kc-override-action-tokens-subtitle"
|
||||||
|
component={TextVariants.h1}
|
||||||
|
>
|
||||||
|
{t("overrideActionTokens")}
|
||||||
|
</Text>
|
||||||
|
<FormGroup
|
||||||
|
label={t("emailVerification")}
|
||||||
|
fieldId="emailVerification"
|
||||||
|
id="email-verification"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="attributes.actionTokenGeneratedByUserLifespan-verify-email"
|
||||||
|
defaultValue={""}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-email-verification"
|
||||||
|
data-testid="email-verification-input"
|
||||||
|
aria-label="email-verification-input"
|
||||||
|
value={value}
|
||||||
|
onChange={(value: any) => onChange(value.toString())}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("idpAccountEmailVerification")}
|
||||||
|
fieldId="idpAccountEmailVerification"
|
||||||
|
id="idp-acct-label"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="attributes.actionTokenGeneratedByUserLifespan-idp-verify-account-via-email"
|
||||||
|
defaultValue={""}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-idp-email-verification"
|
||||||
|
data-testid="idp-email-verification-input"
|
||||||
|
aria-label="idp-email-verification"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("forgotPassword")}
|
||||||
|
fieldId="forgotPassword"
|
||||||
|
id="forgot-password-label"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="attributes.actionTokenGeneratedByUserLifespan-reset-credentials"
|
||||||
|
defaultValue={""}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-forgot-pw"
|
||||||
|
data-testid="forgot-pw-input"
|
||||||
|
aria-label="forgot-pw-input"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("executeActions")}
|
||||||
|
fieldId="executeActions"
|
||||||
|
id="execute-actions"
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
name="attributes.actionTokenGeneratedByUserLifespan-execute-actions"
|
||||||
|
defaultValue={""}
|
||||||
|
control={form.control}
|
||||||
|
render={({ onChange, value }) => (
|
||||||
|
<TimeSelector
|
||||||
|
className="kc-execute-actions"
|
||||||
|
data-testid="execute-actions-input"
|
||||||
|
aria-label="execute-actions-input"
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
units={["minutes", "hours", "days"]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<ActionGroup>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
type="submit"
|
||||||
|
data-testid="tokens-tab-save"
|
||||||
|
isDisabled={!form.formState.isDirty}
|
||||||
|
>
|
||||||
|
{t("common:save")}
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={reset}>
|
||||||
|
{t("common:revert")}
|
||||||
|
</Button>
|
||||||
|
</ActionGroup>
|
||||||
|
</FormAccess>
|
||||||
|
</FormPanel>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -88,5 +88,22 @@ export default {
|
||||||
"Max time a user has to complete a login. This is recommended to be relatively long, such as 30 minutes or more",
|
"Max time a user has to complete a login. This is recommended to be relatively long, such as 30 minutes or more",
|
||||||
loginActionTimeout:
|
loginActionTimeout:
|
||||||
"Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long, such as 5 minutes or more",
|
"Max time a user has to complete login related actions like update password or configure totp. This is recommended to be relatively long, such as 5 minutes or more",
|
||||||
|
defaultSigAlg: "Default algorithm used to sign tokens for the realm",
|
||||||
|
revokeRefreshToken:
|
||||||
|
"If enabled a refresh token can only be used up to 'Refresh Token Max Reuse' and is revoked when a different token is used. Otherwise refresh tokens are not revoked when used and can be used multiple times.",
|
||||||
|
refreshTokenMaxReuse:
|
||||||
|
"Maximum number of times a refresh token can be reused. When a different token is used, revocation is immediate.",
|
||||||
|
accessTokenLifespan:
|
||||||
|
"Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout",
|
||||||
|
accessTokenLifespanImplicitFlow:
|
||||||
|
"Max time before an access token issued during OpenID Connect Implicit Flow is expired. This value is recommended to be shorter than the SSO timeout. There is no possibility to refresh token during implicit flow, that's why there is a separate timeout different to 'Access Token Lifespan'",
|
||||||
|
clientLoginTimeout:
|
||||||
|
"Max time a client has to finish the access token protocol. This should normally be 1 minute.",
|
||||||
|
userInitiatedActionLifespan:
|
||||||
|
"Maximum time before an action permit sent by a user (such as a forgot password e-mail) is expired. This value is recommended to be short because it's expected that the user would react to self-created action quickly.",
|
||||||
|
defaultAdminInitiatedActionLifespan:
|
||||||
|
"Maximum time before an action permit sent to a user by administrator is expired. This value is recommended to be long to allow administrators to send e-mails for users that are currently offline. The default timeout can be overridden immediately before issuing the token.",
|
||||||
|
overrideActionTokens:
|
||||||
|
"Override default settings of maximum time before an action permit sent by a user (such as a forgot password e-mail) is expired for specific action. This value is recommended to be short because it's expected that the user would react to self-created action quickly.",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -171,8 +171,29 @@ export default {
|
||||||
loginSettings: "Login settings",
|
loginSettings: "Login settings",
|
||||||
loginTimeout: "Login timeout",
|
loginTimeout: "Login timeout",
|
||||||
loginActionTimeout: "Login action timeout",
|
loginActionTimeout: "Login action timeout",
|
||||||
|
refreshTokens: "Refresh tokens",
|
||||||
|
accessTokens: "Access tokens",
|
||||||
|
actionTokens: "Action tokens",
|
||||||
|
overrideActionTokens: "Override Action Tokens",
|
||||||
|
defaultSigAlg: "Default Signature Algorithm",
|
||||||
|
revokeRefreshToken: "Revoke Refresh Token",
|
||||||
|
refreshTokenMaxReuse: "Refresh Token Max Reuse",
|
||||||
|
accessTokenLifespan: "Access Token Lifespan",
|
||||||
|
accessTokenLifespanImplicitFlow: "Access Token Lifespan For Implicit Flow",
|
||||||
|
clientLoginTimeout: "Client Login Timeout",
|
||||||
|
userInitiatedActionLifespan: "User-Initiated Action Lifespan",
|
||||||
|
defaultAdminInitiated: "Default Admin-Initated Action Lifespan",
|
||||||
|
emailVerification: "Email Verification",
|
||||||
|
idpAccountEmailVerification: "IdP account email verification",
|
||||||
|
executeActions: "Execute actions",
|
||||||
|
tokens: "Tokens",
|
||||||
key: "Key",
|
key: "Key",
|
||||||
value: "Value",
|
value: "Value",
|
||||||
|
convertedToYearsValue: "{{convertedToYears}}",
|
||||||
|
convertedToDaysValue: "{{convertedToDays}}",
|
||||||
|
convertedToHoursValue: "{{convertedToHours}}",
|
||||||
|
convertedToMinutesValue: "{{convertedToMinutes}}",
|
||||||
|
convertedToSecondsValue: "{{convertedToSeconds}}",
|
||||||
pairCreatedSuccess: "Success! The localization text has been created.",
|
pairCreatedSuccess: "Success! The localization text has been created.",
|
||||||
pairCreatedError: "Error creating localization text.",
|
pairCreatedError: "Error creating localization text.",
|
||||||
supportedLocales: "Supported locales",
|
supportedLocales: "Supported locales",
|
||||||
|
|
|
@ -11,7 +11,8 @@ export type RealmSettingsTab =
|
||||||
| "keys"
|
| "keys"
|
||||||
| "events"
|
| "events"
|
||||||
| "securityDefences"
|
| "securityDefences"
|
||||||
| "sessions";
|
| "sessions"
|
||||||
|
| "tokens";
|
||||||
|
|
||||||
export type RealmSettingsParams = {
|
export type RealmSettingsParams = {
|
||||||
realm: string;
|
realm: string;
|
||||||
|
|
99
src/util.ts
99
src/util.ts
|
@ -4,6 +4,7 @@ import _ from "lodash";
|
||||||
import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
||||||
import type { ProviderRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
|
import type { ProviderRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
|
||||||
import type KeycloakAdminClient from "keycloak-admin";
|
import type KeycloakAdminClient from "keycloak-admin";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const sortProviders = (providers: {
|
export const sortProviders = (providers: {
|
||||||
[index: string]: ProviderRepresentation;
|
[index: string]: ProviderRepresentation;
|
||||||
|
@ -64,9 +65,31 @@ export const convertToFormValues = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const convertFormValuesToObject = (obj: any) => {
|
export const flatten = (
|
||||||
|
obj: Record<string, any> | undefined,
|
||||||
|
path = ""
|
||||||
|
): {} => {
|
||||||
|
if (!(obj instanceof Object)) return { [path.replace(/\.$/g, "")]: obj };
|
||||||
|
|
||||||
|
return Object.keys(obj).reduce((output, key) => {
|
||||||
|
return obj instanceof Array
|
||||||
|
? {
|
||||||
|
...output,
|
||||||
|
...flatten(obj[key as unknown as number], path + "[" + key + "]."),
|
||||||
|
}
|
||||||
|
: { ...output, ...flatten(obj[key], path + key + ".") };
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const convertFormValuesToObject = (
|
||||||
|
obj: any,
|
||||||
|
firstInstanceOnly?: boolean
|
||||||
|
) => {
|
||||||
const keyValues = Object.keys(obj).map((key) => {
|
const keyValues = Object.keys(obj).map((key) => {
|
||||||
const newKey = key.replace(/-/g, ".");
|
const newKey = firstInstanceOnly
|
||||||
|
? key.replace(/-/, ".")
|
||||||
|
: key.replace(/-/g, ".");
|
||||||
|
console.log(newKey);
|
||||||
return { [newKey]: obj[key] };
|
return { [newKey]: obj[key] };
|
||||||
});
|
});
|
||||||
return Object.assign({}, ...keyValues);
|
return Object.assign({}, ...keyValues);
|
||||||
|
@ -94,3 +117,75 @@ export const getBaseUrl = (adminClient: KeycloakAdminClient) => {
|
||||||
|
|
||||||
export const emailRegexPattern =
|
export const emailRegexPattern =
|
||||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
|
||||||
|
export const forHumans = (seconds: number) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const levels: [
|
||||||
|
[number, string],
|
||||||
|
[number, string],
|
||||||
|
[number, string],
|
||||||
|
[number, string],
|
||||||
|
[number, string]
|
||||||
|
] = [
|
||||||
|
[Math.floor(seconds / 31536000), t("common:times.years")],
|
||||||
|
[Math.floor((seconds % 31536000) / 86400), t("common:times.days")],
|
||||||
|
[
|
||||||
|
Math.floor(((seconds % 31536000) % 86400) / 3600),
|
||||||
|
t("common:times.hours"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Math.floor((((seconds % 31536000) % 86400) % 3600) / 60),
|
||||||
|
t("common:times.minutes"),
|
||||||
|
],
|
||||||
|
[(((seconds % 31536000) % 86400) % 3600) % 60, t("common:times.seconds")],
|
||||||
|
];
|
||||||
|
let returntext = "";
|
||||||
|
|
||||||
|
for (let i = 0, max = levels.length; i < max; i++) {
|
||||||
|
if (levels[i][0] === 0) continue;
|
||||||
|
returntext +=
|
||||||
|
" " +
|
||||||
|
levels[i][0] +
|
||||||
|
" " +
|
||||||
|
(levels[i][0] === 1
|
||||||
|
? levels[i][1].substr(0, levels[i][1].length - 1)
|
||||||
|
: levels[i][1]);
|
||||||
|
}
|
||||||
|
return returntext.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const interpolateTimespan = (forHumans: string) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const timespan = forHumans.split(" ");
|
||||||
|
|
||||||
|
if (timespan[1] === "Years") {
|
||||||
|
return t(`realm-settings:convertedToYearsValue`, {
|
||||||
|
convertedToYears: forHumans,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timespan[1] === "Days") {
|
||||||
|
return t(`realm-settings:convertedToDaysValue`, {
|
||||||
|
convertedToYears: forHumans,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timespan[1] === "Hours") {
|
||||||
|
return t(`realm-settings:convertedToHoursValue`, {
|
||||||
|
convertedToHours: forHumans,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timespan[1] === "Minutes") {
|
||||||
|
return t(`realm-settings:convertedToMinutesValue`, {
|
||||||
|
convertedToMinutes: forHumans,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timespan[1] === "Seconds") {
|
||||||
|
return t(`realm-settings:convertedToSecondsValue`, {
|
||||||
|
convertedToSeconds: forHumans,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue