@dloizides/auth-web 1.8.1 → 1.9.1
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/dist/index.d.mts +15 -24
- package/dist/index.d.ts +15 -24
- package/dist/index.js +60 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +110 -110
package/dist/index.d.mts
CHANGED
|
@@ -345,6 +345,13 @@ interface PasskeyLoginLabels {
|
|
|
345
345
|
errorCancelled: string;
|
|
346
346
|
/** Error banner when the ceremony failed. */
|
|
347
347
|
errorFailed: string;
|
|
348
|
+
/**
|
|
349
|
+
* The "try another way" affordance shown after an in-page ceremony error — it
|
|
350
|
+
* hands off to the Keycloak-hosted passkey/password flow. This is the seam that
|
|
351
|
+
* rescues a user whose credential is only enrolled in Keycloak (not the in-page
|
|
352
|
+
* RP store): the in-page ceremony can't see it, so they fall back to KC.
|
|
353
|
+
*/
|
|
354
|
+
tryAnotherWay: string;
|
|
348
355
|
}
|
|
349
356
|
/** Strings rendered by `<PasskeySettingsCard>` — the account passkey card. */
|
|
350
357
|
interface PasskeySettingsLabels {
|
|
@@ -1301,6 +1308,7 @@ declare const AuthTestIds: {
|
|
|
1301
1308
|
readonly passkeyLogin: "auth-passkey-login";
|
|
1302
1309
|
readonly passkeyLoginButton: "auth-passkey-login-button";
|
|
1303
1310
|
readonly passkeyLoginError: "auth-passkey-login-error";
|
|
1311
|
+
readonly passkeyLoginFallback: "auth-passkey-login-fallback";
|
|
1304
1312
|
readonly passkeySettings: "auth-passkey-settings";
|
|
1305
1313
|
readonly passkeySettingsAdd: "auth-passkey-settings-add";
|
|
1306
1314
|
readonly passkeySettingsSuccess: "auth-passkey-settings-success";
|
|
@@ -1686,30 +1694,6 @@ declare const enum PasskeyCeremonyStatus {
|
|
|
1686
1694
|
Error = "error"
|
|
1687
1695
|
}
|
|
1688
1696
|
|
|
1689
|
-
/**
|
|
1690
|
-
* Navigation helpers for the passkey (WebAuthn) BFF endpoints.
|
|
1691
|
-
*
|
|
1692
|
-
* Unlike the device-PIN client (which is plain `fetch`), the passkey login and
|
|
1693
|
-
* registration ceremonies are NOT fetches — they are full-page BROWSER
|
|
1694
|
-
* NAVIGATIONS. The BFF (`Bff.AspNetCore` 1.3.x) drives the WebAuthn ceremony
|
|
1695
|
-
* through Keycloak's hosted pages, then redirects the browser back:
|
|
1696
|
-
* - login: `/bff/passkey/login?returnUrl=<rel>` → on success the browser
|
|
1697
|
-
* lands on `returnUrl` with the session cookie already set;
|
|
1698
|
-
* - registration: `/bff/passkey/register?returnUrl=<rel>` → Keycloak re-auths
|
|
1699
|
-
* the user (password) then runs the credential-creation ceremony, returning
|
|
1700
|
-
* the browser to `returnUrl?passkey=registered`;
|
|
1701
|
-
* - failure / cancel: the BFF redirects to `<login>?passkeyError=cancelled` or
|
|
1702
|
-
* `<login>?passkeyError=failed`.
|
|
1703
|
-
*
|
|
1704
|
-
* react-query-FREE by construction: there is NO fetch here at all — every
|
|
1705
|
-
* function is a pure `window.location` read or `window.location.assign`. The
|
|
1706
|
-
* login route group has no QueryClient provider, so the passkey login button
|
|
1707
|
-
* (which lives there) MUST avoid any react-query primitive; navigation satisfies
|
|
1708
|
-
* that trivially.
|
|
1709
|
-
*
|
|
1710
|
-
* All reads are SSR-safe: when `window` is undefined (server render / native)
|
|
1711
|
-
* they return a benign default rather than throwing.
|
|
1712
|
-
*/
|
|
1713
1697
|
/** The recognised passkey error codes the BFF sends back via `?passkeyError=`. */
|
|
1714
1698
|
type PasskeyErrorCode = 'cancelled' | 'failed';
|
|
1715
1699
|
/**
|
|
@@ -1768,6 +1752,13 @@ interface UsePasskeyCeremonyResult {
|
|
|
1768
1752
|
isSupported: boolean;
|
|
1769
1753
|
/** Start the ceremony (or redirect-fallback when the RP is off / unsupported). */
|
|
1770
1754
|
start: () => void;
|
|
1755
|
+
/**
|
|
1756
|
+
* Hand off to the legacy Keycloak redirect explicitly. Wired to the "try
|
|
1757
|
+
* another way" affordance a surface shows after an in-page error — the escape
|
|
1758
|
+
* hatch for a credential enrolled only in Keycloak (invisible to the in-page
|
|
1759
|
+
* RP store), which the in-page ceremony cannot use.
|
|
1760
|
+
*/
|
|
1761
|
+
fallbackToRedirect: () => void;
|
|
1771
1762
|
/** Clear a terminal state so the affordance can be retried. */
|
|
1772
1763
|
reset: () => void;
|
|
1773
1764
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -345,6 +345,13 @@ interface PasskeyLoginLabels {
|
|
|
345
345
|
errorCancelled: string;
|
|
346
346
|
/** Error banner when the ceremony failed. */
|
|
347
347
|
errorFailed: string;
|
|
348
|
+
/**
|
|
349
|
+
* The "try another way" affordance shown after an in-page ceremony error — it
|
|
350
|
+
* hands off to the Keycloak-hosted passkey/password flow. This is the seam that
|
|
351
|
+
* rescues a user whose credential is only enrolled in Keycloak (not the in-page
|
|
352
|
+
* RP store): the in-page ceremony can't see it, so they fall back to KC.
|
|
353
|
+
*/
|
|
354
|
+
tryAnotherWay: string;
|
|
348
355
|
}
|
|
349
356
|
/** Strings rendered by `<PasskeySettingsCard>` — the account passkey card. */
|
|
350
357
|
interface PasskeySettingsLabels {
|
|
@@ -1301,6 +1308,7 @@ declare const AuthTestIds: {
|
|
|
1301
1308
|
readonly passkeyLogin: "auth-passkey-login";
|
|
1302
1309
|
readonly passkeyLoginButton: "auth-passkey-login-button";
|
|
1303
1310
|
readonly passkeyLoginError: "auth-passkey-login-error";
|
|
1311
|
+
readonly passkeyLoginFallback: "auth-passkey-login-fallback";
|
|
1304
1312
|
readonly passkeySettings: "auth-passkey-settings";
|
|
1305
1313
|
readonly passkeySettingsAdd: "auth-passkey-settings-add";
|
|
1306
1314
|
readonly passkeySettingsSuccess: "auth-passkey-settings-success";
|
|
@@ -1686,30 +1694,6 @@ declare const enum PasskeyCeremonyStatus {
|
|
|
1686
1694
|
Error = "error"
|
|
1687
1695
|
}
|
|
1688
1696
|
|
|
1689
|
-
/**
|
|
1690
|
-
* Navigation helpers for the passkey (WebAuthn) BFF endpoints.
|
|
1691
|
-
*
|
|
1692
|
-
* Unlike the device-PIN client (which is plain `fetch`), the passkey login and
|
|
1693
|
-
* registration ceremonies are NOT fetches — they are full-page BROWSER
|
|
1694
|
-
* NAVIGATIONS. The BFF (`Bff.AspNetCore` 1.3.x) drives the WebAuthn ceremony
|
|
1695
|
-
* through Keycloak's hosted pages, then redirects the browser back:
|
|
1696
|
-
* - login: `/bff/passkey/login?returnUrl=<rel>` → on success the browser
|
|
1697
|
-
* lands on `returnUrl` with the session cookie already set;
|
|
1698
|
-
* - registration: `/bff/passkey/register?returnUrl=<rel>` → Keycloak re-auths
|
|
1699
|
-
* the user (password) then runs the credential-creation ceremony, returning
|
|
1700
|
-
* the browser to `returnUrl?passkey=registered`;
|
|
1701
|
-
* - failure / cancel: the BFF redirects to `<login>?passkeyError=cancelled` or
|
|
1702
|
-
* `<login>?passkeyError=failed`.
|
|
1703
|
-
*
|
|
1704
|
-
* react-query-FREE by construction: there is NO fetch here at all — every
|
|
1705
|
-
* function is a pure `window.location` read or `window.location.assign`. The
|
|
1706
|
-
* login route group has no QueryClient provider, so the passkey login button
|
|
1707
|
-
* (which lives there) MUST avoid any react-query primitive; navigation satisfies
|
|
1708
|
-
* that trivially.
|
|
1709
|
-
*
|
|
1710
|
-
* All reads are SSR-safe: when `window` is undefined (server render / native)
|
|
1711
|
-
* they return a benign default rather than throwing.
|
|
1712
|
-
*/
|
|
1713
1697
|
/** The recognised passkey error codes the BFF sends back via `?passkeyError=`. */
|
|
1714
1698
|
type PasskeyErrorCode = 'cancelled' | 'failed';
|
|
1715
1699
|
/**
|
|
@@ -1768,6 +1752,13 @@ interface UsePasskeyCeremonyResult {
|
|
|
1768
1752
|
isSupported: boolean;
|
|
1769
1753
|
/** Start the ceremony (or redirect-fallback when the RP is off / unsupported). */
|
|
1770
1754
|
start: () => void;
|
|
1755
|
+
/**
|
|
1756
|
+
* Hand off to the legacy Keycloak redirect explicitly. Wired to the "try
|
|
1757
|
+
* another way" affordance a surface shows after an in-page error — the escape
|
|
1758
|
+
* hatch for a credential enrolled only in Keycloak (invisible to the in-page
|
|
1759
|
+
* RP store), which the in-page ceremony cannot use.
|
|
1760
|
+
*/
|
|
1761
|
+
fallbackToRedirect: () => void;
|
|
1771
1762
|
/** Clear a terminal state so the affordance can be retried. */
|
|
1772
1763
|
reset: () => void;
|
|
1773
1764
|
}
|
package/dist/index.js
CHANGED
|
@@ -236,7 +236,8 @@ var DEFAULT_PASSKEY_LOGIN_LABELS = {
|
|
|
236
236
|
signInButton: "Sign in with a passkey",
|
|
237
237
|
signInHint: "Use your fingerprint, face, or security key to sign in",
|
|
238
238
|
errorCancelled: "Passkey sign-in was cancelled. You can try again or sign in another way.",
|
|
239
|
-
errorFailed: "Passkey sign-in didn't work. Try again, or sign in another way."
|
|
239
|
+
errorFailed: "Passkey sign-in didn't work. Try again, or sign in another way.",
|
|
240
|
+
tryAnotherWay: "Sign in another way"
|
|
240
241
|
};
|
|
241
242
|
var DEFAULT_PASSKEY_SETTINGS_LABELS = {
|
|
242
243
|
title: "Passkeys",
|
|
@@ -338,6 +339,7 @@ var AuthTestIds = {
|
|
|
338
339
|
passkeyLogin: "auth-passkey-login",
|
|
339
340
|
passkeyLoginButton: "auth-passkey-login-button",
|
|
340
341
|
passkeyLoginError: "auth-passkey-login-error",
|
|
342
|
+
passkeyLoginFallback: "auth-passkey-login-fallback",
|
|
341
343
|
// Passkey settings card
|
|
342
344
|
passkeySettings: "auth-passkey-settings",
|
|
343
345
|
passkeySettingsAdd: "auth-passkey-settings-add",
|
|
@@ -2518,6 +2520,18 @@ var PasskeyCeremonyMode = /* @__PURE__ */ ((PasskeyCeremonyMode2) => {
|
|
|
2518
2520
|
return PasskeyCeremonyMode2;
|
|
2519
2521
|
})(PasskeyCeremonyMode || {});
|
|
2520
2522
|
|
|
2523
|
+
// src/passkey/browserLocation.ts
|
|
2524
|
+
function getBrowserLocation() {
|
|
2525
|
+
if (typeof window === "undefined") {
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
const browserLocation = window.location;
|
|
2529
|
+
if (browserLocation === void 0) {
|
|
2530
|
+
return null;
|
|
2531
|
+
}
|
|
2532
|
+
return browserLocation;
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2521
2535
|
// src/passkey/passkeyNavigation.ts
|
|
2522
2536
|
var PASSKEY_LOGIN_ENDPOINT = "/bff/passkey/login";
|
|
2523
2537
|
var PASSKEY_REGISTER_ENDPOINT = "/bff/passkey/register";
|
|
@@ -2531,22 +2545,25 @@ function buildNavigationUrl(endpoint, returnUrl) {
|
|
|
2531
2545
|
return `${endpoint}?${RETURN_URL_PARAM}=${encodeURIComponent(returnUrl)}`;
|
|
2532
2546
|
}
|
|
2533
2547
|
function readSearchParams() {
|
|
2534
|
-
|
|
2548
|
+
const browserLocation = getBrowserLocation();
|
|
2549
|
+
if (browserLocation === null) {
|
|
2535
2550
|
return null;
|
|
2536
2551
|
}
|
|
2537
|
-
return new URLSearchParams(
|
|
2552
|
+
return new URLSearchParams(browserLocation.search);
|
|
2538
2553
|
}
|
|
2539
2554
|
function startPasskeyLogin(returnUrl) {
|
|
2540
|
-
|
|
2555
|
+
const browserLocation = getBrowserLocation();
|
|
2556
|
+
if (browserLocation === null) {
|
|
2541
2557
|
return;
|
|
2542
2558
|
}
|
|
2543
|
-
|
|
2559
|
+
browserLocation.assign(buildNavigationUrl(PASSKEY_LOGIN_ENDPOINT, returnUrl));
|
|
2544
2560
|
}
|
|
2545
2561
|
function startPasskeyRegistration(returnUrl) {
|
|
2546
|
-
|
|
2562
|
+
const browserLocation = getBrowserLocation();
|
|
2563
|
+
if (browserLocation === null) {
|
|
2547
2564
|
return;
|
|
2548
2565
|
}
|
|
2549
|
-
|
|
2566
|
+
browserLocation.assign(buildNavigationUrl(PASSKEY_REGISTER_ENDPOINT, returnUrl));
|
|
2550
2567
|
}
|
|
2551
2568
|
function readPasskeyError() {
|
|
2552
2569
|
const params = readSearchParams();
|
|
@@ -2723,8 +2740,9 @@ function usePasskeyCeremony(optionsArg) {
|
|
|
2723
2740
|
onSuccess(user);
|
|
2724
2741
|
return;
|
|
2725
2742
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2743
|
+
const browserLocation = getBrowserLocation();
|
|
2744
|
+
if (mode === "login" /* Login */ && browserLocation !== null) {
|
|
2745
|
+
browserLocation.assign(returnUrl);
|
|
2728
2746
|
}
|
|
2729
2747
|
},
|
|
2730
2748
|
[mode, onSuccess, returnUrl]
|
|
@@ -2746,6 +2764,10 @@ function usePasskeyCeremony(optionsArg) {
|
|
|
2746
2764
|
const envelope = await optionsResponse.json();
|
|
2747
2765
|
const credential = await runCeremony(mode, envelope);
|
|
2748
2766
|
if (credential === null) {
|
|
2767
|
+
if (mode === "login" /* Login */) {
|
|
2768
|
+
redirectFallback(mode, returnUrl);
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2749
2771
|
setStatus("error" /* Error */);
|
|
2750
2772
|
setError(ERROR_FAILED);
|
|
2751
2773
|
return;
|
|
@@ -2777,13 +2799,16 @@ function usePasskeyCeremony(optionsArg) {
|
|
|
2777
2799
|
}
|
|
2778
2800
|
})();
|
|
2779
2801
|
}, [isSupported, mode, returnUrl, succeed]);
|
|
2802
|
+
const fallbackToRedirect = react.useCallback(() => {
|
|
2803
|
+
redirectFallback(mode, returnUrl);
|
|
2804
|
+
}, [mode, returnUrl]);
|
|
2780
2805
|
const reset = react.useCallback(() => {
|
|
2781
2806
|
setStatus("idle" /* Idle */);
|
|
2782
2807
|
setError(null);
|
|
2783
2808
|
}, []);
|
|
2784
2809
|
return react.useMemo(
|
|
2785
|
-
() => ({ status, error, isSupported, start, reset }),
|
|
2786
|
-
[status, error, isSupported, start, reset]
|
|
2810
|
+
() => ({ status, error, isSupported, start, fallbackToRedirect, reset }),
|
|
2811
|
+
[status, error, isSupported, start, fallbackToRedirect, reset]
|
|
2787
2812
|
);
|
|
2788
2813
|
}
|
|
2789
2814
|
var DEFAULT_RETURN_URL = "/";
|
|
@@ -2802,14 +2827,27 @@ function PasskeyLoginButton({
|
|
|
2802
2827
|
const ceremony = usePasskeyCeremony({ mode: "login" /* Login */, returnUrl });
|
|
2803
2828
|
const error = ceremony.error ?? readPasskeyError();
|
|
2804
2829
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { testID: withTestIdPrefix(AuthTestIds.passkeyLogin, testIdPrefix), children: [
|
|
2805
|
-
error !== null ? /* @__PURE__ */ jsxRuntime.
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2830
|
+
error !== null ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2831
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2832
|
+
reactNative.Text,
|
|
2833
|
+
{
|
|
2834
|
+
style: styles3.errorText,
|
|
2835
|
+
testID: withTestIdPrefix(AuthTestIds.passkeyLoginError, testIdPrefix),
|
|
2836
|
+
children: error === "cancelled" ? labels.errorCancelled : labels.errorFailed
|
|
2837
|
+
}
|
|
2838
|
+
),
|
|
2839
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2840
|
+
reactNative.TouchableOpacity,
|
|
2841
|
+
{
|
|
2842
|
+
accessibilityHint: labels.tryAnotherWay,
|
|
2843
|
+
accessibilityLabel: labels.tryAnotherWay,
|
|
2844
|
+
accessibilityRole: "button",
|
|
2845
|
+
testID: withTestIdPrefix(AuthTestIds.passkeyLoginFallback, testIdPrefix),
|
|
2846
|
+
onPress: () => ceremony.fallbackToRedirect(),
|
|
2847
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.linkText, children: labels.tryAnotherWay })
|
|
2848
|
+
}
|
|
2849
|
+
)
|
|
2850
|
+
] }) : null,
|
|
2813
2851
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2814
2852
|
reactNative.TouchableOpacity,
|
|
2815
2853
|
{
|
|
@@ -2826,10 +2864,11 @@ function PasskeyLoginButton({
|
|
|
2826
2864
|
}
|
|
2827
2865
|
var DEFAULT_RETURN_PATH = "/";
|
|
2828
2866
|
function readCurrentPath() {
|
|
2829
|
-
|
|
2867
|
+
const browserLocation = getBrowserLocation();
|
|
2868
|
+
if (browserLocation === null) {
|
|
2830
2869
|
return DEFAULT_RETURN_PATH;
|
|
2831
2870
|
}
|
|
2832
|
-
return
|
|
2871
|
+
return browserLocation.pathname;
|
|
2833
2872
|
}
|
|
2834
2873
|
function PasskeySettingsCard({
|
|
2835
2874
|
returnUrl,
|