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