@dloizides/auth-web 1.10.0 → 1.12.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/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ReactNode, ReactElement } from 'react';
2
- import { BffAuthClient, BffUser, HttpClient, BffLoginRequest, BffForgotPasswordRequest, BffResetPasswordRequest, BffOtpRequestResult } from '@dloizides/auth-client';
3
- export { BffAuthClient, BffAuthClientOptions, BffForgotPasswordRequest, BffLoginRequest, BffOtpRequestRequest, BffOtpRequestResult, BffOtpVerifyRequest, BffPinLoginRequest, BffRegisterRequest, BffResetPasswordRequest, BffUser, HttpClient, HttpRequest, HttpResponse, createFetchHttpClient } from '@dloizides/auth-client';
2
+ import { BffAuthClient, BffUser, DemoCredentials, HttpClient, BffLoginRequest, BffForgotPasswordRequest, BffResetPasswordRequest, BffOtpRequestResult } from '@dloizides/auth-client';
3
+ export { BffAuthClient, BffAuthClientOptions, BffForgotPasswordRequest, BffLoginRequest, BffOtpRequestRequest, BffOtpRequestResult, BffOtpVerifyRequest, BffPinLoginRequest, BffRegisterRequest, BffResetPasswordRequest, BffUser, DemoCredentials, HttpClient, HttpRequest, HttpResponse, createFetchHttpClient } from '@dloizides/auth-client';
4
4
  import { UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
5
5
 
6
6
  /**
@@ -391,6 +391,21 @@ interface PreferredMethodHintLabels {
391
391
  /** Full sentence shown when the cached preference is passkey. */
392
392
  passkey: string;
393
393
  }
394
+ /** Strings rendered by `<DemoCredentialsHint>` — the public-demo credentials panel. */
395
+ interface DemoCredentialsHintLabels {
396
+ /** Panel heading (e.g. "Demo access"). */
397
+ title: string;
398
+ /** Copy under the heading explaining what the demo account is. */
399
+ description: string;
400
+ /** Row label for the demo username. */
401
+ usernameLabel: string;
402
+ /** Row label for the demo password. */
403
+ passwordLabel: string;
404
+ /** The "use these credentials" button that fills the login form. */
405
+ useCredentials: string;
406
+ /** Accessibility hint for that button. */
407
+ useCredentialsHint: string;
408
+ }
394
409
  /** Strings rendered by `<ResetPasswordForm>`. */
395
410
  interface ResetPasswordFormLabels {
396
411
  title: string;
@@ -424,8 +439,30 @@ declare const DEFAULT_PASSKEY_LOGIN_LABELS: PasskeyLoginLabels;
424
439
  declare const DEFAULT_PASSKEY_SETTINGS_LABELS: PasskeySettingsLabels;
425
440
  declare const DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS: PreferredMethodSettingsLabels;
426
441
  declare const DEFAULT_PREFERRED_METHOD_HINT_LABELS: PreferredMethodHintLabels;
442
+ declare const DEFAULT_DEMO_CREDENTIALS_HINT_LABELS: DemoCredentialsHintLabels;
427
443
  declare const DEFAULT_RESET_PASSWORD_LABELS: ResetPasswordFormLabels;
428
444
 
445
+ /**
446
+ * Who asked for a prefill — the difference between "the user requested this" and
447
+ * "the app offered it".
448
+ *
449
+ * A seed that the USER asked for (tapping "use these credentials") must always
450
+ * apply: they can see the fields, they know what is in them, and they asked
451
+ * anyway. A seed the APP offers (a demo pair arriving from `GET /bff/config`
452
+ * mid-typing) must not: it is an unrequested, racing write against a field the
453
+ * user may already be filling in.
454
+ *
455
+ * Collapsing the two into a bare nonce bump is what makes a late-arriving config
456
+ * fetch overwrite the username a real account holder is halfway through typing.
457
+ * `<LoginForm>` switches on this, never on a string literal.
458
+ */
459
+ declare const enum PrefillIntent {
460
+ /** The user asked for it. Always applies, over whatever is in the fields. */
461
+ Explicit = "explicit",
462
+ /** The app offered it. Applies only to untouched, empty fields. */
463
+ Opportunistic = "opportunistic"
464
+ }
465
+
429
466
  /**
430
467
  * `<LoginForm>` — the ready-made, themeable password-login form.
431
468
  *
@@ -467,9 +504,35 @@ interface LoginFormProps {
467
504
  * is byte-identical to before.
468
505
  */
469
506
  chromeless?: boolean;
507
+ /**
508
+ * Seeds the username field's INITIAL value. The field stays fully editable and
509
+ * clearable afterwards — this is a starting value, not a controlled one, so a
510
+ * real account holder can always wipe it and type their own.
511
+ */
512
+ initialUsername?: string;
513
+ /** Seeds the password field's initial value. Editable + clearable, as above. */
514
+ initialPassword?: string;
515
+ /**
516
+ * Re-seed signal. Changing it re-applies `initialUsername` / `initialPassword`
517
+ * to the fields even after the user has edited them — needed because React
518
+ * ignores a `useState` initial value on re-render, so changing the `initial*`
519
+ * props alone does nothing once mounted.
520
+ *
521
+ * Most apps should not manage this by hand: `useDemoCredentials` returns a
522
+ * `loginFormProps` bag carrying all four, ready to spread.
523
+ */
524
+ prefillNonce?: number;
525
+ /**
526
+ * Whether the pending re-seed was requested by the user or offered by the app.
527
+ * An {@link PrefillIntent.Opportunistic} re-seed (e.g. a demo pair arriving
528
+ * from `GET /bff/config` after mount) is DROPPED unless both fields are still
529
+ * empty, so a late fetch never overwrites what someone is typing. Defaults to
530
+ * unconditional — omit it and every nonce bump applies.
531
+ */
532
+ prefillIntent?: PrefillIntent;
470
533
  }
