@aooth/auth-moost 0.1.18 → 0.1.20
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 +449 -1
- package/dist/atscript/index.mjs +1 -1
- package/dist/{forms-D7ZfanKT.mjs → forms-uqegc32h.mjs} +1 -1
- package/dist/index.d.mts +314 -19
- package/dist/index.mjs +434 -60
- package/package.json +19 -19
- package/src/atscript/models/forms.as +8 -3
- package/src/atscript/models/forms.as.d.ts +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -6,7 +6,7 @@ import { FinishWfOpts, WfFinished, useAtscriptWf } from "@atscript/moost-wf";
|
|
|
6
6
|
import { MoostWf, WfOutlet, WfOutletTokenConfig, WfStateStrategy } from "@moostjs/event-wf";
|
|
7
7
|
import { FederatedLoginService, NormalizedProfile, OAuthProviderRegistry } from "@aooth/idp";
|
|
8
8
|
import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
9
|
-
import { AuthCodeStore, ClientRedirectPolicy, IdTokenSigner, OidcClaimsResolver, PendingAuthorizationStore } from "@aooth/auth/authz";
|
|
9
|
+
import { AuthCodeStore, AuthorizationServerMetadata, AuthorizationServerMetadata as AuthorizationServerMetadata$1, BuildAuthorizationServerMetadataOptions, BuildProtectedResourceMetadataOptions, ClientRedirectPolicy, DynamicClientRegistration, DynamicClientRegistration as DynamicClientRegistration$1, DynamicClientRegistrationOptions, IdTokenSigner, OidcClaimsResolver, PendingAuthorizationStore, ProtectedResourceMetadata, WwwAuthenticateBearerChallengeOptions, buildAuthorizationServerMetadata, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer } from "@aooth/auth/authz";
|
|
10
10
|
|
|
11
11
|
//#region src/auth.config.d.ts
|
|
12
12
|
/** Resolved cookie attributes. Same shape is used for both access + refresh. */
|
|
@@ -852,6 +852,7 @@ interface OidcDiscoveryDocument {
|
|
|
852
852
|
authorization_endpoint: string;
|
|
853
853
|
token_endpoint: string;
|
|
854
854
|
jwks_uri: string;
|
|
855
|
+
registration_endpoint?: string;
|
|
855
856
|
response_types_supported: string[];
|
|
856
857
|
grant_types_supported: string[];
|
|
857
858
|
subject_types_supported: string[];
|
|
@@ -860,6 +861,23 @@ interface OidcDiscoveryDocument {
|
|
|
860
861
|
code_challenge_methods_supported: string[];
|
|
861
862
|
token_endpoint_auth_methods_supported: string[];
|
|
862
863
|
}
|
|
864
|
+
/** RFC 7591 §3.2.1 registration response — public client, so no secret fields. */
|
|
865
|
+
interface ClientRegistrationSuccess {
|
|
866
|
+
client_id: string;
|
|
867
|
+
/** Seconds since epoch (the RFC's unit — NOT ms). */
|
|
868
|
+
client_id_issued_at: number;
|
|
869
|
+
redirect_uris: string[];
|
|
870
|
+
token_endpoint_auth_method: string;
|
|
871
|
+
grant_types: string[];
|
|
872
|
+
response_types: string[];
|
|
873
|
+
client_name?: string;
|
|
874
|
+
scope?: string;
|
|
875
|
+
}
|
|
876
|
+
/** RFC 7591 §3.2.2 registration error response. */
|
|
877
|
+
interface ClientRegistrationFailure {
|
|
878
|
+
error: string;
|
|
879
|
+
error_description?: string;
|
|
880
|
+
}
|
|
863
881
|
/**
|
|
864
882
|
* The authorization-server endpoints (AUTH-SERVER.md). Turns the existing login
|
|
865
883
|
* workflow into an OAuth/OIDC authorization server for the app's OWN clients — a
|
|
@@ -909,21 +927,75 @@ declare class AuthorizeController {
|
|
|
909
927
|
* custom login path.
|
|
910
928
|
*/
|
|
911
929
|
protected loginPath(): string;
|
|
912
|
-
|
|
930
|
+
/**
|
|
931
|
+
* The issuer identifier for the RFC 8414 metadata document. Defaults to the
|
|
932
|
+
* Tier-2 signer's issuer; a SIGNER-LESS deployment that serves MCP connector
|
|
933
|
+
* clients must **override** this (return `{origin}/auth`-style, byte-exact —
|
|
934
|
+
* never derived from the Host header, which would let a request inject its
|
|
935
|
+
* host into a cacheable discovery document). `undefined` ⇒ the
|
|
936
|
+
* `oauth-authorization-server` endpoint 404s.
|
|
937
|
+
*/
|
|
938
|
+
protected getIssuer(): string | undefined;
|
|
939
|
+
/**
|
|
940
|
+
* The RFC 7591 dynamic-client-registration operation, or `undefined` (the
|
|
941
|
+
* default) to disable DCR — then `POST /auth/register` 404s and neither
|
|
942
|
+
* discovery document advertises a `registration_endpoint`. **Override** in a
|
|
943
|
+
* subclass: inject a `DynamicClientStore` (the `DYNAMIC_CLIENT_STORE_TOKEN`
|
|
944
|
+
* provider) as a required ctor param, build one `DynamicClientRegistration`
|
|
945
|
+
* around it, and return it here. Same plain-getter pattern as
|
|
946
|
+
* {@link getIdTokenSigner} (an optional `@Inject` panics in moost's
|
|
947
|
+
* route-table pass).
|
|
948
|
+
*/
|
|
949
|
+
protected getDynamicClientRegistration(): DynamicClientRegistration$1 | undefined;
|
|
950
|
+
/**
|
|
951
|
+
* `scopes_supported` for the RFC 8414 document (optional per the RFC; omitted
|
|
952
|
+
* by default — deliberately NOT inheriting the OIDC document's hardcoded
|
|
953
|
+
* list, which describes Tier-2 sign-in scopes). Override to advertise what
|
|
954
|
+
* connector clients may request.
|
|
955
|
+
*/
|
|
956
|
+
protected scopesSupported(): string[] | undefined;
|
|
957
|
+
authorize(responseType: string | undefined, redirectUri: string | undefined, clientId: string | undefined, state: string | undefined, codeChallenge: string | undefined, codeChallengeMethod: string | undefined, scope: string | undefined, nonce: string | undefined, resource: string | undefined): Promise<string>;
|
|
913
958
|
token(body: {
|
|
914
959
|
grant_type?: string;
|
|
915
960
|
code?: string;
|
|
916
961
|
code_verifier?: string;
|
|
917
962
|
client_id?: string;
|
|
918
963
|
client_secret?: string;
|
|
964
|
+
resource?: string | string[];
|
|
919
965
|
} | undefined): Promise<TokenSuccess | TokenError>;
|
|
920
966
|
/**
|
|
921
967
|
* OIDC discovery (Tier 2). Derives every endpoint from the signer's `issuer`
|
|
922
968
|
* (configured as `{origin}/auth`), so a relying `OidcProvider` configured with
|
|
923
969
|
* the same `issuer` resolves `/authorize`, `/token`, and `/jwks` automatically.
|
|
924
|
-
* 404 when no signer is wired (Tier-1-only deployment).
|
|
970
|
+
* 404 when no signer is wired (Tier-1-only deployment). When DCR is also
|
|
971
|
+
* wired, `registration_endpoint` is advertised here too — in a combined
|
|
972
|
+
* deployment a client that prefers `openid-configuration` over the RFC 8414
|
|
973
|
+
* document must see the same capability set.
|
|
925
974
|
*/
|
|
926
975
|
discovery(): OidcDiscoveryDocument | TokenError;
|
|
976
|
+
/**
|
|
977
|
+
* RFC 8414 Authorization Server Metadata — the OAuth-flavored discovery MCP
|
|
978
|
+
* connector clients fetch (OAUTH.md R1). Served signer-INDEPENDENTLY: it
|
|
979
|
+
* needs only an issuer ({@link getIssuer} — overridable for a Tier-1-style
|
|
980
|
+
* deployment with no `IdTokenSigner`). Mounted under the controller this is
|
|
981
|
+
* the suffix form `{issuer}/.well-known/oauth-authorization-server`; the
|
|
982
|
+
* RFC-correct path-insertion form at the HTTP-server ROOT
|
|
983
|
+
* (`/.well-known/oauth-authorization-server/<issuer-path>`) cannot be
|
|
984
|
+
* registered by a prefix-mounted controller — consumers mount it themselves
|
|
985
|
+
* from the exported `buildAuthorizationServerMetadata` (re-exported by this
|
|
986
|
+
* package).
|
|
987
|
+
*/
|
|
988
|
+
oauthServerMetadata(): AuthorizationServerMetadata$1 | TokenError;
|
|
989
|
+
/**
|
|
990
|
+
* RFC 7591 Dynamic Client Registration (OAUTH.md R2) — anonymous by spec;
|
|
991
|
+
* 404 unless a registration operation is wired ({@link
|
|
992
|
+
* getDynamicClientRegistration}). A thin HTTP adapter: validation, abuse
|
|
993
|
+
* knobs (guard / cap / never-used GC) and persistence live in
|
|
994
|
+
* `DynamicClientRegistration` (`@aooth/auth`). Public clients only — the
|
|
995
|
+
* response carries NO `client_secret` and NO `client_secret_expires_at`
|
|
996
|
+
* (RFC 7591 §3.2.1 requires them only when a secret is issued).
|
|
997
|
+
*/
|
|
998
|
+
register(body: unknown): Promise<ClientRegistrationSuccess | ClientRegistrationFailure | TokenError>;
|
|
927
999
|
/** The signer's public JWKS (Tier 2). 404 when no signer is wired. */
|
|
928
1000
|
jwks(): Promise<Awaited<ReturnType<IdTokenSigner["jwks"]>>> | TokenError;
|
|
929
1001
|
/** Fail soft: 302 the validated client redirect with an `?error=` (+ echoed `state`). */
|
|
@@ -1013,6 +1085,17 @@ declare const AUTH_CODE_STORE_TOKEN = "aooth:AuthCodeStore";
|
|
|
1013
1085
|
* `CompositeClientPolicy` of both) under this string.
|
|
1014
1086
|
*/
|
|
1015
1087
|
declare const CLIENT_REDIRECT_POLICY_TOKEN = "aooth:ClientRedirectPolicy";
|
|
1088
|
+
/**
|
|
1089
|
+
* DI token for the {@link import("@aooth/auth/authz").DynamicClientStore}
|
|
1090
|
+
* (RFC 7591 dynamic registrations) — an abstract class, same auto-instantiation
|
|
1091
|
+
* hazard as the store tokens above. The BASE `AuthorizeController` never
|
|
1092
|
+
* injects it (DCR is optional, and an optional `@Inject` panics in moost's
|
|
1093
|
+
* route-table pass): a consumer subclass that enables DCR adds it as a
|
|
1094
|
+
* REQUIRED ctor param, builds a `DynamicClientRegistration` around it, and
|
|
1095
|
+
* overrides `getDynamicClientRegistration()`. The same store instance also
|
|
1096
|
+
* backs the `DynamicClientPolicy` composed into the redirect policy.
|
|
1097
|
+
*/
|
|
1098
|
+
declare const DYNAMIC_CLIENT_STORE_TOKEN = "aooth:DynamicClientStore";
|
|
1016
1099
|
//#endregion
|
|
1017
1100
|
//#region src/workflow/auth-workflow.ctx.d.ts
|
|
1018
1101
|
type MfaTransport = "sms" | "email" | "totp";
|
|
@@ -1147,17 +1230,52 @@ interface AuthWfMfaState {
|
|
|
1147
1230
|
}
|
|
1148
1231
|
/** Channel-onboarding state (login Phase 3). */
|
|
1149
1232
|
interface AuthWfChannelState {
|
|
1233
|
+
/**
|
|
1234
|
+
* The email address `ask/email` collected and sent the enrollment code to —
|
|
1235
|
+
* the sole enrollment ask→verify target, mirroring `phone`. The enrollment
|
|
1236
|
+
* gates key on it. Deliberately SEPARATE from `notice.email` (the
|
|
1237
|
+
* security-notice recipient): that slot may be seeded from
|
|
1238
|
+
* `getCorrespondenceEmail` / a federated profile without any code ever
|
|
1239
|
+
* being sent, so the enrollment gates must never key on it.
|
|
1240
|
+
*/
|
|
1241
|
+
email?: string;
|
|
1150
1242
|
emailConfirmed?: boolean;
|
|
1151
1243
|
phone?: string;
|
|
1152
1244
|
phoneConfirmed?: boolean;
|
|
1153
1245
|
otpDisclosure?: string;
|
|
1154
1246
|
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Security-notice recipient state (login flow). `email` is the address
|
|
1249
|
+
* `notify-new-device` (and any future security notice) delivers to — owned
|
|
1250
|
+
* by the `credentials` / `seedChannelState` seeding (confirmed email-MFA →
|
|
1251
|
+
* `getCorrespondenceEmail` → provider display email) and refreshed by
|
|
1252
|
+
* `verify/email` once a freshly-proven inbox confirms. Server-only — never
|
|
1253
|
+
* `@wf.context.pass`'d, not mirrored into `ctx.public`. Deliberately
|
|
1254
|
+
* distinct from `channel.email` (the enrollment ask→verify target) and from
|
|
1255
|
+
* the flow-subject `ctx.email` that recovery / invite / signup use.
|
|
1256
|
+
*/
|
|
1257
|
+
interface AuthWfNoticeState {
|
|
1258
|
+
email?: string;
|
|
1259
|
+
}
|
|
1155
1260
|
/** Device-trust state (login). */
|
|
1156
1261
|
interface AuthWfTrustState {
|
|
1157
1262
|
deviceTrustToken?: string;
|
|
1158
1263
|
newDevice?: boolean;
|
|
1159
1264
|
rememberDevice?: boolean;
|
|
1160
1265
|
optIn?: boolean;
|
|
1266
|
+
/**
|
|
1267
|
+
* The ARRIVING request presented a valid recognition cookie (stamped by
|
|
1268
|
+
* `device-recognition` BEFORE any mint — pre-mint arrival state). This is
|
|
1269
|
+
* what the notify-new-device gate reads: recognition suppresses the
|
|
1270
|
+
* notification; it never affects MFA (that's `newDevice` / trust).
|
|
1271
|
+
*/
|
|
1272
|
+
recognized?: boolean;
|
|
1273
|
+
/**
|
|
1274
|
+
* Recognition token `issue` must set as a cookie on the finish envelope —
|
|
1275
|
+
* either the re-validated arriving cookie (re-issued with a fresh maxAge)
|
|
1276
|
+
* or a freshly minted one.
|
|
1277
|
+
*/
|
|
1278
|
+
seenDeviceToken?: string;
|
|
1161
1279
|
}
|
|
1162
1280
|
/** Session-policy state (login). */
|
|
1163
1281
|
interface AuthWfSessionState {
|
|
@@ -1289,12 +1407,32 @@ interface AuthWfRecoveryAltActions {
|
|
|
1289
1407
|
*/
|
|
1290
1408
|
interface AuthWfOtpState {
|
|
1291
1409
|
verified?: boolean;
|
|
1410
|
+
/**
|
|
1411
|
+
* The address the last `pincode-send` ACTUALLY delivered to — login MFA
|
|
1412
|
+
* email/SMS challenge, recovery M1 (typed identifier) / M2 (registered
|
|
1413
|
+
* method), and signup's reuse of the recovery pair. Server-only — never
|
|
1414
|
+
* `@wf.context.pass`'d. The email-channel case is consumed by
|
|
1415
|
+
* `pincode-check` as inbox proof (`users.setVerifiedEmail`).
|
|
1416
|
+
*/
|
|
1417
|
+
deliveredTo?: string;
|
|
1418
|
+
/** Wire channel of that delivery — only `email` constitutes an inbox proof. */
|
|
1419
|
+
deliveredChannel?: "email" | "sms";
|
|
1292
1420
|
}
|
|
1293
1421
|
/** Invite admin-side (Phase A) state. */
|
|
1294
1422
|
interface AuthWfAdminState {
|
|
1295
1423
|
availableRoles?: string[];
|
|
1296
1424
|
roles?: string[];
|
|
1297
1425
|
userExtras?: Record<string, unknown>;
|
|
1426
|
+
/**
|
|
1427
|
+
* Re-invite decision stamp — set by `admin-form` when `duplicateInviteCheck`
|
|
1428
|
+
* returns `'reuse'` (the default for a row still parked on
|
|
1429
|
+
* `account.pendingInvitation`). `create-user` then refreshes the existing
|
|
1430
|
+
* row (fresh roles + extras, `pendingInvitation` re-asserted) instead of
|
|
1431
|
+
* creating, and `send-email` mints a fresh magic link. Re-validated in
|
|
1432
|
+
* `create-user` against a fresh read: non-pending row → 409, vanished row →
|
|
1433
|
+
* normal create path.
|
|
1434
|
+
*/
|
|
1435
|
+
reuseExisting?: boolean;
|
|
1298
1436
|
/**
|
|
1299
1437
|
* Outlet-pause idempotency marker for `send-email`. Flipped to `true`
|
|
1300
1438
|
* after the first dispatch so the invitee's magic-link resume — which
|
|
@@ -1577,18 +1715,26 @@ interface AuthWfPublicState {
|
|
|
1577
1715
|
newPasswordRequired?: boolean;
|
|
1578
1716
|
/**
|
|
1579
1717
|
* Mirrors the display-only fields of `ctx.authz` — the requesting client's
|
|
1580
|
-
* id/name
|
|
1581
|
-
*
|
|
1582
|
-
* the
|
|
1718
|
+
* id/name, granted scope, and the VALIDATED redirect host (the trustworthy
|
|
1719
|
+
* identity shown next to the attacker-choosable `clientName`), shown on the
|
|
1720
|
+
* authorize-consent form. The `handle` and the `approved` gate stay
|
|
1721
|
+
* server-only (never whitelisted onto the wire).
|
|
1583
1722
|
*/
|
|
1584
1723
|
authz?: {
|
|
1585
1724
|
clientName?: string;
|
|
1586
1725
|
scope?: string;
|
|
1726
|
+
redirectHost?: string;
|
|
1587
1727
|
};
|
|
1588
1728
|
}
|
|
1589
1729
|
/** Unified workflow context shape — one type for all three flows. */
|
|
1590
1730
|
interface AuthWfCtx {
|
|
1591
1731
|
subject?: string;
|
|
1732
|
+
/**
|
|
1733
|
+
* Flow-subject address — the typed recovery identifier, the invite target,
|
|
1734
|
+
* or the signup email. The LOGIN flow does NOT use this slot: its
|
|
1735
|
+
* security-notice recipient is `notice.email` and its enrollment
|
|
1736
|
+
* ask→verify target is `channel.email`.
|
|
1737
|
+
*/
|
|
1592
1738
|
email?: string;
|
|
1593
1739
|
defaults?: AuthWfDefaults;
|
|
1594
1740
|
pin?: string;
|
|
@@ -1630,6 +1776,7 @@ interface AuthWfCtx {
|
|
|
1630
1776
|
recoveryAltActions?: AuthWfRecoveryAltActions;
|
|
1631
1777
|
mfa?: AuthWfMfaState;
|
|
1632
1778
|
channel?: AuthWfChannelState;
|
|
1779
|
+
notice?: AuthWfNoticeState;
|
|
1633
1780
|
trust?: AuthWfTrustState;
|
|
1634
1781
|
session?: AuthWfSessionState;
|
|
1635
1782
|
altActions?: AuthWfAltActionsState;
|
|
@@ -1657,14 +1804,17 @@ interface AuthWfCtx {
|
|
|
1657
1804
|
* "Continue with <provider>" detour mid-authorize. Presence routes the login
|
|
1658
1805
|
* tail to the `authz-consent` → `mint-authz-code` terminal (deliver an auth
|
|
1659
1806
|
* code to the client) INSTEAD of `issue`/`redirect` — no browser session is
|
|
1660
|
-
* minted. `clientName`/`scope` are staged by `authz-consent`
|
|
1661
|
-
* form's display copy
|
|
1662
|
-
*
|
|
1807
|
+
* minted. `clientName`/`scope`/`redirectHost` are staged by `authz-consent`
|
|
1808
|
+
* for the consent form's display copy (`redirectHost` is parsed from the
|
|
1809
|
+
* VALIDATED redirect — the trustworthy identity next to the registrant-chosen
|
|
1810
|
+
* name); `approved` is the explicit user-consent gate the `mint-authz-code`
|
|
1811
|
+
* terminal requires before it will mint a code.
|
|
1663
1812
|
*/
|
|
1664
1813
|
authz?: {
|
|
1665
1814
|
handle: string;
|
|
1666
1815
|
clientName?: string;
|
|
1667
1816
|
scope?: string;
|
|
1817
|
+
redirectHost?: string;
|
|
1668
1818
|
approved?: boolean;
|
|
1669
1819
|
};
|
|
1670
1820
|
/**
|
|
@@ -1698,6 +1848,24 @@ interface AuthWorkflowOpts {
|
|
|
1698
1848
|
ttlMs?: number;
|
|
1699
1849
|
bindsTo?: "cookie" | "cookie+ip";
|
|
1700
1850
|
};
|
|
1851
|
+
/**
|
|
1852
|
+
* Device RECOGNITION infra — the always-on `seenDevices` ledger + cookie
|
|
1853
|
+
* that suppress the "new sign-in" notification on devices the user has
|
|
1854
|
+
* already logged in from. Strictly a notification suppressor, NOT an MFA
|
|
1855
|
+
* bypass (that is `deviceTrust`, which stays opt-in and strict).
|
|
1856
|
+
* Cookie-only binding by design — recognition is pure noise control, so it
|
|
1857
|
+
* never binds to IP (IP churn must not re-trigger the email).
|
|
1858
|
+
*
|
|
1859
|
+
* `cookieName` defaults to `<deviceTrust.cookieName>_seen` so a consumer
|
|
1860
|
+
* renaming the trust cookie gets a matching recognition name for free.
|
|
1861
|
+
* No policy flags here — the on/off gate is the existing
|
|
1862
|
+
* `resolveFinalize().notifyNewDevice` policy.
|
|
1863
|
+
*/
|
|
1864
|
+
deviceRecognition?: {
|
|
1865
|
+
cookieName?: string;
|
|
1866
|
+
ttlMs?: number; /** Cap on the per-user `seenDevices` ledger — LRU-evicted beyond it. */
|
|
1867
|
+
maxDevices?: number;
|
|
1868
|
+
};
|
|
1701
1869
|
forms?: {
|
|
1702
1870
|
loginCredentials?: TAtscriptAnnotatedType;
|
|
1703
1871
|
invite?: TAtscriptAnnotatedType;
|
|
@@ -1748,6 +1916,11 @@ interface ResolvedAuthWorkflowOpts {
|
|
|
1748
1916
|
ttlMs: number;
|
|
1749
1917
|
bindsTo: "cookie" | "cookie+ip";
|
|
1750
1918
|
};
|
|
1919
|
+
deviceRecognition: {
|
|
1920
|
+
cookieName: string;
|
|
1921
|
+
ttlMs: number;
|
|
1922
|
+
maxDevices: number;
|
|
1923
|
+
};
|
|
1751
1924
|
forms: {
|
|
1752
1925
|
loginCredentials: TAtscriptAnnotatedType;
|
|
1753
1926
|
invite: TAtscriptAnnotatedType;
|
|
@@ -1819,6 +1992,22 @@ type AuthDeliveryPayload = {
|
|
|
1819
1992
|
recipient: string;
|
|
1820
1993
|
deviceLabel?: string;
|
|
1821
1994
|
loginAt: number;
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Consumer-triggered security notice (e.g. impossible-travel detected by a
|
|
1998
|
+
* `resolveRiskStepUp` override) — routed through the same `deliver()` as
|
|
1999
|
+
* every other notice. `reason` is the machine-readable trigger
|
|
2000
|
+
* (e.g. `"impossible-travel"`); `context` is free-form template data
|
|
2001
|
+
* (distances, cities). NEVER auto-sent by the base class — only a consumer
|
|
2002
|
+
* call to `sendSecurityAlert` emits it.
|
|
2003
|
+
*/
|
|
2004
|
+
| {
|
|
2005
|
+
kind: "security-alert";
|
|
2006
|
+
channel: "email";
|
|
2007
|
+
recipient: string;
|
|
2008
|
+
reason: string;
|
|
2009
|
+
loginAt: number;
|
|
2010
|
+
context?: Record<string, unknown>;
|
|
1822
2011
|
};
|
|
1823
2012
|
/**
|
|
1824
2013
|
* Top-level `UserCredentials` keys that workflow-collected profile payloads
|
|
@@ -1834,6 +2023,22 @@ declare const RESERVED_USER_KEYS: ReadonlySet<string>;
|
|
|
1834
2023
|
* Does not mutate the input.
|
|
1835
2024
|
*/
|
|
1836
2025
|
declare function stripReservedUserKeys(profile: Record<string, unknown>): Record<string, unknown>;
|
|
2026
|
+
/**
|
|
2027
|
+
* Best-effort "Browser on OS" label from a raw User-Agent string — feeds the
|
|
2028
|
+
* `name` field of `seenDevices` records so a device list reads "Chrome on
|
|
2029
|
+
* Windows" instead of a token. Deliberately tiny (no UA-parser dependency);
|
|
2030
|
+
* detection order lives in the `UA_BROWSERS` / `UA_OSES` tables above.
|
|
2031
|
+
* Returns just the browser or just the OS when only one side is detected;
|
|
2032
|
+
* `undefined` for empty / fully unrecognized input.
|
|
2033
|
+
*/
|
|
2034
|
+
declare function humanizeUserAgent(ua: string | undefined): string | undefined;
|
|
2035
|
+
declare function haversineKm(a: {
|
|
2036
|
+
lat: number;
|
|
2037
|
+
lon: number;
|
|
2038
|
+
}, b: {
|
|
2039
|
+
lat: number;
|
|
2040
|
+
lon: number;
|
|
2041
|
+
}): number;
|
|
1837
2042
|
/** Trim + de-duplicate role identifiers submitted via the admin invite form. */
|
|
1838
2043
|
declare function parseInviteRoles(input?: string[]): string[];
|
|
1839
2044
|
/**
|
|
@@ -1866,6 +2071,16 @@ declare class AuthWorkflow {
|
|
|
1866
2071
|
* sync-friendly: the default `void` preserves the engine's sync fast path.
|
|
1867
2072
|
*/
|
|
1868
2073
|
protected deliver(_payload: AuthDeliveryPayload): void | Promise<void>;
|
|
2074
|
+
/**
|
|
2075
|
+
* The blessed one-call alert path for risk overrides — emit a
|
|
2076
|
+
* `security-alert` delivery (e.g. from an impossible-travel
|
|
2077
|
+
* `resolveRiskStepUp` override). Recipient comes from `ctx.notice.email`,
|
|
2078
|
+
* the proven-first correspondence chain seeded by `credentials` /
|
|
2079
|
+
* `seedChannelState` and refreshed by `verify/email`. No recipient →
|
|
2080
|
+
* SILENT no-op (a user with no provable inbox simply can't be alerted —
|
|
2081
|
+
* mirrors `notifyNewDevice`'s posture). Never called by the base class.
|
|
2082
|
+
*/
|
|
2083
|
+
protected sendSecurityAlert(ctx: AuthWfCtx, reason: string, context?: Record<string, unknown>): Promise<void>;
|
|
1869
2084
|
/**
|
|
1870
2085
|
* Return the list of selectable role identifiers for the admin invite form.
|
|
1871
2086
|
* Mirrors the prior `InviteWorkflow.getAvailableRoles()` consumer hook —
|
|
@@ -1897,14 +2112,21 @@ declare class AuthWorkflow {
|
|
|
1897
2112
|
email: string;
|
|
1898
2113
|
}): Promise<string[]> | string[];
|
|
1899
2114
|
/**
|
|
1900
|
-
* Override the structural duplicate rule for `admin-form`. Default:
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
2115
|
+
* Override the structural duplicate rule for `admin-form`. Default: a row
|
|
2116
|
+
* still parked on `account.pendingInvitation` → `'reuse'` (re-invite:
|
|
2117
|
+
* `create-user` refreshes the existing record in place and `send-email`
|
|
2118
|
+
* mints a fresh magic link — see `createUser`); any other existing row →
|
|
2119
|
+
* `'reject'`; nothing → `'allow'`.
|
|
2120
|
+
*
|
|
2121
|
+
* Multi-tenant apps that allow re-inviting the same email into a different
|
|
2122
|
+
* tenant override to `'allow'`. Apps that want the strict legacy behavior
|
|
2123
|
+
* ("Invite already pending" error on a duplicate invite of a pending user)
|
|
2124
|
+
* return `'reject'` for pending rows.
|
|
1903
2125
|
*/
|
|
1904
2126
|
protected duplicateInviteCheck(input: {
|
|
1905
2127
|
email: string;
|
|
1906
2128
|
existingUser: UserCredentials | null;
|
|
1907
|
-
}): Promise<"allow" | "reject"> | "allow" | "reject";
|
|
2129
|
+
}): Promise<"allow" | "reject" | "reuse"> | "allow" | "reject" | "reuse";
|
|
1908
2130
|
/**
|
|
1909
2131
|
* Implements the "log out other sessions" branch of `sessionPolicy.concurrencyLimit`.
|
|
1910
2132
|
* Default revokes every existing session via `auth.revokeAllForUser` — which is
|
|
@@ -2201,6 +2423,17 @@ declare class AuthWorkflow {
|
|
|
2201
2423
|
* matching `resolveOtpDisclosure` / the MFA transport.
|
|
2202
2424
|
*/
|
|
2203
2425
|
protected resolvePromoteHandleField(_ctx: AuthWfCtx, _channel: "email" | "sms"): string | undefined | Promise<string | undefined>;
|
|
2426
|
+
/**
|
|
2427
|
+
* Decide whether a verified federated profile's email claim counts as inbox
|
|
2428
|
+
* proof for the CORRESPONDENCE address (`users.setVerifiedEmail`). Default
|
|
2429
|
+
* trusts the provider's `email_verified` claim — a provider trusted to
|
|
2430
|
+
* AUTHENTICATE the user is strictly more trusted than its email claim. The
|
|
2431
|
+
* capture is correspondence-only: it never promotes the address to a login
|
|
2432
|
+
* handle and never resolves accounts by it. Override to exclude providers
|
|
2433
|
+
* whose claim should not be taken at face value (e.g. an internal OIDC
|
|
2434
|
+
* issuer that stamps `email_verified` on unverified directory entries).
|
|
2435
|
+
*/
|
|
2436
|
+
protected resolveFederatedEmailTrust(_ctx: AuthWfCtx, profile: FederatedProfileSnapshot): boolean | Promise<boolean>;
|
|
2204
2437
|
/**
|
|
2205
2438
|
* Route a form alt-action click to a canonical outcome. Defaults match the
|
|
2206
2439
|
* action ids the bundled `PincodeForm` declares; customers override per
|
|
@@ -2496,6 +2729,19 @@ declare class AuthWorkflow {
|
|
|
2496
2729
|
* Create the user row from `ctx.admin.userExtras` (plus the admin-supplied
|
|
2497
2730
|
* `ctx.admin.roles`), then stamp `pendingInvitation = true` via a follow-up
|
|
2498
2731
|
* deep-merge update so `createUser`-applied account defaults survive.
|
|
2732
|
+
*
|
|
2733
|
+
* Re-invite (`ctx.admin.reuseExisting`, stamped by `admin-form` on a
|
|
2734
|
+
* `'reuse'` verdict): REFRESH the existing row instead of creating — apply
|
|
2735
|
+
* the freshly-picked roles + `prepareUser` extras, re-assert
|
|
2736
|
+
* `pendingInvitation`, and leave password/MFA state untouched (a pending
|
|
2737
|
+
* record never had usable credentials). `send-email` downstream then mints
|
|
2738
|
+
* a fresh durable handle, i.e. a new full-TTL magic link. Guarded by a
|
|
2739
|
+
* FRESH `pendingInvitation` read: a `'reuse'` verdict for an accepted
|
|
2740
|
+
* account 409s as a logic error rather than silently re-pending a live
|
|
2741
|
+
* user; a row that vanished since `admin-form` falls through to the normal
|
|
2742
|
+
* create path. The refresh is a deep-merge update: arrays (`roles`)
|
|
2743
|
+
* replace wholesale, but extras keys the current `prepareUser` no longer
|
|
2744
|
+
* returns linger from the original invite.
|
|
2499
2745
|
*/
|
|
2500
2746
|
createUser(ctx: AuthWfCtx): Promise<undefined>;
|
|
2501
2747
|
/**
|
|
@@ -2790,6 +3036,29 @@ declare class AuthWorkflow {
|
|
|
2790
3036
|
* the MFA-form `hidden` expression on `rememberDevice`).
|
|
2791
3037
|
*/
|
|
2792
3038
|
deviceTrust(ctx: AuthWfCtx): Promise<undefined>;
|
|
3039
|
+
/**
|
|
3040
|
+
* Always-on device RECOGNITION — verify-or-mint the long-lived recognition
|
|
3041
|
+
* cookie against the `seenDevices` ledger. Recognition is a notification
|
|
3042
|
+
* suppressor ONLY (the notify-new-device gate reads `trust.recognized`); it
|
|
3043
|
+
* never skips MFA — that is `deviceTrust`, which stays opt-in and strict.
|
|
3044
|
+
*
|
|
3045
|
+
* Deliberately a SEPARATE step from `check-trusted-device`: that step is
|
|
3046
|
+
* schema-gated on `deviceTrust.enabled && skipsMfa`, so recognition must
|
|
3047
|
+
* not piggyback on it or recognition dies whenever trust is disabled —
|
|
3048
|
+
* exactly the consumers who get the noisiest notify behaviour today.
|
|
3049
|
+
* Verify-or-mint lives in ONE step so `trust.recognized` captures the
|
|
3050
|
+
* PRE-MINT arrival state the notify gate needs (a freshly minted token
|
|
3051
|
+
* must not mark the current login as recognized).
|
|
3052
|
+
*
|
|
3053
|
+
* A valid arriving cookie is verified with `slideTtlMs` (LRU bump) and
|
|
3054
|
+
* re-stashed on `trust.seenDeviceToken` so `issue` re-sets it with a fresh
|
|
3055
|
+
* maxAge. An unrecognized arrival mints + persists a new record (capped at
|
|
3056
|
+
* `deviceRecognition.maxDevices`) and stashes the new token — `recognized`
|
|
3057
|
+
* stays unset so the notification still fires for this login. Degrades
|
|
3058
|
+
* gracefully to a no-op when no `deviceTrust.secret` is configured,
|
|
3059
|
+
* preserving the legacy notify behaviour for those consumers.
|
|
3060
|
+
*/
|
|
3061
|
+
deviceRecognition(ctx: AuthWfCtx): Promise<undefined>;
|
|
2793
3062
|
/**
|
|
2794
3063
|
* Standalone terms-bump prompt for returning users whose accepted terms
|
|
2795
3064
|
* version is stale and no carrier form ran. Delegates to
|
|
@@ -2850,7 +3119,16 @@ declare class AuthWorkflow {
|
|
|
2850
3119
|
/**
|
|
2851
3120
|
* Notify the user of a login from a new device via the unified `deliver`
|
|
2852
3121
|
* hook. Gated upstream by
|
|
2853
|
-
* `!ctx.isFirstLogin && !!ctx.finalize.notifyNewDevice &&
|
|
3122
|
+
* `!ctx.isFirstLogin && !!ctx.finalize.notifyNewDevice && !ctx.trust.recognized`
|
|
3123
|
+
* — "not recognized" (no valid recognition cookie on arrival), NOT "no
|
|
3124
|
+
* valid trust cookie": users who decline remember-me, or whose strict trust
|
|
3125
|
+
* cookie expired / failed IP binding, must not get the email on every
|
|
3126
|
+
* login. Recognition is the loose always-on ledger minted by
|
|
3127
|
+
* `device-recognition`; trust stays strict and drives MFA skip only.
|
|
3128
|
+
*
|
|
3129
|
+
* Recipient is `notice.email` — the security-notice slot owned by the
|
|
3130
|
+
* `credentials` / `seedChannelState` seeding and refreshed by
|
|
3131
|
+
* `verify/email`. No recipient seeded → silently skips.
|
|
2854
3132
|
*/
|
|
2855
3133
|
notifyNewDevice(ctx: AuthWfCtx): Promise<undefined>;
|
|
2856
3134
|
/**
|
|
@@ -3014,14 +3292,31 @@ declare class AuthWorkflow {
|
|
|
3014
3292
|
*/
|
|
3015
3293
|
private finishOAuth;
|
|
3016
3294
|
/**
|
|
3017
|
-
* Seed `ctx.email` / `ctx.channel` from a resolved user's confirmed channels —
|
|
3295
|
+
* Seed `ctx.notice.email` / `ctx.channel` from a resolved user's confirmed channels —
|
|
3018
3296
|
* shared by `ssoCallback` (linked / created / auto-linked) and `proveControl`
|
|
3019
3297
|
* (interactively-linked) so the post-success channel shape can't drift between
|
|
3020
|
-
* the two federated entry points. Mirrors `credentials`' post-login seeding
|
|
3021
|
-
*
|
|
3022
|
-
*
|
|
3298
|
+
* the two federated entry points. Mirrors `credentials`' post-login seeding,
|
|
3299
|
+
* including the correspondence fallback (confirmed email-MFA →
|
|
3300
|
+
* `users.getCorrespondenceEmail` → provider display email).
|
|
3301
|
+
*
|
|
3302
|
+
* Also the single federated capture point for `users.setVerifiedEmail`: a
|
|
3303
|
+
* trusted `profile.email` (per `resolveFederatedEmailTrust`) is recorded as
|
|
3304
|
+
* the proven correspondence address on EVERY federated login — first-time
|
|
3305
|
+
* create, returning link, and interactive link alike (the store write is
|
|
3306
|
+
* skipped when the capture is already current). The profile email is
|
|
3307
|
+
* otherwise a DISPLAY fallback only — never promoted to the unique login
|
|
3308
|
+
* handle (a gated, later-phase concern).
|
|
3023
3309
|
*/
|
|
3024
3310
|
private seedChannelState;
|
|
3311
|
+
/**
|
|
3312
|
+
* Correspondence tail of the `ctx.notice.email` seeding — shared by
|
|
3313
|
+
* `credentials` (post-login) and `seedChannelState` (federated) so the
|
|
3314
|
+
* fallback chain (`users.getCorrespondenceEmail` → optional display email)
|
|
3315
|
+
* can't drift between the two. Does NOT set `channel.emailConfirmed` — that
|
|
3316
|
+
* flag means "confirmed email-MFA channel" and gates enrolment; a
|
|
3317
|
+
* correspondence address is a notice recipient, not a proven OTP channel.
|
|
3318
|
+
*/
|
|
3319
|
+
private seedCorrespondenceEmail;
|
|
3025
3320
|
/**
|
|
3026
3321
|
* `needs-link` setup (decision A — password, OTP fallback). Decide how the
|
|
3027
3322
|
* user will prove control of the matched account, stash the pending-link
|
|
@@ -3186,4 +3481,4 @@ interface AuditEmitter {
|
|
|
3186
3481
|
emit(event: AuditEvent): Promise<void> | void;
|
|
3187
3482
|
}
|
|
3188
3483
|
//#endregion
|
|
3189
|
-
export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, type AuditEmitter, type AuditEvent, type AuthBindings, type AuthContext, AuthController, type AuthDeliveryPayload, type AuthEmailEvent, type AuthEmailKind, type AuthEmailOutletDeps, AuthGuarded, type AuthLoginResponse, type AuthLogoutBody, type AuthOkResponse, type AuthOptions, type AuthRefreshBody, type AuthSmsEvent, type AuthSmsKind, type AuthWfCompletionState, type AuthWfConsentsState, type AuthWfCtx, type AuthWfMfaEnrollState, type AuthWfOAuthState, type AuthWfPasswordUiState, type AuthWfPincodeUiState, AuthWorkflow, type AuthWorkflowOpts, AuthorizeController, AuthorizeRuntime, type BuildMagicLinkUrl, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, type ConcurrencyLimitOptions, type ConnectedAccount, type ConsentDescriptor, type ConsentDescriptorLike, type ConsentEvent, ConsentStore, DEFAULT_AUTH_WORKFLOWS, type EmailSender, type EnrichedSession, FEDERATED_IDENTITY_STORE_TOKEN, type IssueResult, type LoginRedirect, type MfaSummary, type MfaTransport, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, Public, RESERVED_USER_KEYS, type ResolvedAuthCookieConfig, type ResolvedAuthOptions, type ResolvedAuthWorkflowOpts, type SessionEnricher, SessionEnricherProvider, type SessionInfo, SessionsController, type SmsSender, type SsoProvider, type TAuthMeta, UserId, WfTrigger, type WfTriggerOpts, WfTriggerProvider, authGuardInterceptor, authzBindingCookieAttrs, buildInviteAlreadyAcceptedEnvelope, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
|
|
3484
|
+
export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, type AuditEmitter, type AuditEvent, type AuthBindings, type AuthContext, AuthController, type AuthDeliveryPayload, type AuthEmailEvent, type AuthEmailKind, type AuthEmailOutletDeps, AuthGuarded, type AuthLoginResponse, type AuthLogoutBody, type AuthOkResponse, type AuthOptions, type AuthRefreshBody, type AuthSmsEvent, type AuthSmsKind, type AuthWfCompletionState, type AuthWfConsentsState, type AuthWfCtx, type AuthWfMfaEnrollState, type AuthWfOAuthState, type AuthWfPasswordUiState, type AuthWfPincodeUiState, AuthWorkflow, type AuthWorkflowOpts, type AuthorizationServerMetadata, AuthorizeController, AuthorizeRuntime, type BuildAuthorizationServerMetadataOptions, type BuildMagicLinkUrl, type BuildProtectedResourceMetadataOptions, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, type ConcurrencyLimitOptions, type ConnectedAccount, type ConsentDescriptor, type ConsentDescriptorLike, type ConsentEvent, ConsentStore, DEFAULT_AUTH_WORKFLOWS, DYNAMIC_CLIENT_STORE_TOKEN, DynamicClientRegistration, type DynamicClientRegistrationOptions, type EmailSender, type EnrichedSession, FEDERATED_IDENTITY_STORE_TOKEN, type IssueResult, type LoginRedirect, type MfaSummary, type MfaTransport, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, type ProtectedResourceMetadata, Public, RESERVED_USER_KEYS, type ResolvedAuthCookieConfig, type ResolvedAuthOptions, type ResolvedAuthWorkflowOpts, type SessionEnricher, SessionEnricherProvider, type SessionInfo, SessionsController, type SmsSender, type SsoProvider, type TAuthMeta, UserId, WfTrigger, type WfTriggerOpts, WfTriggerProvider, type WwwAuthenticateBearerChallengeOptions, authGuardInterceptor, authzBindingCookieAttrs, buildAuthorizationServerMetadata, buildInviteAlreadyAcceptedEnvelope, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, haversineKm, humanizeUserAgent, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
|