@aooth/auth-moost 0.1.18 → 0.1.19

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.
@@ -1,2 +1,2 @@
1
- import { C as Select2faForm, D as WithInlineConsentForm, E as TermsBumpForm, S as RemoveMfaConfirmForm, T as SignupForm, _ as PincodeForm, a as ConcurrencyLimitForm, b as RecoveryFactorForm, c as EnrollConfirmForm, d as InviteForm, f as LoginCredentialsForm, g as PasswordReauthForm, h as MfaCodeForm, i as ChangePasswordForm, l as EnrollPickMethodForm, m as ManageMfaForm, n as AskPhoneForm, o as EmailIdentifierForm, p as MagicLinkRequestForm, r as AuthorizeConsentForm, s as EnrollAddressForm, t as AskEmailForm, u as EnrollTotpQrForm, v as ProveControlForm, w as SetPasswordForm, x as RecoveryModeSelectForm, y as ProveControlOtpForm } from "../forms-D7ZfanKT.mjs";
1
+ import { C as Select2faForm, D as WithInlineConsentForm, E as TermsBumpForm, S as RemoveMfaConfirmForm, T as SignupForm, _ as PincodeForm, a as ConcurrencyLimitForm, b as RecoveryFactorForm, c as EnrollConfirmForm, d as InviteForm, f as LoginCredentialsForm, g as PasswordReauthForm, h as MfaCodeForm, i as ChangePasswordForm, l as EnrollPickMethodForm, m as ManageMfaForm, n as AskPhoneForm, o as EmailIdentifierForm, p as MagicLinkRequestForm, r as AuthorizeConsentForm, s as EnrollAddressForm, t as AskEmailForm, u as EnrollTotpQrForm, v as ProveControlForm, w as SetPasswordForm, x as RecoveryModeSelectForm, y as ProveControlOtpForm } from "../forms-uqegc32h.mjs";
2
2
  export { AskEmailForm, AskPhoneForm, AuthorizeConsentForm, ChangePasswordForm, ConcurrencyLimitForm, EmailIdentifierForm, EnrollAddressForm, EnrollConfirmForm, EnrollPickMethodForm, EnrollTotpQrForm, InviteForm, LoginCredentialsForm, MagicLinkRequestForm, ManageMfaForm, MfaCodeForm, PasswordReauthForm, PincodeForm, ProveControlForm, ProveControlOtpForm, RecoveryFactorForm, RecoveryModeSelectForm, RemoveMfaConfirmForm, Select2faForm, SetPasswordForm, SignupForm, TermsBumpForm, WithInlineConsentForm };
@@ -1,2 +1,2 @@
1
- import { C as Select2faForm, D as WithInlineConsentForm, E as TermsBumpForm, S as RemoveMfaConfirmForm, T as SignupForm, _ as PincodeForm, a as ConcurrencyLimitForm, b as RecoveryFactorForm, c as EnrollConfirmForm, d as InviteForm, f as LoginCredentialsForm, g as PasswordReauthForm, h as MfaCodeForm, i as ChangePasswordForm, l as EnrollPickMethodForm, m as ManageMfaForm, n as AskPhoneForm, o as EmailIdentifierForm, p as MagicLinkRequestForm, r as AuthorizeConsentForm, s as EnrollAddressForm, t as AskEmailForm, u as EnrollTotpQrForm, v as ProveControlForm, w as SetPasswordForm, x as RecoveryModeSelectForm, y as ProveControlOtpForm } from "../forms-D7ZfanKT.mjs";
1
+ import { C as Select2faForm, D as WithInlineConsentForm, E as TermsBumpForm, S as RemoveMfaConfirmForm, T as SignupForm, _ as PincodeForm, a as ConcurrencyLimitForm, b as RecoveryFactorForm, c as EnrollConfirmForm, d as InviteForm, f as LoginCredentialsForm, g as PasswordReauthForm, h as MfaCodeForm, i as ChangePasswordForm, l as EnrollPickMethodForm, m as ManageMfaForm, n as AskPhoneForm, o as EmailIdentifierForm, p as MagicLinkRequestForm, r as AuthorizeConsentForm, s as EnrollAddressForm, t as AskEmailForm, u as EnrollTotpQrForm, v as ProveControlForm, w as SetPasswordForm, x as RecoveryModeSelectForm, y as ProveControlOtpForm } from "../forms-uqegc32h.mjs";
2
2
  export { AskEmailForm, AskPhoneForm, AuthorizeConsentForm, ChangePasswordForm, ConcurrencyLimitForm, EmailIdentifierForm, EnrollAddressForm, EnrollConfirmForm, EnrollPickMethodForm, EnrollTotpQrForm, InviteForm, LoginCredentialsForm, MagicLinkRequestForm, ManageMfaForm, MfaCodeForm, PasswordReauthForm, PincodeForm, ProveControlForm, ProveControlOtpForm, RecoveryFactorForm, RecoveryModeSelectForm, RemoveMfaConfirmForm, Select2faForm, SetPasswordForm, SignupForm, TermsBumpForm, WithInlineConsentForm };
@@ -447,7 +447,7 @@ defineAnnotatedType("object", ProveControlOtpForm).prop("intro", defineAnnotated
447
447
  name: "align",
448
448
  value: "center"
449
449
  }, true).annotate("ui.form.pushDown", true).optional().$type).annotate("meta.label", "Confirm your identity").annotate("wf.context.pass", "public", true).annotate("ui.form.submit.text", "Verify and link");
