Rename kcContext -> KcContext and improve consistency

This commit is contained in:
Joseph Garrone
2024-06-05 21:13:58 +02:00
parent 3e6d679838
commit e8a11991a0
84 changed files with 191 additions and 184 deletions

View File

@ -0,0 +1,778 @@
import type {
ThemeType,
LoginThemePageId,
nameOfTheLocalizationRealmOverridesUserProfileProperty
} from "keycloakify/bin/shared/constants";
import type { ExtractAfterStartingWith } from "keycloakify/tools/ExtractAfterStartingWith";
import type { ValueOf } from "keycloakify/tools/ValueOf";
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
import type { MessageKey } from "../i18n/i18n";
export type ExtendKcContext<
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
KcContextExtraPropertiesPerPage extends Record<string, Record<string, unknown>>
> = ValueOf<{
[PageId in keyof KcContextExtraPropertiesPerPage | KcContext["pageId"]]: Extract<
KcContext,
{ pageId: PageId }
> extends never
? KcContext.Common &
KcContextExtraProperties & {
pageId: PageId;
} & KcContextExtraPropertiesPerPage[PageId]
: Extract<KcContext, { pageId: PageId }> &
KcContextExtraProperties &
KcContextExtraPropertiesPerPage[PageId];
}>;
/** Take theses type definition with a grain of salt.
* Some values might be undefined on some pages.
* (ex: url.loginAction is undefined on error.ftl)
*/
export type KcContext =
| KcContext.Login
| KcContext.Register
| KcContext.Info
| KcContext.Error
| KcContext.LoginResetPassword
| KcContext.LoginVerifyEmail
| KcContext.Terms
| KcContext.LoginDeviceVerifyUserCode
| KcContext.LoginOauthGrant
| KcContext.LoginOtp
| KcContext.LoginUsername
| KcContext.WebauthnAuthenticate
| KcContext.WebauthnRegister
| KcContext.LoginPassword
| KcContext.LoginUpdatePassword
| KcContext.LoginUpdateProfile
| KcContext.LoginIdpLinkConfirm
| KcContext.LoginIdpLinkEmail
| KcContext.LoginPageExpired
| KcContext.LoginConfigTotp
| KcContext.LogoutConfirm
| KcContext.IdpReviewUserProfile
| KcContext.UpdateEmail
| KcContext.SelectAuthenticator
| KcContext.SamlPostForm
| KcContext.DeleteCredential
| KcContext.Code
| KcContext.DeleteAccountConfirm
| KcContext.FrontchannelLogout
| KcContext.LoginRecoveryAuthnCodeConfig
| KcContext.LoginRecoveryAuthnCodeInput
| KcContext.LoginResetOtp
| KcContext.LoginX509Info
| KcContext.WebauthnError;
assert<KcContext["themeType"] extends ThemeType ? true : false>();
export declare namespace KcContext {
export type Common = {
themeVersion: string;
keycloakifyVersion: string;
themeType: "login";
themeName: string;
url: {
loginAction: string;
resourcesPath: string;
resourcesCommonPath: string;
loginRestartFlowUrl: string;
loginUrl: string;
ssoLoginInOtherTabsUrl: string;
};
realm: {
name: string;
displayName?: string;
displayNameHtml?: string;
internationalizationEnabled: boolean;
registrationEmailAsUsername: boolean;
};
/** Undefined if !realm.internationalizationEnabled */
locale?: {
supported: {
url: string;
label: string;
languageTag: string;
}[];
currentLanguageTag: string;
};
auth?: {
showUsername?: boolean;
showResetCredentials?: boolean;
showTryAnotherWayLink?: boolean;
attemptedUsername?: string;
};
scripts: string[];
message?: {
type: "success" | "warning" | "error" | "info";
summary: string;
};
client: {
clientId: string;
name?: string;
description?: string;
attributes: Record<string, string>;
};
isAppInitiatedAction?: boolean;
messagesPerField: {
/**
* Return text if message for given field exists. Useful eg. to add css styles for fields with message.
*
* @param fieldName to check for
* @param text to return
* @return text if message exists for given field, else undefined
*/
printIfExists: <T extends string>(
fieldName: string,
text: T
) => T | undefined;
/**
* Check if exists error message for given fields
*
* @param fields
* @return boolean
*/
existsError: (fieldName: string, ...otherFiledNames: string[]) => boolean;
/**
* Get message for given field.
*
* @param fieldName
* @return message text or empty string
*/
get: (fieldName: string) => string;
/**
* Check if message for given field exists
*
* @param field
* @return boolean
*/
exists: (fieldName: string) => boolean;
getFirstError: (...fieldNames: string[]) => string;
};
authenticationSession?: {
authSessionId: string;
tabId: string;
ssoLoginInOtherTabsUrl: string;
};
properties: {};
__localizationRealmOverridesUserProfile?: Record<string, string>;
};
export type SamlPostForm = Common & {
pageId: "saml-post-form.ftl";
samlPost: {
url: string;
SAMLRequest?: string;
SAMLResponse?: string;
relayState?: string;
};
};
export type Login = Common & {
pageId: "login.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
loginWithEmailAllowed: boolean;
rememberMe: boolean;
password: boolean;
resetPasswordAllowed: boolean;
registrationAllowed: boolean;
};
auth: {
selectedCredential?: string;
};
registrationDisabled: boolean;
login: {
username?: string;
rememberMe?: string; // "on" | undefined
password?: string;
};
usernameHidden?: boolean;
social: {
displayInfo: boolean;
providers?: {
loginUrl: string;
alias: string;
providerId: string;
displayName: string;
iconClasses?: string;
}[];
};
};
export type Register = Common & {
pageId: "register.ftl";
profile: UserProfile;
url: {
registrationAction: string;
};
passwordRequired: boolean;
recaptchaRequired: boolean;
recaptchaSiteKey?: string;
/**
* Theses values are added by: https://github.com/jcputney/keycloak-theme-additional-info-extension
* A Keycloak Java extension used as dependency in Keycloakify.
*/
passwordPolicies?: PasswordPolicies;
termsAcceptanceRequired?: boolean;
};
export type Info = Common & {
pageId: "info.ftl";
messageHeader?: string;
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[];
skipLink: boolean;
pageRedirectUri?: string;
actionUri?: string;
client: {
baseUrl?: string;
};
};
export type Error = Common & {
pageId: "error.ftl";
client?: {
baseUrl?: string;
};
message: NonNullable<Common["message"]>;
skipLink?: boolean;
};
export type LoginResetPassword = Common & {
pageId: "login-reset-password.ftl";
realm: {
loginWithEmailAllowed: boolean;
duplicateEmailsAllowed: boolean;
};
url: {
loginResetCredentialsUrl: string;
};
auth: {
attemptedUsername?: string;
};
};
export type LoginVerifyEmail = Common & {
pageId: "login-verify-email.ftl";
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
user?: {
email: string;
};
};
export type Terms = Common & {
pageId: "terms.ftl";
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
user?: {
id: string;
username: string;
attributes: Record<string, string[]>;
email: string;
emailVerified: boolean;
firstName?: string;
lastName?: string;
markedForEviction?: boolean;
};
};
export type LoginDeviceVerifyUserCode = Common & {
pageId: "login-oauth2-device-verify-user-code.ftl";
url: {
oauth2DeviceVerificationAction: string;
};
};
export type LoginOauthGrant = Common & {
pageId: "login-oauth-grant.ftl";
oauth: {
code: string;
client: string;
clientScopesRequested: {
consentScreenText: string;
dynamicScopeParameter?: string;
}[];
};
url: {
oauthAction: string;
};
};
export type LoginOtp = Common & {
pageId: "login-otp.ftl";
otpLogin: {
userOtpCredentials: {
id: string;
userLabel: string;
}[];
selectedCredentialId?: string;
};
};
export type LoginUsername = Common & {
pageId: "login-username.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
loginWithEmailAllowed: boolean;
rememberMe: boolean;
password: boolean;
resetPasswordAllowed: boolean;
registrationAllowed: boolean;
};
registrationDisabled: boolean;
login: {
username?: string;
rememberMe?: string;
};
usernameHidden?: boolean;
social: Login["social"];
};
export type LoginPassword = Common & {
pageId: "login-password.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
resetPasswordAllowed: boolean;
};
auth?: {
showUsername?: boolean;
showResetCredentials?: boolean;
showTryAnotherWayLink?: boolean;
attemptedUsername?: string;
};
social: {
displayInfo: boolean;
};
};
export type WebauthnAuthenticate = Common & {
pageId: "webauthn-authenticate.ftl";
authenticators: {
authenticators: WebauthnAuthenticate.WebauthnAuthenticator[];
};
challenge: string;
// I hate this:
userVerification: UserVerificationRequirement | "not specified";
rpId: string;
createTimeout: string;
isUserIdentified: "true" | "false";
shouldDisplayAuthenticators: boolean;
social: {
displayInfo: boolean;
};
login: {};
realm: {
password: boolean;
registrationAllowed: boolean;
};
registrationDisabled?: boolean;
url: {
registrationUrl?: string;
};
};
export namespace WebauthnAuthenticate {
export type WebauthnAuthenticator = {
credentialId: string;
transports: {
iconClass: string;
displayNameProperties?: MessageKey[];
};
label: string;
createdAt: string;
};
}
export type WebauthnRegister = Common & {
pageId: "webauthn-register.ftl";
challenge: string;
userid: string;
username: string;
signatureAlgorithms: string[];
rpEntityName: string;
rpId: string;
attestationConveyancePreference: string;
authenticatorAttachment: string;
requireResidentKey: string;
userVerificationRequirement: string;
createTimeout: number;
excludeCredentialIds: string;
isSetRetry?: boolean;
isAppInitiatedAction?: boolean;
};
export type LoginUpdatePassword = Common & {
pageId: "login-update-password.ftl";
};
export type LoginIdpLinkConfirm = Common & {
pageId: "login-idp-link-confirm.ftl";
idpAlias: string;
};
export type LoginIdpLinkEmail = Common & {
pageId: "login-idp-link-email.ftl";
brokerContext: {
username: string;
};
idpAlias: string;
};
export type LoginPageExpired = Common & {
pageId: "login-page-expired.ftl";
};
export type LoginConfigTotp = Common & {
pageId: "login-config-totp.ftl";
mode?: "qr" | "manual" | undefined | null;
totp: {
totpSecretEncoded: string;
qrUrl: string;
policy: {
algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
digits: number;
lookAheadWindow: number;
getAlgorithmKey: () => string;
} & (
| {
type: "totp";
period: number;
}
| {
type: "hotp";
initialCounter: number;
}
);
supportedApplications: string[];
totpSecretQrCode: string;
manualUrl: string;
totpSecret: string;
otpCredentials: { id: string; userLabel: string }[];
};
};
export type LogoutConfirm = Common & {
pageId: "logout-confirm.ftl";
url: {
logoutConfirmAction: string;
};
client: {
baseUrl?: string;
};
logoutConfirm: {
code: string;
skipLink?: boolean;
};
};
export type LoginUpdateProfile = Common & {
pageId: "login-update-profile.ftl";
profile: UserProfile;
};
export type IdpReviewUserProfile = Common & {
pageId: "idp-review-user-profile.ftl";
profile: UserProfile;
};
export type UpdateEmail = Common & {
pageId: "update-email.ftl";
profile: UserProfile;
};
export type SelectAuthenticator = Common & {
pageId: "select-authenticator.ftl";
auth: {
authenticationSelections: SelectAuthenticator.AuthenticationSelection[];
};
};
export namespace SelectAuthenticator {
export type AuthenticationSelection = {
authExecId: string;
displayName:
| "otp-display-name"
| "password-display-name"
| "auth-username-form-display-name"
| "auth-username-password-form-display-name"
| "webauthn-display-name"
| "webauthn-passwordless-display-name";
helpText:
| "otp-help-text"
| "password-help-text"
| "auth-username-form-help-text"
| "auth-username-password-form-help-text"
| "webauthn-help-text"
| "webauthn-passwordless-help-text";
iconCssClass?:
| "kcAuthenticatorDefaultClass"
| "kcAuthenticatorPasswordClass"
| "kcAuthenticatorOTPClass"
| "kcAuthenticatorWebAuthnClass"
| "kcAuthenticatorWebAuthnPasswordlessClass";
};
}
export type DeleteCredential = Common & {
pageId: "delete-credential.ftl";
credentialLabel: string;
};
export type Code = Common & {
pageId: "code.ftl";
code: {
success: boolean;
code?: string;
error?: string;
};
};
export type DeleteAccountConfirm = Common & {
pageId: "delete-account-confirm.ftl";
triggered_from_aia: boolean;
};
export type FrontchannelLogout = Common & {
pageId: "frontchannel-logout.ftl";
logout: {
clients: {
name: string;
frontChannelLogoutUrl: string;
}[];
logoutRedirectUri?: string;
};
};
export type LoginRecoveryAuthnCodeConfig = Common & {
pageId: "login-recovery-authn-code-config.ftl";
recoveryAuthnCodesConfigBean: {
generatedRecoveryAuthnCodesList: string[];
generatedRecoveryAuthnCodesAsString: string;
generatedAt: number;
};
};
export type LoginRecoveryAuthnCodeInput = Common & {
pageId: "login-recovery-authn-code-input.ftl";
recoveryAuthnCodesInputBean: {
codeNumber: number;
};
};
export type LoginResetOtp = Common & {
pageId: "login-reset-otp.ftl";
configuredOtpCredentials: {
userOtpCredentials: {
id: string;
userLabel: string;
}[];
selectedCredentialId: string;
};
};
export type LoginX509Info = Common & {
pageId: "login-x509-info.ftl";
x509: {
formData: {
subjectDN?: string;
isUserEnabled?: boolean;
username?: string;
};
};
};
export type WebauthnError = Common & {
pageId: "webauthn-error.ftl";
isAppInitiatedAction?: boolean;
};
}
export type UserProfile = {
attributesByName: Record<string, Attribute>;
html5DataAnnotations?: Record<string, string>;
};
export type Attribute = {
name: string;
displayName?: string;
required: boolean;
value?: string;
values?: string[];
group?: {
html5DataAnnotations: Record<string, string>;
displayHeader?: string;
name: string;
displayDescription?: string;
};
html5DataAnnotations?: {
kcNumberFormat?: string;
kcNumberUnFormat?: string;
};
readOnly: boolean;
validators: Validators;
annotations: {
inputType?: string;
inputTypeSize?: `${number}` | number;
inputOptionsFromValidation?: string;
inputOptionLabels?: Record<string, string | undefined>;
inputOptionLabelsI18nPrefix?: string;
inputTypeCols?: `${number}` | number;
inputTypeRows?: `${number}` | number;
inputTypeMaxlength?: `${number}` | number;
inputHelperTextBefore?: string;
inputHelperTextAfter?: string;
inputTypePlaceholder?: string;
inputTypePattern?: string;
inputTypeMinlength?: `${number}` | number;
inputTypeMax?: string;
inputTypeMin?: string;
inputTypeStep?: string;
};
multivalued?: boolean;
autocomplete?:
| "on"
| "off"
| "name"
| "honorific-prefix"
| "given-name"
| "additional-name"
| "family-name"
| "honorific-suffix"
| "nickname"
| "email"
| "username"
| "new-password"
| "current-password"
| "one-time-code"
| "organization-title"
| "organization"
| "street-address"
| "address-line1"
| "address-line2"
| "address-line3"
| "address-level4"
| "address-level3"
| "address-level2"
| "address-level1"
| "country"
| "country-name"
| "postal-code"
| "cc-name"
| "cc-given-name"
| "cc-additional-name"
| "cc-family-name"
| "cc-number"
| "cc-exp"
| "cc-exp-month"
| "cc-exp-year"
| "cc-csc"
| "cc-type"
| "transaction-currency"
| "transaction-amount"
| "language"
| "bday"
| "bday-day"
| "bday-month"
| "bday-year"
| "sex"
| "tel"
| "tel-country-code"
| "tel-national"
| "tel-area-code"
| "tel-local"
| "tel-extension"
| "impp"
| "url"
| "photo";
};
export type Validators = {
length?: Validators.DoIgnoreEmpty & Validators.Range;
integer?: Validators.DoIgnoreEmpty & Validators.Range;
email?: Validators.DoIgnoreEmpty;
pattern?: Validators.DoIgnoreEmpty & Validators.ErrorMessage & { pattern: string };
options?: Validators.Options;
multivalued?: Validators.DoIgnoreEmpty & Validators.Range;
// NOTE: Following are the validators for which we don't implement client side validation yet
// or for which the validation can't be performed on the client side.
/*
double?: Validators.DoIgnoreEmpty & Validators.Range;
"up-immutable-attribute"?: {};
"up-attribute-required-by-metadata-value"?: {};
"up-username-has-value"?: {};
"up-duplicate-username"?: {};
"up-username-mutation"?: {};
"up-email-exists-as-username"?: {};
"up-blank-attribute-value"?: Validators.ErrorMessage & { "fail-on-null": boolean; };
"up-duplicate-email"?: {};
"local-date"?: Validators.DoIgnoreEmpty;
"person-name-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
uri?: Validators.DoIgnoreEmpty;
"username-prohibited-characters"?: Validators.DoIgnoreEmpty & Validators.ErrorMessage;
*/
};
export declare namespace Validators {
export type DoIgnoreEmpty = {
"ignore.empty.value"?: boolean;
};
export type ErrorMessage = {
"error-message"?: string;
};
export type Range = {
min?: `${number}` | number;
max?: `${number}` | number;
};
export type Options = {
options: string[];
};
}
{
type Got = KcContext["pageId"];
type Expected = LoginThemePageId;
type OnlyInGot = Exclude<Got, Expected>;
type OnlyInExpected = Exclude<Expected, Got>;
assert<Equals<OnlyInGot, never>>();
assert<Equals<OnlyInExpected, never>>();
}
export type PasswordPolicies = {
/** The minimum length of the password */
length?: number;
/** The minimum number of digits required in the password */
digits?: number;
/** The minimum number of lowercase characters required in the password */
lowerCase?: number;
/** The minimum number of uppercase characters required in the password */
upperCase?: number;
/** The minimum number of special characters required in the password */
specialChars?: number;
/** Whether the password can be the username */
notUsername?: boolean;
/** Whether the password can be the email address */
notEmail?: boolean;
};
assert<
KcContext.Common extends Partial<
Record<typeof nameOfTheLocalizationRealmOverridesUserProfileProperty, unknown>
>
? true
: false
>();

