Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
3c96d2ea42 | |||
ab81481e5a | |||
a429ad5dcf | |||
5e1c5b510b | |||
9e63183f4b | |||
b1e740f026 | |||
ce4ea55438 | |||
18ab7cd22f | |||
8807743daf | |||
4b3ae58ea7 |
@ -49,6 +49,10 @@
|
|||||||
|
|
||||||
# Changelog highlights
|
# Changelog highlights
|
||||||
|
|
||||||
|
## 6.7.0
|
||||||
|
|
||||||
|
- Add support for `webauthn-authenticate.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/185).
|
||||||
|
|
||||||
## 6.6.0
|
## 6.6.0
|
||||||
|
|
||||||
- Add support for `login-password.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/184).
|
- Add support for `login-password.ftl` thanks to [@mstrodl](https://github.com/Mstrodl)'s hacktoberfest [PR](https://github.com/InseeFrLab/keycloakify/pull/184).
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "6.6.1",
|
"version": "6.7.2",
|
||||||
"description": "Keycloak theme generator for Reacts app",
|
"description": "Keycloak theme generator for Reacts app",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -78,15 +78,16 @@
|
|||||||
"@octokit/rest": "^18.12.0",
|
"@octokit/rest": "^18.12.0",
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.5",
|
||||||
"cli-select": "^1.1.2",
|
"cli-select": "^1.1.2",
|
||||||
"evt": "^2.4.4",
|
"evt": "^2.4.5",
|
||||||
"memoizee": "^0.4.15",
|
"memoizee": "^0.4.15",
|
||||||
"minimal-polyfills": "^2.2.2",
|
"minimal-polyfills": "^2.2.2",
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"powerhooks": "^0.20.20",
|
"powerhooks": "^0.20.22",
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
|
"rfc4648": "^1.5.2",
|
||||||
"scripting-tools": "^0.19.13",
|
"scripting-tools": "^0.19.13",
|
||||||
"tsafe": "^1.1.1",
|
"tsafe": "^1.1.3",
|
||||||
"tss-react": "^4.3.4",
|
"tss-react": "^4.3.4",
|
||||||
"zod": "^3.17.10"
|
"zod": "^3.17.10"
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ export const pageIds = [
|
|||||||
"login.ftl",
|
"login.ftl",
|
||||||
"login-username.ftl",
|
"login-username.ftl",
|
||||||
"login-password.ftl",
|
"login-password.ftl",
|
||||||
|
"webauthn-authenticate.ftl",
|
||||||
"register.ftl",
|
"register.ftl",
|
||||||
"register-user-profile.ftl",
|
"register-user-profile.ftl",
|
||||||
"info.ftl",
|
"info.ftl",
|
||||||
|
@ -15,6 +15,7 @@ const Terms = lazy(() => import("./Terms"));
|
|||||||
const LoginOtp = lazy(() => import("./LoginOtp"));
|
const LoginOtp = lazy(() => import("./LoginOtp"));
|
||||||
const LoginPassword = lazy(() => import("./LoginPassword"));
|
const LoginPassword = lazy(() => import("./LoginPassword"));
|
||||||
const LoginUsername = lazy(() => import("./LoginUsername"));
|
const LoginUsername = lazy(() => import("./LoginUsername"));
|
||||||
|
const WebauthnAuthenticate = lazy(() => import("./WebauthnAuthenticate"));
|
||||||
const LoginUpdatePassword = lazy(() => import("./LoginUpdatePassword"));
|
const LoginUpdatePassword = lazy(() => import("./LoginUpdatePassword"));
|
||||||
const LoginUpdateProfile = lazy(() => import("./LoginUpdateProfile"));
|
const LoginUpdateProfile = lazy(() => import("./LoginUpdateProfile"));
|
||||||
const LoginIdpLinkConfirm = lazy(() => import("./LoginIdpLinkConfirm"));
|
const LoginIdpLinkConfirm = lazy(() => import("./LoginIdpLinkConfirm"));
|
||||||
@ -73,6 +74,8 @@ const KcApp = memo(
|
|||||||
return <LoginUsername {...{ kcContext, ...props }} />;
|
return <LoginUsername {...{ kcContext, ...props }} />;
|
||||||
case "login-password.ftl":
|
case "login-password.ftl":
|
||||||
return <LoginPassword {...{ kcContext, ...props }} />;
|
return <LoginPassword {...{ kcContext, ...props }} />;
|
||||||
|
case "webauthn-authenticate.ftl":
|
||||||
|
return <WebauthnAuthenticate {...{ kcContext, ...props }} />;
|
||||||
case "login-update-password.ftl":
|
case "login-update-password.ftl":
|
||||||
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
|
return <LoginUpdatePassword {...{ kcContext, ...props }} />;
|
||||||
case "login-update-profile.ftl":
|
case "login-update-profile.ftl":
|
||||||
|
@ -84,6 +84,7 @@ export type KcProps = KcPropsGeneric<
|
|||||||
| "kcFormSocialAccountDoubleListClass"
|
| "kcFormSocialAccountDoubleListClass"
|
||||||
| "kcFormSocialAccountListLinkClass"
|
| "kcFormSocialAccountListLinkClass"
|
||||||
| "kcWebAuthnKeyIcon"
|
| "kcWebAuthnKeyIcon"
|
||||||
|
| "kcWebAuthnDefaultIcon"
|
||||||
| "kcFormClass"
|
| "kcFormClass"
|
||||||
| "kcFormGroupErrorClass"
|
| "kcFormGroupErrorClass"
|
||||||
| "kcLabelClass"
|
| "kcLabelClass"
|
||||||
@ -105,12 +106,16 @@ export type KcProps = KcPropsGeneric<
|
|||||||
| "kcSrOnlyClass"
|
| "kcSrOnlyClass"
|
||||||
| "kcSelectAuthListClass"
|
| "kcSelectAuthListClass"
|
||||||
| "kcSelectAuthListItemClass"
|
| "kcSelectAuthListItemClass"
|
||||||
|
| "kcSelectAuthListItemFillClass"
|
||||||
| "kcSelectAuthListItemInfoClass"
|
| "kcSelectAuthListItemInfoClass"
|
||||||
| "kcSelectAuthListItemLeftClass"
|
| "kcSelectAuthListItemLeftClass"
|
||||||
| "kcSelectAuthListItemBodyClass"
|
| "kcSelectAuthListItemBodyClass"
|
||||||
| "kcSelectAuthListItemDescriptionClass"
|
| "kcSelectAuthListItemDescriptionClass"
|
||||||
| "kcSelectAuthListItemHeadingClass"
|
| "kcSelectAuthListItemHeadingClass"
|
||||||
| "kcSelectAuthListItemHelpTextClass"
|
| "kcSelectAuthListItemHelpTextClass"
|
||||||
|
| "kcSelectAuthListItemIconPropertyClass"
|
||||||
|
| "kcSelectAuthListItemIconClass"
|
||||||
|
| "kcSelectAuthListItemTitle"
|
||||||
| "kcAuthenticatorDefaultClass"
|
| "kcAuthenticatorDefaultClass"
|
||||||
| "kcAuthenticatorPasswordClass"
|
| "kcAuthenticatorPasswordClass"
|
||||||
| "kcAuthenticatorOTPClass"
|
| "kcAuthenticatorOTPClass"
|
||||||
@ -138,6 +143,7 @@ export const defaultKcProps = {
|
|||||||
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
|
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
|
||||||
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
|
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
|
||||||
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
|
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
|
||||||
|
"kcWebAuthnDefaultIcon": ["pficon", "pficon-key"],
|
||||||
|
|
||||||
"kcFormClass": ["form-horizontal"],
|
"kcFormClass": ["form-horizontal"],
|
||||||
"kcFormGroupErrorClass": ["has-error"],
|
"kcFormGroupErrorClass": ["has-error"],
|
||||||
@ -173,6 +179,10 @@ export const defaultKcProps = {
|
|||||||
// css classes for select-authenticator form
|
// css classes for select-authenticator form
|
||||||
"kcSelectAuthListClass": ["list-group", "list-view-pf"],
|
"kcSelectAuthListClass": ["list-group", "list-view-pf"],
|
||||||
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
|
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
|
||||||
|
"kcSelectAuthListItemFillClass": ["pf-l-split__item", "pf-m-fill"],
|
||||||
|
"kcSelectAuthListItemIconPropertyClass": ["fa-2x", "select-auth-box-icon-properties"],
|
||||||
|
"kcSelectAuthListItemIconClass": ["pf-l-split__item", "select-auth-box-icon"],
|
||||||
|
"kcSelectAuthListItemTitle": ["select-auth-box-paragraph"],
|
||||||
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
|
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
|
||||||
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
|
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
|
||||||
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"],
|
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"],
|
||||||
|
204
src/lib/components/WebauthnAuthenticate.tsx
Normal file
204
src/lib/components/WebauthnAuthenticate.tsx
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import React, { useRef, useState, memo } from "react";
|
||||||
|
import Template from "./Template";
|
||||||
|
import type { KcProps } from "./KcProps";
|
||||||
|
import type { KcContextBase } from "../getKcContext/KcContextBase";
|
||||||
|
import { useCssAndCx } from "../tools/useCssAndCx";
|
||||||
|
import type { I18n, MessageKeyBase } from "../i18n";
|
||||||
|
import { base64url } from "rfc4648";
|
||||||
|
import { useConstCallback } from "powerhooks/useConstCallback";
|
||||||
|
|
||||||
|
const WebauthnAuthenticate = memo(
|
||||||
|
({
|
||||||
|
kcContext,
|
||||||
|
i18n,
|
||||||
|
doFetchDefaultThemeResources = true,
|
||||||
|
...props
|
||||||
|
}: { kcContext: KcContextBase.WebauthnAuthenticate; i18n: I18n; doFetchDefaultThemeResources?: boolean } & KcProps) => {
|
||||||
|
const { url } = kcContext;
|
||||||
|
|
||||||
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
|
const { authenticators, challenge, shouldDisplayAuthenticators, userVerification, rpId } = kcContext;
|
||||||
|
const createTimeout = Number(kcContext.createTimeout);
|
||||||
|
const isUserIdentified = kcContext.isUserIdentified == "true";
|
||||||
|
|
||||||
|
const { cx } = useCssAndCx();
|
||||||
|
|
||||||
|
const webAuthnAuthenticate = useConstCallback(async () => {
|
||||||
|
if (!isUserIdentified) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const allowCredentials = authenticators.authenticators.map(
|
||||||
|
authenticator =>
|
||||||
|
({
|
||||||
|
id: base64url.parse(authenticator.credentialId, { loose: true }),
|
||||||
|
type: "public-key"
|
||||||
|
} as PublicKeyCredentialDescriptor)
|
||||||
|
);
|
||||||
|
// Check if WebAuthn is supported by this browser
|
||||||
|
if (!window.PublicKeyCredential) {
|
||||||
|
setError(msgStr("webauthn-unsupported-browser-text"));
|
||||||
|
submitForm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey: PublicKeyCredentialRequestOptions = {
|
||||||
|
rpId,
|
||||||
|
challenge: base64url.parse(challenge, { loose: true })
|
||||||
|
};
|
||||||
|
|
||||||
|
if (createTimeout !== 0) {
|
||||||
|
publicKey.timeout = createTimeout * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowCredentials.length) {
|
||||||
|
publicKey.allowCredentials = allowCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userVerification !== "not specified") {
|
||||||
|
publicKey.userVerification = userVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resultRaw = await navigator.credentials.get({ publicKey });
|
||||||
|
if (!resultRaw || resultRaw.type != "public-key") return;
|
||||||
|
const result = resultRaw as PublicKeyCredential;
|
||||||
|
if (!("authenticatorData" in result.response)) return;
|
||||||
|
const response = result.response as AuthenticatorAssertionResponse;
|
||||||
|
const clientDataJSON = response.clientDataJSON;
|
||||||
|
const authenticatorData = response.authenticatorData;
|
||||||
|
const signature = response.signature;
|
||||||
|
|
||||||
|
setClientDataJSON(base64url.stringify(new Uint8Array(clientDataJSON), { pad: false }));
|
||||||
|
setAuthenticatorData(base64url.stringify(new Uint8Array(authenticatorData), { pad: false }));
|
||||||
|
setSignature(base64url.stringify(new Uint8Array(signature), { pad: false }));
|
||||||
|
setCredentialId(result.id);
|
||||||
|
setUserHandle(base64url.stringify(new Uint8Array(response.userHandle!), { pad: false }));
|
||||||
|
submitForm();
|
||||||
|
} catch (err) {
|
||||||
|
setError(String(err));
|
||||||
|
submitForm();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const webAuthForm = useRef<HTMLFormElement>(null);
|
||||||
|
const submitForm = useConstCallback(() => {
|
||||||
|
webAuthForm.current!.submit();
|
||||||
|
});
|
||||||
|
|
||||||
|
const [clientDataJSON, setClientDataJSON] = useState("");
|
||||||
|
const [authenticatorData, setAuthenticatorData] = useState("");
|
||||||
|
const [signature, setSignature] = useState("");
|
||||||
|
const [credentialId, setCredentialId] = useState("");
|
||||||
|
const [userHandle, setUserHandle] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Template
|
||||||
|
{...{ kcContext, i18n, doFetchDefaultThemeResources, ...props }}
|
||||||
|
headerNode={msg("webauthn-login-title")}
|
||||||
|
formNode={
|
||||||
|
<div id="kc-form-webauthn" className={cx(props.kcFormClass)}>
|
||||||
|
<form id="webauth" action={url.loginAction} ref={webAuthForm} method="post">
|
||||||
|
<input type="hidden" id="clientDataJSON" name="clientDataJSON" value={clientDataJSON} />
|
||||||
|
<input type="hidden" id="authenticatorData" name="authenticatorData" value={authenticatorData} />
|
||||||
|
<input type="hidden" id="signature" name="signature" value={signature} />
|
||||||
|
<input type="hidden" id="credentialId" name="credentialId" value={credentialId} />
|
||||||
|
<input type="hidden" id="userHandle" name="userHandle" value={userHandle} />
|
||||||
|
<input type="hidden" id="error" name="error" value={error} />
|
||||||
|
</form>
|
||||||
|
<div className={cx(props.kcFormGroupClass)}>
|
||||||
|
{authenticators &&
|
||||||
|
(() => (
|
||||||
|
<form id="authn_select" className={cx(props.kcFormClass)}>
|
||||||
|
{authenticators.authenticators.map(authenticator => (
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="authn_use_chk"
|
||||||
|
value={authenticator.credentialId}
|
||||||
|
key={authenticator.credentialId}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</form>
|
||||||
|
))()}
|
||||||
|
{authenticators &&
|
||||||
|
shouldDisplayAuthenticators &&
|
||||||
|
(() => (
|
||||||
|
<>
|
||||||
|
{authenticators.authenticators.length > 1 && (
|
||||||
|
<p className={cx(props.kcSelectAuthListItemTitle)}>{msg("webauthn-available-authenticators")}</p>
|
||||||
|
)}
|
||||||
|
<div className={cx(props.kcFormClass)}>
|
||||||
|
{authenticators.authenticators.map(authenticator => (
|
||||||
|
<div id="kc-webauthn-authenticator" className={cx(props.kcSelectAuthListItemClass)}>
|
||||||
|
<div className={cx(props.kcSelectAuthListItemIconClass)}>
|
||||||
|
<i
|
||||||
|
className={cx(
|
||||||
|
props[authenticator.transports.iconClass] ?? props.kcWebAuthnDefaultIcon,
|
||||||
|
props.kcSelectAuthListItemIconPropertyClass
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcSelectAuthListItemBodyClass)}>
|
||||||
|
<div
|
||||||
|
id="kc-webauthn-authenticator-label"
|
||||||
|
className={cx(props.kcSelectAuthListItemHeadingClass)}
|
||||||
|
>
|
||||||
|
{authenticator.label}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{authenticator.transports && authenticator.transports.displayNameProperties.length && (
|
||||||
|
<div
|
||||||
|
id="kc-webauthn-authenticator-transport"
|
||||||
|
className={cx(props.kcSelectAuthListItemDescriptionClass)}
|
||||||
|
>
|
||||||
|
{authenticator.transports.displayNameProperties.map(
|
||||||
|
(transport: MessageKeyBase, index: number) => (
|
||||||
|
<>
|
||||||
|
<span>{msg(transport)}</span>
|
||||||
|
{index < authenticator.transports.displayNameProperties.length - 1 && (
|
||||||
|
<span>{", "}</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={cx(props.kcSelectAuthListItemDescriptionClass)}>
|
||||||
|
<span id="kc-webauthn-authenticator-created-label">
|
||||||
|
{msg("webauthn-createdAt-label")}
|
||||||
|
</span>
|
||||||
|
<span id="kc-webauthn-authenticator-created">{authenticator.createdAt}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={cx(props.kcSelectAuthListItemFillClass)} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))()}
|
||||||
|
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
|
||||||
|
<input
|
||||||
|
id="authenticateWebAuthnButton"
|
||||||
|
type="button"
|
||||||
|
onClick={webAuthnAuthenticate}
|
||||||
|
autoFocus={true}
|
||||||
|
value={msgStr("webauthn-doAuthenticate")}
|
||||||
|
className={cx(
|
||||||
|
props.kcButtonClass,
|
||||||
|
props.kcButtonPrimaryClass,
|
||||||
|
props.kcButtonBlockClass,
|
||||||
|
props.kcButtonLargeClass
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WebauthnAuthenticate;
|
@ -2,6 +2,7 @@ import type { PageId } from "../../bin/keycloakify/generateFtl";
|
|||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import type { MessageKeyBase } from "../i18n";
|
import type { MessageKeyBase } from "../i18n";
|
||||||
|
import type { KcTemplateClassKey } from "../components/KcProps";
|
||||||
|
|
||||||
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
type ExtractAfterStartingWith<Prefix extends string, StrEnum> = StrEnum extends `${Prefix}${infer U}` ? U : never;
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ export type KcContextBase =
|
|||||||
| KcContextBase.Terms
|
| KcContextBase.Terms
|
||||||
| KcContextBase.LoginOtp
|
| KcContextBase.LoginOtp
|
||||||
| KcContextBase.LoginUsername
|
| KcContextBase.LoginUsername
|
||||||
|
| KcContextBase.WebauthnAuthenticate
|
||||||
| KcContextBase.LoginPassword
|
| KcContextBase.LoginPassword
|
||||||
| KcContextBase.LoginUpdatePassword
|
| KcContextBase.LoginUpdatePassword
|
||||||
| KcContextBase.LoginUpdateProfile
|
| KcContextBase.LoginUpdateProfile
|
||||||
@ -31,6 +33,16 @@ export type KcContextBase =
|
|||||||
| KcContextBase.UpdateUserProfile
|
| KcContextBase.UpdateUserProfile
|
||||||
| KcContextBase.IdpReviewUserProfile;
|
| KcContextBase.IdpReviewUserProfile;
|
||||||
|
|
||||||
|
export type WebauthnAuthenticator = {
|
||||||
|
credentialId: string;
|
||||||
|
transports: {
|
||||||
|
iconClass: KcTemplateClassKey;
|
||||||
|
displayNameProperties: MessageKeyBase[];
|
||||||
|
};
|
||||||
|
label: string;
|
||||||
|
createdAt: string;
|
||||||
|
};
|
||||||
|
|
||||||
export declare namespace KcContextBase {
|
export declare namespace KcContextBase {
|
||||||
export type Common = {
|
export type Common = {
|
||||||
url: {
|
url: {
|
||||||
@ -253,6 +265,24 @@ export declare namespace KcContextBase {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type WebauthnAuthenticate = Common & {
|
||||||
|
pageId: "webauthn-authenticate.ftl";
|
||||||
|
authenticators: {
|
||||||
|
authenticators: WebauthnAuthenticator[];
|
||||||
|
};
|
||||||
|
challenge: string;
|
||||||
|
// I hate this:
|
||||||
|
userVerification: UserVerificationRequirement | "not specified";
|
||||||
|
rpId: string;
|
||||||
|
createTimeout: string;
|
||||||
|
isUserIdentified: "true" | "false";
|
||||||
|
shouldDisplayAuthenticators: boolean;
|
||||||
|
social: {
|
||||||
|
displayInfo: boolean;
|
||||||
|
};
|
||||||
|
login: {};
|
||||||
|
};
|
||||||
|
|
||||||
export type LoginUpdatePassword = Common & {
|
export type LoginUpdatePassword = Common & {
|
||||||
pageId: "login-update-password.ftl";
|
pageId: "login-update-password.ftl";
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -10,6 +10,7 @@ import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
|||||||
import { pathJoin } from "../../bin/tools/pathJoin";
|
import { pathJoin } from "../../bin/tools/pathJoin";
|
||||||
import { pathBasename } from "../tools/pathBasename";
|
import { pathBasename } from "../tools/pathBasename";
|
||||||
import { mockTestingResourcesCommonPath } from "../../bin/mockTestingResourcesPath";
|
import { mockTestingResourcesCommonPath } from "../../bin/mockTestingResourcesPath";
|
||||||
|
import { symToStr } from "tsafe/symToStr";
|
||||||
|
|
||||||
export function getKcContext<KcContextExtended extends { pageId: string } = never>(params?: {
|
export function getKcContext<KcContextExtended extends { pageId: string } = never>(params?: {
|
||||||
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
|
mockPageId?: ExtendsKcContextBase<KcContextExtended>["pageId"];
|
||||||
@ -17,9 +18,19 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
|||||||
}): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined } {
|
}): { kcContext: ExtendsKcContextBase<KcContextExtended> | undefined } {
|
||||||
const { mockPageId, mockData } = params ?? {};
|
const { mockPageId, mockData } = params ?? {};
|
||||||
|
|
||||||
if (mockPageId !== undefined) {
|
const realKcContext = getKcContextFromWindow<KcContextExtended>();
|
||||||
|
|
||||||
|
if (mockPageId !== undefined && realKcContext === undefined) {
|
||||||
//TODO maybe trow if no mock fo custom page
|
//TODO maybe trow if no mock fo custom page
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
[
|
||||||
|
`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`,
|
||||||
|
`If assets are missing make sure you have built your Keycloak theme at least once.`
|
||||||
|
].join(" "),
|
||||||
|
"background: red; color: yellow; font-size: medium"
|
||||||
|
);
|
||||||
|
|
||||||
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
|
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
|
||||||
|
|
||||||
const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId);
|
const partialKcContextCustomMock = mockData?.find(({ pageId }) => pageId === mockPageId);
|
||||||
@ -106,13 +117,15 @@ export function getKcContext<KcContextExtended extends { pageId: string } = neve
|
|||||||
return { kcContext };
|
return { kcContext };
|
||||||
}
|
}
|
||||||
|
|
||||||
const kcContext = getKcContextFromWindow<KcContextExtended>();
|
if (realKcContext === undefined) {
|
||||||
|
return { "kcContext": undefined };
|
||||||
|
}
|
||||||
|
|
||||||
if (kcContext !== undefined) {
|
{
|
||||||
const { url } = kcContext;
|
const { url } = realKcContext;
|
||||||
|
|
||||||
url.resourcesCommonPath = pathJoin(url.resourcesPath, pathBasename(mockTestingResourcesCommonPath));
|
url.resourcesCommonPath = pathJoin(url.resourcesPath, pathBasename(mockTestingResourcesCommonPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { kcContext };
|
return { "kcContext": realKcContext };
|
||||||
}
|
}
|
||||||
|
@ -393,6 +393,27 @@ export const kcContextMocks: KcContextBase[] = [
|
|||||||
},
|
},
|
||||||
"login": {}
|
"login": {}
|
||||||
}),
|
}),
|
||||||
|
id<KcContextBase.WebauthnAuthenticate>({
|
||||||
|
...kcContextCommonMock,
|
||||||
|
"pageId": "webauthn-authenticate.ftl",
|
||||||
|
"url": loginUrl,
|
||||||
|
"authenticators": {
|
||||||
|
"authenticators": []
|
||||||
|
},
|
||||||
|
"realm": {
|
||||||
|
...kcContextCommonMock.realm
|
||||||
|
},
|
||||||
|
"challenge": "",
|
||||||
|
"userVerification": "not specified",
|
||||||
|
"rpId": "",
|
||||||
|
"createTimeout": "0",
|
||||||
|
"isUserIdentified": "false",
|
||||||
|
"shouldDisplayAuthenticators": false,
|
||||||
|
"social": {
|
||||||
|
"displayInfo": false
|
||||||
|
},
|
||||||
|
"login": {}
|
||||||
|
}),
|
||||||
id<KcContextBase.LoginUpdatePassword>({
|
id<KcContextBase.LoginUpdatePassword>({
|
||||||
...kcContextCommonMock,
|
...kcContextCommonMock,
|
||||||
"pageId": "login-update-password.ftl",
|
"pageId": "login-update-password.ftl",
|
||||||
|
40
yarn.lock
40
yarn.lock
@ -967,14 +967,14 @@ event-emitter@^0.3.5:
|
|||||||
d "1"
|
d "1"
|
||||||
es5-ext "~0.10.14"
|
es5-ext "~0.10.14"
|
||||||
|
|
||||||
evt@^2.4.4:
|
evt@^2.4.5:
|
||||||
version "2.4.4"
|
version "2.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.4.tgz#37d6e28ccb5b1bc91162fc3d5bcfbeb1ef3191cf"
|
resolved "https://registry.yarnpkg.com/evt/-/evt-2.4.5.tgz#9e383c20a7917977e26b9fbad178585916ea8142"
|
||||||
integrity sha512-w/ZYdPCRdSfslOhcQHq7DuYoaU04YZKkFPyBwF8pYmOkRizivpbI0jZ8ffY/jITzbLo7RZ0wxN2dqyi62kyGwg==
|
integrity sha512-shiXMrEhTHqTl5PacT3vdA1U9i3usnInuxt77Lj0Ph9igNM982b1Uf/3L24OSzI0SqLx/XIRVIJcgzUZ2Ij8AA==
|
||||||
dependencies:
|
dependencies:
|
||||||
minimal-polyfills "^2.2.2"
|
minimal-polyfills "^2.2.2"
|
||||||
run-exclusive "^2.2.16"
|
run-exclusive "^2.2.16"
|
||||||
tsafe "^1.1.1"
|
tsafe "^1.1.2"
|
||||||
|
|
||||||
execa@^5.1.1:
|
execa@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
@ -1634,15 +1634,15 @@ please-upgrade-node@^3.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver-compare "^1.0.0"
|
semver-compare "^1.0.0"
|
||||||
|
|
||||||
powerhooks@^0.20.20:
|
powerhooks@^0.20.22:
|
||||||
version "0.20.20"
|
version "0.20.22"
|
||||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.20.tgz#f9b2549710f5166f63d80e07c46c16d6da4c6f78"
|
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.20.22.tgz#469ba048dfa9c1d549325c73a091f8f3b96895b3"
|
||||||
integrity sha512-98Ymz0bjo5Ds9u273wYz1tdJ51sB1jcyjqGa08mRY5dKumewydA/+71zrFelfgkOLRRhVZ+mWynG6DZ7zOVjrQ==
|
integrity sha512-xFv5s7JTkwQh+lDVR1yLgGXPYfpbmdmdg6qT4VTF4EArieaImhCXFp7arSul55FvYoCuD5+gR9ooctDG53LeLg==
|
||||||
dependencies:
|
dependencies:
|
||||||
evt "^2.4.4"
|
evt "^2.4.5"
|
||||||
memoizee "^0.4.15"
|
memoizee "^0.4.15"
|
||||||
resize-observer-polyfill "^1.5.1"
|
resize-observer-polyfill "^1.5.1"
|
||||||
tsafe "^1.1.1"
|
tsafe "^1.1.3"
|
||||||
|
|
||||||
prettier@^2.3.0:
|
prettier@^2.3.0:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
@ -1765,6 +1765,11 @@ restore-cursor@^3.1.0:
|
|||||||
onetime "^5.1.0"
|
onetime "^5.1.0"
|
||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
|
|
||||||
|
rfc4648@^1.5.2:
|
||||||
|
version "1.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rfc4648/-/rfc4648-1.5.2.tgz#cf5dac417dd83e7f4debf52e3797a723c1373383"
|
||||||
|
integrity sha512-tLOizhR6YGovrEBLatX1sdcuhoSCXddw3mqNVAcKxGJ+J0hFeJ+SjeWCv5UPA/WU3YzWPPuCVYgXBKZUPGpKtg==
|
||||||
|
|
||||||
rfdc@^1.3.0:
|
rfdc@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
|
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
|
||||||
@ -1987,10 +1992,15 @@ trough@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
||||||
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
|
||||||
|
|
||||||
tsafe@^1.1.1:
|
tsafe@^1.1.2:
|
||||||
version "1.1.1"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.1.1.tgz#8d6998c726f8c63c518e1d1e283bbcd282a2b9a9"
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.1.2.tgz#cece1900bca89e25a84a65e7087f00dff3664b2e"
|
||||||
integrity sha512-Ogblm3uh0dVupcCcC4IT641rnSQ7CW9IO0q8yIncG8OBe4DDXEqGtUE8LWf7+0MK1qZGeWPWEqSxlLzY2xzREA==
|
integrity sha512-jom5KsB9vpvOE9dLx2yTrPAJzzwU9CRPgoatoD7m2Zb7FCqo6ueEdZ+AZk+OysM4N+m8EUnIa1s9Pq3IMRDYLA==
|
||||||
|
|
||||||
|
tsafe@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.1.3.tgz#fa7c2ae7da689884292b70ee30febbca5c665d03"
|
||||||
|
integrity sha512-QQQoed5Acb7Qe/sjMwE/qg57mxD5MXqY5HcGN4i3QyhJiplpo79ABEZiOBvL1cRTOYIzeryWvWQ3xsotTA49MQ==
|
||||||
|
|
||||||
tslib@^2.1.0:
|
tslib@^2.1.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
|
Reference in New Issue
Block a user