@aooth/auth-moost 0.1.21 → 0.1.23
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/atscript/index.d.mts +38 -20
- package/dist/atscript/index.mjs +2 -2
- package/dist/{forms-uqegc32h.mjs → forms-xaBNc5Ng.mjs} +19 -3
- package/dist/index.d.mts +267 -21
- package/dist/index.mjs +399 -72
- package/package.json +9 -9
- package/src/atscript/models/forms.as +45 -2
- package/src/atscript/models/forms.as.d.ts +38 -19
package/dist/index.d.mts
CHANGED
|
@@ -1178,6 +1178,16 @@ interface AuthWfMfaEnrollState {
|
|
|
1178
1178
|
*/
|
|
1179
1179
|
mode?: "required" | "optional" | "manage";
|
|
1180
1180
|
done?: boolean;
|
|
1181
|
+
/**
|
|
1182
|
+
* Set by `enroll-send` when `resolveEnrollPreConfirmed` vouches that the
|
|
1183
|
+
* staged sms/email address is verified-by-construction (e.g. the invited
|
|
1184
|
+
* email the user just proved by redeeming the magic link delivered to it).
|
|
1185
|
+
* `enroll-send` then skips the pincode dispatch and `enroll-confirm` runs
|
|
1186
|
+
* its write-on-confirm tail with no code-entry pause. Server-only (decided
|
|
1187
|
+
* by the resolver, never read from the wire); never set for TOTP —
|
|
1188
|
+
* possession of an authenticator cannot be proven by construction.
|
|
1189
|
+
*/
|
|
1190
|
+
preConfirmed?: boolean;
|
|
1181
1191
|
/**
|
|
1182
1192
|
* Gates the standalone `enroll-totp-qr` step (TOTP only). Set once the user
|
|
1183
1193
|
* has been shown the QR/secret and clicked Continue, so the QR pause fires
|
|
@@ -1580,12 +1590,40 @@ interface AuthWfAddMfaState {
|
|
|
1580
1590
|
* replay-resistant). Gates the one-time swap + the management menu.
|
|
1581
1591
|
*/
|
|
1582
1592
|
stepUpDone?: boolean;
|
|
1593
|
+
/**
|
|
1594
|
+
* The user has explicitly consented to the step-up pincode dispatch —
|
|
1595
|
+
* either by submitting `manage-stepup-confirm`'s "we'll send a code to
|
|
1596
|
+
* ma•••@x" notice, or by picking a factor on `select-2fa` (choosing
|
|
1597
|
+
* "Email (ma•••@x)" and submitting IS the consent). Gates the
|
|
1598
|
+
* `manage-stepup-confirm` pause so opening the manage dialog never
|
|
1599
|
+
* dispatches a code as a side effect. Also set when
|
|
1600
|
+
* `resolveStepUpConfirmBeforeSend` opts the deployment out of the pause.
|
|
1601
|
+
* Server-only; sms/email step-up only (TOTP dispatches nothing).
|
|
1602
|
+
*/
|
|
1603
|
+
stepUpConfirmed?: boolean;
|
|
1583
1604
|
/** The management action the user picked on the menu. */
|
|
1584
1605
|
action?: "add" | "replace" | "remove";
|
|
1585
1606
|
/** The transport the chosen `action` applies to. */
|
|
1586
1607
|
target?: MfaTransport;
|
|
1587
1608
|
/** Set by `confirm-remove-mfa` so `finish-add-mfa` can report which factor was removed. */
|
|
1588
1609
|
removed?: MfaTransport;
|
|
1610
|
+
/**
|
|
1611
|
+
* Removing a factor is currently impossible: the user has exactly one
|
|
1612
|
+
* confirmed (policy-allowed) factor and `mfaPolicy.mode === 'required'`.
|
|
1613
|
+
* Computed by `manage-menu` before its pause and mirrored to
|
|
1614
|
+
* `public.manage.removeBlocked` so `ManageMfaForm` omits the Remove option
|
|
1615
|
+
* (and explains why) instead of offering an operation that can never
|
|
1616
|
+
* succeed. Re-checked server-side in `manage-menu` + `confirm-remove-mfa`.
|
|
1617
|
+
*/
|
|
1618
|
+
removeBlocked?: boolean;
|
|
1619
|
+
/**
|
|
1620
|
+
* Set by `confirm-remove-mfa` when it arrives in an un-removable state
|
|
1621
|
+
* (stale/crafted route — the menu filters these): the step aborts to the
|
|
1622
|
+
* `finish-add-mfa` terminal with a reason-specific message instead of
|
|
1623
|
+
* pausing on a form whose only submit re-throws the same guard error
|
|
1624
|
+
* (a dead-end loop, since the manage forms hide their built-in cancel).
|
|
1625
|
+
*/
|
|
1626
|
+
blocked?: "last-required-factor" | "method-locked";
|
|
1589
1627
|
}
|
|
1590
1628
|
/**
|
|
1591
1629
|
* Self-signup flow state. Populated by `init-signup` (policy from
|
|
@@ -1684,12 +1722,15 @@ interface AuthWfPublicState {
|
|
|
1684
1722
|
mfaEnroll?: Pick<AuthWfMfaEnrollState, "method" | "mode" | "availableTransports" | "secret" | "uri">;
|
|
1685
1723
|
/**
|
|
1686
1724
|
* Mirrors the manage-MFA menu inputs — the un-enrolled transports the user
|
|
1687
|
-
* can Add
|
|
1688
|
-
*
|
|
1725
|
+
* can Add, the locked transports to omit from Change/Remove, and the
|
|
1726
|
+
* `removeBlocked` flag that omits the Remove option for the last confirmed
|
|
1727
|
+
* factor under a `required` policy. The enrolled method list the menu
|
|
1728
|
+
* cross-references is `public.mfa.enrolledMethods`.
|
|
1689
1729
|
*/
|
|
1690
1730
|
manage?: {
|
|
1691
1731
|
candidates?: MfaTransport[];
|
|
1692
1732
|
locked?: MfaTransport[];
|
|
1733
|
+
removeBlocked?: boolean;
|
|
1693
1734
|
};
|
|
1694
1735
|
/** Mirrors `ctx.defaults` — prefill source for the recovery email field. */
|
|
1695
1736
|
defaults?: {
|
|
@@ -1878,7 +1919,8 @@ interface AuthWorkflowOpts {
|
|
|
1878
1919
|
enrollConfirm?: TAtscriptAnnotatedType; /** Manage-MFA menu (Add / Change / Remove) shown after step-up. */
|
|
1879
1920
|
manageMfa?: TAtscriptAnnotatedType; /** Confirm-removal pause for the manage-MFA "Remove" action. */
|
|
1880
1921
|
removeMfaConfirm?: TAtscriptAnnotatedType; /** Password re-auth — step-up fallback when no factor is MFA-challengeable. */
|
|
1881
|
-
passwordReauth?: TAtscriptAnnotatedType;
|
|
1922
|
+
passwordReauth?: TAtscriptAnnotatedType; /** Step-up dispatch consent — "we'll send a code to ma•••@x" notice before the step-up pincode send. */
|
|
1923
|
+
stepUpConfirm?: TAtscriptAnnotatedType;
|
|
1882
1924
|
select2fa?: TAtscriptAnnotatedType;
|
|
1883
1925
|
mfaCode?: TAtscriptAnnotatedType;
|
|
1884
1926
|
pincode?: TAtscriptAnnotatedType;
|
|
@@ -1934,6 +1976,7 @@ interface ResolvedAuthWorkflowOpts {
|
|
|
1934
1976
|
manageMfa: TAtscriptAnnotatedType;
|
|
1935
1977
|
removeMfaConfirm: TAtscriptAnnotatedType;
|
|
1936
1978
|
passwordReauth: TAtscriptAnnotatedType;
|
|
1979
|
+
stepUpConfirm: TAtscriptAnnotatedType;
|
|
1937
1980
|
select2fa: TAtscriptAnnotatedType;
|
|
1938
1981
|
mfaCode: TAtscriptAnnotatedType;
|
|
1939
1982
|
pincode: TAtscriptAnnotatedType;
|
|
@@ -2201,6 +2244,64 @@ declare class AuthWorkflow {
|
|
|
2201
2244
|
* Reached from login.flow.
|
|
2202
2245
|
*/
|
|
2203
2246
|
protected resolveEnrollment(_ctx: AuthWfCtx): NonNullable<AuthWfCtx["enrollment"]> | Promise<NonNullable<AuthWfCtx["enrollment"]>>;
|
|
2247
|
+
/**
|
|
2248
|
+
* Vouch that an MFA-enrolment address is verified-by-construction, skipping
|
|
2249
|
+
* the pincode round-trip: `enroll-send` sends nothing and `enroll-confirm`
|
|
2250
|
+
* writes the confirmed factor (+ `verifiedEmail` for email) with no
|
|
2251
|
+
* code-entry pause. Asked once per dispatch with the staged transport +
|
|
2252
|
+
* normalized address (ctx-first, extra positional args — same convention as
|
|
2253
|
+
* `resolveOtpDisclosure`).
|
|
2254
|
+
*
|
|
2255
|
+
* Default `false` — every enrolment proves its address. The canonical
|
|
2256
|
+
* override is the invite-accept case, where the user is inside the flow
|
|
2257
|
+
* only because they redeemed a magic link delivered to that exact address
|
|
2258
|
+
* minutes earlier (the same proof `activate-user` trusts to write
|
|
2259
|
+
* `account.verifiedEmail`):
|
|
2260
|
+
*
|
|
2261
|
+
* ```ts
|
|
2262
|
+
* protected resolveEnrollPreConfirmed(ctx: AuthWfCtx, method: MfaTransport, address: string) {
|
|
2263
|
+
* return !!ctx.accept && method === "email" && address === ctx.email;
|
|
2264
|
+
* }
|
|
2265
|
+
* ```
|
|
2266
|
+
*
|
|
2267
|
+
* Keep the equality check — the proof transfers ONLY to the address the
|
|
2268
|
+
* magic link was delivered to; vouching for a different address the user
|
|
2269
|
+
* typed would confirm an unproven inbox. Never asked for TOTP.
|
|
2270
|
+
*/
|
|
2271
|
+
protected resolveEnrollPreConfirmed(_ctx: AuthWfCtx, _method: MfaTransport, _address: string): boolean | Promise<boolean>;
|
|
2272
|
+
/**
|
|
2273
|
+
* Pin the enrolment address for an sms/email transport — the policy seam
|
|
2274
|
+
* for deployments whose factor must be BOUND to an account record (e.g.
|
|
2275
|
+
* staff MFA locked to the work mailbox so portal access dies with it at
|
|
2276
|
+
* offboarding; a free-text form would let a self-service swap to a personal
|
|
2277
|
+
* inbox defeat that control entirely). Asked by `enroll-address` BEFORE its
|
|
2278
|
+
* form renders, with the staged transport (ctx-first, extra positional arg —
|
|
2279
|
+
* same convention as `resolveEnrollPreConfirmed`).
|
|
2280
|
+
*
|
|
2281
|
+
* Returning a string stages it as the enrolment address (normalized via
|
|
2282
|
+
* `normalizeMfaAddress`; the free-text form is SKIPPED — the same staging
|
|
2283
|
+
* seam consumer pre-seeding uses, so the user is never shown a form whose
|
|
2284
|
+
* only valid input is one known string). `'collect'` (the default) keeps
|
|
2285
|
+
* the free-text form. A pinned address composes with the rest of the trio
|
|
2286
|
+
* machinery untouched: `enroll-send` dispatches the pincode to it, and
|
|
2287
|
+
* `resolveEnrollPreConfirmed` may vouch it (a deployment pinning to a
|
|
2288
|
+
* verified-by-construction address gets the no-code path for free).
|
|
2289
|
+
*
|
|
2290
|
+
* ```ts
|
|
2291
|
+
* protected async resolveEnrollAddress(ctx: AuthWfCtx, method: MfaTransport) {
|
|
2292
|
+
* if (method !== "email") return "collect";
|
|
2293
|
+
* const user = await this.users.getUser(ctx.subject!);
|
|
2294
|
+
* return (user as { email?: string }).email ?? "collect";
|
|
2295
|
+
* }
|
|
2296
|
+
* ```
|
|
2297
|
+
*
|
|
2298
|
+
* The returned address is trusted as-is (no `validateMfaAddress` pass) —
|
|
2299
|
+
* the deployment is authoritative for its own records. An empty/blank
|
|
2300
|
+
* return falls back to `'collect'`. For nuanced RULES on a user-typed
|
|
2301
|
+
* address (domain allowlists, record comparisons) override the ctx-first
|
|
2302
|
+
* {@link validateMfaAddress} instead.
|
|
2303
|
+
*/
|
|
2304
|
+
protected resolveEnrollAddress(_ctx: AuthWfCtx, _method: MfaTransport): string | "collect" | Promise<string | "collect">;
|
|
2204
2305
|
/**
|
|
2205
2306
|
* Resolve the finalize policy. Reached from login.flow. `auditLogin` is
|
|
2206
2307
|
* dropped from the shape per §2 — audit moved out of the workflow layer.
|
|
@@ -2280,6 +2381,33 @@ declare class AuthWorkflow {
|
|
|
2280
2381
|
* ```
|
|
2281
2382
|
*/
|
|
2282
2383
|
protected resolveLockedMfaTransports(_ctx: AuthWfCtx): MfaTransport[] | Promise<MfaTransport[]>;
|
|
2384
|
+
/**
|
|
2385
|
+
* Whether the manage-MFA step-up must collect explicit consent BEFORE
|
|
2386
|
+
* dispatching its sms/email pincode (the `manage-stepup-confirm` pause:
|
|
2387
|
+
* "To continue, we will send a verification code to ma•••@x"). Default
|
|
2388
|
+
* `true` — nothing should email/text the user as a side effect of opening
|
|
2389
|
+
* a manage dialog: a user who opened it by mistake (or just to look)
|
|
2390
|
+
* closes it with zero codes consumed, no resend cooldown burnt. Override
|
|
2391
|
+
* to `false` to restore the zero-click dispatch (the code is already in
|
|
2392
|
+
* flight when the first form renders). Never asked for TOTP step-up
|
|
2393
|
+
* (nothing is dispatched) and not consulted by the login flow (its
|
|
2394
|
+
* challenge is mid-authentication, where zero-click is the norm).
|
|
2395
|
+
*/
|
|
2396
|
+
protected resolveStepUpConfirmBeforeSend(_ctx: AuthWfCtx): boolean | Promise<boolean>;
|
|
2397
|
+
/**
|
|
2398
|
+
* What the user's authenticator app shows as the ACCOUNT half of
|
|
2399
|
+
* "issuer: account" for a TOTP enrolment (the issuer half is
|
|
2400
|
+
* `resolveMfaPolicy().issuer` / `opts.totpIssuer`). Cosmetic only — never
|
|
2401
|
+
* used for lookup — but it is how a user with several entries tells
|
|
2402
|
+
* accounts apart, and it is encoded into the `otpauth://` URI at
|
|
2403
|
+
* secret-provisioning time, so it lives in the authenticator FOREVER
|
|
2404
|
+
* (re-labeling requires re-enrolment). Default prefers a human-readable
|
|
2405
|
+
* identifier the flow already carries (`ctx.email` — invite/recovery/
|
|
2406
|
+
* signup) and otherwise loads the user's `username`; the stable-uuid
|
|
2407
|
+
* `ctx.subject` is the last resort. Override for a richer label (display
|
|
2408
|
+
* name, tenant-qualified email, …).
|
|
2409
|
+
*/
|
|
2410
|
+
protected resolveTotpAccountLabel(ctx: AuthWfCtx): string | Promise<string>;
|
|
2283
2411
|
/**
|
|
2284
2412
|
* Resolve the channel-OTP disclosure copy rendered beneath the email/phone
|
|
2285
2413
|
* input on `AskEmailForm` / `AskPhoneForm`. Reached from login.flow Phase 3.
|
|
@@ -2536,10 +2664,30 @@ declare class AuthWorkflow {
|
|
|
2536
2664
|
protected mfaKindOf(methodName: string): MfaTransport | null;
|
|
2537
2665
|
/**
|
|
2538
2666
|
* Send an enrolment pincode and stamp `ctx.pincode.sentTo` with the masked
|
|
2539
|
-
* recipient.
|
|
2540
|
-
*
|
|
2667
|
+
* recipient. Reached only through {@link mintAndSendEnrollPincode}; kept
|
|
2668
|
+
* separate as the delivery-only override seam.
|
|
2541
2669
|
*/
|
|
2542
2670
|
protected sendEnrollPincode(ctx: AuthWfCtx, address: string, code: string): Promise<void>;
|
|
2671
|
+
/**
|
|
2672
|
+
* The single enrol-dispatch implementation: mint a fresh pin, arm the
|
|
2673
|
+
* resend cooldown + the code-length form hint, and deliver the code.
|
|
2674
|
+
* Shared by `enrollSend` (initial dispatch) and the resend arm inside
|
|
2675
|
+
* `enrollConfirm`, so the arming policy cannot drift between first send
|
|
2676
|
+
* and resend.
|
|
2677
|
+
*/
|
|
2678
|
+
private mintAndSendEnrollPincode;
|
|
2679
|
+
/**
|
|
2680
|
+
* Idempotent TOTP secret provisioning into wf-state ONLY (the QR renders
|
|
2681
|
+
* from `public.mfaEnroll.secret/uri`; the user record is written on confirm
|
|
2682
|
+
* — write-on-confirm). The single implementation behind BOTH provisioning
|
|
2683
|
+
* sites — `enroll-pick-method`'s auto-pick/picker tail and `enroll-totp-qr`
|
|
2684
|
+
* (covers the manage add/change path where the picker is skipped) — so the
|
|
2685
|
+
* account label baked into the `otpauth://` URI cannot drift between them.
|
|
2686
|
+
* The label comes from {@link resolveTotpAccountLabel} (human-readable
|
|
2687
|
+
* default); blank falls back to the subject uuid so the URI always carries
|
|
2688
|
+
* SOME account discriminator.
|
|
2689
|
+
*/
|
|
2690
|
+
private provisionTotpSecret;
|
|
2543
2691
|
/**
|
|
2544
2692
|
* Drop the per-enrolment scratch fields (the `mfaEnroll` provisioning fields +
|
|
2545
2693
|
* the pincode timers/`sentTo`) off ctx — the shared teardown used by the
|
|
@@ -2557,8 +2705,24 @@ declare class AuthWorkflow {
|
|
|
2557
2705
|
* or `undefined` when valid. Email must look like an email; SMS is permissive
|
|
2558
2706
|
* E.164-ish (normalized by {@link normalizeMfaAddress}). Override for stricter
|
|
2559
2707
|
* (e.g. libphonenumber) validation.
|
|
2708
|
+
*
|
|
2709
|
+
* Ctx-first and async-capable, so record-based rules need no ctx-stash
|
|
2710
|
+
* workaround — an override can load the account and compare directly
|
|
2711
|
+
* (e.g. domain-allowlist the typed inbox, or require it to match a
|
|
2712
|
+
* record field). To PIN the address outright — never show the free-text
|
|
2713
|
+
* form at all — use {@link resolveEnrollAddress} instead; this hook is for
|
|
2714
|
+
* nuanced rules on what the user typed.
|
|
2715
|
+
*
|
|
2716
|
+
* ```ts
|
|
2717
|
+
* protected async validateMfaAddress(ctx: AuthWfCtx, method: MfaTransport, value: string) {
|
|
2718
|
+
* if (method === "email" && !value.trim().toLowerCase().endsWith("@corp.example")) {
|
|
2719
|
+
* return "Use your corporate email address";
|
|
2720
|
+
* }
|
|
2721
|
+
* return super.validateMfaAddress(ctx, method, value);
|
|
2722
|
+
* }
|
|
2723
|
+
* ```
|
|
2560
2724
|
*/
|
|
2561
|
-
protected validateMfaAddress(method: MfaTransport, value: string): string | undefined
|
|
2725
|
+
protected validateMfaAddress(_ctx: AuthWfCtx, method: MfaTransport, value: string): string | undefined | Promise<string | undefined>;
|
|
2562
2726
|
/**
|
|
2563
2727
|
* Light normalization of a validated MFA address before it is stored. Default:
|
|
2564
2728
|
* trims, and strips spacing/punctuation from SMS numbers while KEEPING the
|
|
@@ -2826,10 +2990,17 @@ declare class AuthWorkflow {
|
|
|
2826
2990
|
/**
|
|
2827
2991
|
* Terminal for the manage-MFA flow. The user KEEPS their current session (no
|
|
2828
2992
|
* re-issue, no cookies) — a plain data finish. Outcomes, in priority order:
|
|
2829
|
-
* removed → changed (`replace` + done) → added (done) →
|
|
2830
|
-
* (
|
|
2993
|
+
* removed → changed (`replace` + done) → added (done) → blocked
|
|
2994
|
+
* (un-removable operation aborted by `confirm-remove-mfa`) →
|
|
2995
|
+
* nothing-available (zero candidates, never had to step-up) → cancelled.
|
|
2831
2996
|
*/
|
|
2832
2997
|
finishAddMfa(ctx: AuthWfCtx): undefined;
|
|
2998
|
+
/**
|
|
2999
|
+
* `finish-add-mfa`'s envelope construction, extracted pure so the outcome
|
|
3000
|
+
* priority (removed → changed → added → blocked → nothing-available →
|
|
3001
|
+
* cancelled) is unit-testable without a wf event context.
|
|
3002
|
+
*/
|
|
3003
|
+
protected buildAddMfaFinishEnvelope(ctx: AuthWfCtx): WfFinished;
|
|
2833
3004
|
/**
|
|
2834
3005
|
* Resolve which transports the user may NOT change/remove via the manage flow
|
|
2835
3006
|
* (calls {@link resolveLockedMfaTransports}) and write them to
|
|
@@ -2844,6 +3015,22 @@ declare class AuthWorkflow {
|
|
|
2844
3015
|
* encapsulated when no durable store is wired (the registry default).
|
|
2845
3016
|
*/
|
|
2846
3017
|
manageStepUpDone(ctx: AuthWfCtx): undefined;
|
|
3018
|
+
/**
|
|
3019
|
+
* Manage-MFA step-up consent — pauses on `StepUpConfirmForm` ("To continue,
|
|
3020
|
+
* we will send a verification code to ma•••@x") BEFORE `pincode-send`
|
|
3021
|
+
* dispatches the step-up code, so opening the manage dialog never consumes
|
|
3022
|
+
* a code send as a side effect. Fires only on the auto-picked paths (single
|
|
3023
|
+
* factor, or default factor) — an explicit `select-2fa` pick already
|
|
3024
|
+
* counts as consent (`select2fa` sets `stepUpConfirmed`). `Continue`
|
|
3025
|
+
* consents and the SAME engine pass mints + sends; `useDifferentMethod`
|
|
3026
|
+
* re-opens the picker; `cancel` aborts with nothing dispatched (the
|
|
3027
|
+
* schema's `{ break: aborted }` right after this step keeps the pair from
|
|
3028
|
+
* sending the declined code). Gated by {@link resolveStepUpConfirmBeforeSend}
|
|
3029
|
+
* (default on) — an opt-out marks consent and falls straight through.
|
|
3030
|
+
*/
|
|
3031
|
+
manageStepUpConfirm(ctx: AuthWfCtx): undefined | Promise<undefined>;
|
|
3032
|
+
/** `manage-stepup-confirm` tail — opt-out fall-through or the consent pause. */
|
|
3033
|
+
private applyStepUpConfirm;
|
|
2847
3034
|
/**
|
|
2848
3035
|
* Manage-MFA password re-auth — the step-up FALLBACK when the user's only
|
|
2849
3036
|
* confirmed factor(s) are of kinds the policy no longer allows, so nothing is
|
|
@@ -2857,6 +3044,15 @@ declare class AuthWorkflow {
|
|
|
2857
3044
|
* subject), and `verifyPassword` is the same check `changePassword` enforces.
|
|
2858
3045
|
*/
|
|
2859
3046
|
managePasswordReauth(ctx: AuthWfCtx): Promise<undefined>;
|
|
3047
|
+
/**
|
|
3048
|
+
* The keep-at-least-one rule: removing the user's LAST confirmed factor under
|
|
3049
|
+
* a `required` policy can never succeed. The single source for the predicate
|
|
3050
|
+
* `manage-menu` mirrors into `addMfa.removeBlocked` (to drop the Remove option)
|
|
3051
|
+
* AND `confirm-remove-mfa` re-checks before its pause (defence in depth) — so
|
|
3052
|
+
* a policy change (e.g. "keep at least two", or a backup-codes exception)
|
|
3053
|
+
* lands in one place, not two copies that can drift.
|
|
3054
|
+
*/
|
|
3055
|
+
private isLastRequiredFactor;
|
|
2860
3056
|
/**
|
|
2861
3057
|
* Manage-MFA menu — pauses on `ManageMfaForm` and routes the chosen
|
|
2862
3058
|
* `operation` (`add:<t>` / `replace:<t>` / `remove:<t>`). Only reached when
|
|
@@ -2869,8 +3065,13 @@ declare class AuthWorkflow {
|
|
|
2869
3065
|
/**
|
|
2870
3066
|
* Manage-MFA remove confirmation. Pauses on `RemoveMfaConfirmForm`; the
|
|
2871
3067
|
* 'Remove' submit performs the removal, 'Cancel' aborts. Re-checks the locked
|
|
2872
|
-
* set
|
|
2873
|
-
*
|
|
3068
|
+
* set and the keep-at-least-one rule (LAST confirmed factor under a
|
|
3069
|
+
* `required` policy) BEFORE the pause — and an un-removable state aborts to
|
|
3070
|
+
* the `finish-add-mfa` terminal (reason on `addMfa.blocked`) instead of
|
|
3071
|
+
* pausing: `manage-menu` filters these operations out, so arriving here
|
|
3072
|
+
* blocked means a stale/crafted route, and a retryable form whose only
|
|
3073
|
+
* submit re-throws the same guard would be a dead-end loop (the manage
|
|
3074
|
+
* forms hide their built-in cancel — the host owns it).
|
|
2874
3075
|
*/
|
|
2875
3076
|
confirmRemoveMfa(ctx: AuthWfCtx): Promise<undefined>;
|
|
2876
3077
|
askChannel(ctx: AuthWfCtx, channel: "email" | "phone"): Promise<unknown>;
|
|
@@ -2952,15 +3153,43 @@ declare class AuthWorkflow {
|
|
|
2952
3153
|
*/
|
|
2953
3154
|
enrollPickMethod(ctx: AuthWfCtx): undefined | Promise<undefined>;
|
|
2954
3155
|
/**
|
|
2955
|
-
* Unified MFA-enrol phase 2 (collect sms/email address
|
|
2956
|
-
*
|
|
2957
|
-
*
|
|
2958
|
-
*
|
|
2959
|
-
*
|
|
2960
|
-
*
|
|
2961
|
-
*
|
|
2962
|
-
|
|
2963
|
-
|
|
3156
|
+
* Unified MFA-enrol phase 2 (collect sms/email address). Not invoked for
|
|
3157
|
+
* totp. Asks {@link resolveEnrollAddress} FIRST — a deployment that pins
|
|
3158
|
+
* the address (factor bound to an account record) stages it here and the
|
|
3159
|
+
* free-text form never renders; this single call site covers every trio
|
|
3160
|
+
* path (picker, auto-pick, manage add/replace pre-seed), and `enroll-send`
|
|
3161
|
+
* dispatches to the pinned address in the same engine pass. Otherwise
|
|
3162
|
+
* (`'collect'`) handles `skip` (opt-in) / `cancel` (manage) /
|
|
3163
|
+
* `useDifferentMethod`, validates the typed address server-side via the
|
|
3164
|
+
* ctx-first {@link validateMfaAddress} (the client `@ui.form.validate`
|
|
3165
|
+
* hint is advisory), then STAGES the candidate value in wf-state
|
|
3166
|
+
* (`m.address`) — the user record is written only on confirm
|
|
3167
|
+
* (write-on-confirm), so an ADD leaves no partial row and a REPLACE keeps
|
|
3168
|
+
* the old confirmed value live until the new code verifies in
|
|
3169
|
+
* `enroll-confirm`. Collection ONLY: the pincode dispatch lives in
|
|
3170
|
+
* `enroll-send` (same engine pass, no extra round-trip), so a consumer
|
|
3171
|
+
* pre-seeding `mfaEnroll.address` — which skips this whole step via its
|
|
3172
|
+
* schema condition — still gets exactly one code.
|
|
3173
|
+
*/
|
|
3174
|
+
enrollAddress(ctx: AuthWfCtx): undefined | Promise<undefined>;
|
|
3175
|
+
/**
|
|
3176
|
+
* `enroll-address` tail — stage a pinned address, or run the free-text
|
|
3177
|
+
* collect pause (skip/cancel/useDifferentMethod triage + ctx-first
|
|
3178
|
+
* validation + write-on-confirm staging).
|
|
3179
|
+
*/
|
|
3180
|
+
private collectEnrollAddress;
|
|
3181
|
+
/**
|
|
3182
|
+
* Unified MFA-enrol dispatch (sms/email only) — the trio's ONLY pincode
|
|
3183
|
+
* send. A separate step (the canonical "send if no pin" gate, mirroring
|
|
3184
|
+
* `pincode-send`) so both address paths share one dispatch site: collected
|
|
3185
|
+
* by `enroll-address`, or pre-seeded by a consumer (which skips
|
|
3186
|
+
* `enroll-address` entirely — previously skipping the dispatch with it and
|
|
3187
|
+
* stranding the user on a code form no code was sent for). Asks
|
|
3188
|
+
* `resolveEnrollPreConfirmed` first: a verified-by-construction address
|
|
3189
|
+
* (e.g. the invite email the magic link just proved) skips the round-trip —
|
|
3190
|
+
* `enroll-confirm` then writes the factor without pausing.
|
|
3191
|
+
*/
|
|
3192
|
+
enrollSend(ctx: AuthWfCtx): Promise<undefined>;
|
|
2964
3193
|
/**
|
|
2965
3194
|
* MFA-enrol TOTP QR step — shown on its OWN pause between method-pick and
|
|
2966
3195
|
* code-entry (so the user scans first, types the code next). Idempotently
|
|
@@ -2985,6 +3214,16 @@ declare class AuthWorkflow {
|
|
|
2985
3214
|
* a fresh row for an ADD.
|
|
2986
3215
|
*/
|
|
2987
3216
|
enrollConfirm(ctx: AuthWfCtx): Promise<undefined>;
|
|
3217
|
+
/**
|
|
3218
|
+
* `enroll-confirm`'s write-on-confirm tail (uniform for ADD and REPLACE, all
|
|
3219
|
+
* transports): the staged value (sms/email `address`, totp `secret`) is now
|
|
3220
|
+
* proven — by a verified pincode/TOTP code, or vouched by
|
|
3221
|
+
* `resolveEnrollPreConfirmed` — so upsert it as confirmed. `addMfaMethod`
|
|
3222
|
+
* replaces any row of the same name, so a REPLACE atomically swaps in the
|
|
3223
|
+
* new value with no pre-confirm clobber window, and an ADD creates the row
|
|
3224
|
+
* fresh.
|
|
3225
|
+
*/
|
|
3226
|
+
private confirmEnrolledFactor;
|
|
2988
3227
|
/**
|
|
2989
3228
|
* Promote a freshly-confirmed channel into its login-handle column so future
|
|
2990
3229
|
* login + recovery resolve the account by it (`findByHandle`). Runs once,
|
|
@@ -3408,12 +3647,19 @@ declare class AuthWorkflow {
|
|
|
3408
3647
|
* 3. STEP-UP (only when `stepUpRequired`): re-verify identity before any
|
|
3409
3648
|
* change — `mfaStepUpLoop` challenges an EXISTING factor when one is still
|
|
3410
3649
|
* challengeable (`stepUpMode==='mfa'`), else `manage-password-reauth` falls
|
|
3411
|
-
* back to the account password (`stepUpMode==='password'`).
|
|
3650
|
+
* back to the account password (`stepUpMode==='password'`). The sms/email
|
|
3651
|
+
* challenge collects explicit consent (`manage-stepup-confirm`) BEFORE
|
|
3652
|
+
* dispatching its pincode — opening the dialog never sends a code as a
|
|
3653
|
+
* side effect (see `resolveStepUpConfirmBeforeSend`). On success
|
|
3412
3654
|
* `manage-stepup-done` swaps off the encapsulated start onto the durable
|
|
3413
3655
|
* `store` strategy (server-anchored, replay-resistant; mirrors login's
|
|
3414
3656
|
* swap-after-credentials).
|
|
3415
3657
|
* 4. `manage-menu` (only when `stepUpRequired`) — pick add / change / remove +
|
|
3416
|
-
* target; pre-seeds `mfaEnroll.method` for add/change.
|
|
3658
|
+
* target; pre-seeds `mfaEnroll.method` for add/change. Un-offerable
|
|
3659
|
+
* operations never render: locked transports drop their Change/Remove
|
|
3660
|
+
* options, and the LAST factor under a `required` policy drops Remove
|
|
3661
|
+
* (`removeBlocked`) — `confirm-remove-mfa` aborts to the finish terminal
|
|
3662
|
+
* if a blocked remove arrives anyway (no retryable dead-end form).
|
|
3417
3663
|
* 5. Route: `confirm-remove-mfa` for remove; otherwise the REUSED enrol trio
|
|
3418
3664
|
* (`enroll-pick-method` → `enroll-address` / `enroll-totp-qr` →
|
|
3419
3665
|
* `enroll-confirm`). A zero-MFA user skips step-up + menu and lands on the
|