705 lines
20 KiB
TypeScript
Raw Normal View History

import type { LoginThemePageId } from "keycloakify/bin/keycloakify/generateFtl";
import { type ThemeType } from "keycloakify/bin/constants";
2021-10-11 20:56:43 +02:00
import { assert } from "tsafe/assert";
import type { Equals } from "tsafe";
2023-03-19 14:48:01 +01:00
import type { MessageKey } from "../i18n/i18n";
2021-02-23 15:32:37 +01:00
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
2021-02-23 15:32:37 +01:00
/** 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)
*/
2023-03-18 18:27:50 +01:00
export type KcContext =
| KcContext.Login
| KcContext.Register
| KcContext.Info
| KcContext.Error
| KcContext.LoginResetPassword
| KcContext.LoginVerifyEmail
| KcContext.Terms
| KcContext.LoginDeviceVerifyUserCode
| KcContext.LoginOauthGrant
2023-03-18 18:27:50 +01:00
| KcContext.LoginOtp
| KcContext.LoginUsername
| KcContext.WebauthnAuthenticate
2024-05-10 21:12:35 +02:00
| KcContext.WebauthnRegister
2023-03-18 18:27:50 +01:00
| KcContext.LoginPassword
| KcContext.LoginUpdatePassword
| KcContext.LoginUpdateProfile
| KcContext.LoginIdpLinkConfirm
| KcContext.LoginIdpLinkEmail
| KcContext.LoginPageExpired
| KcContext.LoginConfigTotp
| KcContext.LogoutConfirm
2023-03-27 13:02:44 +03:00
| KcContext.IdpReviewUserProfile
2023-03-31 17:38:22 +02:00
| KcContext.UpdateEmail
2023-04-20 20:51:46 +02:00
| KcContext.SelectAuthenticator
2024-05-08 16:54:04 +02:00
| KcContext.SamlPostForm
2024-05-10 21:40:23 +02:00
| KcContext.DeleteCredential
2024-05-10 21:48:47 +02:00
| KcContext.Code
2024-05-11 00:05:58 +02:00
| KcContext.DeleteAccountConfirm
| KcContext.FrontchannelLogout;
2023-03-18 18:27:50 +01:00
assert<KcContext["themeType"] extends ThemeType ? true : false>();
2023-03-18 18:27:50 +01:00
export declare namespace KcContext {
2023-03-18 16:17:33 +01:00
export type Common = {
2023-11-22 18:00:29 +01:00
themeVersion: string;
keycloakifyVersion: string;
2023-04-27 11:52:02 +02:00
themeType: "login";
2023-06-08 23:09:14 +02:00
themeName: string;
2023-03-18 16:17:33 +01:00
url: {
loginAction: string;
resourcesPath: string;
resourcesCommonPath: string;
loginRestartFlowUrl: string;
loginUrl: string;
ssoLoginInOtherTabsUrl: string;
};
2023-03-18 16:17:33 +01:00
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;
2021-10-11 03:25:02 +02:00
};
2023-03-18 16:17:33 +01:00
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>;
2023-03-18 16:17:33 +01:00
};
isAppInitiatedAction?: boolean;
2023-03-18 16:17:33 +01:00
messagesPerField: {
2023-06-15 17:10:15 +02:00
/**
* 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;
2023-06-15 17:10:15 +02:00
/**
* Get message for given field.
*
* @param fieldName
* @return message text or empty string
*/
2023-03-18 16:17:33 +01:00
get: (fieldName: string) => string;
2023-06-15 17:10:15 +02:00
/**
* Check if message for given field exists
*
* @param field
* @return boolean
*/
2023-03-18 16:17:33 +01:00
exists: (fieldName: string) => boolean;
getFirstError: (...fieldNames: string[]) => string;
2023-03-18 16:17:33 +01:00
};
properties: Record<string, string | undefined>;
authenticationSession?: {
authSessionId: string;
tabId: string;
ssoLoginInOtherTabsUrl: string;
};
2023-03-18 16:17:33 +01:00
};
2021-03-04 21:14:54 +01:00
2023-04-20 20:51:46 +02:00
export type SamlPostForm = Common & {
pageId: "saml-post-form.ftl";
samlPost: {
url: string;
SAMLRequest?: string;
SAMLResponse?: string;
2023-05-02 16:53:43 +03:00
relayState?: string;
2023-04-20 20:51:46 +02:00
};
};
2023-03-18 16:17:33 +01:00
export type Login = Common & {
2021-03-06 22:41:36 +01:00
pageId: "login.ftl";
2021-03-04 21:14:54 +01:00
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
loginWithEmailAllowed: boolean;
rememberMe: boolean;
2021-03-06 23:03:03 +01:00
password: boolean;
2021-03-04 21:14:54 +01:00
resetPasswordAllowed: boolean;
2021-03-04 23:24:43 +01:00
registrationAllowed: boolean;
2021-03-04 21:14:54 +01:00
};
auth: {
selectedCredential?: string;
};
registrationDisabled: boolean;
login: {
2021-03-04 18:15:48 +01:00
username?: string;
2024-05-07 20:46:02 +02:00
rememberMe?: string; // "on" | undefined
password?: string;
2021-03-04 21:14:54 +01:00
};
usernameHidden?: boolean;
2021-03-04 21:14:54 +01:00
social: {
displayInfo: boolean;
providers?: {
loginUrl: string;
alias: string;
providerId: string;
displayName: string;
iconClasses?: string;
}[];
2021-03-04 21:14:54 +01:00
};
};
export type Register = Common & {
2024-05-06 19:16:17 +02:00
pageId: "register.ftl" | "register-user-profile.ftl";
2024-05-08 19:24:18 +02:00
profile: UserProfile;
url: {
registrationAction: string;
};
passwordRequired: boolean;
recaptchaRequired: boolean;
recaptchaSiteKey?: string;
2024-05-06 19:16:17 +02:00
/**
* 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;
2024-05-08 19:48:16 +02:00
termsAcceptanceRequired?: boolean;
};
2023-03-17 20:40:29 +01:00
2023-03-18 16:17:33 +01:00
export type Info = Common & {
2021-03-06 22:41:36 +01:00
pageId: "info.ftl";
2021-03-06 14:42:56 +01:00
messageHeader?: string;
2023-03-19 14:48:01 +01:00
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[];
2021-03-06 14:42:56 +01:00
skipLink: boolean;
pageRedirectUri?: string;
actionUri?: string;
client: {
baseUrl?: string;
};
2021-03-06 14:42:56 +01:00
};
2023-03-18 16:17:33 +01:00
export type Error = Common & {
2021-03-06 23:03:03 +01:00
pageId: "error.ftl";
client?: {
baseUrl?: string;
};
2023-03-18 16:17:33 +01:00
message: NonNullable<Common["message"]>;
2024-05-10 21:51:46 +02:00
skipLink?: boolean;
2021-03-06 22:41:36 +01:00
};
2021-03-04 21:14:54 +01:00
2023-03-18 16:17:33 +01:00
export type LoginResetPassword = Common & {
2021-03-07 14:57:53 +01:00
pageId: "login-reset-password.ftl";
realm: {
loginWithEmailAllowed: boolean;
};
url: {
loginResetCredentialsUrl: string;
2023-03-27 21:03:11 +02:00
};
2021-03-07 14:57:53 +01:00
};
2023-03-18 16:17:33 +01:00
export type LoginVerifyEmail = Common & {
2021-03-07 15:37:37 +01:00
pageId: "login-verify-email.ftl";
2022-07-06 02:05:08 +02:00
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
user?: {
email: string;
};
2021-03-07 15:37:37 +01:00
};
2023-03-18 16:17:33 +01:00
export type Terms = Common & {
2021-04-08 15:41:40 +02:00
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;
};
2021-04-08 15:41:40 +02:00
};
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;
2024-05-11 00:47:18 +02:00
dynamicScopeParameter?: string;
}[];
};
url: {
oauthAction: string;
};
};
2023-03-18 16:17:33 +01:00
export type LoginOtp = Common & {
2021-05-01 14:55:58 +02:00
pageId: "login-otp.ftl";
otpLogin: {
2024-05-11 01:13:09 +02:00
userOtpCredentials: {
id: string;
userLabel: string;
}[];
selectedCredentialId?: string;
};
2021-05-01 14:55:58 +02:00
};
2023-03-18 16:17:33 +01:00
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: {
displayInfo: boolean;
providers?: {
loginUrl: string;
alias: string;
providerId: string;
displayName: string;
}[];
};
};
2023-03-18 16:17:33 +01:00
export type LoginPassword = Common & {
2022-10-04 15:01:08 -04:00
pageId: "login-password.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
};
realm: {
resetPasswordAllowed: boolean;
};
auth?: {
showUsername?: boolean;
showResetCredentials?: boolean;
showTryAnotherWayLink?: boolean;
attemptedUsername?: string;
};
social: {
displayInfo: boolean;
};
};
2023-03-18 16:17:33 +01:00
export type WebauthnAuthenticate = Common & {
pageId: "webauthn-authenticate.ftl";
authenticators: {
2023-03-17 20:40:29 +01:00
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: {};
2024-05-10 18:30:48 +02:00
realm: {
password: boolean;
registrationAllowed: boolean;
};
registrationDisabled?: boolean;
url: {
registrationUrl?: string;
};
};
2023-03-17 20:40:29 +01:00
export namespace WebauthnAuthenticate {
export type WebauthnAuthenticator = {
credentialId: string;
transports: {
2023-03-18 06:14:05 +01:00
iconClass: string;
2024-05-10 18:30:48 +02:00
displayNameProperties?: MessageKey[];
2023-03-17 20:40:29 +01:00
};
label: string;
createdAt: string;
};
}
2024-05-10 21:12:35 +02:00
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: string;
excludeCredentialIds: string;
isSetRetry?: boolean;
isAppInitiatedAction?: boolean;
};
2023-03-18 16:17:33 +01:00
export type LoginUpdatePassword = Common & {
2021-12-28 00:08:25 +03:00
pageId: "login-update-password.ftl";
username: string;
};
2023-03-18 16:17:33 +01:00
export type LoginIdpLinkConfirm = Common & {
2021-06-14 21:19:46 +02:00
pageId: "login-idp-link-confirm.ftl";
idpAlias: string;
};
2022-01-01 18:44:05 +02:00
2023-03-18 16:17:33 +01:00
export type LoginIdpLinkEmail = Common & {
2022-04-22 17:54:47 +03:00
pageId: "login-idp-link-email.ftl";
brokerContext: {
username: string;
};
2022-04-22 17:54:47 +03:00
idpAlias: string;
};
2023-03-18 16:17:33 +01:00
export type LoginPageExpired = Common & {
2022-01-01 18:44:05 +02:00
pageId: "login-page-expired.ftl";
};
2022-06-28 14:37:17 -04:00
2023-03-18 16:17:33 +01:00
export type LoginConfigTotp = Common & {
2022-06-28 14:37:17 -04:00
pageId: "login-config-totp.ftl";
mode?: "qr" | "manual" | undefined | null;
totp: {
totpSecretEncoded: string;
qrUrl: string;
policy: {
algorithm: "HmacSHA1" | "HmacSHA256" | "HmacSHA512";
digits: number;
lookAheadWindow: number;
} & (
| {
type: "totp";
period: number;
}
| {
type: "hotp";
initialCounter: number;
}
);
supportedApplications: string[];
2022-06-28 14:37:17 -04:00
totpSecretQrCode: string;
manualUrl: string;
totpSecret: string;
otpCredentials: { id: string; userLabel: string }[];
};
};
2022-06-17 00:47:55 +02:00
2023-03-18 16:17:33 +01:00
export type LogoutConfirm = Common & {
2022-06-17 00:47:55 +02:00
pageId: "logout-confirm.ftl";
url: {
logoutConfirmAction: string;
};
client: {
baseUrl?: string;
};
logoutConfirm: {
code: string;
skipLink?: boolean;
};
};
2022-09-09 02:07:29 +02:00
export type LoginUpdateProfile = Common & {
pageId: "login-update-profile.ftl" | "update-user-profile.ftl";
2024-05-08 19:24:18 +02:00
profile: UserProfile;
};
2023-03-18 16:17:33 +01:00
export type IdpReviewUserProfile = Common & {
2022-09-09 12:55:57 +02:00
pageId: "idp-review-user-profile.ftl";
2024-05-08 19:24:18 +02:00
profile: UserProfile;
2022-09-09 12:55:57 +02:00
};
2023-03-27 13:02:44 +03:00
export type UpdateEmail = Common & {
pageId: "update-email.ftl";
2024-05-08 19:24:18 +02:00
profile: UserProfile;
2023-03-27 13:02:44 +03:00
};
2023-03-31 17:38:22 +02:00
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";
};
}
2024-05-08 16:54:04 +02:00
export type DeleteCredential = Common & {
pageId: "delete-credential.ftl";
credentialLabel: string;
};
2024-05-10 21:40:23 +02:00
export type Code = Common & {
pageId: "code.ftl";
code: {
success: boolean;
code?: string;
error?: string;
};
};
2024-05-10 21:48:47 +02:00
export type DeleteAccountConfirm = Common & {
pageId: "delete-account-confirm.ftl";
triggered_from_aia: boolean;
};
2024-05-11 00:05:58 +02:00
export type FrontchannelLogout = Common & {
pageId: "frontchannel-logout.ftl";
logout: {
clients: {
name: string;
frontChannelLogoutUrl: string;
}[];
logoutRedirectUri?: string;
};
};
2021-03-04 21:14:54 +01:00
}
2021-02-23 15:32:37 +01:00
2024-05-08 19:24:18 +02:00
export type UserProfile = {
attributes: Attribute[];
attributesByName: Record<string, Attribute>;
html5DataAnnotations?: Record<string, string>;
};
2021-10-11 20:56:43 +02:00
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;
};
2021-10-11 20:56:43 +02:00
readOnly: boolean;
validators: Validators;
2024-04-30 12:07:35 +02:00
annotations: {
inputType?: string;
inputTypeSize?: `${number}`;
inputOptionsFromValidation?: string;
inputOptionLabels?: Record<string, string | undefined>;
inputOptionLabelsI18nPrefix?: string;
inputTypeCols?: `${number}`;
inputTypeRows?: `${number}`;
inputTypeMaxlength?: `${number}`;
inputHelperTextBefore?: string;
inputHelperTextAfter?: string;
inputTypePlaceholder?: string;
inputTypePattern?: string;
inputTypeMinlength?: `${number}`;
inputTypeMax?: string;
inputTypeMin?: string;
inputTypeStep?: string;
2024-04-30 12:07:35 +02:00
};
multivalued?: boolean;
2021-11-01 22:28:53 +01:00
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";
2021-10-11 20:56:43 +02:00
};
export type Validators = Partial<{
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;
2021-10-11 20:56:43 +02:00
uri: Validators.DoIgnoreEmpty;
"username-prohibited-characters": Validators.DoIgnoreEmpty & Validators.ErrorMessage;
*/
2021-10-11 20:56:43 +02:00
}>;
export declare namespace Validators {
export type DoIgnoreEmpty = {
"ignore.empty.value"?: boolean;
2021-10-11 20:56:43 +02:00
};
export type ErrorMessage = {
"error-message"?: string;
};
2021-10-11 20:56:43 +02:00
export type Range = {
/** "0", "1", "2"... yeah I know, don't tell me */
min?: `${number}`;
max?: `${number}`;
2021-10-11 20:56:43 +02:00
};
2022-03-18 00:46:12 +01:00
export type Options = {
options: string[];
};
2021-10-11 20:56:43 +02:00
}
2023-04-20 20:51:46 +02:00
{
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>>();
}
2023-04-27 11:52:02 +02:00
export type PasswordPolicies = {
/** The minimum length of the password */
2024-04-22 06:34:50 +02:00
length?: `${number}`;
/** The minimum number of digits required in the password */
2024-04-22 06:34:50 +02:00
digits?: `${number}`;
/** The minimum number of lowercase characters required in the password */
2024-04-22 06:34:50 +02:00
lowerCase?: `${number}`;
/** The minimum number of uppercase characters required in the password */
2024-04-22 06:34:50 +02:00
upperCase?: `${number}`;
/** The minimum number of special characters required in the password */
2024-04-22 06:34:50 +02:00
specialChars?: `${number}`;
/** Whether the password can be the username */
notUsername?: boolean;
2024-04-22 06:34:50 +02:00
/** Whether the password can be the email address */
notEmail?: boolean;
};