keycloakify 11.11.3 → 11.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/bin/375.index.js +1 -1
- package/bin/main.js +3 -2
- package/bin/shared/constants.d.ts +2 -2
- package/bin/shared/constants.js +2 -1
- package/bin/shared/constants.js.map +1 -1
- package/login/DefaultPage.js +3 -0
- package/login/DefaultPage.js.map +1 -1
- package/login/KcContext/KcContext.d.ts +25 -1
- package/login/KcContext/KcContext.js.map +1 -1
- package/login/KcContext/getKcContextMock.d.ts +1 -1
- package/login/KcContext/kcContextMocks.d.ts +1 -1
- package/login/KcContext/kcContextMocks.js +7 -2
- package/login/KcContext/kcContextMocks.js.map +1 -1
- package/login/i18n/messages_defaultSet/ar.d.ts +2 -0
- package/login/i18n/messages_defaultSet/ar.js +3 -1
- package/login/i18n/messages_defaultSet/ar.js.map +1 -1
- package/login/i18n/messages_defaultSet/ca.d.ts +2 -0
- package/login/i18n/messages_defaultSet/ca.js +3 -1
- package/login/i18n/messages_defaultSet/ca.js.map +1 -1
- package/login/i18n/messages_defaultSet/cs.d.ts +2 -0
- package/login/i18n/messages_defaultSet/cs.js +3 -1
- package/login/i18n/messages_defaultSet/cs.js.map +1 -1
- package/login/i18n/messages_defaultSet/da.d.ts +2 -0
- package/login/i18n/messages_defaultSet/da.js +3 -1
- package/login/i18n/messages_defaultSet/da.js.map +1 -1
- package/login/i18n/messages_defaultSet/de.d.ts +2 -0
- package/login/i18n/messages_defaultSet/de.js +3 -1
- package/login/i18n/messages_defaultSet/de.js.map +1 -1
- package/login/i18n/messages_defaultSet/el.d.ts +2 -0
- package/login/i18n/messages_defaultSet/el.js +3 -1
- package/login/i18n/messages_defaultSet/el.js.map +1 -1
- package/login/i18n/messages_defaultSet/en.d.ts +2 -0
- package/login/i18n/messages_defaultSet/en.js +3 -1
- package/login/i18n/messages_defaultSet/en.js.map +1 -1
- package/login/i18n/messages_defaultSet/es.d.ts +2 -0
- package/login/i18n/messages_defaultSet/es.js +3 -1
- package/login/i18n/messages_defaultSet/es.js.map +1 -1
- package/login/i18n/messages_defaultSet/fa.d.ts +2 -0
- package/login/i18n/messages_defaultSet/fa.js +3 -1
- package/login/i18n/messages_defaultSet/fa.js.map +1 -1
- package/login/i18n/messages_defaultSet/fi.d.ts +2 -0
- package/login/i18n/messages_defaultSet/fi.js +3 -1
- package/login/i18n/messages_defaultSet/fi.js.map +1 -1
- package/login/i18n/messages_defaultSet/fr.d.ts +2 -0
- package/login/i18n/messages_defaultSet/fr.js +3 -1
- package/login/i18n/messages_defaultSet/fr.js.map +1 -1
- package/login/i18n/messages_defaultSet/hu.d.ts +2 -0
- package/login/i18n/messages_defaultSet/hu.js +3 -1
- package/login/i18n/messages_defaultSet/hu.js.map +1 -1
- package/login/i18n/messages_defaultSet/index.d.ts +20 -0
- package/login/i18n/messages_defaultSet/it.d.ts +2 -0
- package/login/i18n/messages_defaultSet/it.js +3 -1
- package/login/i18n/messages_defaultSet/it.js.map +1 -1
- package/login/i18n/messages_defaultSet/ja.d.ts +2 -0
- package/login/i18n/messages_defaultSet/ja.js +3 -1
- package/login/i18n/messages_defaultSet/ja.js.map +1 -1
- package/login/i18n/messages_defaultSet/ka.d.ts +2 -0
- package/login/i18n/messages_defaultSet/ka.js +3 -1
- package/login/i18n/messages_defaultSet/ka.js.map +1 -1
- package/login/i18n/messages_defaultSet/lt.d.ts +2 -0
- package/login/i18n/messages_defaultSet/lt.js +3 -1
- package/login/i18n/messages_defaultSet/lt.js.map +1 -1
- package/login/i18n/messages_defaultSet/lv.d.ts +2 -0
- package/login/i18n/messages_defaultSet/lv.js +3 -1
- package/login/i18n/messages_defaultSet/lv.js.map +1 -1
- package/login/i18n/messages_defaultSet/nl.d.ts +2 -0
- package/login/i18n/messages_defaultSet/nl.js +3 -1
- package/login/i18n/messages_defaultSet/nl.js.map +1 -1
- package/login/i18n/messages_defaultSet/no.d.ts +2 -0
- package/login/i18n/messages_defaultSet/no.js +3 -1
- package/login/i18n/messages_defaultSet/no.js.map +1 -1
- package/login/i18n/messages_defaultSet/pl.d.ts +2 -0
- package/login/i18n/messages_defaultSet/pl.js +3 -1
- package/login/i18n/messages_defaultSet/pl.js.map +1 -1
- package/login/i18n/messages_defaultSet/pt-BR.d.ts +2 -0
- package/login/i18n/messages_defaultSet/pt-BR.js +3 -1
- package/login/i18n/messages_defaultSet/pt-BR.js.map +1 -1
- package/login/i18n/messages_defaultSet/pt.d.ts +2 -0
- package/login/i18n/messages_defaultSet/pt.js +3 -1
- package/login/i18n/messages_defaultSet/pt.js.map +1 -1
- package/login/i18n/messages_defaultSet/ru.d.ts +2 -0
- package/login/i18n/messages_defaultSet/ru.js +3 -1
- package/login/i18n/messages_defaultSet/ru.js.map +1 -1
- package/login/i18n/messages_defaultSet/sk.d.ts +2 -0
- package/login/i18n/messages_defaultSet/sk.js +3 -1
- package/login/i18n/messages_defaultSet/sk.js.map +1 -1
- package/login/i18n/messages_defaultSet/sv.d.ts +2 -0
- package/login/i18n/messages_defaultSet/sv.js +3 -1
- package/login/i18n/messages_defaultSet/sv.js.map +1 -1
- package/login/i18n/messages_defaultSet/th.d.ts +2 -0
- package/login/i18n/messages_defaultSet/th.js +3 -1
- package/login/i18n/messages_defaultSet/th.js.map +1 -1
- package/login/i18n/messages_defaultSet/tr.d.ts +2 -0
- package/login/i18n/messages_defaultSet/tr.js +3 -1
- package/login/i18n/messages_defaultSet/tr.js.map +1 -1
- package/login/i18n/messages_defaultSet/uk.d.ts +2 -0
- package/login/i18n/messages_defaultSet/uk.js +3 -1
- package/login/i18n/messages_defaultSet/uk.js.map +1 -1
- package/login/i18n/messages_defaultSet/zh-CN.d.ts +2 -0
- package/login/i18n/messages_defaultSet/zh-CN.js +3 -1
- package/login/i18n/messages_defaultSet/zh-CN.js.map +1 -1
- package/login/i18n/messages_defaultSet/zh-TW.d.ts +2 -0
- package/login/i18n/messages_defaultSet/zh-TW.js +3 -1
- package/login/i18n/messages_defaultSet/zh-TW.js.map +1 -1
- package/login/pages/LinkIdpAction.d.ts +7 -0
- package/login/pages/LinkIdpAction.js +13 -0
- package/login/pages/LinkIdpAction.js.map +1 -0
- package/login/pages/Login.js +20 -13
- package/login/pages/Login.js.map +1 -1
- package/login/pages/Login.useScript.d.ts +21 -0
- package/login/pages/Login.useScript.js +47 -0
- package/login/pages/Login.useScript.js.map +1 -0
- package/login/pages/LoginPassword.js +15 -8
- package/login/pages/LoginPassword.js.map +1 -1
- package/login/pages/LoginPassword.useScript.d.ts +21 -0
- package/login/pages/LoginPassword.useScript.js +47 -0
- package/login/pages/LoginPassword.useScript.js.map +1 -0
- package/login/pages/LoginUsername.js +3 -3
- package/login/pages/LoginUsername.js.map +1 -1
- package/login/pages/LoginUsername.useScript.d.ts +1 -1
- package/login/pages/LoginUsername.useScript.js +3 -3
- package/login/pages/LoginUsername.useScript.js.map +1 -1
- package/package.json +14 -1
- package/src/bin/shared/constants.ts +2 -1
- package/src/bin/start-keycloak/start-keycloak.ts +2 -2
- package/src/login/DefaultPage.tsx +3 -0
- package/src/login/KcContext/KcContext.ts +26 -0
- package/src/login/KcContext/kcContextMocks.ts +27 -2
- package/src/login/i18n/messages_defaultSet/ar.ts +3 -1
- package/src/login/i18n/messages_defaultSet/ca.ts +3 -1
- package/src/login/i18n/messages_defaultSet/cs.ts +3 -1
- package/src/login/i18n/messages_defaultSet/da.ts +3 -1
- package/src/login/i18n/messages_defaultSet/de.ts +3 -1
- package/src/login/i18n/messages_defaultSet/el.ts +3 -1
- package/src/login/i18n/messages_defaultSet/en.ts +3 -1
- package/src/login/i18n/messages_defaultSet/es.ts +3 -1
- package/src/login/i18n/messages_defaultSet/fa.ts +3 -1
- package/src/login/i18n/messages_defaultSet/fi.ts +3 -1
- package/src/login/i18n/messages_defaultSet/fr.ts +3 -1
- package/src/login/i18n/messages_defaultSet/hu.ts +3 -1
- package/src/login/i18n/messages_defaultSet/it.ts +3 -1
- package/src/login/i18n/messages_defaultSet/ja.ts +3 -1
- package/src/login/i18n/messages_defaultSet/ka.ts +3 -1
- package/src/login/i18n/messages_defaultSet/lt.ts +3 -1
- package/src/login/i18n/messages_defaultSet/lv.ts +3 -1
- package/src/login/i18n/messages_defaultSet/nl.ts +3 -1
- package/src/login/i18n/messages_defaultSet/no.ts +3 -1
- package/src/login/i18n/messages_defaultSet/pl.ts +3 -1
- package/src/login/i18n/messages_defaultSet/pt-BR.ts +3 -1
- package/src/login/i18n/messages_defaultSet/pt.ts +3 -1
- package/src/login/i18n/messages_defaultSet/ru.ts +3 -1
- package/src/login/i18n/messages_defaultSet/sk.ts +3 -1
- package/src/login/i18n/messages_defaultSet/sv.ts +3 -1
- package/src/login/i18n/messages_defaultSet/th.ts +3 -1
- package/src/login/i18n/messages_defaultSet/tr.ts +3 -1
- package/src/login/i18n/messages_defaultSet/uk.ts +3 -1
- package/src/login/i18n/messages_defaultSet/zh-CN.ts +3 -1
- package/src/login/i18n/messages_defaultSet/zh-TW.ts +3 -1
- package/src/login/pages/LinkIdpAction.tsx +53 -0
- package/src/login/pages/Login.tsx +45 -1
- package/src/login/pages/Login.useScript.tsx +71 -0
- package/src/login/pages/LoginPassword.tsx +44 -1
- package/src/login/pages/LoginPassword.useScript.tsx +71 -0
- package/src/login/pages/LoginUsername.tsx +6 -6
- package/src/login/pages/LoginUsername.useScript.tsx +4 -4
- package/stories/login/pages/LinkIdpAction.stories.tsx +36 -0
- package/vite-plugin/index.js +3 -2
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combined Username + Password login page (login.ftl) with optional WebAuthn passkey support.
|
|
3
|
+
* Renders standard login form plus conditional passkey authenticator section.
|
|
4
|
+
*/
|
|
1
5
|
import type { JSX } from "keycloakify/tools/JSX";
|
|
2
6
|
import { useState } from "react";
|
|
3
7
|
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
|
@@ -7,6 +11,7 @@ import type { PageProps } from "keycloakify/login/pages/PageProps";
|
|
|
7
11
|
import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
|
8
12
|
import type { KcContext } from "../KcContext";
|
|
9
13
|
import type { I18n } from "../i18n";
|
|
14
|
+
import { useScript } from "keycloakify/login/pages/Login.useScript";
|
|
10
15
|
|
|
11
16
|
export default function Login(props: PageProps<Extract<KcContext, { pageId: "login.ftl" }>, I18n>) {
|
|
12
17
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
|
@@ -16,12 +21,21 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
|
|
|
16
21
|
classes
|
|
17
22
|
});
|
|
18
23
|
|
|
19
|
-
const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField } =
|
|
24
|
+
const { social, realm, url, usernameHidden, login, auth, registrationDisabled, messagesPerField, enableWebAuthnConditionalUI, authenticators } =
|
|
25
|
+
kcContext;
|
|
20
26
|
|
|
21
27
|
const { msg, msgStr } = i18n;
|
|
22
28
|
|
|
23
29
|
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
|
24
30
|
|
|
31
|
+
const webAuthnButtonId = "authenticateWebAuthnButton";
|
|
32
|
+
|
|
33
|
+
useScript({
|
|
34
|
+
webAuthnButtonId,
|
|
35
|
+
kcContext,
|
|
36
|
+
i18n
|
|
37
|
+
});
|
|
38
|
+
|
|
25
39
|
return (
|
|
26
40
|
<Template
|
|
27
41
|
kcContext={kcContext}
|
|
@@ -191,6 +205,36 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
|
|
|
191
205
|
)}
|
|
192
206
|
</div>
|
|
193
207
|
</div>
|
|
208
|
+
{enableWebAuthnConditionalUI && (
|
|
209
|
+
<>
|
|
210
|
+
<form id="webauth" action={url.loginAction} method="post">
|
|
211
|
+
<input type="hidden" id="clientDataJSON" name="clientDataJSON" />
|
|
212
|
+
<input type="hidden" id="authenticatorData" name="authenticatorData" />
|
|
213
|
+
<input type="hidden" id="signature" name="signature" />
|
|
214
|
+
<input type="hidden" id="credentialId" name="credentialId" />
|
|
215
|
+
<input type="hidden" id="userHandle" name="userHandle" />
|
|
216
|
+
<input type="hidden" id="error" name="error" />
|
|
217
|
+
</form>
|
|
218
|
+
|
|
219
|
+
{authenticators !== undefined && authenticators.authenticators.length !== 0 && (
|
|
220
|
+
<>
|
|
221
|
+
<form id="authn_select" className={kcClsx("kcFormClass")}>
|
|
222
|
+
{authenticators.authenticators.map((authenticator, i) => (
|
|
223
|
+
<input key={i} type="hidden" name="authn_use_chk" readOnly value={authenticator.credentialId} />
|
|
224
|
+
))}
|
|
225
|
+
</form>
|
|
226
|
+
</>
|
|
227
|
+
)}
|
|
228
|
+
<br />
|
|
229
|
+
|
|
230
|
+
<input
|
|
231
|
+
id={webAuthnButtonId}
|
|
232
|
+
type="button"
|
|
233
|
+
className={kcClsx("kcButtonClass", "kcButtonDefaultClass", "kcButtonBlockClass", "kcButtonLargeClass")}
|
|
234
|
+
value={msgStr("passkey-doAuthenticate")}
|
|
235
|
+
/>
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
194
238
|
</Template>
|
|
195
239
|
);
|
|
196
240
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
|
3
|
+
import { assert } from "keycloakify/tools/assert";
|
|
4
|
+
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
|
5
|
+
import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
|
|
6
|
+
|
|
7
|
+
type KcContextLike = {
|
|
8
|
+
url: {
|
|
9
|
+
resourcesPath: string;
|
|
10
|
+
};
|
|
11
|
+
isUserIdentified: "true" | "false";
|
|
12
|
+
challenge: string;
|
|
13
|
+
userVerification: KcContext.WebauthnAuthenticate["userVerification"];
|
|
14
|
+
rpId: string;
|
|
15
|
+
createTimeout: number | string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
assert<keyof KcContextLike extends keyof KcContext.Login ? true : false>();
|
|
19
|
+
assert<KcContext.Login extends KcContextLike ? true : false>();
|
|
20
|
+
|
|
21
|
+
type I18nLike = {
|
|
22
|
+
msgStr: (key: "webauthn-unsupported-browser-text") => string;
|
|
23
|
+
isFetchingTranslations: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function useScript(params: { webAuthnButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
|
|
27
|
+
const { webAuthnButtonId, kcContext, i18n } = params;
|
|
28
|
+
|
|
29
|
+
const { url, isUserIdentified, challenge, userVerification, rpId, createTimeout } = kcContext;
|
|
30
|
+
|
|
31
|
+
const { msgStr, isFetchingTranslations } = i18n;
|
|
32
|
+
|
|
33
|
+
const { insertScriptTags } = useInsertScriptTags({
|
|
34
|
+
componentOrHookName: "Login",
|
|
35
|
+
scriptTags: [
|
|
36
|
+
{
|
|
37
|
+
type: "module",
|
|
38
|
+
textContent: () => `
|
|
39
|
+
|
|
40
|
+
import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
|
|
41
|
+
const authButton = document.getElementById('${webAuthnButtonId}');
|
|
42
|
+
authButton.addEventListener("click", function() {
|
|
43
|
+
const input = {
|
|
44
|
+
isUserIdentified : ${isUserIdentified},
|
|
45
|
+
challenge : '${challenge}',
|
|
46
|
+
userVerification : '${userVerification}',
|
|
47
|
+
rpId : '${rpId}',
|
|
48
|
+
createTimeout : ${createTimeout},
|
|
49
|
+
errmsg : ${JSON.stringify(msgStr("webauthn-unsupported-browser-text"))}
|
|
50
|
+
};
|
|
51
|
+
authenticateByWebAuthn(input);
|
|
52
|
+
});
|
|
53
|
+
`
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (isFetchingTranslations) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
(async () => {
|
|
64
|
+
await waitForElementMountedOnDom({
|
|
65
|
+
elementId: webAuthnButtonId
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
insertScriptTags();
|
|
69
|
+
})();
|
|
70
|
+
}, [isFetchingTranslations]);
|
|
71
|
+
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password step (login-password.ftl) for flows where username is already captured.
|
|
3
|
+
* Adds conditional WebAuthn passkey authenticate section when enabled.
|
|
4
|
+
*/
|
|
1
5
|
import type { JSX } from "keycloakify/tools/JSX";
|
|
2
6
|
import { useState } from "react";
|
|
3
7
|
import { kcSanitize } from "keycloakify/lib/kcSanitize";
|
|
@@ -7,6 +11,7 @@ import { getKcClsx, type KcClsx } from "keycloakify/login/lib/kcClsx";
|
|
|
7
11
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
|
8
12
|
import type { KcContext } from "../KcContext";
|
|
9
13
|
import type { I18n } from "../i18n";
|
|
14
|
+
import { useScript } from "keycloakify/login/pages/LoginPassword.useScript";
|
|
10
15
|
|
|
11
16
|
export default function LoginPassword(props: PageProps<Extract<KcContext, { pageId: "login-password.ftl" }>, I18n>) {
|
|
12
17
|
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
|
|
@@ -16,12 +21,20 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
|
|
|
16
21
|
classes
|
|
17
22
|
});
|
|
18
23
|
|
|
19
|
-
const { realm, url, messagesPerField } = kcContext;
|
|
24
|
+
const { realm, url, messagesPerField, enableWebAuthnConditionalUI, authenticators } = kcContext;
|
|
20
25
|
|
|
21
26
|
const { msg, msgStr } = i18n;
|
|
22
27
|
|
|
23
28
|
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
|
24
29
|
|
|
30
|
+
const webAuthnButtonId = "authenticateWebAuthnButton";
|
|
31
|
+
|
|
32
|
+
useScript({
|
|
33
|
+
webAuthnButtonId,
|
|
34
|
+
kcContext,
|
|
35
|
+
i18n
|
|
36
|
+
});
|
|
37
|
+
|
|
25
38
|
return (
|
|
26
39
|
<Template
|
|
27
40
|
kcContext={kcContext}
|
|
@@ -98,6 +111,36 @@ export default function LoginPassword(props: PageProps<Extract<KcContext, { page
|
|
|
98
111
|
</form>
|
|
99
112
|
</div>
|
|
100
113
|
</div>
|
|
114
|
+
{enableWebAuthnConditionalUI && (
|
|
115
|
+
<>
|
|
116
|
+
<form id="webauth" action={url.loginAction} method="post">
|
|
117
|
+
<input type="hidden" id="clientDataJSON" name="clientDataJSON" />
|
|
118
|
+
<input type="hidden" id="authenticatorData" name="authenticatorData" />
|
|
119
|
+
<input type="hidden" id="signature" name="signature" />
|
|
120
|
+
<input type="hidden" id="credentialId" name="credentialId" />
|
|
121
|
+
<input type="hidden" id="userHandle" name="userHandle" />
|
|
122
|
+
<input type="hidden" id="error" name="error" />
|
|
123
|
+
</form>
|
|
124
|
+
|
|
125
|
+
{authenticators !== undefined && authenticators.authenticators.length !== 0 && (
|
|
126
|
+
<>
|
|
127
|
+
<form id="authn_select" className={kcClsx("kcFormClass")}>
|
|
128
|
+
{authenticators.authenticators.map((authenticator, i) => (
|
|
129
|
+
<input key={i} type="hidden" name="authn_use_chk" readOnly value={authenticator.credentialId} />
|
|
130
|
+
))}
|
|
131
|
+
</form>
|
|
132
|
+
</>
|
|
133
|
+
)}
|
|
134
|
+
<br />
|
|
135
|
+
|
|
136
|
+
<input
|
|
137
|
+
id={webAuthnButtonId}
|
|
138
|
+
type="button"
|
|
139
|
+
className={kcClsx("kcButtonClass", "kcButtonDefaultClass", "kcButtonBlockClass", "kcButtonLargeClass")}
|
|
140
|
+
value={msgStr("passkey-doAuthenticate")}
|
|
141
|
+
/>
|
|
142
|
+
</>
|
|
143
|
+
)}
|
|
101
144
|
</Template>
|
|
102
145
|
);
|
|
103
146
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useInsertScriptTags } from "keycloakify/tools/useInsertScriptTags";
|
|
3
|
+
import { assert } from "keycloakify/tools/assert";
|
|
4
|
+
import { KcContext } from "keycloakify/login/KcContext/KcContext";
|
|
5
|
+
import { waitForElementMountedOnDom } from "keycloakify/tools/waitForElementMountedOnDom";
|
|
6
|
+
|
|
7
|
+
type KcContextLike = {
|
|
8
|
+
url: {
|
|
9
|
+
resourcesPath: string;
|
|
10
|
+
};
|
|
11
|
+
isUserIdentified: "true" | "false";
|
|
12
|
+
challenge: string;
|
|
13
|
+
userVerification: KcContext.WebauthnAuthenticate["userVerification"];
|
|
14
|
+
rpId: string;
|
|
15
|
+
createTimeout: number | string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
assert<keyof KcContextLike extends keyof KcContext.LoginPassword ? true : false>();
|
|
19
|
+
assert<KcContext.LoginPassword extends KcContextLike ? true : false>();
|
|
20
|
+
|
|
21
|
+
type I18nLike = {
|
|
22
|
+
msgStr: (key: "webauthn-unsupported-browser-text") => string;
|
|
23
|
+
isFetchingTranslations: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function useScript(params: { webAuthnButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
|
|
27
|
+
const { webAuthnButtonId, kcContext, i18n } = params;
|
|
28
|
+
|
|
29
|
+
const { url, isUserIdentified, challenge, userVerification, rpId, createTimeout } = kcContext;
|
|
30
|
+
|
|
31
|
+
const { msgStr, isFetchingTranslations } = i18n;
|
|
32
|
+
|
|
33
|
+
const { insertScriptTags } = useInsertScriptTags({
|
|
34
|
+
componentOrHookName: "LoginPassword",
|
|
35
|
+
scriptTags: [
|
|
36
|
+
{
|
|
37
|
+
type: "module",
|
|
38
|
+
textContent: () => `
|
|
39
|
+
|
|
40
|
+
import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
|
|
41
|
+
const authButton = document.getElementById('${webAuthnButtonId}');
|
|
42
|
+
authButton.addEventListener("click", function() {
|
|
43
|
+
const input = {
|
|
44
|
+
isUserIdentified : ${isUserIdentified},
|
|
45
|
+
challenge : '${challenge}',
|
|
46
|
+
userVerification : '${userVerification}',
|
|
47
|
+
rpId : '${rpId}',
|
|
48
|
+
createTimeout : ${createTimeout},
|
|
49
|
+
errmsg : ${JSON.stringify(msgStr("webauthn-unsupported-browser-text"))}
|
|
50
|
+
};
|
|
51
|
+
authenticateByWebAuthn(input);
|
|
52
|
+
});
|
|
53
|
+
`
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (isFetchingTranslations) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
(async () => {
|
|
64
|
+
await waitForElementMountedOnDom({
|
|
65
|
+
elementId: webAuthnButtonId
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
insertScriptTags();
|
|
69
|
+
})();
|
|
70
|
+
}, [isFetchingTranslations]);
|
|
71
|
+
}
|
|
@@ -21,10 +21,10 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
|
|
|
21
21
|
|
|
22
22
|
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const webAuthnButtonId = "authenticateWebAuthnButton";
|
|
25
25
|
|
|
26
26
|
useScript({
|
|
27
|
-
|
|
27
|
+
webAuthnButtonId,
|
|
28
28
|
kcContext,
|
|
29
29
|
i18n
|
|
30
30
|
});
|
|
@@ -164,7 +164,7 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
|
|
|
164
164
|
<input type="hidden" id="error" name="error" />
|
|
165
165
|
</form>
|
|
166
166
|
|
|
167
|
-
{authenticators !== undefined &&
|
|
167
|
+
{authenticators !== undefined && authenticators.authenticators.length !== 0 && (
|
|
168
168
|
<>
|
|
169
169
|
<form id="authn_select" className={kcClsx("kcFormClass")}>
|
|
170
170
|
{authenticators.authenticators.map((authenticator, i) => (
|
|
@@ -174,13 +174,13 @@ export default function LoginUsername(props: PageProps<Extract<KcContext, { page
|
|
|
174
174
|
</>
|
|
175
175
|
)}
|
|
176
176
|
<br />
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
<input
|
|
179
|
-
id={
|
|
179
|
+
id={webAuthnButtonId}
|
|
180
180
|
type="button"
|
|
181
181
|
className={kcClsx("kcButtonClass", "kcButtonDefaultClass", "kcButtonBlockClass", "kcButtonLargeClass")}
|
|
182
182
|
value={msgStr("passkey-doAuthenticate")}
|
|
183
|
-
|
|
183
|
+
/>
|
|
184
184
|
</>
|
|
185
185
|
)}
|
|
186
186
|
</Template>
|
|
@@ -23,8 +23,8 @@ type I18nLike = {
|
|
|
23
23
|
isFetchingTranslations: boolean;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export function useScript(params: {
|
|
27
|
-
const {
|
|
26
|
+
export function useScript(params: { webAuthnButtonId: string; kcContext: KcContextLike; i18n: I18nLike }) {
|
|
27
|
+
const { webAuthnButtonId, kcContext, i18n } = params;
|
|
28
28
|
|
|
29
29
|
const { url, isUserIdentified, challenge, userVerification, rpId, createTimeout } = kcContext;
|
|
30
30
|
|
|
@@ -38,7 +38,7 @@ export function useScript(params: { authButtonId: string; kcContext: KcContextLi
|
|
|
38
38
|
textContent: () => `
|
|
39
39
|
|
|
40
40
|
import { authenticateByWebAuthn } from "${url.resourcesPath}/js/webauthnAuthenticate.js";
|
|
41
|
-
const authButton = document.getElementById('${
|
|
41
|
+
const authButton = document.getElementById('${webAuthnButtonId}');
|
|
42
42
|
authButton.addEventListener("click", function() {
|
|
43
43
|
const input = {
|
|
44
44
|
isUserIdentified : ${isUserIdentified},
|
|
@@ -62,7 +62,7 @@ export function useScript(params: { authButtonId: string; kcContext: KcContextLi
|
|
|
62
62
|
|
|
63
63
|
(async () => {
|
|
64
64
|
await waitForElementMountedOnDom({
|
|
65
|
-
elementId:
|
|
65
|
+
elementId: webAuthnButtonId
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
insertScriptTags();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
3
|
+
import { createKcPageStory } from "../KcPageStory";
|
|
4
|
+
|
|
5
|
+
const { KcPageStory } = createKcPageStory({ pageId: "link-idp-action.ftl" });
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "login/link-idp-action.ftl",
|
|
9
|
+
component: KcPageStory
|
|
10
|
+
} satisfies Meta<typeof KcPageStory>;
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
type Story = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const Default: Story = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<KcPageStory
|
|
19
|
+
kcContext={{
|
|
20
|
+
idpDisplayName: "GitHub",
|
|
21
|
+
url: { loginAction: "/mock-login-action" }
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const DifferentProvider: Story = {
|
|
28
|
+
render: () => (
|
|
29
|
+
<KcPageStory
|
|
30
|
+
kcContext={{
|
|
31
|
+
idpDisplayName: "Google",
|
|
32
|
+
url: { loginAction: "/custom-login-action" }
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
};
|
package/vite-plugin/index.js
CHANGED
|
@@ -1018,8 +1018,8 @@ __nccwpck_require__.r(__webpack_exports__);
|
|
|
1018
1018
|
/* harmony export */ "CONTAINER_NAME": () => (/* binding */ CONTAINER_NAME),
|
|
1019
1019
|
/* harmony export */ "CUSTOM_HANDLER_ENV_NAMES": () => (/* binding */ CUSTOM_HANDLER_ENV_NAMES),
|
|
1020
1020
|
/* harmony export */ "FALLBACK_LANGUAGE_TAG": () => (/* binding */ FALLBACK_LANGUAGE_TAG),
|
|
1021
|
+
/* harmony export */ "KEYCLOAKIFY_LOGGING_JAR_BASENAME": () => (/* binding */ KEYCLOAKIFY_LOGGING_JAR_BASENAME),
|
|
1021
1022
|
/* harmony export */ "KEYCLOAKIFY_LOGGING_VERSION": () => (/* binding */ KEYCLOAKIFY_LOGGING_VERSION),
|
|
1022
|
-
/* harmony export */ "KEYCLOAKIFY_LOGIN_JAR_BASENAME": () => (/* binding */ KEYCLOAKIFY_LOGIN_JAR_BASENAME),
|
|
1023
1023
|
/* harmony export */ "KEYCLOAKIFY_SPA_DEV_SERVER_PORT": () => (/* binding */ KEYCLOAKIFY_SPA_DEV_SERVER_PORT),
|
|
1024
1024
|
/* harmony export */ "KEYCLOAK_THEME": () => (/* binding */ KEYCLOAK_THEME),
|
|
1025
1025
|
/* harmony export */ "LOGIN_THEME_PAGE_IDS": () => (/* binding */ LOGIN_THEME_PAGE_IDS),
|
|
@@ -1057,6 +1057,7 @@ const LOGIN_THEME_PAGE_IDS = [
|
|
|
1057
1057
|
"login-otp.ftl",
|
|
1058
1058
|
"login-update-profile.ftl",
|
|
1059
1059
|
"login-update-password.ftl",
|
|
1060
|
+
"link-idp-action.ftl",
|
|
1060
1061
|
"login-idp-link-confirm.ftl",
|
|
1061
1062
|
"login-idp-link-email.ftl",
|
|
1062
1063
|
"login-page-expired.ftl",
|
|
@@ -1097,7 +1098,7 @@ const CUSTOM_HANDLER_ENV_NAMES = {
|
|
|
1097
1098
|
const KEYCLOAK_THEME = "keycloak-theme";
|
|
1098
1099
|
const KEYCLOAKIFY_SPA_DEV_SERVER_PORT = "KEYCLOAKIFY_SPA_DEV_SERVER_PORT";
|
|
1099
1100
|
const KEYCLOAKIFY_LOGGING_VERSION = "1.0.3";
|
|
1100
|
-
const
|
|
1101
|
+
const KEYCLOAKIFY_LOGGING_JAR_BASENAME = `keycloakify-logging-${KEYCLOAKIFY_LOGGING_VERSION}.jar`;
|
|
1101
1102
|
const TEST_APP_URL = "https://my-theme.keycloakify.dev";
|
|
1102
1103
|
//# sourceMappingURL=constants.js.map
|
|
1103
1104
|
|