471
534
  /** Themeable password-login form built on `useBffAuth`. */
472
- declare function LoginForm({ client, theme: themeProp, labels: labelsProp, onSuccess, onForgotPassword, onSignUp, testIdPrefix, chromeless, }: Readonly<LoginFormProps>): ReactElement;
535
+ declare function LoginForm({ client, theme: themeProp, labels: labelsProp, onSuccess, onForgotPassword, onSignUp, testIdPrefix, chromeless, initialUsername, initialPassword, prefillNonce, prefillIntent, }: Readonly<LoginFormProps>): ReactElement;
473
536
 
474
537
  /**
475
538
  * `<ForgotPasswordForm>` — the ready-made, themeable "request a reset link"
@@ -768,6 +831,7 @@ declare const enum DevicePinErrorKey {
768
831
  * error) so the unlock / enrol UIs can route on each one rather than collapsing
769
832
  * them into a single opaque failure.
770
833
  */
834
+
771
835
  /**
772
836
  * The per-device half of `GET /bff/config` — what this device remembers about
773
837
  * the last user, all fields safe-defaulted by the parser when absent.
@@ -790,6 +854,14 @@ interface BffLoginConfig {
790
854
  registrationEnabled: boolean;
791
855
  /** The per-device remembered state. */
792
856
  deviceState: BffDeviceState;
857
+ /**
858
+ * The published demo credentials this deployment advertises, or `null` when
859
+ * none is configured. Feeds `useDemoCredentials` / `<DemoCredentialsHint>`:
860
+ * `useDemoCredentials({ credentials: config.demo ?? undefined })`. `auth-client`
861
+ * 4.2.0 populates it; older clients (and hand-rolled objects) simply omit it,
862
+ * which reads as `undefined` — treated the same as "no demo" by the hint.
863
+ */
864
+ demo?: DemoCredentials | null;
793
865
  }
794
866
  /**
795
867
  * Discriminated result of a device-PIN UNLOCK attempt. The client never rejects;
@@ -1211,6 +1283,50 @@ interface PreferredMethodHintProps {
1211
1283
  /** Themeable login-screen preferred-method hint. Renders `null` when there is nothing to say. */
1212
1284
  declare function PreferredMethodHint({ method, theme: themeProp, labels: labelsProp, testIdPrefix, }: Readonly<PreferredMethodHintProps>): ReactElement | null;
1213
1285
 