View File

@ -0,0 +1,80 @@
import type { ExtendKcContext, KcContext as KcContextBase } from "./KcContext";
import type { LoginThemePageId } from "keycloakify/bin/shared/constants";
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
import { deepAssign } from "keycloakify/tools/deepAssign";
import { structuredCloneButFunctions } from "keycloakify/tools/structuredCloneButFunctions";
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
import { exclude } from "tsafe/exclude";
export function createGetKcContextMock<
KcContextExtraProperties extends { properties?: Record<string, string | undefined> },
KcContextExtraPropertiesPerPage extends Record<
`${string}.ftl`,
Record<string, unknown>
>
>(params: {
kcContextExtraProperties: KcContextExtraProperties;
kcContextExtraPropertiesPerPage: KcContextExtraPropertiesPerPage;
overrides?: DeepPartial<KcContextExtraProperties & KcContextBase.Common>;
overridesPerPage?: {
[PageId in
| LoginThemePageId
| keyof KcContextExtraPropertiesPerPage]?: DeepPartial<
Extract<
ExtendKcContext<
KcContextExtraProperties,
KcContextExtraPropertiesPerPage
>,
{ pageId: PageId }
>
>;
};
}) {
const {
kcContextExtraProperties,
kcContextExtraPropertiesPerPage,
overrides: overrides_global,
overridesPerPage: overridesPerPage_global
} = params;
type KcContext = ExtendKcContext<
KcContextExtraProperties,
KcContextExtraPropertiesPerPage
>;
function getKcContextMock<
PageId extends LoginThemePageId | keyof KcContextExtraPropertiesPerPage
>(params: {
pageId: PageId;
overrides?: DeepPartial<Extract<KcContext, { pageId: PageId }>>;
}): Extract<KcContext, { pageId: PageId }> {
const { pageId, overrides } = params;
const kcContextMock = structuredCloneButFunctions(
kcContextMocks.find(kcContextMock => kcContextMock.pageId === pageId) ?? {
...kcContextCommonMock,
pageId
}
);
[
kcContextExtraProperties,
kcContextExtraPropertiesPerPage[pageId],
overrides_global,
overridesPerPage_global?.[pageId],
overrides
]
.filter(exclude(undefined))
.forEach(overrides =>
deepAssign({
target: kcContextMock,
source: overrides
})
);
// @ts-expect-error
return kcContextMock;
}
return { getKcContextMock };
}

