@dloizides/auth-web 1.2.2 → 1.4.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/CHANGELOG.md +32 -0
- package/dist/index.d.mts +802 -1
- package/dist/index.d.ts +802 -1
- package/dist/index.js +1330 -81
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1302 -83
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -52,6 +52,17 @@ interface ForgotPasswordFormLabels {
|
|
|
52
52
|
/** Shown when the entered value is not a valid email. */
|
|
53
53
|
invalidEmail: string;
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Strings rendered by `<ForgotPasswordFields>` — the embedded modal body. It is
|
|
57
|
+
* the forgot-password form plus the two modal affordances (`cancel` alongside
|
|
58
|
+
* submit, `close` on the success state) that a screen-shaped form has no need of.
|
|
59
|
+
*/
|
|
60
|
+
interface ForgotPasswordFieldsLabels extends ForgotPasswordFormLabels {
|
|
61
|
+
/** The "cancel" button shown alongside submit. */
|
|
62
|
+
cancel: string;
|
|
63
|
+
/** The "close" button shown on the success state. */
|
|
64
|
+
close: string;
|
|
65
|
+
}
|
|
55
66
|
/** Strings rendered by `<OtpForm>` — the two-step email-OTP login form. */
|
|
56
67
|
interface OtpFormLabels {
|
|
57
68
|
/** Title shown on the request-a-code step. */
|
|
@@ -104,6 +115,123 @@ interface PinFormLabels {
|
|
|
104
115
|
/** Generic "that PIN was wrong / expired / locked out" message. */
|
|
105
116
|
invalidPin: string;
|
|
106
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Strings rendered by `<DevicePinUnlockScreen>` — the returning-device unlock
|
|
120
|
+
* gate. `{name}` / `{count}` placeholders are substituted by the component.
|
|
121
|
+
*/
|
|
122
|
+
interface DevicePinUnlockLabels {
|
|
123
|
+
/** Greeting with the remembered username; `{name}` is substituted. */
|
|
124
|
+
title: string;
|
|
125
|
+
/** Greeting when no username is remembered. */
|
|
126
|
+
titleNoName: string;
|
|
127
|
+
/** Copy above the PIN pad; `{count}` is substituted with the PIN length. */
|
|
128
|
+
description: string;
|
|
129
|
+
pinLabel: string;
|
|
130
|
+
pinPlaceholder: string;
|
|
131
|
+
/** The "unlock" submit button. */
|
|
132
|
+
submit: string;
|
|
133
|
+
/** Shown while the unlock is in flight. */
|
|
134
|
+
submitting: string;
|
|
135
|
+
/** The "sign in with password instead" escape link. */
|
|
136
|
+
usePasswordInstead: string;
|
|
137
|
+
/** Accessibility hint for the escape link. */
|
|
138
|
+
usePasswordHint: string;
|
|
139
|
+
/** Error: not enough digits entered; `{count}` is substituted. */
|
|
140
|
+
errorIncomplete: string;
|
|
141
|
+
/** Error: wrong PIN / unknown device (generic). */
|
|
142
|
+
errorInvalid: string;
|
|
143
|
+
/** Error: locked out, no retry hint. */
|
|
144
|
+
errorLockedOut: string;
|
|
145
|
+
/** Error: locked out with a retry hint; `{count}` is the seconds. */
|
|
146
|
+
errorLockedOutRetry: string;
|
|
147
|
+
/** Error: rate-limited, no retry hint. */
|
|
148
|
+
errorRateLimited: string;
|
|
149
|
+
/** Error: rate-limited with a retry hint; `{count}` is the seconds. */
|
|
150
|
+
errorRateLimitedRetry: string;
|
|
151
|
+
/** Error: anything else (network / unexpected). */
|
|
152
|
+
errorGeneric: string;
|
|
153
|
+
/** Accessibility label for a filled PIN dot. */
|
|
154
|
+
digitFilledHint: string;
|
|
155
|
+
/** Accessibility label for an empty PIN dot. */
|
|
156
|
+
digitEmptyHint: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Strings rendered by `<DevicePinEnrollForm>` (and the length picker it hosts)
|
|
160
|
+
* plus the `<DevicePinOffer>` wrapper. `{count}` placeholders are substituted.
|
|
161
|
+
*/
|
|
162
|
+
interface DevicePinEnrollLabels {
|
|
163
|
+
offerTitle: string;
|
|
164
|
+
offerDescription: string;
|
|
165
|
+
offerAccept: string;
|
|
166
|
+
offerAcceptHint: string;
|
|
167
|
+
offerSkip: string;
|
|
168
|
+
offerSkipHint: string;
|
|
169
|
+
formTitle: string;
|
|
170
|
+
/** Copy above the form; `{count}` is substituted with the chosen length. */
|
|
171
|
+
formDescription: string;
|
|
172
|
+
lengthLabel: string;
|
|
173
|
+
/** Accessibility hint for a length pill; `{count}` is substituted. */
|
|
174
|
+
lengthOptionHint: string;
|
|
175
|
+
pinLabel: string;
|
|
176
|
+
pinPlaceholder: string;
|
|
177
|
+
confirmLabel: string;
|
|
178
|
+
confirmPlaceholder: string;
|
|
179
|
+
submit: string;
|
|
180
|
+
submitting: string;
|
|
181
|
+
cancel: string;
|
|
182
|
+
cancelHint: string;
|
|
183
|
+
/** Error: PINs do not match / wrong length; `{count}` is substituted. */
|
|
184
|
+
errorMismatch: string;
|
|
185
|
+
/** Error: the current session is not authenticated. */
|
|
186
|
+
errorUnauthorized: string;
|
|
187
|
+
/** Error: the session already has a PIN / is forbidden. */
|
|
188
|
+
errorForbidden: string;
|
|
189
|
+
/** Error: the PIN failed server-side validation. */
|
|
190
|
+
errorInvalidPin: string;
|
|
191
|
+
/** Error: any other failure. */
|
|
192
|
+
errorFailed: string;
|
|
193
|
+
}
|
|
194
|
+
/** Strings rendered by `<DevicePinSettingsCard>` — the account toggle. */
|
|
195
|
+
interface DevicePinSettingsLabels {
|
|
196
|
+
title: string;
|
|
197
|
+
description: string;
|
|
198
|
+
/** Status line when a device PIN is enabled. */
|
|
199
|
+
statusEnabled: string;
|
|
200
|
+
/** Status line when no device PIN is set. */
|
|
201
|
+
statusDisabled: string;
|
|
202
|
+
/** The "enable" button. */
|
|
203
|
+
enable: string;
|
|
204
|
+
enableHint: string;
|
|
205
|
+
/** The "disable" button. */
|
|
206
|
+
disable: string;
|
|
207
|
+
disableHint: string;
|
|
208
|
+
/** Shown while disabling is in flight. */
|
|
209
|
+
disabling: string;
|
|
210
|
+
/** Error shown when disabling fails. */
|
|
211
|
+
disableFailed: string;
|
|
212
|
+
}
|
|
213
|
+
/** Strings rendered by `<PasskeyLoginButton>` — the login-surface passkey CTA. */
|
|
214
|
+
interface PasskeyLoginLabels {
|
|
215
|
+
/** The "sign in with a passkey" button. */
|
|
216
|
+
signInButton: string;
|
|
217
|
+
signInHint: string;
|
|
218
|
+
/** Error banner when the ceremony was cancelled. */
|
|
219
|
+
errorCancelled: string;
|
|
220
|
+
/** Error banner when the ceremony failed. */
|
|
221
|
+
errorFailed: string;
|
|
222
|
+
}
|
|
223
|
+
/** Strings rendered by `<PasskeySettingsCard>` — the account passkey card. */
|
|
224
|
+
interface PasskeySettingsLabels {
|
|
225
|
+
title: string;
|
|
226
|
+
description: string;
|
|
227
|
+
/** Italic hint about the re-auth step. */
|
|
228
|
+
reauthHint: string;
|
|
229
|
+
/** The "add a passkey" button. */
|
|
230
|
+
addButton: string;
|
|
231
|
+
addHint: string;
|
|
232
|
+
/** Success line shown after returning from a registration. */
|
|
233
|
+
registeredSuccess: string;
|
|
234
|
+
}
|
|
107
235
|
/** Strings rendered by `<ResetPasswordForm>`. */
|
|
108
236
|
interface ResetPasswordFormLabels {
|
|
109
237
|
title: string;
|
|
@@ -127,8 +255,14 @@ interface ResetPasswordFormLabels {
|
|
|
127
255
|
}
|
|
128
256
|
declare const DEFAULT_LOGIN_LABELS: LoginFormLabels;
|
|
129
257
|
declare const DEFAULT_FORGOT_PASSWORD_LABELS: ForgotPasswordFormLabels;
|
|
258
|
+
declare const DEFAULT_FORGOT_PASSWORD_FIELDS_LABELS: ForgotPasswordFieldsLabels;
|
|
130
259
|
declare const DEFAULT_OTP_LABELS: OtpFormLabels;
|
|
131
260
|
declare const DEFAULT_PIN_LABELS: PinFormLabels;
|
|
261
|
+
declare const DEFAULT_DEVICE_PIN_UNLOCK_LABELS: DevicePinUnlockLabels;
|
|
262
|
+
declare const DEFAULT_DEVICE_PIN_ENROLL_LABELS: DevicePinEnrollLabels;
|
|
263
|
+
declare const DEFAULT_DEVICE_PIN_SETTINGS_LABELS: DevicePinSettingsLabels;
|
|
264
|
+
declare const DEFAULT_PASSKEY_LOGIN_LABELS: PasskeyLoginLabels;
|
|
265
|
+
declare const DEFAULT_PASSKEY_SETTINGS_LABELS: PasskeySettingsLabels;
|
|
132
266
|
declare const DEFAULT_RESET_PASSWORD_LABELS: ResetPasswordFormLabels;
|
|
133
267
|
|
|
134
268
|
/**
|
|
@@ -284,6 +418,56 @@ interface ForgotPasswordFormProps {
|
|
|
284
418
|
/** Themeable "request a reset link" form built on `useBffForgotPassword`. */
|
|
285
419
|
declare function ForgotPasswordForm({ client, theme: themeProp, labels: labelsProp, resetUrlTemplate, onSuccess, testIdPrefix, }: Readonly<ForgotPasswordFormProps>): ReactElement;
|
|
286
420
|
|
|
421
|
+
/**
|
|
422
|
+
* `<ForgotPasswordFields>` — the embedded, themeable "request a reset link"
|
|
423
|
+
* body, sized to live INSIDE an app-owned modal shell (not a full screen).
|
|
424
|
+
*
|
|
425
|
+
* The product apps each show "Forgot password?" as a modal launched from their
|
|
426
|
+
* login route, wrapping their own modal chrome (title bar + close affordance)
|
|
427
|
+
* around an identical email field + submit/cancel body. This is that body,
|
|
428
|
+
* promoted into the package so the three apps stop hand-rolling it.
|
|
429
|
+
*
|
|
430
|
+
* Built on the react-query-free `useForgotPasswordSubmit` hook, so it renders +
|
|
431
|
+
* submits on a provider-less login route (where `useMutation` would crash). The
|
|
432
|
+
* backend is anti-enumeration, so a successful request swaps to a generic
|
|
433
|
+
* confirmation that never reveals whether the address is registered.
|
|
434
|
+
*
|
|
435
|
+
* Differs from the screen-shaped `<ForgotPasswordForm>`: no outer screen/card
|
|
436
|
+
* wrapper and no title (the host modal owns those), plus two modal affordances —
|
|
437
|
+
* a `Cancel` button beside submit and a `Close` button on the success state —
|
|
438
|
+
* each rendered only when its handler is supplied.
|
|
439
|
+
*/
|
|
440
|
+
|
|
441
|
+
interface ForgotPasswordFieldsProps {
|
|
442
|
+
/** The same-origin BFF client (build it with `createBffAuthClient`). */
|
|
443
|
+
client: BffAuthClient;
|
|
444
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
445
|
+
theme?: AuthTheme;
|
|
446
|
+
/** Localised copy. Partial — unspecified keys fall back to English defaults. */
|
|
447
|
+
labels?: Partial<ForgotPasswordFieldsLabels>;
|
|
448
|
+
/**
|
|
449
|
+
* Full URL with a `{token}` placeholder, forwarded to the backend so it can
|
|
450
|
+
* build the reset-email link without hardcoding a frontend host.
|
|
451
|
+
*/
|
|
452
|
+
resetUrlTemplate?: string;
|
|
453
|
+
/**
|
|
454
|
+
* The host modal's open state. When it flips to `false` the body clears
|
|
455
|
+
* itself, so reopening shows a fresh form regardless of which affordance
|
|
456
|
+
* closed it. Defaults to `true` for non-modal embedding.
|
|
457
|
+
*/
|
|
458
|
+
visible?: boolean;
|
|
459
|
+
/** Called once the request succeeds (the generic-confirmation state). */
|
|
460
|
+
onSuccess?: () => void;
|
|
461
|
+
/** When supplied, renders a Cancel button beside submit. */
|
|
462
|
+
onCancel?: () => void;
|
|
463
|
+
/** When supplied, renders a Close button on the success state. */
|
|
464
|
+
onClose?: () => void;
|
|
465
|
+
/** Prefix applied to every `testID`. */
|
|
466
|
+
testIdPrefix?: string;
|
|
467
|
+
}
|
|
468
|
+
/** Embedded "request a reset link" body for an app-owned modal. */
|
|
469
|
+
declare function ForgotPasswordFields({ client, theme: themeProp, labels: labelsProp, resetUrlTemplate, visible, onSuccess, onCancel, onClose, testIdPrefix, }: Readonly<ForgotPasswordFieldsProps>): ReactElement;
|
|
470
|
+
|
|
287
471
|
/**
|
|
288
472
|
* `<ResetPasswordForm>` — the ready-made, themeable "choose a new password"
|
|
289
473
|
* form.
|
|
@@ -385,6 +569,421 @@ interface PinFormProps {
|
|
|
385
569
|
/** Themeable single-step event-PIN login form built on `usePinLogin`. */
|
|
386
570
|
declare function PinForm({ client, eventExternalId, theme: themeProp, labels: labelsProp, onSuccess, testIdPrefix, }: Readonly<PinFormProps>): ReactElement;
|
|
387
571
|
|
|
572
|
+
/**
|
|
573
|
+
* A masked numeric PIN input for the device-PIN unlock + enrol surfaces.
|
|
574
|
+
*
|
|
575
|
+
* A single hidden secure `TextInput` captures the digits (numeric keyboard,
|
|
576
|
+
* length-capped, digits-only); a row of dot cells visualises how many have been
|
|
577
|
+
* entered. Tapping anywhere on the row focuses the field. Purely presentational
|
|
578
|
+
* — all submit / error logic lives in the unlock / enrol hooks. Styled from the
|
|
579
|
+
* `AuthTheme` token bag (no hardcoded brand colours).
|
|
580
|
+
*/
|
|
581
|
+
|
|
582
|
+
interface DevicePinInputProps {
|
|
583
|
+
/** The current PIN value (digits only). */
|
|
584
|
+
value: string;
|
|
585
|
+
/** Number of dot cells to render — the enrolled / chosen PIN length. */
|
|
586
|
+
length: number;
|
|
587
|
+
/** Disable input while a request is in flight. */
|
|
588
|
+
disabled: boolean;
|
|
589
|
+
/** Base testID for the underlying field (a11y + E2E targeting). */
|
|
590
|
+
testID: string;
|
|
591
|
+
/** Optional prefix applied to the testID. */
|
|
592
|
+
testIdPrefix?: string;
|
|
593
|
+
/** Accessibility label for the field. */
|
|
594
|
+
accessibilityLabel: string;
|
|
595
|
+
/** Accessibility hint for the field. */
|
|
596
|
+
accessibilityHint: string;
|
|
597
|
+
/** Accessibility label for a filled dot. */
|
|
598
|
+
filledHint: string;
|
|
599
|
+
/** Accessibility label for an empty dot. */
|
|
600
|
+
emptyHint: string;
|
|
601
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
602
|
+
theme?: AuthTheme;
|
|
603
|
+
/** Emit the next digits-only value (already length-capped). */
|
|
604
|
+
onChange: (next: string) => void;
|
|
605
|
+
}
|
|
606
|
+
/** Themeable masked PIN pad — dots reflect entered digits; a hidden field captures them. */
|
|
607
|
+
declare function DevicePinInput({ value, length, disabled, testID, testIdPrefix, accessibilityLabel, accessibilityHint, filledHint, emptyHint, theme: themeProp, onChange, }: Readonly<DevicePinInputProps>): ReactElement;
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* The PIN-length (4/6/8) picker row for `<DevicePinEnrollForm>`.
|
|
611
|
+
*
|
|
612
|
+
* Presentational: renders one pill per allowed length and reports the chosen
|
|
613
|
+
* one. Styled from the `AuthTheme` token bag. Extracted from the enrol form to
|
|
614
|
+
* keep that component focused.
|
|
615
|
+
*/
|
|
616
|
+
|
|
617
|
+
interface DevicePinLengthPickerProps {
|
|
618
|
+
/** The currently-selected PIN length. */
|
|
619
|
+
selected: number;
|
|
620
|
+
/** Disable while a request is in flight. */
|
|
621
|
+
disabled: boolean;
|
|
622
|
+
/** Accessibility hint template for a pill; `{count}` is substituted. */
|
|
623
|
+
optionHint: string;
|
|
624
|
+
/** Optional prefix applied to each pill's testID. */
|
|
625
|
+
testIdPrefix?: string;
|
|
626
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
627
|
+
theme?: AuthTheme;
|
|
628
|
+
/** Report the newly-chosen length. */
|
|
629
|
+
onSelect: (digits: number) => void;
|
|
630
|
+
}
|
|
631
|
+
/** Themeable 4/6/8 length chooser. */
|
|
632
|
+
declare function DevicePinLengthPicker({ selected, disabled, optionHint, testIdPrefix, theme: themeProp, onSelect, }: Readonly<DevicePinLengthPickerProps>): ReactElement;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* The localizable error states the device-PIN UNLOCK surface can show.
|
|
636
|
+
*
|
|
637
|
+
* Kept deliberately coarse so the UI never reveals whether a failed unlock was a
|
|
638
|
+
* wrong PIN or an unknown / revoked device (both map to `Invalid`). `LockedOut`
|
|
639
|
+
* and `RateLimited` carry no count — the human-readable "try again later" copy
|
|
640
|
+
* is built from the `retryAfterSeconds` hint at the call site, not encoded here.
|
|
641
|
+
*/
|
|
642
|
+
declare const enum DevicePinErrorKey {
|
|
643
|
+
/** The PIN entered was too short for the enrolled length. */
|
|
644
|
+
Incomplete = "incomplete",
|
|
645
|
+
/** Wrong PIN, or unknown / revoked device. Generic on purpose. */
|
|
646
|
+
Invalid = "invalid",
|
|
647
|
+
/** Locked out after too many wrong attempts. */
|
|
648
|
+
LockedOut = "locked_out",
|
|
649
|
+
/** Rate-limited by the BFF (too many requests). */
|
|
650
|
+
RateLimited = "rate_limited",
|
|
651
|
+
/** Anything else (network / unexpected). */
|
|
652
|
+
Generic = "generic"
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Structural prop/result contracts for the device-PIN surfaces.
|
|
657
|
+
*
|
|
658
|
+
* `@dloizides/auth-web` deliberately does NOT take a hard dependency on the
|
|
659
|
+
* device-PIN methods that `@dloizides/auth-client` 3.3.0 adds. Instead the
|
|
660
|
+
* components depend on the **structural** shapes below — exactly what each one
|
|
661
|
+
* needs and no more. A real client (auth-client 3.3.0) satisfies
|
|
662
|
+
* {@link DevicePinCapableClient} by duck-typing; an app can equally pass a thin
|
|
663
|
+
* hand-rolled object. This keeps the two packages decoupled and lets a consumer
|
|
664
|
+
* upgrade either independently.
|
|
665
|
+
*
|
|
666
|
+
* The discriminated result shapes mirror the BFF's meaningfully-distinct
|
|
667
|
+
* outcomes (a wrong PIN, a lockout with a retry hint, a rate-limit, a generic
|
|
668
|
+
* error) so the unlock / enrol UIs can route on each one rather than collapsing
|
|
669
|
+
* them into a single opaque failure.
|
|
670
|
+
*/
|
|
671
|
+
/**
|
|
672
|
+
* The per-device half of `GET /bff/config` — what this device remembers about
|
|
673
|
+
* the last user, all fields safe-defaulted by the parser when absent.
|
|
674
|
+
*/
|
|
675
|
+
interface BffDeviceState {
|
|
676
|
+
/** Non-secret username this device remembers, or `null` when none. */
|
|
677
|
+
rememberedUsername: string | null;
|
|
678
|
+
/** `true` when this device has an enrolled device PIN. */
|
|
679
|
+
hasPin: boolean;
|
|
680
|
+
/** The enrolled PIN length (4/6/8), or `null` when unknown / no PIN. */
|
|
681
|
+
pinDigits: number | null;
|
|
682
|
+
/** The device-local preferred-method hint (e.g. `"pin"`), or `null`. */
|
|
683
|
+
preferredMethod: string | null;
|
|
684
|
+
}
|
|
685
|
+
/** The shape `GET /bff/config` resolves to (as the client parses it). */
|
|
686
|
+
interface BffLoginConfig {
|
|
687
|
+
/** The enabled login methods, lower-cased (e.g. `["password", "otp"]`). */
|
|
688
|
+
methods: string[];
|
|
689
|
+
/** `true` when self-serve registration is enabled for this BFF. */
|
|
690
|
+
registrationEnabled: boolean;
|
|
691
|
+
/** The per-device remembered state. */
|
|
692
|
+
deviceState: BffDeviceState;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Discriminated result of a device-PIN UNLOCK attempt. The client never rejects;
|
|
696
|
+
* it resolves one of these so the UI can route on the distinct outcomes (a
|
|
697
|
+
* lockout / rate-limit carries an optional `retryAfterSeconds` hint).
|
|
698
|
+
*/
|
|
699
|
+
type DevicePinUnlockResult = {
|
|
700
|
+
status: 'success';
|
|
701
|
+
user: {
|
|
702
|
+
[claim: string]: unknown;
|
|
703
|
+
};
|
|
704
|
+
} | {
|
|
705
|
+
status: 'invalid';
|
|
706
|
+
} | {
|
|
707
|
+
status: 'locked';
|
|
708
|
+
retryAfterSeconds: number | null;
|
|
709
|
+
} | {
|
|
710
|
+
status: 'rateLimited';
|
|
711
|
+
retryAfterSeconds: number | null;
|
|
712
|
+
} | {
|
|
713
|
+
status: 'error';
|
|
714
|
+
};
|
|
715
|
+
/**
|
|
716
|
+
* Discriminated result of a device-PIN ENROL attempt. The client never rejects;
|
|
717
|
+
* it resolves one of these so the form can show the precise failure cause.
|
|
718
|
+
*/
|
|
719
|
+
type DevicePinEnrollResult = {
|
|
720
|
+
status: 'success';
|
|
721
|
+
} | {
|
|
722
|
+
status: 'unauthorized';
|
|
723
|
+
} | {
|
|
724
|
+
status: 'forbidden';
|
|
725
|
+
} | {
|
|
726
|
+
status: 'invalidPin';
|
|
727
|
+
} | {
|
|
728
|
+
status: 'error';
|
|
729
|
+
};
|
|
730
|
+
/**
|
|
731
|
+
* The client capability the device-PIN components depend on. `auth-client`
|
|
732
|
+
* 3.3.0 satisfies this structurally; a consuming app may also pass a thin
|
|
733
|
+
* hand-rolled object exposing exactly these four methods.
|
|
734
|
+
*/
|
|
735
|
+
interface DevicePinCapableClient {
|
|
736
|
+
/** Read `GET /bff/config` (methods + per-device state). */
|
|
737
|
+
getLoginConfig(): Promise<BffLoginConfig>;
|
|
738
|
+
/** Bind a device PIN of `digits` length to the current session. */
|
|
739
|
+
enrollDevicePin(request: {
|
|
740
|
+
pin: string;
|
|
741
|
+
digits: number;
|
|
742
|
+
}): Promise<DevicePinEnrollResult>;
|
|
743
|
+
/** Re-establish a session from a remembered device using `pin`. */
|
|
744
|
+
unlockWithDevicePin(request: {
|
|
745
|
+
pin: string;
|
|
746
|
+
}): Promise<DevicePinUnlockResult>;
|
|
747
|
+
/** Drop the device PIN for the current session. Resolves `true` on success. */
|
|
748
|
+
disableDevicePin(): Promise<boolean>;
|
|
749
|
+
}
|
|
750
|
+
/** The minimal slice {@link useBffLoginConfig} needs from a client. */
|
|
751
|
+
interface LoginConfigCapableClient {
|
|
752
|
+
getLoginConfig(): Promise<BffLoginConfig>;
|
|
753
|
+
}
|
|
754
|
+
/** The minimal slice {@link useDevicePinUnlock} needs from a client. */
|
|
755
|
+
interface DevicePinUnlockCapableClient {
|
|
756
|
+
unlockWithDevicePin(request: {
|
|
757
|
+
pin: string;
|
|
758
|
+
}): Promise<DevicePinUnlockResult>;
|
|
759
|
+
}
|
|
760
|
+
/** The minimal slice {@link useDevicePinEnroll} needs from a client. */
|
|
761
|
+
interface DevicePinEnrollCapableClient {
|
|
762
|
+
enrollDevicePin(request: {
|
|
763
|
+
pin: string;
|
|
764
|
+
digits: number;
|
|
765
|
+
}): Promise<DevicePinEnrollResult>;
|
|
766
|
+
}
|
|
767
|
+
/** The minimal slice {@link DevicePinSettingsCard} needs to disable a PIN. */
|
|
768
|
+
interface DevicePinDisableCapableClient {
|
|
769
|
+
disableDevicePin(): Promise<boolean>;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** The signed-in user shape the success path hands back (claim bag). */
|
|
773
|
+
type DevicePinUnlockedUser = {
|
|
774
|
+
[claim: string]: unknown;
|
|
775
|
+
};
|
|
776
|
+
interface UseDevicePinUnlockArgs {
|
|
777
|
+
/** The client exposing `unlockWithDevicePin`. */
|
|
778
|
+
client: DevicePinUnlockCapableClient;
|
|
779
|
+
/** Enrolled PIN length (4/6/8) — a shorter entry is rejected before any call. */
|
|
780
|
+
digits: number;
|
|
781
|
+
/** Called with the fresh user after a successful unlock. */
|
|
782
|
+
onSignedIn: (user: DevicePinUnlockedUser) => void;
|
|
783
|
+
}
|
|
784
|
+
interface UseDevicePinUnlockResult {
|
|
785
|
+
/** The PIN currently entered. */
|
|
786
|
+
pin: string;
|
|
787
|
+
/** `true` while an unlock call is in flight. */
|
|
788
|
+
submitting: boolean;
|
|
789
|
+
/** The current error key, or `null` when there is none. */
|
|
790
|
+
errorKey: DevicePinErrorKey | null;
|
|
791
|
+
/** Seconds until retry, set with a `LockedOut`/`RateLimited` error; else `null`. */
|
|
792
|
+
retryAfterSeconds: number | null;
|
|
793
|
+
/** Replace the entered PIN. Clears a stale error. */
|
|
794
|
+
setPin: (next: string) => void;
|
|
795
|
+
/** Run the unlock attempt for the current PIN. */
|
|
796
|
+
submit: () => void;
|
|
797
|
+
}
|
|
798
|
+
declare function useDevicePinUnlock({ client, digits, onSignedIn, }: UseDevicePinUnlockArgs): UseDevicePinUnlockResult;
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* The device-PIN UNLOCK gate — shown on the login route for a returning,
|
|
802
|
+
* logged-OUT user whose device is remembered (`hasPin && rememberedUsername`).
|
|
803
|
+
*
|
|
804
|
+
* Renders a masked PIN pad of the enrolled length; on submit it calls
|
|
805
|
+
* `client.unlockWithDevicePin` (via {@link useDevicePinUnlock}) and, on success,
|
|
806
|
+
* routes through the SAME `onSignedIn` path as the password tab. A
|
|
807
|
+
* "Sign in with password instead" escape calls `onUsePassword` (the caller swaps
|
|
808
|
+
* this screen out). All five result statuses are handled, including the lockout /
|
|
809
|
+
* rate-limit retry countdown surfaced from `retryAfterSeconds`.
|
|
810
|
+
*
|
|
811
|
+
* react-query-FREE: all logic lives in `useDevicePinUnlock`, which uses plain
|
|
812
|
+
* `useState` + `.then()` over the client — never `useMutation`. Presentational
|
|
813
|
+
* only here. Distinct from the event-staff PIN (`PinForm`); this is `devicePin`.
|
|
814
|
+
*/
|
|
815
|
+
|
|
816
|
+
interface DevicePinUnlockScreenProps {
|
|
817
|
+
/** The client exposing `unlockWithDevicePin`. */
|
|
818
|
+
client: DevicePinUnlockCapableClient;
|
|
819
|
+
/** Enrolled PIN length (4/6/8). */
|
|
820
|
+
digits: number;
|
|
821
|
+
/** Remembered username, shown in the greeting (may be empty). */
|
|
822
|
+
rememberedUsername: string;
|
|
823
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
824
|
+
theme?: AuthTheme;
|
|
825
|
+
/** Localised copy. Partial — unspecified keys fall back to English defaults. */
|
|
826
|
+
labels?: Partial<DevicePinUnlockLabels>;
|
|
827
|
+
/** Prefix applied to every `testID`. */
|
|
828
|
+
testIdPrefix?: string;
|
|
829
|
+
/** Called with the fresh user after a successful unlock. */
|
|
830
|
+
onSignedIn: (user: DevicePinUnlockedUser) => void;
|
|
831
|
+
/** Escape hatch → render the normal password/OTP/event-PIN tabs. */
|
|
832
|
+
onUsePassword: () => void;
|
|
833
|
+
}
|
|
834
|
+
/** Themeable device-PIN unlock gate built on `useDevicePinUnlock`. */
|
|
835
|
+
declare function DevicePinUnlockScreen({ client, digits, rememberedUsername, theme: themeProp, labels: labelsProp, testIdPrefix, onSignedIn, onUsePassword, }: Readonly<DevicePinUnlockScreenProps>): ReactElement;
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* The shared device-PIN ENROL form — reused by the post-login offer
|
|
839
|
+
* ({@link DevicePinOffer}) and the settings toggle ({@link DevicePinSettingsCard}).
|
|
840
|
+
*
|
|
841
|
+
* Lets the user pick a PIN length (4/6/8), enter + confirm a PIN, and submit via
|
|
842
|
+
* `client.enrollDevicePin` (through {@link useDevicePinEnroll}). On success it
|
|
843
|
+
* calls `onEnrolled`; `onCancel` dismisses without saving. Presentational only —
|
|
844
|
+
* all submit / mismatch / status logic lives in the hook (react-query-free).
|
|
845
|
+
* Styled from the `AuthTheme` token bag.
|
|
846
|
+
*/
|
|
847
|
+
|
|
848
|
+
interface DevicePinEnrollFormProps {
|
|
849
|
+
/** The client exposing `enrollDevicePin`. */
|
|
850
|
+
client: DevicePinEnrollCapableClient;
|
|
851
|
+
/** Initial PIN length (defaults to {@link DEVICE_PIN_DEFAULT_DIGITS}). */
|
|
852
|
+
initialDigits?: number;
|
|
853
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
854
|
+
theme?: AuthTheme;
|
|
855
|
+
/** Localised copy. Partial — unspecified keys fall back to English defaults. */
|
|
856
|
+
labels?: Partial<DevicePinEnrollLabels>;
|
|
857
|
+
/** Prefix applied to every `testID`. */
|
|
858
|
+
testIdPrefix?: string;
|
|
859
|
+
/** Called after a successful enrol. */
|
|
860
|
+
onEnrolled: () => void;
|
|
861
|
+
/** Called when the user cancels without saving. */
|
|
862
|
+
onCancel: () => void;
|
|
863
|
+
}
|
|
864
|
+
/** Themeable device-PIN enrol form built on `useDevicePinEnroll`. */
|
|
865
|
+
declare function DevicePinEnrollForm({ client, initialDigits, theme: themeProp, labels: labelsProp, testIdPrefix, onEnrolled, onCancel, }: Readonly<DevicePinEnrollFormProps>): ReactElement;
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* The skippable post-login "Set a PIN for faster sign-in?" offer.
|
|
869
|
+
*
|
|
870
|
+
* Mount on the post-login landing for an authenticated user who does NOT already
|
|
871
|
+
* have a device PIN. It is dismissible and non-blocking: an initial prompt with
|
|
872
|
+
* "Set up a PIN" / "Not now"; tapping accept reveals the shared
|
|
873
|
+
* {@link DevicePinEnrollForm}; on success or skip it calls `onDismiss` so the
|
|
874
|
+
* host can hide it (and remember the dismissal). Self-contained, react-query-free.
|
|
875
|
+
*/
|
|
876
|
+
|
|
877
|
+
interface DevicePinOfferProps {
|
|
878
|
+
/** The client exposing `enrollDevicePin`. */
|
|
879
|
+
client: DevicePinEnrollCapableClient;
|
|
880
|
+
/** Initial PIN length offered in the enrol form. */
|
|
881
|
+
initialDigits?: number;
|
|
882
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
883
|
+
theme?: AuthTheme;
|
|
884
|
+
/** Localised copy. Partial — unspecified keys fall back to English defaults. */
|
|
885
|
+
labels?: Partial<DevicePinEnrollLabels>;
|
|
886
|
+
/** Prefix applied to every `testID`. */
|
|
887
|
+
testIdPrefix?: string;
|
|
888
|
+
/** Called when the offer is finished — accepted+enrolled, or skipped. */
|
|
889
|
+
onDismiss: () => void;
|
|
890
|
+
}
|
|
891
|
+
/** Themeable, skippable post-login device-PIN setup offer. */
|
|
892
|
+
declare function DevicePinOffer({ client, initialDigits, theme: themeProp, labels: labelsProp, testIdPrefix, onDismiss, }: Readonly<DevicePinOfferProps>): ReactElement;
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* The authenticated "Quick PIN sign-in" settings control.
|
|
896
|
+
*
|
|
897
|
+
* Mount on an authenticated settings/account surface. Seeded with `initialHasPin`
|
|
898
|
+
* (from `GET /bff/config`); it tracks its own enabled/disabled state thereafter
|
|
899
|
+
* and offers either:
|
|
900
|
+
* - Enable → reveals the shared {@link DevicePinEnrollForm} → `client.enrollDevicePin`;
|
|
901
|
+
* - Disable → `client.disableDevicePin` (revokes the offline token + clears the
|
|
902
|
+
* device record server-side).
|
|
903
|
+
*
|
|
904
|
+
* `onChanged(hasPin)` fires whenever the enabled state flips, so the host can
|
|
905
|
+
* persist / re-fetch. react-query-free: the disable logic lives in
|
|
906
|
+
* {@link useDevicePinDisable}; the enrol logic in the shared form's hook.
|
|
907
|
+
*/
|
|
908
|
+
|
|
909
|
+
interface DevicePinSettingsCardProps {
|
|
910
|
+
/** The client exposing both `enrollDevicePin` and `disableDevicePin`. */
|
|
911
|
+
client: DevicePinEnrollCapableClient & DevicePinDisableCapableClient;
|
|
912
|
+
/** Whether this device already has an enrolled PIN (seeded from config). */
|
|
913
|
+
initialHasPin: boolean;
|
|
914
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
915
|
+
theme?: AuthTheme;
|
|
916
|
+
/** Settings-card copy. Partial — unspecified keys fall back to English. */
|
|
917
|
+
labels?: Partial<DevicePinSettingsLabels>;
|
|
918
|
+
/** Enrol-form copy passed through to the embedded form. */
|
|
919
|
+
enrollLabels?: Partial<DevicePinEnrollLabels>;
|
|
920
|
+
/** Prefix applied to every `testID`. */
|
|
921
|
+
testIdPrefix?: string;
|
|
922
|
+
/** Called whenever the enabled state flips (enabled / disabled). */
|
|
923
|
+
onChanged?: (hasPin: boolean) => void;
|
|
924
|
+
}
|
|
925
|
+
/** Themeable authenticated device-PIN enable/disable card. */
|
|
926
|
+
declare function DevicePinSettingsCard({ client, initialHasPin, theme: themeProp, labels: labelsProp, enrollLabels, testIdPrefix, onChanged, }: Readonly<DevicePinSettingsCardProps>): ReactElement;
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* The "Sign in with a passkey" button rendered on the unified login surface when
|
|
930
|
+
* the BFF advertises the `passkey` method.
|
|
931
|
+
*
|
|
932
|
+
* A passkey login is a full-page BROWSER NAVIGATION, not a fetch: tapping the
|
|
933
|
+
* button hands off to `/bff/passkey/login`, which drives the WebAuthn ceremony
|
|
934
|
+
* through Keycloak and returns the browser to `returnUrl` (default `'/'`, which
|
|
935
|
+
* routes an authenticated user to their dashboard) with a session cookie set. On
|
|
936
|
+
* a cancelled / failed ceremony the BFF bounces back to `?passkeyError=…`; we
|
|
937
|
+
* surface that as an inline error banner above the button.
|
|
938
|
+
*
|
|
939
|
+
* react-query-free: it is pure `window.location` navigation (see
|
|
940
|
+
* `passkeyNavigation`), so it is safe to mount inside the login route group which
|
|
941
|
+
* has no QueryClient provider.
|
|
942
|
+
*/
|
|
943
|
+
|
|
944
|
+
interface PasskeyLoginButtonProps {
|
|
945
|
+
/** Relative app path the BFF returns to on success. Defaults to `'/'`. */
|
|
946
|
+
returnUrl?: string;
|
|
947
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
948
|
+
theme?: AuthTheme;
|
|
949
|
+
/** Localised copy. Partial — unspecified keys fall back to English defaults. */
|
|
950
|
+
labels?: Partial<PasskeyLoginLabels>;
|
|
951
|
+
/** Prefix applied to every `testID`. */
|
|
952
|
+
testIdPrefix?: string;
|
|
953
|
+
}
|
|
954
|
+
/** Themeable passkey login CTA + error banner. */
|
|
955
|
+
declare function PasskeyLoginButton({ returnUrl, theme: themeProp, labels: labelsProp, testIdPrefix, }: Readonly<PasskeyLoginButtonProps>): ReactElement;
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* The authenticated "Passkeys" settings card.
|
|
959
|
+
*
|
|
960
|
+
* Mount on an authenticated settings / account surface (alongside the device-PIN
|
|
961
|
+
* card). Adding a passkey is a full-page BROWSER NAVIGATION to
|
|
962
|
+
* `/bff/passkey/register`: Keycloak re-authenticates the user (password) then
|
|
963
|
+
* runs the WebAuthn credential-creation ceremony, returning the browser to the
|
|
964
|
+
* SAME settings page with `?passkey=registered` appended. On mount we read that
|
|
965
|
+
* param and, when present, show a success line.
|
|
966
|
+
*
|
|
967
|
+
* react-query-free: the add action is pure `window.location` navigation (see
|
|
968
|
+
* `passkeyNavigation`); there is no fetch here. Mirrors `DevicePinSettingsCard`.
|
|
969
|
+
*/
|
|
970
|
+
|
|
971
|
+
interface PasskeySettingsCardProps {
|
|
972
|
+
/**
|
|
973
|
+
* Relative app path the BFF returns to after registration. Defaults to the
|
|
974
|
+
* current `window.location.pathname` so the user lands back on this page.
|
|
975
|
+
*/
|
|
976
|
+
returnUrl?: string;
|
|
977
|
+
/** Explicit theme; overrides any `<AuthThemeProvider>`. */
|
|
978
|
+
theme?: AuthTheme;
|
|
979
|
+
/** Localised copy. Partial — unspecified keys fall back to English defaults. */
|
|
980
|
+
labels?: Partial<PasskeySettingsLabels>;
|
|
981
|
+
/** Prefix applied to every `testID`. */
|
|
982
|
+
testIdPrefix?: string;
|
|
983
|
+
}
|
|
984
|
+
/** Themeable authenticated passkey add card. */
|
|
985
|
+
declare function PasskeySettingsCard({ returnUrl, theme: themeProp, labels: labelsProp, testIdPrefix, }: Readonly<PasskeySettingsCardProps>): ReactElement;
|
|
986
|
+
|
|
388
987
|
/**
|
|
389
988
|
* React context for the `AuthTheme`.
|
|
390
989
|
*
|
|
@@ -438,6 +1037,8 @@ declare const AuthTestIds: {
|
|
|
438
1037
|
readonly forgotPasswordForm: "auth-forgot-form";
|
|
439
1038
|
readonly forgotPasswordEmailInput: "auth-forgot-email";
|
|
440
1039
|
readonly forgotPasswordSubmitButton: "auth-forgot-submit";
|
|
1040
|
+
readonly forgotPasswordCancelButton: "auth-forgot-cancel";
|
|
1041
|
+
readonly forgotPasswordCloseButton: "auth-forgot-close";
|
|
441
1042
|
readonly forgotPasswordError: "auth-forgot-error";
|
|
442
1043
|
readonly forgotPasswordSuccess: "auth-forgot-success";
|
|
443
1044
|
readonly resetPasswordForm: "auth-reset-form";
|
|
@@ -457,6 +1058,32 @@ declare const AuthTestIds: {
|
|
|
457
1058
|
readonly pinInput: "auth-pin-input";
|
|
458
1059
|
readonly pinSubmitButton: "auth-pin-submit";
|
|
459
1060
|
readonly pinError: "auth-pin-error";
|
|
1061
|
+
readonly devicePinUnlock: "auth-device-pin-unlock";
|
|
1062
|
+
readonly devicePinUnlockInput: "auth-device-pin-unlock-input";
|
|
1063
|
+
readonly devicePinUnlockSubmit: "auth-device-pin-unlock-submit";
|
|
1064
|
+
readonly devicePinUnlockUsePassword: "auth-device-pin-unlock-use-password";
|
|
1065
|
+
readonly devicePinUnlockError: "auth-device-pin-unlock-error";
|
|
1066
|
+
readonly devicePinEnrollForm: "auth-device-pin-enroll-form";
|
|
1067
|
+
readonly devicePinEnrollLength: "auth-device-pin-enroll-length";
|
|
1068
|
+
readonly devicePinEnrollPin: "auth-device-pin-enroll-pin";
|
|
1069
|
+
readonly devicePinEnrollConfirm: "auth-device-pin-enroll-confirm";
|
|
1070
|
+
readonly devicePinEnrollSubmit: "auth-device-pin-enroll-submit";
|
|
1071
|
+
readonly devicePinEnrollCancel: "auth-device-pin-enroll-cancel";
|
|
1072
|
+
readonly devicePinEnrollError: "auth-device-pin-enroll-error";
|
|
1073
|
+
readonly devicePinOffer: "auth-device-pin-offer";
|
|
1074
|
+
readonly devicePinOfferAccept: "auth-device-pin-offer-accept";
|
|
1075
|
+
readonly devicePinOfferSkip: "auth-device-pin-offer-skip";
|
|
1076
|
+
readonly devicePinSettings: "auth-device-pin-settings";
|
|
1077
|
+
readonly devicePinSettingsStatus: "auth-device-pin-settings-status";
|
|
1078
|
+
readonly devicePinSettingsEnable: "auth-device-pin-settings-enable";
|
|
1079
|
+
readonly devicePinSettingsDisable: "auth-device-pin-settings-disable";
|
|
1080
|
+
readonly devicePinSettingsError: "auth-device-pin-settings-error";
|
|
1081
|
+
readonly passkeyLogin: "auth-passkey-login";
|
|
1082
|
+
readonly passkeyLoginButton: "auth-passkey-login-button";
|
|
1083
|
+
readonly passkeyLoginError: "auth-passkey-login-error";
|
|
1084
|
+
readonly passkeySettings: "auth-passkey-settings";
|
|
1085
|
+
readonly passkeySettingsAdd: "auth-passkey-settings-add";
|
|
1086
|
+
readonly passkeySettingsSuccess: "auth-passkey-settings-success";
|
|
460
1087
|
};
|
|
461
1088
|
/** Apply an optional prefix to a base test ID. */
|
|
462
1089
|
declare function withTestIdPrefix(baseId: string, prefix?: string): string;
|
|
@@ -590,6 +1217,38 @@ interface UseResetPasswordFormResult {
|
|
|
590
1217
|
*/
|
|
591
1218
|
declare function useResetPasswordForm({ client, token, onSuccess, }: UseResetPasswordFormArgs): UseResetPasswordFormResult;
|
|
592
1219
|
|
|
1220
|
+
/** `true` when `value` (after trimming) looks like an email address. */
|
|
1221
|
+
declare function isValidForgotPasswordEmail(value: string): boolean;
|
|
1222
|
+
interface UseForgotPasswordSubmitArgs {
|
|
1223
|
+
/** The same-origin BFF client (build it with `createBffAuthClient`). */
|
|
1224
|
+
client: BffAuthClient;
|
|
1225
|
+
/**
|
|
1226
|
+
* Full URL with a `{token}` placeholder, forwarded to the backend so it can
|
|
1227
|
+
* build the reset-email link without hardcoding a frontend host.
|
|
1228
|
+
*/
|
|
1229
|
+
resetUrlTemplate?: string;
|
|
1230
|
+
/** Invoked once the request succeeds (the generic-confirmation state). */
|
|
1231
|
+
onSuccess?: () => void;
|
|
1232
|
+
}
|
|
1233
|
+
interface UseForgotPasswordSubmitResult {
|
|
1234
|
+
email: string;
|
|
1235
|
+
setEmail: (value: string) => void;
|
|
1236
|
+
/** `true` once the request has succeeded (show the generic confirmation). */
|
|
1237
|
+
submitted: boolean;
|
|
1238
|
+
/** `true` while the request is in flight. */
|
|
1239
|
+
isSubmitting: boolean;
|
|
1240
|
+
/** `true` when the last submit hit a network / 5xx failure. */
|
|
1241
|
+
hasNetworkError: boolean;
|
|
1242
|
+
/** `true` when the email is valid and no request is in flight. */
|
|
1243
|
+
canSubmit: boolean;
|
|
1244
|
+
/** Validate, then fire the request (no-op when `canSubmit` is false). */
|
|
1245
|
+
submit: () => void;
|
|
1246
|
+
/** Clear all state — call when the modal closes. */
|
|
1247
|
+
reset: () => void;
|
|
1248
|
+
}
|
|
1249
|
+
/** Headless, react-query-free "request a reset link" logic. */
|
|
1250
|
+
declare function useForgotPasswordSubmit({ client, resetUrlTemplate, onSuccess, }: UseForgotPasswordSubmitArgs): UseForgotPasswordSubmitResult;
|
|
1251
|
+
|
|
593
1252
|
/**
|
|
594
1253
|
* The two discrete steps of the email-OTP login flow.
|
|
595
1254
|
*
|
|
@@ -679,6 +1338,148 @@ interface UsePinLoginResult {
|
|
|
679
1338
|
*/
|
|
680
1339
|
declare function usePinLogin(options: UsePinLoginOptions): UsePinLoginResult;
|
|
681
1340
|
|
|
1341
|
+
/** What {@link useBffLoginConfig} returns. */
|
|
1342
|
+
interface UseBffLoginConfigResult {
|
|
1343
|
+
/** The parsed login config; the empty fallback until the fetch resolves. */
|
|
1344
|
+
config: BffLoginConfig;
|
|
1345
|
+
/** `true` until the initial `getLoginConfig` call settles. */
|
|
1346
|
+
loading: boolean;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Fetch `getLoginConfig()` once on mount and expose `{ config, loading }`. The
|
|
1350
|
+
* empty fallback config is used until the call resolves and on any failure. The
|
|
1351
|
+
* effect guards against a `setState` after the consumer unmounts mid-request.
|
|
1352
|
+
*/
|
|
1353
|
+
declare function useBffLoginConfig(client: LoginConfigCapableClient): UseBffLoginConfigResult;
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* The localizable failure states the device-PIN ENROL form can show — shared by
|
|
1357
|
+
* the post-login offer and the settings toggle.
|
|
1358
|
+
*/
|
|
1359
|
+
declare const enum DevicePinEnrollErrorKey {
|
|
1360
|
+
/** PIN length wrong or the confirmation didn't match (no network call). */
|
|
1361
|
+
Mismatch = "mismatch",
|
|
1362
|
+
/** The current session is not authenticated (401). */
|
|
1363
|
+
Unauthorized = "unauthorized",
|
|
1364
|
+
/** The session already has a PIN, or is otherwise forbidden (403). */
|
|
1365
|
+
Forbidden = "forbidden",
|
|
1366
|
+
/** The PIN failed server-side validation (400). */
|
|
1367
|
+
InvalidPin = "invalid_pin",
|
|
1368
|
+
/** Any other failure (grant / network / unexpected). */
|
|
1369
|
+
Failed = "failed"
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
interface UseDevicePinEnrollArgs {
|
|
1373
|
+
/** The client exposing `enrollDevicePin`. */
|
|
1374
|
+
client: DevicePinEnrollCapableClient;
|
|
1375
|
+
/** Chosen PIN length (4/6/8). */
|
|
1376
|
+
digits: number;
|
|
1377
|
+
/** Called after a successful enrol so the surface can dismiss / flip state. */
|
|
1378
|
+
onEnrolled: () => void;
|
|
1379
|
+
}
|
|
1380
|
+
interface UseDevicePinEnrollResult {
|
|
1381
|
+
pin: string;
|
|
1382
|
+
confirmPin: string;
|
|
1383
|
+
submitting: boolean;
|
|
1384
|
+
errorKey: DevicePinEnrollErrorKey | null;
|
|
1385
|
+
setPin: (next: string) => void;
|
|
1386
|
+
setConfirmPin: (next: string) => void;
|
|
1387
|
+
submit: () => void;
|
|
1388
|
+
}
|
|
1389
|
+
declare function useDevicePinEnroll({ client, digits, onEnrolled, }: UseDevicePinEnrollArgs): UseDevicePinEnrollResult;
|
|
1390
|
+
|
|
1391
|
+
interface UseDevicePinDisableArgs {
|
|
1392
|
+
/** The client exposing `disableDevicePin`. */
|
|
1393
|
+
client: DevicePinDisableCapableClient;
|
|
1394
|
+
/** Whether this device already has an enrolled PIN (seeded from config). */
|
|
1395
|
+
initialHasPin: boolean;
|
|
1396
|
+
/** Called whenever the enabled state changes (enabled / disabled). */
|
|
1397
|
+
onChanged?: (hasPin: boolean) => void;
|
|
1398
|
+
}
|
|
1399
|
+
interface UseDevicePinDisableResult {
|
|
1400
|
+
/** `true` when the device currently has an enrolled PIN. */
|
|
1401
|
+
hasPin: boolean;
|
|
1402
|
+
/** `true` while a disable call is in flight. */
|
|
1403
|
+
disabling: boolean;
|
|
1404
|
+
/** `true` when the last disable attempt failed. */
|
|
1405
|
+
failed: boolean;
|
|
1406
|
+
/** Run the disable call for the current session. */
|
|
1407
|
+
disable: () => void;
|
|
1408
|
+
/** Flip the card to enabled (called by the host after a successful enrol). */
|
|
1409
|
+
markEnabled: () => void;
|
|
1410
|
+
}
|
|
1411
|
+
declare function useDevicePinDisable({ client, initialHasPin, onChanged, }: UseDevicePinDisableArgs): UseDevicePinDisableResult;
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Shared device-PIN constants for the unlock + enrol surfaces.
|
|
1415
|
+
*
|
|
1416
|
+
* The allowed PIN lengths (4 / 6 / 8) mirror the BFF's accepted `digits` values
|
|
1417
|
+
* (`POST /bff/pin/enroll`). Centralised here so neither the enrol form nor the
|
|
1418
|
+
* unlock screen carries magic numbers, and so the default length is defined once.
|
|
1419
|
+
*/
|
|
1420
|
+
/** The PIN lengths the BFF accepts on enrol (`digits` ∈ {4, 6, 8}). */
|
|
1421
|
+
declare const DEVICE_PIN_ALLOWED_DIGITS: readonly number[];
|
|
1422
|
+
/** Default PIN length offered when the BFF reports no stored `pinDigits`. */
|
|
1423
|
+
declare const DEVICE_PIN_DEFAULT_DIGITS = 4;
|
|
1424
|
+
/** Guard: is `value` one of the BFF-accepted PIN lengths? */
|
|
1425
|
+
declare function isAllowedPinDigits(value: number): boolean;
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* Navigation helpers for the passkey (WebAuthn) BFF endpoints.
|
|
1429
|
+
*
|
|
1430
|
+
* Unlike the device-PIN client (which is plain `fetch`), the passkey login and
|
|
1431
|
+
* registration ceremonies are NOT fetches — they are full-page BROWSER
|
|
1432
|
+
* NAVIGATIONS. The BFF (`Bff.AspNetCore` 1.3.x) drives the WebAuthn ceremony
|
|
1433
|
+
* through Keycloak's hosted pages, then redirects the browser back:
|
|
1434
|
+
* - login: `/bff/passkey/login?returnUrl=<rel>` → on success the browser
|
|
1435
|
+
* lands on `returnUrl` with the session cookie already set;
|
|
1436
|
+
* - registration: `/bff/passkey/register?returnUrl=<rel>` → Keycloak re-auths
|
|
1437
|
+
* the user (password) then runs the credential-creation ceremony, returning
|
|
1438
|
+
* the browser to `returnUrl?passkey=registered`;
|
|
1439
|
+
* - failure / cancel: the BFF redirects to `<login>?passkeyError=cancelled` or
|
|
1440
|
+
* `<login>?passkeyError=failed`.
|
|
1441
|
+
*
|
|
1442
|
+
* react-query-FREE by construction: there is NO fetch here at all — every
|
|
1443
|
+
* function is a pure `window.location` read or `window.location.assign`. The
|
|
1444
|
+
* login route group has no QueryClient provider, so the passkey login button
|
|
1445
|
+
* (which lives there) MUST avoid any react-query primitive; navigation satisfies
|
|
1446
|
+
* that trivially.
|
|
1447
|
+
*
|
|
1448
|
+
* All reads are SSR-safe: when `window` is undefined (server render / native)
|
|
1449
|
+
* they return a benign default rather than throwing.
|
|
1450
|
+
*/
|
|
1451
|
+
/** The recognised passkey error codes the BFF sends back via `?passkeyError=`. */
|
|
1452
|
+
type PasskeyErrorCode = 'cancelled' | 'failed';
|
|
1453
|
+
/**
|
|
1454
|
+
* Start the passkey LOGIN ceremony by navigating the browser to the BFF's
|
|
1455
|
+
* `/bff/passkey/login` endpoint. The BFF redirects through Keycloak's WebAuthn
|
|
1456
|
+
* ceremony and, on success, returns the browser to `returnUrl` with a session
|
|
1457
|
+
* cookie set. No-op off-web (no `window`). `returnUrl` should be a relative
|
|
1458
|
+
* app path (e.g. `'/'`).
|
|
1459
|
+
*/
|
|
1460
|
+
declare function startPasskeyLogin(returnUrl: string): void;
|
|
1461
|
+
/**
|
|
1462
|
+
* Start the passkey REGISTRATION ceremony by navigating the browser to the BFF's
|
|
1463
|
+
* `/bff/passkey/register` endpoint. Requires an authenticated session; Keycloak
|
|
1464
|
+
* re-authenticates the user (password) then runs the credential-creation
|
|
1465
|
+
* ceremony, returning the browser to `returnUrl?passkey=registered`. No-op
|
|
1466
|
+
* off-web. `returnUrl` should be a relative app path so the user lands back on
|
|
1467
|
+
* the same settings surface.
|
|
1468
|
+
*/
|
|
1469
|
+
declare function startPasskeyRegistration(returnUrl: string): void;
|
|
1470
|
+
/**
|
|
1471
|
+
* Read the `?passkeyError=` query param the BFF appends on a failed / cancelled
|
|
1472
|
+
* ceremony. Returns `'cancelled'` or `'failed'` for the two known values, or
|
|
1473
|
+
* `null` for an absent / unknown value. SSR-safe (returns `null` off-web).
|
|
1474
|
+
*/
|
|
1475
|
+
declare function readPasskeyError(): PasskeyErrorCode | null;
|
|
1476
|
+
/**
|
|
1477
|
+
* `true` when the browser has just returned from a successful passkey
|
|
1478
|
+
* registration, i.e. the `?passkey=registered` query param is present. SSR-safe
|
|
1479
|
+
* (returns `false` off-web).
|
|
1480
|
+
*/
|
|
1481
|
+
declare function readPasskeyRegistered(): boolean;
|
|
1482
|
+
|
|
682
1483
|
/**
|
|
683
1484
|
* `createBffAuthClient` — the one-line wiring of a same-origin `BffAuthClient`.
|
|
684
1485
|
*
|
|
@@ -814,4 +1615,4 @@ declare function validatePasswordPolicy(password: string): PasswordPolicyError[]
|
|
|
814
1615
|
/** `true` when the password satisfies every policy rule. */
|
|
815
1616
|
declare function isPasswordValid(password: string): boolean;
|
|
816
1617
|
|
|
817
|
-
export { AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type CreateBffAuthClientOptions, DEFAULT_FORGOT_PASSWORD_LABELS, DEFAULT_LOGIN_LABELS, DEFAULT_OTP_LABELS, DEFAULT_PIN_LABELS, DEFAULT_RESET_PASSWORD_LABELS, ForgotPasswordForm, type ForgotPasswordFormLabels, type ForgotPasswordFormProps, LoginForm, type LoginFormLabels, type LoginFormProps, OtpForm, type OtpFormLabels, type OtpFormProps, OtpLoginStep, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, ResetPasswordError, ResetPasswordForm, type ResetPasswordFormLabels, type ResetPasswordFormProps, type RoleRoute, type RoleRouteTable, type UseBffAuthOptions, type UseBffAuthResult, type UseBffForgotPasswordOptions, type UseBffResetPasswordOptions, type UseOtpLoginOptions, type UseOtpLoginResult, type UsePinLoginOptions, type UsePinLoginResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, collectUserRoles, createBffAuthClient, defaultAuthTheme, isPasswordValid, resolvePostLoginRoute, useAuthTheme, useBffAuth, useBffForgotPassword, useBffResetPassword, useOtpLogin, usePinLogin, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
|
|
1618
|
+
export { AuthTestIds, type AuthTheme, type AuthThemeColors, AuthThemeProvider, type AuthThemeProviderProps, type AuthThemeRadii, type AuthThemeSpacing, type AuthThemeTypography, BffAuthStatus, type BffDeviceState, type BffLoginConfig, type CreateBffAuthClientOptions, 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_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, type PasskeyErrorCode, PasskeyLoginButton, type PasskeyLoginButtonProps, type PasskeyLoginLabels, PasskeySettingsCard, type PasskeySettingsCardProps, type PasskeySettingsLabels, PasswordPolicyError, PinForm, type PinFormLabels, type PinFormProps, 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 UsePinLoginOptions, type UsePinLoginResult, type UseResetPasswordFormArgs, type UseResetPasswordFormResult, collectUserRoles, createBffAuthClient, defaultAuthTheme, isAllowedPinDigits, isPasswordValid, isValidForgotPasswordEmail, readPasskeyError, readPasskeyRegistered, resolvePostLoginRoute, startPasskeyLogin, startPasskeyRegistration, useAuthTheme, useBffAuth, useBffForgotPassword, useBffLoginConfig, useBffResetPassword, useDevicePinDisable, useDevicePinEnroll, useDevicePinUnlock, useForgotPasswordSubmit, useOtpLogin, usePinLogin, useResetPasswordForm, validatePasswordPolicy, withTestIdPrefix };
|