1286
+ /**
1287
+ * `<DemoCredentialsHint>` — the public-demo credentials panel.
1288
+ *
1289
+ * Mount on the login surface of a product with an OPEN demo account whose
1290
+ * credentials are already published (typically on the marketing site). It shows
1291
+ * the username and password and offers a one-tap "use these credentials" action
1292
+ * that fills the form, so a non-technical evaluator never has to copy-paste a
1293
+ * password between two tabs to see the product.
1294
+ *
1295
+ * It authenticates nothing. It fills fields; `<LoginForm>` then submits them
1296
+ * down the ordinary path — same BFF, same Keycloak, same session, same roles.
1297
+ * There is no bypass here and no credential in this package: the host supplies
1298
+ * the pair, and the demo account's blast radius is whatever the IdP grants it.
1299
+ *
1300
+ * Fails closed: no credentials (or a half-configured pair) renders `null` — zero
1301
+ * DOM — so a misconfigured env var can never publish an empty demo panel.
1302
+ *
1303
+ * Compose it as a child of `<AuthScreen>`, above a `chromeless` `<LoginForm>`;
1304
+ * `useDemoCredentials` wires the two together. Read-only mode (no `onUse`) drops
1305
+ * the button and just displays the credentials.
1306
+ */
1307
+
1308
+ interface DemoCredentialsHintProps {
1309
+ /**
1310
+ * The demo credentials to advertise. `undefined` — or a pair with a blank
1311
+ * username or password — renders nothing at all.
1312
+ */
1313
+ credentials?: DemoCredentials;
1314
+ /** Explicit theme; overrides any `<AuthThemeProvider>`. */
1315
+ theme?: AuthTheme;
1316
+ /** Localised copy. Partial — unspecified keys fall back to English defaults. */
1317
+ labels?: Partial<DemoCredentialsHintLabels>;
1318
+ /** Prefix applied to every `testID`. */
1319
+ testIdPrefix?: string;
1320
+ /**
1321
+ * Called with the resolved credentials when the user taps "use these
1322
+ * credentials". Omit to render the panel read-only (no button) — the values
1323
+ * are still selectable, so a visitor can copy them by hand.
1324
+ */
1325
+ onUse?: (credentials: DemoCredentials) => void;
1326
+ }
1327
+ /** Themeable public-demo credentials panel. Renders `null` when there is no demo. */
1328
+ declare function DemoCredentialsHint({ credentials, theme: themeProp, labels: labelsProp, testIdPrefix, onUse, }: Readonly<DemoCredentialsHintProps>): ReactElement | null;
1329
+
1214
1330
  /**
1215
1331
  * React context for the `AuthTheme`.
1216
1332
  *
@@ -1316,6 +1432,10 @@ declare const AuthTestIds: {
1316
1432
  readonly preferredMethodSettingsOption: "auth-preferred-method-settings-option";
1317
1433
  readonly preferredMethodSettingsError: "auth-preferred-method-settings-error";
1318
1434
  readonly preferredMethodHint: "auth-preferred-method-hint";
1435
+ readonly demoCredentialsHint: "auth-demo-credentials-hint";
1436
+ readonly demoCredentialsUsername: "auth-demo-credentials-username";
1437
+ readonly demoCredentialsPassword: "auth-demo-credentials-password";
1438
+ readonly demoCredentialsUseButton: "auth-demo-credentials-use";
1319
1439
  };
1320
1440
  /** Apply an optional prefix to a base test ID. */
1321
1441
  declare function withTestIdPrefix(baseId: string, prefix?: string): string;
@@ -1818,6 +1938,78 @@ interface UsePreferredMethodSettingResult {
1818
1938
  }
1819
1939
  declare function usePreferredMethodSetting({ client, onChanged, }: UsePreferredMethodSettingArgs): UsePreferredMethodSettingResult;
1820
1940
 
1941
+ /** Options for {@link useDemoCredentials}. */
1942
+ interface UseDemoCredentialsOptions {
1943
+ /**
1944
+ * The demo credentials to advertise. Omit (or pass a half-configured pair) and
1945
+ * the whole surface disappears: the hint renders nothing and the form is never
1946
+ * seeded. See `resolveDemoCredentials`.
1947
+ */
1948
+ credentials?: DemoCredentials;
1949
+ /**
1950
+ * When `true` the form arrives ALREADY filled, so signing in as the demo user
1951
+ * is a single tap on "Sign in" — the hint panel then explains why the fields
1952
+ * are populated and re-fills them if the visitor clears them.
1953
+ *
1954
+ * Defaults to `false`: the fields start empty and the visitor opts in by
1955
+ * tapping "Use these credentials". Prefer the default unless the page is a
1956
+ * genuinely public demo — a login form that arrives pre-filled with someone
1957
+ * else's credentials is confusing on a page real account holders also use.
1958
+ */
1959
+ prefillOnMount?: boolean;
1960
+ }
1961
+ /** The `<LoginForm>` props that carry the demo prefill. Spread onto the form. */
1962
+ interface DemoCredentialsLoginFormProps {
1963
+ initialUsername?: string;
1964
+ initialPassword?: string;
1965
+ prefillNonce: number;
1966
+ prefillIntent: PrefillIntent;
1967
+ }
1968
+ /** The `<DemoCredentialsHint>` props that carry the credentials + fill action. */
1969
+ interface DemoCredentialsHintWiring {
1970
+ credentials?: DemoCredentials;
1971
+ onUse: (credentials: DemoCredentials) => void;
1972
+ }
1973
+ /** What {@link useDemoCredentials} hands back. */
1974
+ interface UseDemoCredentialsResult {
1975
+ /** Spread onto `<LoginForm>`. */
1976
+ loginFormProps: DemoCredentialsLoginFormProps;
1977
+ /** Spread onto `<DemoCredentialsHint>`. */
1978
+ hintProps: DemoCredentialsHintWiring;
1979
+ }
1980
+ /** Wire a demo-credentials hint to a login form. Returns two prop bags to spread. */
1981
+ declare function useDemoCredentials({ credentials, prefillOnMount, }?: Readonly<UseDemoCredentialsOptions>): UseDemoCredentialsResult;
1982
+
1983
+ /**
1984
+ * `resolveDemoCredentials` — the single fail-closed gate for the demo surface.
1985
+ *
1986
+ * Every demo affordance in the package (the `<DemoCredentialsHint>` panel and
1987
+ * the `useDemoCredentials` wiring hook) routes its input through this function,
1988
+ * so "should we advertise a demo account at all?" is decided in exactly ONE
1989
+ * place and always the same way.
1990
+ *
1991
+ * Fail closed, deliberately. The realistic source of these values is an env var
1992
+ * (`process.env.EXPO_PUBLIC_DEMO_USERNAME`), which is `string | undefined` at
1993
+ * runtime no matter what the type says — a consumer writing `username: env.X!`
1994
+ * hands us `undefined` and TypeScript never notices. A half-configured demo must
1995
+ * render NOTHING rather than an empty panel inviting a client to sign in with
1996
+ * blank credentials, so anything that is not a genuinely non-empty string pair
1997
+ * resolves to `null`.
1998
+ */
1999
+
2000
+ /**
2001
+ * Narrow an unvalidated demo-credential pair to a usable one, or `null`.
2002
+ *
2003
+ * Returns `null` for: `undefined` / `null`, a non-string field (runtime junk
2004
+ * from an unset env var), a blank/whitespace-only username, and an empty
2005
+ * password. Otherwise returns a NEW pair with the username trimmed.
2006
+ *
2007
+ * The password is deliberately NOT trimmed: leading/trailing whitespace can be
2008
+ * a legitimate part of a password, and silently altering it would turn a
2009
+ * working credential into a mystery login failure.
2010
+ */
2011
+ declare function resolveDemoCredentials(credentials?: DemoCredentials | null): DemoCredentials | null;
2012
+
1821
2013
  /**
1822
2014
  * `createBffAuthClient` — the one-line wiring of a same-origin `BffAuthClient`.
1823
2015
  *
@@ -2061,4 +2253,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
2061
2253
  /** `true` when the password satisfies every policy rule. */
