@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.
- package/dist/atscript/index.d.mts +1 -1
- package/dist/atscript/index.mjs +1 -1
- package/dist/{forms-D7ZfanKT.mjs → forms-uqegc32h.mjs} +1 -1
- package/dist/index.d.mts +132 -14
- package/dist/index.mjs +207 -14
- package/package.json +5 -5
- package/src/atscript/models/forms.as +8 -3
- package/src/atscript/models/forms.as.d.ts +1 -1
|
@@ -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-
|
|
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 };
|
package/dist/atscript/index.mjs
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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
|
|
1581
|
-
*
|
|
1582
|
-
* the
|
|
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`
|
|
1661
|
-
* form's display copy
|
|
1662
|
-
*
|
|
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:
|
|
1901
|
-
*
|
|
1902
|
-
*
|
|
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-
|
|
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 {
|
|
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:
|
|
1174
|
-
*
|
|
1175
|
-
*
|
|
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, [
|
|
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
|
-
|
|
2456
|
+
const action = await this.duplicateInviteCheck({
|
|
2434
2457
|
email,
|
|
2435
2458
|
existingUser: existing
|
|
2436
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6069
|
+
} catch {
|
|
5986
6070
|
res.status = 400;
|
|
5987
|
-
return
|
|
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.
|
|
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/
|
|
63
|
-
"@aooth/
|
|
64
|
-
"@aooth/idp": "0.1.
|
|
65
|
-
"@aooth/user": "0.1.
|
|
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'`
|
|
995
|
-
* whitelist (display-only — the handle / approval gate stay
|
|
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:
|
|
477
|
+
* @see {@link ./forms.as:1005:18}
|
|
478
478
|
*/
|
|
479
479
|
export declare class AuthorizeConsentForm {
|
|
480
480
|
// notice: ui.paragraph
|