450
- defineAnnotatedType("object", AuthorizeConsentForm).prop("notice", defineAnnotatedType().designType("phantom").tags("paragraph", "ui").annotate("ui.form.order", 1).annotate("ui.form.fn.value", "(_, _d, ctx) => { const a = ctx.public?.authz; const who = a?.clientName ? \"“\" + a.clientName + \"”\" : \"A local application\"; const sc = a?.scope ? \" It is requesting access to: \" + a.scope + \".\" : \"\"; return who + \" wants to sign in to your account.\" + sc + \" Authorize this only if you started it.\"; }").$type).prop("deny", defineAnnotatedType().designType("phantom").tags("action", "ui").annotate("ui.form.order", 10).annotate("ui.form.action", {
450
+ defineAnnotatedType("object", AuthorizeConsentForm).prop("notice", defineAnnotatedType().designType("phantom").tags("paragraph", "ui").annotate("ui.form.order", 1).annotate("ui.form.fn.value", "(_, _d, ctx) => { const a = ctx.public?.authz; const host = a?.clientName && a?.redirectHost ? \" (\" + a.redirectHost + \")\" : \"\"; const who = a?.clientName ? \"“\" + a.clientName + \"”\" + host : \"A local application\"; const sc = a?.scope ? \" It is requesting access to: \" + a.scope + \".\" : \"\"; return who + \" wants to sign in to your account.\" + sc + \" Authorize this only if you started it.\"; }").$type).prop("deny", defineAnnotatedType().designType("phantom").tags("action", "ui").annotate("ui.form.order", 10).annotate("ui.form.action", {
451
451
  id: "deny",
452
452
  label: "Deny"
453
453
  }).annotate("ui.form.attr", {
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
- 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): Promise<string>;
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";
@@ -1295,6 +1378,16 @@ interface AuthWfAdminState {
1295
1378
  availableRoles?: string[];
1296
1379
  roles?: string[];
1297
1380
  userExtras?: Record<string, unknown>;
1381
+ /**
1382
+ * Re-invite decision stamp — set by `admin-form` when `duplicateInviteCheck`
1383
+ * returns `'reuse'` (the default for a row still parked on
1384
+ * `account.pendingInvitation`). `create-user` then refreshes the existing
1385
+ * row (fresh roles + extras, `pendingInvitation` re-asserted) instead of
1386
+ * creating, and `send-email` mints a fresh magic link. Re-validated in
1387
+ * `create-user` against a fresh read: non-pending row → 409, vanished row →
1388
+ * normal create path.
1389
+ */
1390
+ reuseExisting?: boolean;
1298
1391
  /**
1299
1392
  * Outlet-pause idempotency marker for `send-email`. Flipped to `true`
1300
1393
  * after the first dispatch so the invitee's magic-link resume — which
@@ -1577,13 +1670,15 @@ interface AuthWfPublicState {
1577
1670
  newPasswordRequired?: boolean;
1578
1671
  /**
1579
1672
  * Mirrors the display-only fields of `ctx.authz` — the requesting client's
1580
- * id/name and granted scope, shown on the authorize-consent form. The
1581
- * `handle` and the `approved` gate stay server-only (never whitelisted onto
1582
- * the wire).
1673
+ * id/name, granted scope, and the VALIDATED redirect host (the trustworthy
1674
+ * identity shown next to the attacker-choosable `clientName`), shown on the
1675
+ * authorize-consent form. The `handle` and the `approved` gate stay
1676
+ * server-only (never whitelisted onto the wire).
1583
1677
  */
1584
1678
  authz?: {
1585
1679
  clientName?: string;
1586
1680
  scope?: string;
1681
+ redirectHost?: string;
1587
1682
  };
1588
1683
  }
1589
1684
  /** Unified workflow context shape — one type for all three flows. */
@@ -1657,14 +1752,17 @@ interface AuthWfCtx {
1657
1752
  * "Continue with <provider>" detour mid-authorize. Presence routes the login
1658
1753
  * tail to the `authz-consent` → `mint-authz-code` terminal (deliver an auth
1659
1754
  * code to the client) INSTEAD of `issue`/`redirect` — no browser session is
1660
- * minted. `clientName`/`scope` are staged by `authz-consent` for the consent
1661
- * form's display copy; `approved` is the explicit user-consent gate the
1662
- * `mint-authz-code` terminal requires before it will mint a code.
1755
+ * minted. `clientName`/`scope`/`redirectHost` are staged by `authz-consent`
1756
+ * for the consent form's display copy (`redirectHost` is parsed from the
1757
+ * VALIDATED redirect the trustworthy identity next to the registrant-chosen
1758
+ * name); `approved` is the explicit user-consent gate the `mint-authz-code`
1759
+ * terminal requires before it will mint a code.
1663
1760
  */
1664
1761
  authz?: {
1665
1762
  handle: string;
1666
1763
  clientName?: string;
1667
1764
  scope?: string;
1765
+ redirectHost?: string;
1668
1766
  approved?: boolean;
1669
1767
  };
1670
1768
  /**
@@ -1897,14 +1995,21 @@ declare class AuthWorkflow {
1897
1995
  email: string;
1898
1996
  }): Promise<string[]> | string[];
1899
1997
  /**
1900
- * Override the structural duplicate rule for `admin-form`. Default: any
1901
- * existing row `'reject'`; nothing → `'allow'`. Multi-tenant apps that
1902
- * allow re-inviting the same email into a different tenant override.
1998
+ * Override the structural duplicate rule for `admin-form`. Default: a row
1999
+ * still parked on `account.pendingInvitation` → `'reuse'` (re-invite:
2000
+ * `create-user` refreshes the existing record in place and `send-email`
2001
+ * mints a fresh magic link — see `createUser`); any other existing row →
2002
+ * `'reject'`; nothing → `'allow'`.
2003
+ *
2004
+ * Multi-tenant apps that allow re-inviting the same email into a different
2005
+ * tenant override to `'allow'`. Apps that want the strict legacy behavior
2006
+ * ("Invite already pending" error on a duplicate invite of a pending user)
2007
+ * return `'reject'` for pending rows.
1903
2008
  */
1904
2009
  protected duplicateInviteCheck(input: {
1905
2010
  email: string;
1906
2011
  existingUser: UserCredentials | null;
1907
- }): Promise<"allow" | "reject"> | "allow" | "reject";
2012
+ }): Promise<"allow" | "reject" | "reuse"> | "allow" | "reject" | "reuse";
1908
2013
  /**
1909
2014
  * Implements the "log out other sessions" branch of `sessionPolicy.concurrencyLimit`.
1910
2015
  * Default revokes every existing session via `auth.revokeAllForUser` — which is
@@ -2496,6 +2601,19 @@ declare class AuthWorkflow {
2496
2601
  * Create the user row from `ctx.admin.userExtras` (plus the admin-supplied
2497
2602
  * `ctx.admin.roles`), then stamp `pendingInvitation = true` via a follow-up
2498
2603
  * deep-merge update so `createUser`-applied account defaults survive.
2604
+ *
2605
+ * Re-invite (`ctx.admin.reuseExisting`, stamped by `admin-form` on a
2606
+ * `'reuse'` verdict): REFRESH the existing row instead of creating — apply
2607
+ * the freshly-picked roles + `prepareUser` extras, re-assert
2608
+ * `pendingInvitation`, and leave password/MFA state untouched (a pending
2609
+ * record never had usable credentials). `send-email` downstream then mints
2610
+ * a fresh durable handle, i.e. a new full-TTL magic link. Guarded by a
2611
+ * FRESH `pendingInvitation` read: a `'reuse'` verdict for an accepted
2612
+ * account 409s as a logic error rather than silently re-pending a live
2613
+ * user; a row that vanished since `admin-form` falls through to the normal
2614
+ * create path. The refresh is a deep-merge update: arrays (`roles`)
2615
+ * replace wholesale, but extras keys the current `prepareUser` no longer
2616
+ * returns linger from the original invite.
2499
2617
  */
2500
2618
  createUser(ctx: AuthWfCtx): Promise<undefined>;
2501
2619
  /**
@@ -3186,4 +3304,4 @@ interface AuditEmitter {
3186
3304
  emit(event: AuditEvent): Promise<void> | void;
3187
3305
  }
3188
3306
  //#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 };
3307
+ 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, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as Select2faForm, E as TermsBumpForm, S as RemoveMfaConfirmForm, T as SignupForm, _ as PincodeForm, a as ConcurrencyLimitForm, c as EnrollConfirmForm, d as InviteForm, f as LoginCredentialsForm, g as PasswordReauthForm, h as MfaCodeForm, i as ChangePasswordForm, l as EnrollPickMethodForm, m as ManageMfaForm, n as AskPhoneForm, o as EmailIdentifierForm, r as AuthorizeConsentForm, s as EnrollAddressForm, t as AskEmailForm, u as EnrollTotpQrForm, v as ProveControlForm, w as SetPasswordForm, y as ProveControlOtpForm } from "./forms-D7ZfanKT.mjs";
1
+ import { C as Select2faForm, E as TermsBumpForm, S as RemoveMfaConfirmForm, T as SignupForm, _ as PincodeForm, a as ConcurrencyLimitForm, c as EnrollConfirmForm, d as InviteForm, f as LoginCredentialsForm, g as PasswordReauthForm, h as MfaCodeForm, i as ChangePasswordForm, l as EnrollPickMethodForm, m as ManageMfaForm, n as AskPhoneForm, o as EmailIdentifierForm, r as AuthorizeConsentForm, s as EnrollAddressForm, t as AskEmailForm, u as EnrollTotpQrForm, v as ProveControlForm, w as SetPasswordForm, y as ProveControlOtpForm } from "./forms-uqegc32h.mjs";
2
2
  import { Controller, HandlerPaths, Inherit, Inject, InjectMoostLogger, Injectable, Intercept, MoostInit, Optional, Param, Resolve, TInterceptorPriority, defineAfterInterceptor, defineBeforeInterceptor, getMoostMate, useControllerContext } from "moost";
3
3
  import { AuthCredential, AuthError, generateMagicLinkToken } from "@aooth/auth";
4
4
  import { current, defineWook, eventTypeKey, key } from "@wooksjs/event-core";
@@ -10,7 +10,7 @@ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
10
10
  import { createAsHttpOutlet, finishWf, handleAsOutletRequest, useAtscriptWf } from "@atscript/moost-wf";
11
11
  import { EncapsulatedStateStrategy, MoostWf, Step, StepRetriableError, StepTTL, Workflow, WorkflowParam, WorkflowSchema, createEmailOutlet, outletEmail, swapStrategy, useWfFinished, useWfState } from "@moostjs/event-wf";
12
12
  import { FederatedLoginService, OAuthError, OAuthProviderRegistry, generateRandomState, pkceChallengeFor } from "@aooth/idp";
13
- import { AuthorizeError, NoopOidcClaimsResolver } from "@aooth/auth/authz";
13
+ import { ClientRegistrationError, DynamicClientRegistration, NoopOidcClaimsResolver, buildAuthorizationServerMetadata, buildAuthorizationServerMetadata as buildAuthorizationServerMetadata$1, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer, canonicalizeIssuer as canonicalizeIssuer$1 } from "@aooth/auth/authz";
14
14
  //#region \0@oxc-project+runtime@0.133.0/helpers/esm/decorate.js
15
15
  function __decorate(decorators, target, key, desc) {
16
16
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -705,6 +705,17 @@ const AUTH_CODE_STORE_TOKEN = "aooth:AuthCodeStore";
705
705
  * `CompositeClientPolicy` of both) under this string.
706
706
  */
707
707
  const CLIENT_REDIRECT_POLICY_TOKEN = "aooth:ClientRedirectPolicy";
708
+ /**
709
+ * DI token for the {@link import("@aooth/auth/authz").DynamicClientStore}
710
+ * (RFC 7591 dynamic registrations) — an abstract class, same auto-instantiation
711
+ * hazard as the store tokens above. The BASE `AuthorizeController` never
712
+ * injects it (DCR is optional, and an optional `@Inject` panics in moost's
713
+ * route-table pass): a consumer subclass that enables DCR adds it as a
714
+ * REQUIRED ctor param, builds a `DynamicClientRegistration` around it, and
715
+ * overrides `getDynamicClientRegistration()`. The same store instance also
716
+ * backs the `DynamicClientPolicy` composed into the redirect policy.
717
+ */
718
+ const DYNAMIC_CLIENT_STORE_TOKEN = "aooth:DynamicClientStore";
708
719
  //#endregion
709
720
  //#region \0@oxc-project+runtime@0.133.0/helpers/esm/decorateParam.js
710
721
  function __decorateParam(paramIndex, decorator) {
@@ -1170,11 +1181,19 @@ let AuthWorkflow = class AuthWorkflow {
1170
1181
  return [];
1171
1182
  }
1172
1183
  /**
1173
- * Override the structural duplicate rule for `admin-form`. Default: any
1174
- * existing row `'reject'`; nothing → `'allow'`. Multi-tenant apps that
1175
- * allow re-inviting the same email into a different tenant override.
1184
+ * Override the structural duplicate rule for `admin-form`. Default: a row
1185
+ * still parked on `account.pendingInvitation` → `'reuse'` (re-invite:
1186
+ * `create-user` refreshes the existing record in place and `send-email`
1187
+ * mints a fresh magic link — see `createUser`); any other existing row →
1188
+ * `'reject'`; nothing → `'allow'`.
1189
+ *
1190
+ * Multi-tenant apps that allow re-inviting the same email into a different
1191
+ * tenant override to `'allow'`. Apps that want the strict legacy behavior
1192
+ * ("Invite already pending" error on a duplicate invite of a pending user)
1193
+ * return `'reject'` for pending rows.
1176
1194
  */
1177
1195
  duplicateInviteCheck(input) {
1196
+ if (input.existingUser?.account?.pendingInvitation) return "reuse";
1178
1197
  return input.existingUser ? "reject" : "allow";
1179
1198
  }
1180
1199
  /**
@@ -1751,7 +1770,11 @@ let AuthWorkflow = class AuthWorkflow {
1751
1770
  }
1752
1771
  if (ctx.newPasswordRequired !== void 0) pub.newPasswordRequired = ctx.newPasswordRequired;
1753
1772
  if (ctx.authz) {
1754
- const sub = pickDefined(ctx.authz, ["clientName", "scope"]);
1773
+ const sub = pickDefined(ctx.authz, [
1774
+ "clientName",
1775
+ "scope",
1776
+ "redirectHost"
1777
+ ]);
1755
1778
  if (sub) pub.authz = sub;
1756
1779
  }
1757
1780
  ctx.public = pub;
@@ -2430,14 +2453,16 @@ let AuthWorkflow = class AuthWorkflow {
2430
2453
  if (parsed.find((r) => !allowed.has(r)) !== void 0) throw this.throwPublic(ctx, wf, { errors: { roles: "Invalid role" } });
2431
2454
  }
2432
2455
  const existing = await this.users.findByHandle(email);
2433
- if (await this.duplicateInviteCheck({
2456
+ const action = await this.duplicateInviteCheck({
2434
2457
  email,
2435
2458
  existingUser: existing
2436
- }) === "reject") {
2459
+ });
2460
+ if (action === "reject") {
2437
2461
  if (existing?.account?.pendingInvitation) throw this.throwPublic(ctx, wf, { errors: { email: "Invite already pending" } });
2438
2462
  if (existing) throw this.throwPublic(ctx, wf, { errors: { email: "User already exists" } });
2439
2463
  throw this.throwPublic(ctx, wf, { errors: { email: "Duplicate invite rejected" } });
2440
2464
  }
2465
+ if (action === "reuse") (ctx.admin ??= {}).reuseExisting = true;
2441
2466
  ctx.email = email;
2442
2467
  if (parsed.length > 0) (ctx.admin ??= {}).roles = parsed;
2443
2468
  }
@@ -2473,6 +2498,19 @@ let AuthWorkflow = class AuthWorkflow {
2473
2498
  * Create the user row from `ctx.admin.userExtras` (plus the admin-supplied
2474
2499
  * `ctx.admin.roles`), then stamp `pendingInvitation = true` via a follow-up
2475
2500
  * deep-merge update so `createUser`-applied account defaults survive.
2501
+ *
2502
+ * Re-invite (`ctx.admin.reuseExisting`, stamped by `admin-form` on a
2503
+ * `'reuse'` verdict): REFRESH the existing row instead of creating — apply
2504
+ * the freshly-picked roles + `prepareUser` extras, re-assert
2505
+ * `pendingInvitation`, and leave password/MFA state untouched (a pending
2506
+ * record never had usable credentials). `send-email` downstream then mints
2507
+ * a fresh durable handle, i.e. a new full-TTL magic link. Guarded by a
2508
+ * FRESH `pendingInvitation` read: a `'reuse'` verdict for an accepted
2509
+ * account 409s as a logic error rather than silently re-pending a live
2510
+ * user; a row that vanished since `admin-form` falls through to the normal
2511
+ * create path. The refresh is a deep-merge update: arrays (`roles`)
2512
+ * replace wholesale, but extras keys the current `prepareUser` no longer
2513
+ * returns linger from the original invite.
2476
2514
  */
2477
2515
  async createUser(ctx) {
2478
2516
  if (!ctx.email) throw new HttpError$1(500, "Workflow state corrupted: missing email");
@@ -2481,6 +2519,18 @@ let AuthWorkflow = class AuthWorkflow {
2481
2519
  ...ctx.admin?.userExtras,
2482
2520
  ...adminRoles && adminRoles.length > 0 && { roles: adminRoles }
2483
2521
  };
2522
+ if (ctx.admin?.reuseExisting) {
2523
+ const existing = await this.users.findByHandle(ctx.email);
2524
+ if (existing) {
2525
+ if (!existing.account?.pendingInvitation) throw new HttpError$1(409, "User already exists");
2526
+ await this.users.update(existing.id, {
2527
+ ...fields,
2528
+ account: { pendingInvitation: true }
2529
+ });
2530
+ ctx.subject = existing.id;
2531
+ return;
2532
+ }
2533
+ }
2484
2534
  let created;
2485
2535
  try {
2486
2536
  created = await this.users.createUser(ctx.email, void 0, fields);
@@ -3696,8 +3746,12 @@ let AuthWorkflow = class AuthWorkflow {
3696
3746
  } });
3697
3747
  return;
3698
3748
  }
3699
- if (req.clientId !== void 0) authz.clientName = req.clientId;
3749
+ const clientName = req.clientName ?? req.clientId;
3750
+ if (clientName !== void 0) authz.clientName = clientName;
3700
3751
  if (req.scope !== void 0) authz.scope = req.scope;
3752
+ try {
3753
+ authz.redirectHost = new URL(req.redirectUri).host;
3754
+ } catch {}
3701
3755
  const wf = this.useAtscriptWfPublic(ctx, this.opts.forms.authzConsent);
3702
3756
  if (wf.resolveAction() === "deny") {
3703
3757
  await pending.delete(authz.handle);
@@ -3762,6 +3816,7 @@ let AuthWorkflow = class AuthWorkflow {
3762
3816
  redirectUri: req.redirectUri,
3763
3817
  ...req.clientId !== void 0 && { clientId: req.clientId },
3764
3818
  ...req.scope !== void 0 && { scope: req.scope },
3819
+ ...req.resource !== void 0 && { resource: req.resource },
3765
3820
  ...req.nonce !== void 0 && { nonce: req.nonce },
3766
3821
  ...req.idToken !== void 0 && { idToken: req.idToken },
3767
3822
  ...req.accessToken !== void 0 && { accessToken: req.accessToken },
@@ -5969,7 +6024,36 @@ let AuthorizeController = class AuthorizeController {
5969
6024
  loginPath() {
5970
6025
  return "/login";
5971
6026
  }
5972
- async authorize(responseType, redirectUri, clientId, state, codeChallenge, codeChallengeMethod, scope, nonce) {
6027
+ /**
6028
+ * The issuer identifier for the RFC 8414 metadata document. Defaults to the
6029
+ * Tier-2 signer's issuer; a SIGNER-LESS deployment that serves MCP connector
6030
+ * clients must **override** this (return `{origin}/auth`-style, byte-exact —
6031
+ * never derived from the Host header, which would let a request inject its
6032
+ * host into a cacheable discovery document). `undefined` ⇒ the
6033
+ * `oauth-authorization-server` endpoint 404s.
6034
+ */
6035
+ getIssuer() {
6036
+ return this.getIdTokenSigner()?.issuer;
6037
+ }
6038
+ /**
6039
+ * The RFC 7591 dynamic-client-registration operation, or `undefined` (the
6040
+ * default) to disable DCR — then `POST /auth/register` 404s and neither
6041
+ * discovery document advertises a `registration_endpoint`. **Override** in a
6042
+ * subclass: inject a `DynamicClientStore` (the `DYNAMIC_CLIENT_STORE_TOKEN`
6043
+ * provider) as a required ctor param, build one `DynamicClientRegistration`
6044
+ * around it, and return it here. Same plain-getter pattern as
6045
+ * {@link getIdTokenSigner} (an optional `@Inject` panics in moost's
6046
+ * route-table pass).
6047
+ */
6048
+ getDynamicClientRegistration() {}
6049
+ /**
6050
+ * `scopes_supported` for the RFC 8414 document (optional per the RFC; omitted
6051
+ * by default — deliberately NOT inheriting the OIDC document's hardcoded
6052
+ * list, which describes Tier-2 sign-in scopes). Override to advertise what
6053
+ * connector clients may request.
6054
+ */
6055
+ scopesSupported() {}
6056
+ async authorize(responseType, redirectUri, clientId, state, codeChallenge, codeChallengeMethod, scope, nonce, resource) {
5973
6057
  const res = useResponse(current());
5974
6058
  if (!redirectUri) {
5975
6059
  res.status = 400;
@@ -5982,17 +6066,22 @@ let AuthorizeController = class AuthorizeController {
5982
6066
  redirectUri,
5983
6067
  ...scope !== void 0 && { scope }
5984
6068
  });
5985
- } catch (e) {
6069
+ } catch {
5986
6070
  res.status = 400;
5987
- return e instanceof AuthorizeError ? `invalid request: ${e.code}` : "invalid request";
6071
+ return "invalid request";
5988
6072
  }
5989
6073
  if (responseType !== "code" || !codeChallenge || codeChallengeMethod !== "S256") return this.redirectError(resolved.redirectUri, "invalid_request", state);
6074
+ if (resource !== void 0) {
6075
+ if (useUrlParams(current()).params().getAll("resource").length > 1 || resource.length > 2e3) return this.redirectError(resolved.redirectUri, "invalid_target", state);
6076
+ }
5990
6077
  const binding = randomBytes(32).toString("base64url");
5991
6078
  const { handle, expiresAt } = await this.pending.create({
5992
6079
  ...resolved.clientId !== void 0 && { clientId: resolved.clientId },
6080
+ ...resolved.clientName !== void 0 && { clientName: resolved.clientName },
5993
6081
  redirectUri: resolved.redirectUri,
5994
6082
  codeChallenge,
5995
6083
  ...state !== void 0 && { clientState: state },
6084
+ ...resource !== void 0 && { resource },
5996
6085
  ...resolved.scope !== void 0 && { scope: resolved.scope },
5997
6086
  ...nonce !== void 0 && { nonce },
5998
6087
  ...resolved.idToken !== void 0 && { idToken: resolved.idToken },
@@ -6049,6 +6138,14 @@ let AuthorizeController = class AuthorizeController {
6049
6138
  res.status = 401;
6050
6139
  return { error: "invalid_client" };
6051
6140
  }
6141
+ if (Array.isArray(body.resource)) {
6142
+ res.status = 400;
6143
+ return { error: "invalid_target" };
6144
+ }
6145
+ if (row.resource !== void 0 && body.resource !== void 0 && row.resource !== body.resource) {
6146
+ res.status = 400;
6147
+ return { error: "invalid_target" };
6148
+ }
6052
6149
  const wantIdToken = row.idToken === true;
6053
6150
  const wantAccessToken = row.accessToken !== false;
6054
6151
  if (!wantIdToken && !wantAccessToken) {
@@ -6100,7 +6197,10 @@ let AuthorizeController = class AuthorizeController {
6100
6197
  * OIDC discovery (Tier 2). Derives every endpoint from the signer's `issuer`
6101
6198
  * (configured as `{origin}/auth`), so a relying `OidcProvider` configured with
6102
6199
  * the same `issuer` resolves `/authorize`, `/token`, and `/jwks` automatically.
6103
- * 404 when no signer is wired (Tier-1-only deployment).
6200
+ * 404 when no signer is wired (Tier-1-only deployment). When DCR is also
6201
+ * wired, `registration_endpoint` is advertised here too — in a combined
6202
+ * deployment a client that prefers `openid-configuration` over the RFC 8414
6203
+ * document must see the same capability set.
6104
6204
  */
6105
6205
  discovery() {
6106
6206
  const res = useResponse(current());
@@ -6115,6 +6215,7 @@ let AuthorizeController = class AuthorizeController {
6115
6215
  authorization_endpoint: `${iss}/authorize`,
6116
6216
  token_endpoint: `${iss}/token`,
6117
6217
  jwks_uri: `${iss}/jwks`,
6218
+ ...this.getDynamicClientRegistration() && { registration_endpoint: `${iss}/register` },
6118
6219
  response_types_supported: ["code"],
6119
6220
  grant_types_supported: ["authorization_code"],
6120
6221
  subject_types_supported: ["public"],
@@ -6128,6 +6229,81 @@ let AuthorizeController = class AuthorizeController {
6128
6229
  token_endpoint_auth_methods_supported: ["none", "client_secret_post"]
6129
6230
  };
6130
6231
  }
6232
+ /**
6233
+ * RFC 8414 Authorization Server Metadata — the OAuth-flavored discovery MCP
6234
+ * connector clients fetch (OAUTH.md R1). Served signer-INDEPENDENTLY: it
6235
+ * needs only an issuer ({@link getIssuer} — overridable for a Tier-1-style
6236
+ * deployment with no `IdTokenSigner`). Mounted under the controller this is
6237
+ * the suffix form `{issuer}/.well-known/oauth-authorization-server`; the
6238
+ * RFC-correct path-insertion form at the HTTP-server ROOT
6239
+ * (`/.well-known/oauth-authorization-server/<issuer-path>`) cannot be
6240
+ * registered by a prefix-mounted controller — consumers mount it themselves
6241
+ * from the exported `buildAuthorizationServerMetadata` (re-exported by this
6242
+ * package).
6243
+ */
6244
+ oauthServerMetadata() {
6245
+ const res = useResponse(current());
6246
+ const rawIssuer = this.getIssuer();
6247
+ if (!rawIssuer) {
6248
+ res.status = 404;
6249
+ return { error: "not_found" };
6250
+ }
6251
+ const issuer = canonicalizeIssuer$1(rawIssuer);
6252
+ return buildAuthorizationServerMetadata$1({
6253
+ issuer,
6254
+ ...this.getDynamicClientRegistration() && { registrationEndpoint: `${issuer}/register` },
6255
+ ...this.getIdTokenSigner() && { jwksUri: `${issuer}/jwks` },
6256
+ ...this.scopesSupported() && { scopesSupported: this.scopesSupported() }
6257
+ });
6258
+ }
6259
+ /**
6260
+ * RFC 7591 Dynamic Client Registration (OAUTH.md R2) — anonymous by spec;
6261
+ * 404 unless a registration operation is wired ({@link
6262
+ * getDynamicClientRegistration}). A thin HTTP adapter: validation, abuse
6263
+ * knobs (guard / cap / never-used GC) and persistence live in
6264
+ * `DynamicClientRegistration` (`@aooth/auth`). Public clients only — the
6265
+ * response carries NO `client_secret` and NO `client_secret_expires_at`
6266
+ * (RFC 7591 §3.2.1 requires them only when a secret is issued).
6267
+ */
6268
+ async register(body) {
6269
+ const res = useResponse(current());
6270
+ const registration = this.getDynamicClientRegistration();
6271
+ if (!registration) {
6272
+ res.status = 404;
6273
+ return { error: "not_found" };
6274
+ }
6275
+ if (!(useHeaders(current())["content-type"] ?? "").includes("application/json")) {
6276
+ res.status = 400;
6277
+ return {
6278
+ error: "invalid_client_metadata",
6279
+ error_description: "registration requests must be application/json"
6280
+ };
6281
+ }
6282
+ try {
6283
+ const client = await registration.register(body);
6284
+ res.status = 201;
6285
+ return {
6286
+ client_id: client.clientId,
6287
+ client_id_issued_at: Math.floor(client.createdAt / 1e3),
6288
+ redirect_uris: client.redirectUris,
6289
+ token_endpoint_auth_method: client.tokenEndpointAuthMethod,
6290
+ grant_types: client.grantTypes,
6291
+ response_types: client.responseTypes,
6292
+ ...client.clientName !== void 0 && { client_name: client.clientName },
6293
+ ...client.scope !== void 0 && { scope: client.scope }
6294
+ };
6295
+ } catch (e) {
6296
+ if (e instanceof ClientRegistrationError) {
6297
+ res.status = 400;
6298
+ return {
6299
+ error: e.code,
6300
+ error_description: e.message
6301
+ };
6302
+ }
6303
+ res.status = 500;
6304
+ return { error: "server_error" };
6305
+ }
6306
+ }
6131
6307
  /** The signer's public JWKS (Tier 2). 404 when no signer is wired. */
6132
6308
  jwks() {
6133
6309
  const res = useResponse(current());
@@ -6160,6 +6336,7 @@ __decorate([
6160
6336
  __decorateParam(5, Query("code_challenge_method")),
6161
6337
  __decorateParam(6, Query("scope")),
6162
6338
  __decorateParam(7, Query("nonce")),
6339
+ __decorateParam(8, Query("resource")),
6163
6340
  __decorateMetadata("design:type", Function),
6164
6341
  __decorateMetadata("design:paramtypes", [
6165
6342
  Object,
@@ -6169,6 +6346,7 @@ __decorate([
6169
6346
  Object,
6170
6347
  Object,
6171
6348
  Object,
6349
+ Object,
6172
6350
  Object
6173
6351
  ]),
6174
6352
  __decorateMetadata("design:returntype", Promise)
@@ -6188,6 +6366,21 @@ __decorate([
6188
6366
  __decorateMetadata("design:paramtypes", []),
6189
6367
  __decorateMetadata("design:returntype", Object)
6190
6368
  ], AuthorizeController.prototype, "discovery", null);
6369
+ __decorate([
6370
+ Get(".well-known/oauth-authorization-server"),
6371
+ Public(),
6372
+ __decorateMetadata("design:type", Function),
6373
+ __decorateMetadata("design:paramtypes", []),
6374
+ __decorateMetadata("design:returntype", Object)
6375
+ ], AuthorizeController.prototype, "oauthServerMetadata", null);
6376
+ __decorate([
6377
+ Post("register"),
6378
+ Public(),
6379
+ __decorateParam(0, Body()),
6380
+ __decorateMetadata("design:type", Function),
6381
+ __decorateMetadata("design:paramtypes", [Object]),
6382
+ __decorateMetadata("design:returntype", Promise)
6383
+ ], AuthorizeController.prototype, "register", null);
6191
6384
  __decorate([
6192
6385
  Get("jwks"),
6193
6386
  Public(),
@@ -6244,4 +6437,4 @@ function createAuthEmailOutlet(deps) {
6244
6437
  });
6245
6438
  }
6246
6439
  //#endregion
6247
- export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, AuthController, AuthGuarded, AuthWorkflow, AuthorizeController, AuthorizeRuntime, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, ConsentStore, DEFAULT_AUTH_WORKFLOWS, FEDERATED_IDENTITY_STORE_TOKEN, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, Public, RESERVED_USER_KEYS, SessionEnricherProvider, SessionsController, UserId, WfTrigger, WfTriggerProvider, authGuardInterceptor, authzBindingCookieAttrs, buildInviteAlreadyAcceptedEnvelope, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
6440
+ export { ADD_MFA_WORKFLOW, AUTHZ_BINDING_COOKIE, AUTH_CODE_STORE_TOKEN, AuthController, AuthGuarded, AuthWorkflow, AuthorizeController, AuthorizeRuntime, CHANGE_PASSWORD_WORKFLOW, CLIENT_REDIRECT_POLICY_TOKEN, ConsentStore, DEFAULT_AUTH_WORKFLOWS, DYNAMIC_CLIENT_STORE_TOKEN, DynamicClientRegistration, FEDERATED_IDENTITY_STORE_TOKEN, OAUTH_CSRF_COOKIE, OAuthController, OAuthRuntime, PENDING_AUTHORIZATION_STORE_TOKEN, Public, RESERVED_USER_KEYS, SessionEnricherProvider, SessionsController, UserId, WfTrigger, WfTriggerProvider, authGuardInterceptor, authzBindingCookieAttrs, buildAuthorizationServerMetadata, buildInviteAlreadyAcceptedEnvelope, buildProtectedResourceMetadata, buildWwwAuthenticateBearerChallenge, canonicalizeIssuer, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aooth/auth-moost",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Moost auth integration for aoothjs — AuthGuard interceptor, useAuth composable, REST endpoints, workflows",
5
5
  "keywords": [
6
6
  "aoothjs",
@@ -59,10 +59,10 @@
59
59
  "dependencies": {
60
60
  "@atscript/moost-wf": "^0.1.98",
61
61
  "@wooksjs/http-body": "^0.7.19",
62
- "@aooth/arbac-moost": "^0.1.18",
63
- "@aooth/auth": "0.1.18",
64
- "@aooth/idp": "0.1.18",
65
- "@aooth/user": "0.1.18"
62
+ "@aooth/auth": "0.1.19",
63
+ "@aooth/arbac-moost": "^0.1.19",
64
+ "@aooth/idp": "0.1.19",
65
+ "@aooth/user": "0.1.19"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@atscript/core": "^0.1.75",
@@ -991,15 +991,20 @@ export interface ProveControlOtpForm {
991
991
  * client it never approved. Fieldless apart from the explanatory paragraph; the
992
992
  * primary submit ('Authorize') records consent and proceeds to the mint, the
993
993
  * 'Deny' action 302s the client back with `error=access_denied`. The requesting
994
- * client + scope ride the `@wf.context.pass 'public'` `ctx.public.authz`
995
- * whitelist (display-only — the handle / approval gate stay server-side).
994
+ * client + scope + validated redirect host ride the `@wf.context.pass 'public'`
995
+ * `ctx.public.authz` whitelist (display-only — the handle / approval gate stay
996
+ * server-side). `clientName` is REGISTRANT-SUPPLIED text (a DCR client names
997
+ * itself), so the copy pairs it with the validated redirect host — where the
998
+ * code is actually delivered, which a self-chosen name can't fake — and the
999
+ * `ui.paragraph` renderer emits a TEXT node (never markup/links), so a
1000
+ * malicious name can't become a clickable phish.
996
1001
  */
997
1002
  @meta.label 'Authorize access'
998
1003
  @wf.context.pass 'public'
999
1004
  @ui.form.submit.text 'Authorize'
1000
1005
  export interface AuthorizeConsentForm {
1001
1006
  @ui.form.order 1
1002
- @ui.form.fn.value '(_, _d, ctx) => { const a = ctx.public?.authz; const who = a?.clientName ? "“" + a.clientName + "”" : "A local application"; const sc = a?.scope ? " It is requesting access to: " + a.scope + "." : ""; return who + " wants to sign in to your account." + sc + " Authorize this only if you started it."; }'
1007
+ @ui.form.fn.value '(_, _d, ctx) => { const a = ctx.public?.authz; const host = a?.clientName && a?.redirectHost ? " (" + a.redirectHost + ")" : ""; const who = a?.clientName ? "“" + a.clientName + "”" + host : "A local application"; const sc = a?.scope ? " It is requesting access to: " + a.scope + "." : ""; return who + " wants to sign in to your account." + sc + " Authorize this only if you started it."; }'
1003
1008
  notice: ui.paragraph
1004
1009
 
1005
1010
  @ui.form.order 10
@@ -474,7 +474,7 @@ export declare class ProveControlOtpForm {
474
474
 
475
475
  /**
476
476
  * Atscript interface **AuthorizeConsentForm**
477
- * @see {@link ./forms.as:1000:18}
477
+ * @see {@link ./forms.as:1005:18}
478
478
  */
479
479
  export declare class AuthorizeConsentForm {
480
480
  // notice: ui.paragraph