2062
2254
  declare function isPasswordValid(password: string): boolean;
2063
2255
 
2064
- export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type BffLogoutClient, type CreateBffAuthClientOptions, 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, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, type PerformBffLogoutOptions, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, performBffLogout, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
2256
+ export { AuthScreen, type AuthScreenProps, AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type BffLogoutClient, type CreateBffAuthClientOptions, DEFAULT_AUTH_CARD_MAX_WIDTH, DEFAULT_DEMO_CREDENTIALS_HINT_LABELS, 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, DemoCredentialsHint, type DemoCredentialsHintLabels, type DemoCredentialsHintProps, type DemoCredentialsHintWiring, type DemoCredentialsLoginFormProps, type DevicePinCapableClient, type DevicePinDisableCapableClient, type DevicePinEnrollCapableClient, DevicePinEnrollErrorKey, DevicePinEnrollForm, type DevicePinEnrollFormProps, type DevicePinEnrollLabels, type DevicePinEnrollResult, DevicePinErrorKey, DevicePinInput, type DevicePinInputProps, DevicePinLengthPicker, type DevicePinLengthPickerProps, DevicePinOffer, type DevicePinOfferProps, DevicePinSettingsCard, type DevicePinSettingsCardProps, type DevicePinSettingsLabels, type DevicePinUnlockCapableClient, type DevicePinUnlockLabels, type DevicePinUnlockResult, DevicePinUnlockScreen, type DevicePinUnlockScreenProps, type DevicePinUnlockedUser, ForgotPasswordFields, type ForgotPasswordFieldsLabels, type ForgotPasswordFieldsProps, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, type LoginConfigCapableClient, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasskeyCeremonyMode, PasskeyCeremonyStatus, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, type PerformBffLogoutOptions, PinForm, type PinFormLabels, type PinFormProps, type PreferencesClientOptions, type PreferredMethodClient, PreferredMethodHint, type PreferredMethodHintLabels, type PreferredMethodHintProps, PreferredMethodSettingsCard, type PreferredMethodSettingsCardProps, type PreferredMethodSettingsLabels, PrefillIntent, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffLoginConfigResult, type UseBffResetPasswordOptions, type UseDemoCredentialsOptions, type UseDemoCredentialsResult, type UseDevicePinDisableArgs, type UseDevicePinDisableResult, type UseDevicePinEnrollArgs, type UseDevicePinEnrollResult, type UseDevicePinUnlockArgs, type UseDevicePinUnlockResult, type UseForgotPasswordSubmitArgs, type UseForgotPasswordSubmitResult, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePasskeyCeremonyOptions, type UsePasskeyCeremonyResult, type UsePinLoginOptions, type UsePinLoginResult, type UsePreferredMethodSettingArgs, type UsePreferredMethodSettingResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, cachePreferredMethod, clearCachedPreferredMethod, collectUserRoles, createBffAuthClient, createPreferencesClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, performBffLogout, readCachedPreferredMethod, readPasskeyError, readPasskeyRegistered, resolveAccessibleRoutes, resolveDemoCredentials, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, syncPreferredMethodFromServer, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDemoCredentials, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePasskeyCeremony, usePinLogin, usePreferredMethodSetting, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
package/dist/index.js CHANGED
@@ -262,6 +262,14 @@ var DEFAULT_PREFERRED_METHOD_HINT_LABELS = {
262
262
  pin: "You usually sign in with your quick PIN on this account.",
263
263
  passkey: "You usually sign in with a passkey."
264
264
  };
265
+ var DEFAULT_DEMO_CREDENTIALS_HINT_LABELS = {
266
+ title: "Demo access",
267
+ description: "Explore the full product with our shared demo account.",
268
+ usernameLabel: "Username",
269
+ passwordLabel: "Password",
270
+ useCredentials: "Use these credentials",
271
+ useCredentialsHint: "Fill the sign-in form with the demo username and password"
272
+ };
265
273
  var DEFAULT_RESET_PASSWORD_LABELS = {
266
274
  title: "Reset password",
267
275
  description: "Choose a new password for your account.",
@@ -350,7 +358,12 @@ var AuthTestIds = {
350
358
  preferredMethodSettingsOption: "auth-preferred-method-settings-option",
351
359
  preferredMethodSettingsError: "auth-preferred-method-settings-error",
352
360
  // Preferred-method login-screen hint
353
- preferredMethodHint: "auth-preferred-method-hint"
361
+ preferredMethodHint: "auth-preferred-method-hint",
362
+ // Public-demo credentials panel
363
+ demoCredentialsHint: "auth-demo-credentials-hint",
364
+ demoCredentialsUsername: "auth-demo-credentials-username",
365
+ demoCredentialsPassword: "auth-demo-credentials-password",
366
+ demoCredentialsUseButton: "auth-demo-credentials-use"
354
367
  };
355
368
  function withTestIdPrefix(baseId, prefix) {
356
369
  return prefix === void 0 || prefix === "" ? baseId : `${prefix}-${baseId}`;
@@ -481,6 +494,35 @@ function useAuthStyles(theme) {
481
494
  });
482
495
  }, [theme]);