View File

@ -0,0 +1,8 @@
export type {
ExtendKcContext,
KcContext,
Attribute,
PasswordPolicies,
Validators
} from "./KcContext";
export { createGetKcContextMock } from "./getKcContextMock";

View File

@ -0,0 +1,572 @@
import "keycloakify/tools/Object.fromEntries";
import type { KcContext, Attribute } from "./KcContext";
import {
resources_common,
keycloak_resources,
type LoginThemePageId
} from "keycloakify/bin/shared/constants";
import { id } from "tsafe/id";
import { assert, type Equals } from "tsafe/assert";
import { BASE_URL } from "keycloakify/lib/BASE_URL";
const attributesByName = Object.fromEntries(
id<Attribute[]>([
{
validators: {
length: {
"ignore.empty.value": true,
min: "3",
max: "255"
}
},
displayName: "${username}",
annotations: {},
required: true,
autocomplete: "username",
readOnly: false,
name: "username"
},
{
validators: {
length: {
max: "255",
"ignore.empty.value": true
},
email: {
"ignore.empty.value": true
},
pattern: {
"ignore.empty.value": true,
pattern: "gmail\\.com$"
}
},
displayName: "${email}",
annotations: {},
required: true,
autocomplete: "email",
readOnly: false,
name: "email"
},
{
validators: {
length: {
max: "255",
"ignore.empty.value": true
}
},
displayName: "${firstName}",
annotations: {},
required: true,
readOnly: false,
name: "firstName"
},
{
validators: {
length: {
max: "255",
"ignore.empty.value": true
}
},
displayName: "${lastName}",
annotations: {},
required: true,
readOnly: false,
name: "lastName"
}
]).map(attribute => [attribute.name, attribute])
);
const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`;
export const kcContextCommonMock: KcContext.Common = {
themeVersion: "0.0.0",
keycloakifyVersion: "0.0.0",
themeType: "login",
themeName: "my-theme-name",
url: {
loginAction: "#",
resourcesPath,
resourcesCommonPath: `${resourcesPath}/${resources_common}`,
loginRestartFlowUrl:
"/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
loginUrl:
"/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg",
ssoLoginInOtherTabsUrl:
"/auth/realms/myrealm/login-actions/switch?client_id=account&tab_id=HoAx28ja4xg"
},
realm: {
name: "myrealm",
displayName: "myrealm",
displayNameHtml: "myrealm",
internationalizationEnabled: true,
registrationEmailAsUsername: false
},
messagesPerField: {
printIfExists: () => {
return undefined;
},
existsError: () => false,
get: fieldName => `Fake error for ${fieldName}`,
exists: () => false,
getFirstError: fieldName => `Fake error for ${fieldName}`
},
locale: {
supported: [
/* spell-checker: disable */
["de", "Deutsch"],
["no", "Norsk"],
["ru", "Русский"],
["sv", "Svenska"],
["pt-BR", "Português (Brasil)"],
["lt", "Lietuvių"],
["en", "English"],
["it", "Italiano"],
["fr", "Français"],
["zh-CN", "中文简体"],
["es", "Español"],
["cs", "Čeština"],
["ja", "日本語"],
["sk", "Slovenčina"],
["pl", "Polski"],
["ca", "Català"],
["nl", "Nederlands"],
["tr", "Türkçe"]
/* spell-checker: enable */
].map(
([languageTag, label]) =>
({
languageTag,
label,
url: "https://gist.github.com/garronej/52baaca1bb925f2296ab32741e062b8e"
}) as const
),
currentLanguageTag: "en"
},
auth: {
showUsername: false,
showResetCredentials: false,
showTryAnotherWayLink: false
},
client: {
clientId: "myApp",
attributes: {}
},
scripts: [],
isAppInitiatedAction: false,
properties: {},
__localizationRealmOverridesUserProfile: {}
};
const loginUrl = {
...kcContextCommonMock.url,
loginResetCredentialsUrl:
"/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
registrationUrl:
"/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg",
oauth2DeviceVerificationAction: "/auth/realms/myrealm/device",
oauthAction:
"/auth/realms/myrealm/login-actions/consent?client_id=account&tab_id=HoAx28ja4xg"
};
export const kcContextMocks = [
id<KcContext.Login>({
...kcContextCommonMock,
pageId: "login.ftl",
url: loginUrl,
realm: {
...kcContextCommonMock.realm,
loginWithEmailAllowed: true,
rememberMe: true,
password: true,
resetPasswordAllowed: true,
registrationAllowed: true
},
auth: kcContextCommonMock.auth!,
social: {
displayInfo: true
},
usernameHidden: false,
login: {},
registrationDisabled: false
}),
id<KcContext.Register>({
...kcContextCommonMock,
url: {
...loginUrl,
registrationAction:
"http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
},
isAppInitiatedAction: false,
passwordRequired: true,
recaptchaRequired: false,
pageId: "register.ftl",
profile: {
attributesByName
},
scripts: [
//"https://www.google.com/recaptcha/api.js"
]
}),
id<KcContext.Info>({
...kcContextCommonMock,
pageId: "info.ftl",
messageHeader: "<Message header>",
requiredActions: undefined,
skipLink: false,
actionUri: "#",
client: {
clientId: "myApp",
baseUrl: "#",
attributes: {}
}
}),
id<KcContext.Error>({
...kcContextCommonMock,
pageId: "error.ftl",
client: {
clientId: "myApp",
baseUrl: "#",
attributes: {}
},
message: {
type: "error",
summary: "This is the error message"
}
}),
id<KcContext.LoginResetPassword>({
...kcContextCommonMock,
pageId: "login-reset-password.ftl",
realm: {
...kcContextCommonMock.realm,
loginWithEmailAllowed: false,
duplicateEmailsAllowed: false
},
url: loginUrl,
auth: {}
}),
id<KcContext.LoginVerifyEmail>({
...kcContextCommonMock,
pageId: "login-verify-email.ftl",
user: {
email: "john.doe@gmail.com"
}
}),
id<KcContext.Terms>({
...kcContextCommonMock,
pageId: "terms.ftl"
}),
id<KcContext.LoginDeviceVerifyUserCode>({
...kcContextCommonMock,
pageId: "login-oauth2-device-verify-user-code.ftl",
url: loginUrl
}),
id<KcContext.LoginOauthGrant>({
...kcContextCommonMock,
pageId: "login-oauth-grant.ftl",
oauth: {
code: "5-1N4CIzfi1aprIQjmylI-9e3spLCWW9i5d-GDcs-Sw",
clientScopesRequested: [
{ consentScreenText: "${profileScopeConsentText}" },
{ consentScreenText: "${rolesScopeConsentText}" },
{ consentScreenText: "${emailScopeConsentText}" }
],
client: "account"
},
url: loginUrl
}),
id<KcContext.LoginOtp>({
...kcContextCommonMock,
pageId: "login-otp.ftl",
otpLogin: {
userOtpCredentials: [
{
id: "id1",
userLabel: "label1"
},
{
id: "id2",
userLabel: "label2"
}
]
}
}),
id<KcContext.LoginUsername>({
...kcContextCommonMock,
pageId: "login-username.ftl",
url: loginUrl,
realm: {
...kcContextCommonMock.realm,
loginWithEmailAllowed: true,
rememberMe: true,
password: true,
resetPasswordAllowed: true,
registrationAllowed: true
},
social: {
displayInfo: true
},
usernameHidden: false,
login: {},
registrationDisabled: false
}),
id<KcContext.LoginPassword>({
...kcContextCommonMock,
pageId: "login-password.ftl",
url: loginUrl,
realm: {
...kcContextCommonMock.realm,
resetPasswordAllowed: true
},
social: {
displayInfo: false
}
}),
id<KcContext.WebauthnAuthenticate>({
...kcContextCommonMock,
pageId: "webauthn-authenticate.ftl",
url: loginUrl,
authenticators: {
authenticators: []
},
realm: {
...kcContextCommonMock.realm,
password: true,
registrationAllowed: true
},
challenge: "",
userVerification: "not specified",
rpId: "",
createTimeout: "0",
isUserIdentified: "false",
shouldDisplayAuthenticators: false,
social: {
displayInfo: false
},
login: {}
}),
id<KcContext.LoginUpdatePassword>({
...kcContextCommonMock,
pageId: "login-update-password.ftl"
}),
id<KcContext.LoginUpdateProfile>({
...kcContextCommonMock,
pageId: "login-update-profile.ftl",
profile: {
attributesByName
}
}),
id<KcContext.LoginIdpLinkConfirm>({
...kcContextCommonMock,
pageId: "login-idp-link-confirm.ftl",
idpAlias: "FranceConnect"
}),
id<KcContext.LoginIdpLinkEmail>({
...kcContextCommonMock,
pageId: "login-idp-link-email.ftl",
idpAlias: "FranceConnect",
brokerContext: {
username: "anUsername"
}
}),
id<KcContext.LoginConfigTotp>({
...kcContextCommonMock,
pageId: "login-config-totp.ftl",
totp: {
totpSecretEncoded: "KVVF G2BY N4YX S6LB IUYT K2LH IFYE 4SBV",
qrUrl: "#",
totpSecretQrCode:
"iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACM0lEQVR4Xu3OIZJgOQwDUDFd2UxiurLAVnnbHw4YGDKtSiWOn4Gxf81//7r/+q8b4HfLGBZDK9d85NmNR+sB42sXvOYrN5P1DcgYYFTGfOlbzE8gzwy3euweGizw7cfdl34/GRhlkxjKNV+5AebPXPORX1JuB9x8ZfbyyD2y1krWAKsbMq1HnqQDaLfa77p4+MqvzEGSqvSAD/2IHW2yHaigR9tX3m8dDIYGcNf3f+gDpVBZbZU77zyJ6Rlcy+qoTMG887KAPD9hsh6a1Sv3gJUHGHUAxSMzj7zqDDe7Phmt2eG+8UsMxjRGm816MAO+8VMl1R1jGHOrZB/5Zo/WXAPgxixm9Mo96vDGrM1eOto8c4Ax4wF437mifOXlpiPzCnN7Y9l95NnEMxgMY9AAGA8fucH14Y1aVb6N/cqrmyh0BVht7k1e+bU8LK0Cg5vmVq9c5vHIjOfqxDIfeTraNVTwewa4wVe+SW5N+uP1qACeudUZbqGOfA6VZV750Noq2Xx3kpveV44ZelSV1V7KFHzkWyVrrlUwG0Pl9pWnoy3vsQoME6vKI69i5osVgwWzHT7zjmJtMcNUSVn1oYMd7ZodbgowZl45VG0uVuLPUr1yc79uaQBag/mqR34xhlWyHm1prplHboCWdZ4TeZjsK8+dI+jbz1C5hl65mcpgB5dhcj8+dGO+0Ko68+lD37JDD83dpDLzzK+TrQyaVwGj6pUboGV+7+AyN8An/pf84/7rv/4/1l4OCc/1BYMAAAAASUVORK5CYII=",
manualUrl: "#",
totpSecret: "G4nsI8lQagRMUchH8jEG",
otpCredentials: [],
supportedApplications: ["FreeOTP", "Google Authenticator"],
policy: {
algorithm: "HmacSHA1",
digits: 6,
lookAheadWindow: 1,
type: "totp",
period: 30,
getAlgorithmKey: () => "SHA1"
}
}
}),
id<KcContext.LogoutConfirm>({
...kcContextCommonMock,
pageId: "logout-confirm.ftl",
url: {
...kcContextCommonMock.url,
logoutConfirmAction: "Continuer?"
},
client: {
clientId: "myApp",
baseUrl: "#",
attributes: {}
},
logoutConfirm: { code: "123", skipLink: false }
}),
id<KcContext.IdpReviewUserProfile>({
...kcContextCommonMock,
pageId: "idp-review-user-profile.ftl",
profile: {
attributesByName
}
}),
id<KcContext.UpdateEmail>({
...kcContextCommonMock,
pageId: "update-email.ftl",
profile: {
attributesByName: {
email: attributesByName["email"]
}
}
}),
id<KcContext.SelectAuthenticator>({
...kcContextCommonMock,
pageId: "select-authenticator.ftl",
auth: {
authenticationSelections: [
{
authExecId: "f607f83c-537e-42b7-99d7-c52d459afe84",
displayName: "otp-display-name",
helpText: "otp-help-text",
iconCssClass: "kcAuthenticatorOTPClass"
},
{
authExecId: "5ed881b1-84cd-4e9b-b4d9-f329ea61a58c",
displayName: "webauthn-display-name",
helpText: "webauthn-help-text",
iconCssClass: "kcAuthenticatorWebAuthnClass"
}
]
}
}),
id<KcContext.SamlPostForm>({
...kcContextCommonMock,
pageId: "saml-post-form.ftl",
samlPost: {
url: ""
}
}),
id<KcContext.LoginPageExpired>({
...kcContextCommonMock,
pageId: "login-page-expired.ftl"
}),
id<KcContext.FrontchannelLogout>({
...kcContextCommonMock,
pageId: "frontchannel-logout.ftl",
logout: {
clients: [
{
name: "myApp",
frontChannelLogoutUrl: "#"
},
{
name: "myApp2",
frontChannelLogoutUrl: "#"
}
]
}
}),
id<KcContext.WebauthnRegister>({
pageId: "webauthn-register.ftl",
...kcContextCommonMock,
challenge: "random-challenge-string",
userid: "user123",
username: "johndoe",
signatureAlgorithms: ["ES256", "RS256"],
rpEntityName: "Example Corp",
rpId: "example.com",
attestationConveyancePreference: "direct",
authenticatorAttachment: "platform",
requireResidentKey: "required",
userVerificationRequirement: "preferred",
createTimeout: 60000,
excludeCredentialIds: "credId123,credId456",
isSetRetry: false,
isAppInitiatedAction: true
}),
id<KcContext.DeleteCredential>({
pageId: "delete-credential.ftl",
...kcContextCommonMock,
credentialLabel: "myCredential"
}),
id<KcContext.Code>({
pageId: "code.ftl",
...kcContextCommonMock,
code: {
success: true,
code: "123456"
}
}),
id<KcContext.DeleteAccountConfirm>({
pageId: "delete-account-confirm.ftl",
...kcContextCommonMock,
triggered_from_aia: true
}),
id<KcContext.LoginRecoveryAuthnCodeConfig>({
pageId: "login-recovery-authn-code-config.ftl",
...kcContextCommonMock,
recoveryAuthnCodesConfigBean: {
generatedRecoveryAuthnCodesList: ["code123", "code456", "code789"],
generatedRecoveryAuthnCodesAsString: "code123, code456, code789",
generatedAt: new Date().getTime()
}
}),
id<KcContext.LoginRecoveryAuthnCodeInput>({
pageId: "login-recovery-authn-code-input.ftl",
...kcContextCommonMock,
recoveryAuthnCodesInputBean: {
codeNumber: 1234
}
}),
id<KcContext.LoginResetOtp>({
pageId: "login-reset-otp.ftl",
...kcContextCommonMock,
configuredOtpCredentials: {
userOtpCredentials: [
{
id: "otpId1",
userLabel: "OTP Device 1"
},
{
id: "otpId2",
userLabel: "OTP Device 2"
},
{
id: "otpId3",
userLabel: "Backup OTP"
}
],
selectedCredentialId: "otpId2"
}
}),
id<KcContext.LoginX509Info>({
pageId: "login-x509-info.ftl",
...kcContextCommonMock,
x509: {
formData: {
subjectDN: "CN=John Doe, O=Example Corp, C=US",
isUserEnabled: true,
username: "johndoe"
}
}
}),
id<KcContext.WebauthnError>({
pageId: "webauthn-error.ftl",
...kcContextCommonMock,
isAppInitiatedAction: true
})
];
{
type Got = (typeof kcContextMocks)[number]["pageId"];
type Expected = LoginThemePageId;
type OnlyInGot = Exclude<Got, Expected>;
type OnlyInExpected = Exclude<Expected, Got>;
assert<Equals<OnlyInGot, never>>();
assert<Equals<OnlyInExpected, never>>();
}