@dloizides/auth-web 1.6.0 → 1.8.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/CHANGELOG.md +49 -0
- package/dist/index.d.mts +107 -17
- package/dist/index.d.ts +107 -17
- package/dist/index.js +255 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +252 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -411,6 +411,11 @@ function useAuthStyles(theme) {
|
|
|
411
411
|
primaryButton: {
|
|
412
412
|
borderRadius: radii.input,
|
|
413
413
|
paddingVertical: BUTTON_VERTICAL_PADDING,
|
|
414
|
+
paddingHorizontal: spacing.md,
|
|
415
|
+
// Never shrink below the label width: in a row of actions (e.g. the
|
|
416
|
+
// forgot-password modal's Cancel + submit), shrinking would clip a long
|
|
417
|
+
// label like "Send reset link" past the button's rounded edge.
|
|
418
|
+
flexShrink: 0,
|
|
414
419
|
alignItems: "center",
|
|
415
420
|
justifyContent: "center",
|
|
416
421
|
marginTop: spacing.xs,
|
|
@@ -453,6 +458,8 @@ function useAuthStyles(theme) {
|
|
|
453
458
|
borderWidth: 1,
|
|
454
459
|
borderColor: colors.primary,
|
|
455
460
|
paddingVertical: BUTTON_VERTICAL_PADDING,
|
|
461
|
+
paddingHorizontal: spacing.md,
|
|
462
|
+
flexShrink: 0,
|
|
456
463
|
alignItems: "center",
|
|
457
464
|
justifyContent: "center",
|
|
458
465
|
marginTop: spacing.sm,
|
|
@@ -895,6 +902,9 @@ function useModalStyles(theme) {
|
|
|
895
902
|
flexDirection: "row",
|
|
896
903
|
justifyContent: "flex-end",
|
|
897
904
|
alignItems: "center",
|
|
905
|
+
// Wrap (rather than clip) when a long submit label + Cancel can't
|
|
906
|
+
// fit a narrow modal on one line.
|
|
907
|
+
flexWrap: "wrap",
|
|
898
908
|
marginTop: theme.spacing.md,
|
|
899
909
|
gap: theme.spacing.sm
|
|
900
910
|
},
|
|
@@ -902,6 +912,8 @@ function useModalStyles(theme) {
|
|
|
902
912
|
borderRadius: theme.radii.input,
|
|
903
913
|
paddingVertical: theme.spacing.sm,
|
|
904
914
|
paddingHorizontal: theme.spacing.md,
|
|
915
|
+
// Match the submit button: never shrink below the label width.
|
|
916
|
+
flexShrink: 0,
|
|
905
917
|
borderWidth: SECONDARY_BORDER_WIDTH,
|
|
906
918
|
borderColor: theme.colors.border
|
|
907
919
|
},
|
|
@@ -2499,6 +2511,13 @@ function DevicePinSettingsCard({
|
|
|
2499
2511
|
}
|
|
2500
2512
|
}
|
|
2501
2513
|
|
|
2514
|
+
// src/passkey/PasskeyCeremonyMode.ts
|
|
2515
|
+
var PasskeyCeremonyMode = /* @__PURE__ */ ((PasskeyCeremonyMode2) => {
|
|
2516
|
+
PasskeyCeremonyMode2["Login"] = "login";
|
|
2517
|
+
PasskeyCeremonyMode2["Register"] = "register";
|
|
2518
|
+
return PasskeyCeremonyMode2;
|
|
2519
|
+
})(PasskeyCeremonyMode || {});
|
|
2520
|
+
|
|
2502
2521
|
// src/passkey/passkeyNavigation.ts
|
|
2503
2522
|
var PASSKEY_LOGIN_ENDPOINT = "/bff/passkey/login";
|
|
2504
2523
|
var PASSKEY_REGISTER_ENDPOINT = "/bff/passkey/register";
|
|
@@ -2550,6 +2569,223 @@ function readPasskeyRegistered() {
|
|
|
2550
2569
|
}
|
|
2551
2570
|
return params.get(PASSKEY_PARAM) === PASSKEY_REGISTERED_VALUE;
|
|
2552
2571
|
}
|
|
2572
|
+
|
|
2573
|
+
// src/passkey/PasskeyCeremonyStatus.ts
|
|
2574
|
+
var PasskeyCeremonyStatus = /* @__PURE__ */ ((PasskeyCeremonyStatus2) => {
|
|
2575
|
+
PasskeyCeremonyStatus2["Idle"] = "idle";
|
|
2576
|
+
PasskeyCeremonyStatus2["Running"] = "running";
|
|
2577
|
+
PasskeyCeremonyStatus2["Success"] = "success";
|
|
2578
|
+
PasskeyCeremonyStatus2["Error"] = "error";
|
|
2579
|
+
return PasskeyCeremonyStatus2;
|
|
2580
|
+
})(PasskeyCeremonyStatus || {});
|
|
2581
|
+
|
|
2582
|
+
// src/passkey/base64url.ts
|
|
2583
|
+
function base64UrlToBytes(value) {
|
|
2584
|
+
const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
2585
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
|
2586
|
+
const binary = atob(padded);
|
|
2587
|
+
const bytes = new Uint8Array(new ArrayBuffer(binary.length));
|
|
2588
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
2589
|
+
bytes[i] = binary.charCodeAt(i);
|
|
2590
|
+
}
|
|
2591
|
+
return bytes;
|
|
2592
|
+
}
|
|
2593
|
+
function bytesToBase64Url(buffer) {
|
|
2594
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
2595
|
+
let binary = "";
|
|
2596
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
2597
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2598
|
+
}
|
|
2599
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// src/passkey/webAuthnCoercion.ts
|
|
2603
|
+
var PUBLIC_KEY_TYPE = "public-key";
|
|
2604
|
+
function toDescriptors(value) {
|
|
2605
|
+
if (!Array.isArray(value)) {
|
|
2606
|
+
return void 0;
|
|
2607
|
+
}
|
|
2608
|
+
return value.map((entry) => {
|
|
2609
|
+
const descriptor = entry;
|
|
2610
|
+
return {
|
|
2611
|
+
type: PUBLIC_KEY_TYPE,
|
|
2612
|
+
id: base64UrlToBytes(descriptor.id),
|
|
2613
|
+
transports: descriptor.transports
|
|
2614
|
+
};
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
function toCreationOptions(options) {
|
|
2618
|
+
const user = options.user;
|
|
2619
|
+
return {
|
|
2620
|
+
...options,
|
|
2621
|
+
challenge: base64UrlToBytes(options.challenge),
|
|
2622
|
+
user: { ...user, id: base64UrlToBytes(user.id) },
|
|
2623
|
+
excludeCredentials: toDescriptors(options.excludeCredentials)
|
|
2624
|
+
};
|
|
2625
|
+
}
|
|
2626
|
+
function toRequestOptions(options) {
|
|
2627
|
+
return {
|
|
2628
|
+
...options,
|
|
2629
|
+
challenge: base64UrlToBytes(options.challenge),
|
|
2630
|
+
allowCredentials: toDescriptors(options.allowCredentials)
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
function readTransports(response) {
|
|
2634
|
+
return typeof response.getTransports === "function" ? response.getTransports() : [];
|
|
2635
|
+
}
|
|
2636
|
+
function encodeAttestation(credential) {
|
|
2637
|
+
const response = credential.response;
|
|
2638
|
+
return {
|
|
2639
|
+
id: credential.id,
|
|
2640
|
+
rawId: bytesToBase64Url(credential.rawId),
|
|
2641
|
+
type: credential.type,
|
|
2642
|
+
response: {
|
|
2643
|
+
attestationObject: bytesToBase64Url(response.attestationObject),
|
|
2644
|
+
clientDataJSON: bytesToBase64Url(response.clientDataJSON),
|
|
2645
|
+
transports: readTransports(response)
|
|
2646
|
+
},
|
|
2647
|
+
clientExtensionResults: credential.getClientExtensionResults()
|
|
2648
|
+
};
|
|
2649
|
+
}
|
|
2650
|
+
function encodeAssertion(credential) {
|
|
2651
|
+
const response = credential.response;
|
|
2652
|
+
const userHandle = response.userHandle !== null ? bytesToBase64Url(response.userHandle) : null;
|
|
2653
|
+
return {
|
|
2654
|
+
id: credential.id,
|
|
2655
|
+
rawId: bytesToBase64Url(credential.rawId),
|
|
2656
|
+
type: credential.type,
|
|
2657
|
+
response: {
|
|
2658
|
+
authenticatorData: bytesToBase64Url(response.authenticatorData),
|
|
2659
|
+
clientDataJSON: bytesToBase64Url(response.clientDataJSON),
|
|
2660
|
+
signature: bytesToBase64Url(response.signature),
|
|
2661
|
+
userHandle
|
|
2662
|
+
},
|
|
2663
|
+
clientExtensionResults: credential.getClientExtensionResults()
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
// src/passkey/usePasskeyCeremony.ts
|
|
2668
|
+
var ERROR_CANCELLED = "cancelled";
|
|
2669
|
+
var ERROR_FAILED = "failed";
|
|
2670
|
+
var CANCEL_ERROR_NAMES = ["NotAllowedError", "AbortError"];
|
|
2671
|
+
var ENDPOINTS = {
|
|
2672
|
+
["login" /* Login */]: {
|
|
2673
|
+
options: "/bff/passkey/login/options",
|
|
2674
|
+
verify: "/bff/passkey/login/verify"
|
|
2675
|
+
},
|
|
2676
|
+
["register" /* Register */]: {
|
|
2677
|
+
options: "/bff/passkey/register/options",
|
|
2678
|
+
verify: "/bff/passkey/register/verify"
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
function detectSupport() {
|
|
2682
|
+
return typeof window !== "undefined" && typeof navigator !== "undefined" && typeof navigator.credentials !== "undefined" && typeof window.PublicKeyCredential !== "undefined";
|
|
2683
|
+
}
|
|
2684
|
+
function postJson(url, body) {
|
|
2685
|
+
return fetch(url, {
|
|
2686
|
+
method: "POST",
|
|
2687
|
+
credentials: "same-origin",
|
|
2688
|
+
headers: { "Content-Type": "application/json" },
|
|
2689
|
+
body: JSON.stringify(body ?? {})
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
async function runCeremony(mode, envelope) {
|
|
2693
|
+
const credential = mode === "register" /* Register */ ? await navigator.credentials.create({
|
|
2694
|
+
publicKey: toCreationOptions(envelope.options)
|
|
2695
|
+
}) : await navigator.credentials.get({
|
|
2696
|
+
publicKey: toRequestOptions(envelope.options)
|
|
2697
|
+
});
|
|
2698
|
+
return credential;
|
|
2699
|
+
}
|
|
2700
|
+
function buildVerifyBody(mode, challengeId, credential) {
|
|
2701
|
+
return mode === "register" /* Register */ ? { challengeId, attestationResponse: encodeAttestation(credential) } : { challengeId, assertionResponse: encodeAssertion(credential) };
|
|
2702
|
+
}
|
|
2703
|
+
function redirectFallback(mode, returnUrl) {
|
|
2704
|
+
if (mode === "register" /* Register */) {
|
|
2705
|
+
startPasskeyRegistration(returnUrl);
|
|
2706
|
+
return;
|
|
2707
|
+
}
|
|
2708
|
+
startPasskeyLogin(returnUrl);
|
|
2709
|
+
}
|
|
2710
|
+
function classifyError(caught) {
|
|
2711
|
+
const name = caught instanceof Error ? caught.name : "";
|
|
2712
|
+
return CANCEL_ERROR_NAMES.includes(name) ? ERROR_CANCELLED : ERROR_FAILED;
|
|
2713
|
+
}
|
|
2714
|
+
function usePasskeyCeremony(optionsArg) {
|
|
2715
|
+
const { mode, returnUrl, onSuccess } = optionsArg;
|
|
2716
|
+
const [status, setStatus] = react.useState("idle" /* Idle */);
|
|
2717
|
+
const [error, setError] = react.useState(null);
|
|
2718
|
+
const isSupported = react.useMemo(detectSupport, []);
|
|
2719
|
+
const succeed = react.useCallback(
|
|
2720
|
+
(user) => {
|
|
2721
|
+
setStatus("success" /* Success */);
|
|
2722
|
+
if (onSuccess !== void 0) {
|
|
2723
|
+
onSuccess(user);
|
|
2724
|
+
return;
|
|
2725
|
+
}
|
|
2726
|
+
if (mode === "login" /* Login */ && typeof window !== "undefined") {
|
|
2727
|
+
window.location.assign(returnUrl);
|
|
2728
|
+
}
|
|
2729
|
+
},
|
|
2730
|
+
[mode, onSuccess, returnUrl]
|
|
2731
|
+
);
|
|
2732
|
+
const start = react.useCallback(() => {
|
|
2733
|
+
if (!isSupported) {
|
|
2734
|
+
redirectFallback(mode, returnUrl);
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2737
|
+
setStatus("running" /* Running */);
|
|
2738
|
+
setError(null);
|
|
2739
|
+
void (async () => {
|
|
2740
|
+
try {
|
|
2741
|
+
const optionsResponse = await postJson(ENDPOINTS[mode].options, {});
|
|
2742
|
+
if (!optionsResponse.ok) {
|
|
2743
|
+
redirectFallback(mode, returnUrl);
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
const envelope = await optionsResponse.json();
|
|
2747
|
+
const credential = await runCeremony(mode, envelope);
|
|
2748
|
+
if (credential === null) {
|
|
2749
|
+
setStatus("error" /* Error */);
|
|
2750
|
+
setError(ERROR_FAILED);
|
|
2751
|
+
return;
|
|
2752
|
+
}
|
|
2753
|
+
const verifyResponse = await postJson(
|
|
2754
|
+
ENDPOINTS[mode].verify,
|
|
2755
|
+
buildVerifyBody(mode, envelope.challengeId, credential)
|
|
2756
|
+
);
|
|
2757
|
+
if (!verifyResponse.ok) {
|
|
2758
|
+
setStatus("error" /* Error */);
|
|
2759
|
+
setError(ERROR_FAILED);
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2762
|
+
if (mode === "register" /* Register */) {
|
|
2763
|
+
const { success } = await verifyResponse.json();
|
|
2764
|
+
if (!success) {
|
|
2765
|
+
setStatus("error" /* Error */);
|
|
2766
|
+
setError(ERROR_FAILED);
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
succeed(void 0);
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2772
|
+
const { user } = await verifyResponse.json();
|
|
2773
|
+
succeed(user);
|
|
2774
|
+
} catch (caught) {
|
|
2775
|
+
setStatus("error" /* Error */);
|
|
2776
|
+
setError(classifyError(caught));
|
|
2777
|
+
}
|
|
2778
|
+
})();
|
|
2779
|
+
}, [isSupported, mode, returnUrl, succeed]);
|
|
2780
|
+
const reset = react.useCallback(() => {
|
|
2781
|
+
setStatus("idle" /* Idle */);
|
|
2782
|
+
setError(null);
|
|
2783
|
+
}, []);
|
|
2784
|
+
return react.useMemo(
|
|
2785
|
+
() => ({ status, error, isSupported, start, reset }),
|
|
2786
|
+
[status, error, isSupported, start, reset]
|
|
2787
|
+
);
|
|
2788
|
+
}
|
|
2553
2789
|
var DEFAULT_RETURN_URL = "/";
|
|
2554
2790
|
function PasskeyLoginButton({
|
|
2555
2791
|
returnUrl = DEFAULT_RETURN_URL,
|
|
@@ -2563,7 +2799,8 @@ function PasskeyLoginButton({
|
|
|
2563
2799
|
() => ({ ...DEFAULT_PASSKEY_LOGIN_LABELS, ...labelsProp }),
|
|
2564
2800
|
[labelsProp]
|
|
2565
2801
|
);
|
|
2566
|
-
const
|
|
2802
|
+
const ceremony = usePasskeyCeremony({ mode: "login" /* Login */, returnUrl });
|
|
2803
|
+
const error = ceremony.error ?? readPasskeyError();
|
|
2567
2804
|
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { testID: withTestIdPrefix(AuthTestIds.passkeyLogin, testIdPrefix), children: [
|
|
2568
2805
|
error !== null ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2569
2806
|
reactNative.Text,
|
|
@@ -2581,7 +2818,7 @@ function PasskeyLoginButton({
|
|
|
2581
2818
|
accessibilityRole: "button",
|
|
2582
2819
|
style: styles3.secondaryButton,
|
|
2583
2820
|
testID: withTestIdPrefix(AuthTestIds.passkeyLoginButton, testIdPrefix),
|
|
2584
|
-
onPress: () =>
|
|
2821
|
+
onPress: () => ceremony.start(),
|
|
2585
2822
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.secondaryButtonText, children: labels.signInButton })
|
|
2586
2823
|
}
|
|
2587
2824
|
)
|
|
@@ -2606,8 +2843,12 @@ function PasskeySettingsCard({
|
|
|
2606
2843
|
() => ({ ...DEFAULT_PASSKEY_SETTINGS_LABELS, ...labelsProp }),
|
|
2607
2844
|
[labelsProp]
|
|
2608
2845
|
);
|
|
2609
|
-
const justRegistered = readPasskeyRegistered();
|
|
2610
2846
|
const target = returnUrl ?? readCurrentPath();
|
|
2847
|
+
const ceremony = usePasskeyCeremony({
|
|
2848
|
+
mode: "register" /* Register */,
|
|
2849
|
+
returnUrl: target
|
|
2850
|
+
});
|
|
2851
|
+
const justRegistered = ceremony.status === "success" /* Success */ || readPasskeyRegistered();
|
|
2611
2852
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2612
2853
|
reactNative.View,
|
|
2613
2854
|
{
|
|
@@ -2633,7 +2874,7 @@ function PasskeySettingsCard({
|
|
|
2633
2874
|
accessibilityRole: "button",
|
|
2634
2875
|
style: styles3.primaryButton,
|
|
2635
2876
|
testID: withTestIdPrefix(AuthTestIds.passkeySettingsAdd, testIdPrefix),
|
|
2636
|
-
onPress: () =>
|
|
2877
|
+
onPress: () => ceremony.start(),
|
|
2637
2878
|
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.primaryButtonText, children: labels.addButton })
|
|
2638
2879
|
}
|
|
2639
2880
|
)
|
|
@@ -3000,6 +3241,12 @@ function resolvePostLoginRoute(user, table) {
|
|
|
3000
3241
|
return table.fallback ?? null;
|
|
3001
3242
|
}
|
|
3002
3243
|
|
|
3244
|
+
// src/router/resolveAccessibleRoutes.ts
|
|
3245
|
+
function resolveAccessibleRoutes(user, table) {
|
|
3246
|
+
const userRoles = new Set(collectUserRoles(user));
|
|
3247
|
+
return table.routes.filter((entry) => userRoles.has(entry.role));
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3003
3250
|
Object.defineProperty(exports, "BffAuthClient", {
|
|
3004
3251
|
enumerable: true,
|
|
3005
3252
|
get: function () { return authClient.BffAuthClient; }
|
|
@@ -3043,6 +3290,8 @@ exports.OtpForm = OtpForm;
|
|
|
3043
3290
|
exports.OtpLoginStep = OtpLoginStep;
|
|
3044
3291
|
exports.PASSWORD_MAX_LENGTH = PASSWORD_MAX_LENGTH;
|
|
3045
3292
|
exports.PASSWORD_MIN_LENGTH = PASSWORD_MIN_LENGTH;
|
|
3293
|
+
exports.PasskeyCeremonyMode = PasskeyCeremonyMode;
|
|
3294
|
+
exports.PasskeyCeremonyStatus = PasskeyCeremonyStatus;
|
|
3046
3295
|
exports.PasskeyLoginButton = PasskeyLoginButton;
|
|
3047
3296
|
exports.PasskeySettingsCard = PasskeySettingsCard;
|
|
3048
3297
|
exports.PasswordPolicyError = PasswordPolicyError;
|
|
@@ -3063,6 +3312,7 @@ exports.isValidForgotPasswordEmail = isValidForgotPasswordEmail;
|
|
|
3063
3312
|
exports.readCachedPreferredMethod = readCachedPreferredMethod;
|
|
3064
3313
|
exports.readPasskeyError = readPasskeyError;
|
|
3065
3314
|
exports.readPasskeyRegistered = readPasskeyRegistered;
|
|
3315
|
+
exports.resolveAccessibleRoutes = resolveAccessibleRoutes;
|
|
3066
3316
|
exports.resolvePostLoginRoute = resolvePostLoginRoute;
|
|
3067
3317
|
exports.startPasskeyLogin = startPasskeyLogin;
|
|
3068
3318
|
exports.startPasskeyRegistration = startPasskeyRegistration;
|
|
@@ -3077,6 +3327,7 @@ exports.useDevicePinEnroll = useDevicePinEnroll;
|
|
|
3077
3327
|
exports.useDevicePinUnlock = useDevicePinUnlock;
|
|
3078
3328
|
exports.useForgotPasswordSubmit = useForgotPasswordSubmit;
|
|
3079
3329
|
exports.useOtpLogin = useOtpLogin;
|
|
3330
|
+
exports.usePasskeyCeremony = usePasskeyCeremony;
|
|
3080
3331
|
exports.usePinLogin = usePinLogin;
|
|
3081
3332
|
exports.usePreferredMethodSetting = usePreferredMethodSetting;
|
|
3082
3333
|
exports.useResetPasswordForm = useResetPasswordForm;
|