483
496
  }
497
+
498
+ // src/components/PrefillIntent.ts
499
+ var PrefillIntent = /* @__PURE__ */ ((PrefillIntent2) => {
500
+ PrefillIntent2["Explicit"] = "explicit";
501
+ PrefillIntent2["Opportunistic"] = "opportunistic";
502
+ return PrefillIntent2;
503
+ })(PrefillIntent || {});
504
+
505
+ // src/components/useLoginPrefill.ts
506
+ function useLoginPrefill({
507
+ initialUsername,
508
+ initialPassword,
509
+ prefillNonce,
510
+ prefillIntent
511
+ }) {
512
+ const [username, setUsername] = react.useState(initialUsername ?? "");
513
+ const [password, setPassword] = react.useState(initialPassword ?? "");
514
+ const [appliedNonce, setAppliedNonce] = react.useState(prefillNonce);
515
+ if (prefillNonce !== appliedNonce) {
516
+ setAppliedNonce(prefillNonce);
517
+ const isPristine = username.length === 0 && password.length === 0;
518
+ const mayApply = prefillIntent !== "opportunistic" /* Opportunistic */ || isPristine;
519
+ if (mayApply) {
520
+ setUsername(initialUsername ?? "");
521
+ setPassword(initialPassword ?? "");
522
+ }
523
+ }
524
+ return { username, password, setUsername, setPassword };
525
+ }
484
526
  var BffAuthStatus = /* @__PURE__ */ ((BffAuthStatus2) => {
485
527
  BffAuthStatus2["Loading"] = "loading";
486
528
  BffAuthStatus2["Authenticated"] = "authenticated";
@@ -592,7 +634,11 @@ function LoginForm({
592
634
  onForgotPassword,
593
635
  onSignUp,
594
636
  testIdPrefix,
595
- chromeless = false
637
+ chromeless = false,
638
+ initialUsername,
639
+ initialPassword,
640
+ prefillNonce,
641
+ prefillIntent
596
642
  }) {
597
643
  const theme = useAuthTheme(themeProp);
598
644
  const styles3 = useAuthStyles(theme);
@@ -601,8 +647,12 @@ function LoginForm({
601
647
  [labelsProp]
602
648
  );
603
649
  const { login, isSubmitting } = useBffAuth({ client, probeOnMount: false });
604
- const [username, setUsername] = react.useState("");
605
- const [password, setPassword] = react.useState("");
650
+ const { username, password, setUsername, setPassword } = useLoginPrefill({
651
+ initialUsername,
652
+ initialPassword,
653
+ prefillNonce,
654
+ prefillIntent
655
+ });
606
656
  const [errorText, setErrorText] = react.useState(null);
607
657
  const runLogin = react.useCallback(async () => {
608
658
  setErrorText(null);
@@ -3132,6 +3182,156 @@ function PreferredMethodHint({
3132
3182
  }
3133
3183
  return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { testID: withTestIdPrefix(AuthTestIds.preferredMethodHint, testIdPrefix), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.helperText, children: text }) });
3134
3184
  }
3185
+ var PANEL_BORDER_WIDTH = 1;
3186
+ var ACCENT_RAIL_WIDTH = 3;
3187
+ var TITLE_LETTER_SPACING = 0.8;
3188
+ var DESCRIPTION_LINE_HEIGHT_RATIO = 1.45;
3189
+ var HALF = 2;
3190
+ var MONOSPACE_FONT = reactNative.Platform.select({
3191
+ ios: "Menlo",
3192
+ android: "monospace",
3193
+ default: "monospace"
3194
+ });
3195
+ function useDemoCredentialsStyles(theme) {
3196
+ return react.useMemo(() => {
3197
+ const { colors, radii, spacing, typography } = theme;
3198
+ return reactNative.StyleSheet.create({
3199
+ panel: {
3200
+ borderWidth: PANEL_BORDER_WIDTH,
3201
+ borderLeftWidth: ACCENT_RAIL_WIDTH,
3202
+ borderRadius: radii.card,
3203
+ borderColor: colors.border,
3204
+ borderLeftColor: colors.primary,
3205
+ padding: spacing.md,
3206
+ marginBottom: spacing.md,
3207
+ backgroundColor: colors.background
3208
+ },
3209
+ title: {
3210
+ fontSize: typography.caption,
3211
+ fontWeight: "700",
3212
+ letterSpacing: TITLE_LETTER_SPACING,
3213
+ textTransform: "uppercase",
3214
+ color: colors.primary
3215
+ },
3216
+ description: {
3217
+ fontSize: typography.caption,
3218
+ lineHeight: typography.caption * DESCRIPTION_LINE_HEIGHT_RATIO,
3219
+ marginTop: spacing.xs / HALF,
3220
+ marginBottom: spacing.sm,
3221
+ color: colors.textSecondary
3222
+ },
3223
+ row: {
3224
+ flexDirection: "row",
3225
+ alignItems: "center",
3226
+ justifyContent: "space-between",
3227
+ marginTop: spacing.xs / HALF
3228
+ },
3229
+ rowLabel: {
3230
+ fontSize: typography.caption,
3231
+ color: colors.textSecondary
3232
+ },
3233
+ rowValue: {
3234
+ fontFamily: MONOSPACE_FONT,
3235
+ fontSize: typography.caption,
3236
+ fontWeight: "600",
3237
+ borderWidth: PANEL_BORDER_WIDTH,
3238
+ borderRadius: radii.input,
3239
+ borderColor: colors.border,
3240
+ paddingVertical: spacing.xs / HALF,
3241
+ paddingHorizontal: spacing.xs,
3242
+ // Never let a long password squeeze the label off the row.
3243
+ flexShrink: 1,
3244
+ backgroundColor: colors.surface,
3245
+ color: colors.text
3246
+ }
3247
+ });
3248
+ }, [theme]);
3249
+ }
3250
+
3251
+ // src/demo/resolveDemoCredentials.ts
3252
+ function resolveDemoCredentials(credentials) {
3253
+ if (credentials === void 0 || credentials === null) {
3254
+ return null;
3255
+ }
3256
+ const username = typeof credentials.username === "string" ? credentials.username.trim() : "";
3257
+ const password = typeof credentials.password === "string" ? credentials.password : "";
3258
+ const isUnusable = username.length === 0 || password.length === 0;
3259
+ if (isUnusable) {
3260
+ return null;
3261
+ }
3262
+ return { username, password };
3263
+ }
3264
+ function DemoCredentialsHint({
3265
+ credentials,
3266
+ theme: themeProp,
3267
+ labels: labelsProp,
3268
+ testIdPrefix,
3269
+ onUse
3270
+ }) {
3271
+ const theme = useAuthTheme(themeProp);
3272
+ const authStyles = useAuthStyles(theme);
3273
+ const styles3 = useDemoCredentialsStyles(theme);
3274
+ const labels = react.useMemo(
3275
+ () => ({ ...DEFAULT_DEMO_CREDENTIALS_HINT_LABELS, ...labelsProp }),
3276
+ [labelsProp]
3277
+ );
3278
+ const resolved = react.useMemo(() => resolveDemoCredentials(credentials), [credentials]);
3279
+ const handleUse = react.useCallback(() => {
3280
+ if (resolved !== null && onUse !== void 0) {
3281
+ onUse(resolved);
3282
+ }
3283
+ }, [resolved, onUse]);
3284
+ if (resolved === null) {
3285
+ return null;
3286
+ }
3287
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3288
+ reactNative.View,
3289
+ {
3290
+ style: styles3.panel,
3291
+ testID: withTestIdPrefix(AuthTestIds.demoCredentialsHint, testIdPrefix),
3292
+ children: [
3293
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.title, children: labels.title }),
3294
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.description, children: labels.description }),
3295
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.row, children: [
3296
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.rowLabel, children: labels.usernameLabel }),
3297
+ /* @__PURE__ */ jsxRuntime.jsx(
3298
+ reactNative.Text,
3299
+ {
3300
+ selectable: true,
3301
+ style: styles3.rowValue,
3302
+ testID: withTestIdPrefix(AuthTestIds.demoCredentialsUsername, testIdPrefix),
3303
+ children: resolved.username
3304
+ }
3305
+ )
3306
+ ] }),
3307
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.row, children: [
3308
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.rowLabel, children: labels.passwordLabel }),
3309
+ /* @__PURE__ */ jsxRuntime.jsx(
3310
+ reactNative.Text,
3311
+ {
3312
+ selectable: true,
3313
+ style: styles3.rowValue,
3314
+ testID: withTestIdPrefix(AuthTestIds.demoCredentialsPassword, testIdPrefix),
3315
+ children: resolved.password
3316
+ }
3317
+ )
3318
+ ] }),
3319
+ onUse !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
3320
+ reactNative.TouchableOpacity,
3321
+ {
3322
+ accessibilityHint: labels.useCredentialsHint,
3323
+ accessibilityLabel: labels.useCredentials,
3324
+ accessibilityRole: "button",
3325
+ style: authStyles.secondaryButton,
3326
+ testID: withTestIdPrefix(AuthTestIds.demoCredentialsUseButton, testIdPrefix),
3327
+ onPress: handleUse,
3328
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: authStyles.secondaryButtonText, children: labels.useCredentials })
3329
+ }
3330
+ ) : null
3331
+ ]
3332
+ }
3333
+ );
3334
+ }
3135
3335
  var EMPTY_CONFIG = {
3136
3336
  methods: [],
3137
3337
  registrationEnabled: false,
@@ -3140,7 +3340,8 @@ var EMPTY_CONFIG = {
3140
3340
  hasPin: false,
3141
3341
  pinDigits: null,
3142
3342
  preferredMethod: null
3143
- }
3343
+ },
3344
+ demo: null
3144
3345
  };
3145
3346
  function useBffLoginConfig(client) {
3146
3347
  const [config, setConfig] = react.useState(EMPTY_CONFIG);
@@ -3237,6 +3438,55 @@ async function syncPreferredMethodFromServer(client) {
3237
3438
  return null;
3238
3439
  }
3239
3440
  }
3441
+ function isSamePair(a, b) {
3442
+ if (a === null || b === null) {
3443
+ return a === b;
3444
+ }
3445
+ return a.username === b.username && a.password === b.password;
3446
+ }
3447
+ function useDemoCredentials({
3448
+ credentials,
3449
+ prefillOnMount = false
3450
+ } = {}) {
3451
+ const resolved = react.useMemo(() => resolveDemoCredentials(credentials), [credentials]);
3452
+ const [filled, setFilled] = react.useState(
3453
+ prefillOnMount ? resolved : null
3454
+ );
3455
+ const [prefillNonce, setPrefillNonce] = react.useState(0);
3456
+ const [prefillIntent, setPrefillIntent] = react.useState(
3457
+ "opportunistic" /* Opportunistic */
3458
+ );
3459
+ const [autoSeeded, setAutoSeeded] = react.useState(
3460
+ prefillOnMount ? resolved : null
3461
+ );
3462
+ const shouldAutoSeed = prefillOnMount && resolved !== null && !isSamePair(resolved, autoSeeded);
3463
+ if (shouldAutoSeed) {
3464
+ setAutoSeeded(resolved);
3465
+ setFilled(resolved);
3466
+ setPrefillNonce((current) => current + 1);
3467
+ setPrefillIntent("opportunistic" /* Opportunistic */);
3468
+ }
3469
+ const handleUse = react.useCallback((next) => {
3470
+ setFilled(next);
3471
+ setPrefillNonce((current) => current + 1);
3472
+ setPrefillIntent("explicit" /* Explicit */);
3473
+ }, []);
3474
+ return react.useMemo(
3475
+ () => ({
3476
+ loginFormProps: {
3477
+ initialUsername: filled?.username,
3478
+ initialPassword: filled?.password,
3479
+ prefillNonce,
3480
+ prefillIntent
3481
+ },
3482
+ // `credentials` is passed through unresolved on purpose: the hint applies
3483
+ // `resolveDemoCredentials` itself, so it fails closed even when it is used
3484
+ // standalone, without this hook.
3485
+ hintProps: { credentials, onUse: handleUse }
3486
+ }),
3487
+ [filled, prefillNonce, prefillIntent, credentials, handleUse]
3488
+ );
3489
+ }
3240
3490
  async function lazyFetchHttpClient(request) {
3241
3491
  const fetchImpl = typeof fetch === "function" ? fetch.bind(globalThis) : void 0;
3242
3492
  if (fetchImpl === void 0) {
@@ -3336,6 +3586,7 @@ exports.AuthTestIds = AuthTestIds;
3336
3586
  exports.AuthThemeProvider = AuthThemeProvider;
3337
3587
  exports.BffAuthStatus = BffAuthStatus;
3338
3588
  exports.DEFAULT_AUTH_CARD_MAX_WIDTH = DEFAULT_AUTH_CARD_MAX_WIDTH;
3589
+ exports.DEFAULT_DEMO_CREDENTIALS_HINT_LABELS = DEFAULT_DEMO_CREDENTIALS_HINT_LABELS;
3339
3590
  exports.DEFAULT_DEVICE_PIN_ENROLL_LABELS = DEFAULT_DEVICE_PIN_ENROLL_LABELS;
3340
3591
  exports.DEFAULT_DEVICE_PIN_SETTINGS_LABELS = DEFAULT_DEVICE_PIN_SETTINGS_LABELS;
3341
3592
  exports.DEFAULT_DEVICE_PIN_UNLOCK_LABELS = DEFAULT_DEVICE_PIN_UNLOCK_LABELS;
@@ -3351,6 +3602,7 @@ exports.DEFAULT_PREFERRED_METHOD_SETTINGS_LABELS = DEFAULT_PREFERRED_METHOD_SETT
3351
3602
  exports.DEFAULT_RESET_PASSWORD_LABELS = DEFAULT_RESET_PASSWORD_LABELS;
3352
3603
  exports.DEVICE_PIN_ALLOWED_DIGITS = DEVICE_PIN_ALLOWED_DIGITS;
3353
3604
  exports.DEVICE_PIN_DEFAULT_DIGITS = DEVICE_PIN_DEFAULT_DIGITS;
3605
+ exports.DemoCredentialsHint = DemoCredentialsHint;
3354
3606
  exports.DevicePinEnrollErrorKey = DevicePinEnrollErrorKey;
3355
3607
  exports.DevicePinEnrollForm = DevicePinEnrollForm;
3356
3608
  exports.DevicePinErrorKey = DevicePinErrorKey;
@@ -3374,6 +3626,7 @@ exports.PasswordPolicyError = PasswordPolicyError;
3374
3626
  exports.PinForm = PinForm;
3375
3627
  exports.PreferredMethodHint = PreferredMethodHint;
3376
3628
  exports.PreferredMethodSettingsCard = PreferredMethodSettingsCard;
3629
+ exports.PrefillIntent = PrefillIntent;
3377
3630
  exports.ResetPasswordError = ResetPasswordError;
3378
3631
  exports.ResetPasswordForm = ResetPasswordForm;
3379
3632
  exports.cachePreferredMethod = cachePreferredMethod;
@@ -3390,6 +3643,7 @@ exports.readCachedPreferredMethod = readCachedPreferredMethod;
3390
3643
  exports.readPasskeyError = readPasskeyError;
3391
3644
  exports.readPasskeyRegistered = readPasskeyRegistered;
3392
3645
  exports.resolveAccessibleRoutes = resolveAccessibleRoutes;
3646
+ exports.resolveDemoCredentials = resolveDemoCredentials;
3393
3647
  exports.resolvePostLoginRoute = resolvePostLoginRoute;
3394
3648
  exports.startPasskeyLogin = startPasskeyLogin;
3395
3649
  exports.startPasskeyRegistration = startPasskeyRegistration;
@@ -3399,6 +3653,7 @@ exports.useBffAuth = useBffAuth;
3399
3653
  exports.useBffForgotPassword = useBffForgotPassword;
3400
3654
  exports.useBffLoginConfig = useBffLoginConfig;
3401
3655
  exports.useBffResetPassword = useBffResetPassword;
3656
+ exports.useDemoCredentials = useDemoCredentials;
3402
3657
  exports.useDevicePinDisable = useDevicePinDisable;
3403
3658
  exports.useDevicePinEnroll = useDevicePinEnroll;
3404
3659
  exports.useDevicePinUnlock = useDevicePinUnlock;