@aooth/auth-moost 0.1.15 → 0.1.17
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 +2 -2
- package/dist/atscript/index.mjs +2 -2
- package/dist/{forms-11EDZgS1.mjs → forms-D7ZfanKT.mjs} +20 -4
- package/dist/index.d.mts +85 -5
- package/dist/index.mjs +168 -18
- package/package.json +24 -24
- package/src/atscript/models/forms.as +29 -3
- package/src/atscript/models/forms.as.d.ts +17 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
export { AskEmailForm, AskPhoneForm, 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
|
+
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";
|
|
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
|
|
2
|
-
export { AskEmailForm, AskPhoneForm, 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
|
+
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";
|
|
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 };
|
|
@@ -234,6 +234,15 @@ var ProveControlOtpForm = class {
|
|
|
234
234
|
throwFeatureDisabled("JSON Schema", "jsonSchema", "emit.jsonSchema");
|
|
235
235
|
}
|
|
236
236
|
};
|
|
237
|
+
var AuthorizeConsentForm = class {
|
|
238
|
+
static __is_atscript_annotated_type = true;
|
|
239
|
+
static type = {};
|
|
240
|
+
static metadata = /* @__PURE__ */ new Map();
|
|
241
|
+
static id = "AuthorizeConsentForm";
|
|
242
|
+
static toJsonSchema() {
|
|
243
|
+
throwFeatureDisabled("JSON Schema", "jsonSchema", "emit.jsonSchema");
|
|
244
|
+
}
|
|
245
|
+
};
|
|
237
246
|
defineAnnotatedType("object", WithInlineConsentForm).prop("consents", defineAnnotatedType("array").of(defineAnnotatedType().designType("string").tags("string").$type).annotate("meta.label", "Pending consents").annotate("ui.form.component", "AsConsentArray").annotate("ui.form.fn.attr", {
|
|
238
247
|
name: "pendingConsents",
|
|
239
248
|
fn: "(_, _d, ctx) => ctx.public?.consents?.pending"
|
|
@@ -304,7 +313,7 @@ defineAnnotatedType("object", SetPasswordForm).prop("consents", defineAnnotatedT
|
|
|
304
313
|
}, true).annotate("ui.form.fn.attr", {
|
|
305
314
|
name: "password",
|
|
306
315
|
fn: "(_, data) => data.newPassword"
|
|
307
|
-
}, true).annotate("ui.form.grid.colSpan", { desktop: "12" }).$type).annotate("ui.form.fn.title", "(_,
|
|
316
|
+
}, true).annotate("ui.form.grid.colSpan", { desktop: "12" }).$type).annotate("ui.form.fn.title", "(_, ctx) => ctx.public?.password?.heading || \"Set your password\"").annotate("wf.context.pass", "public", true);
|
|
308
317
|
defineAnnotatedType("object", InviteForm).prop("email", defineAnnotatedType().designType("string").tags("email", "string").annotate("ui.form.order", 10).annotate("ui.form.type", "text").annotate("meta.label", "Email").annotate("ui.form.autocomplete", "email").annotate("meta.required", {}).annotate("expect.pattern", {
|
|
309
318
|
pattern: "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$",
|
|
310
319
|
flags: "",
|
|
@@ -355,7 +364,7 @@ defineAnnotatedType("object", EnrollAddressForm).prop("address", defineAnnotated
|
|
|
355
364
|
}).annotate("ui.form.fn.hidden", "() => true").optional().$type).prop("useDifferentMethod", defineAnnotatedType().designType("phantom").tags("action", "ui").annotate("ui.form.action", {
|
|
356
365
|
id: "useDifferentMethod",
|
|
357
366
|
label: "Use a different method"
|
|
358
|
-
}).annotate("ui.form.fn.hidden", "(_, _d, ctx) => (ctx.public?.mfaEnroll?.availableTransports?.length ?? 0) < 2 || ctx.public?.mfaEnroll?.mode === \"manage\"").optional().$type).annotate("ui.form.fn.title", "(_,
|
|
367
|
+
}).annotate("ui.form.fn.hidden", "(_, _d, ctx) => (ctx.public?.mfaEnroll?.availableTransports?.length ?? 0) < 2 || ctx.public?.mfaEnroll?.mode === \"manage\"").optional().$type).annotate("ui.form.fn.title", "(_, ctx) => ctx.public?.mfaEnroll?.method === \"sms\" ? \"Add your phone number\" : \"Add your email\"").annotate("meta.description", "We will send you a one-time code to confirm.").annotate("wf.context.pass", "public", true).annotate("ui.form.submit.text", "Send code");
|
|
359
368
|
defineAnnotatedType("object", EnrollConfirmForm).prop("transportHint", defineAnnotatedType().designType("phantom").tags("paragraph", "ui").annotate("ui.form.order", 1).annotate("ui.form.fn.value", "(_, _d, ctx) => ctx.public?.mfaEnroll?.method === \"totp\" ? \"Enter the 6-digit code from your authenticator app.\" : ctx.public?.pincode?.sentTo ? \"Code sent to \" + ctx.public.pincode.sentTo + \". Enter it below to confirm.\" : \"Enter the code to confirm enrollment.\"").optional().$type).prop("code", defineAnnotatedType().designType("string").tags("string").annotate("ui.form.order", 10).annotate("ui.form.type", "text").annotate("meta.label", "Code").annotate("ui.form.autocomplete", "one-time-code").annotate("meta.required", {}).annotate("expect.minLength", { length: 4 }).annotate("expect.maxLength", { length: 12 }).annotate("expect.pattern", { pattern: "^[0-9]+$" }, true).$type).prop("resend", defineAnnotatedType().designType("phantom").tags("action", "ui").annotate("ui.form.action", {
|
|
360
369
|
id: "resend",
|
|
361
370
|
label: "Resend code"
|
|
@@ -417,7 +426,7 @@ defineAnnotatedType("object", ChangePasswordForm).prop("intro", defineAnnotatedT
|
|
|
417
426
|
}, true).annotate("ui.form.fn.attr", {
|
|
418
427
|
name: "password",
|
|
419
428
|
fn: "(_, data) => data.newPassword"
|
|
420
|
-
}, true).annotate("ui.form.grid.colSpan", { desktop: "12" }).$type).annotate("ui.form.fn.title", "(_,
|
|
429
|
+
}, true).annotate("ui.form.grid.colSpan", { desktop: "12" }).$type).annotate("ui.form.fn.title", "(_, ctx) => ctx.public?.password?.heading || \"Change your password\"").annotate("ui.form.submit.text", "Change password").annotate("wf.context.pass", "public", true);
|
|
421
430
|
defineAnnotatedType("object", ProveControlForm).prop("intro", defineAnnotatedType().designType("phantom").tags("paragraph", "ui").annotate("ui.form.order", 5).annotate("ui.form.fn.value", "(_, _d, ctx) => ctx.public?.proveControl?.hint ? \"An account for \" + ctx.public.proveControl.hint + \" already exists. Enter its password to link this sign-in method.\" : \"Enter your existing account password to link this sign-in method.\"").$type).prop("password", defineAnnotatedType().designType("string").tags("string").annotate("ui.form.order", 10).annotate("ui.form.type", "password").annotate("meta.label", "Password").annotate("ui.form.autocomplete", "current-password").annotate("meta.sensitive", true).annotate("meta.required", {}).annotate("expect.minLength", { length: 1 }).$type).prop("cancel", defineAnnotatedType().designType("phantom").tags("action", "ui").annotate("ui.form.order", 20).annotate("ui.form.action", {
|
|
422
431
|
id: "cancel",
|
|
423
432
|
label: "Cancel"
|
|
@@ -438,5 +447,12 @@ defineAnnotatedType("object", ProveControlOtpForm).prop("intro", defineAnnotated
|
|
|
438
447
|
name: "align",
|
|
439
448
|
value: "center"
|
|
440
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", {
|
|
451
|
+
id: "deny",
|
|
452
|
+
label: "Deny"
|
|
453
|
+
}).annotate("ui.form.attr", {
|
|
454
|
+
name: "align",
|
|
455
|
+
value: "center"
|
|
456
|
+
}, true).annotate("ui.form.pushDown", true).optional().$type).annotate("meta.label", "Authorize access").annotate("wf.context.pass", "public", true).annotate("ui.form.submit.text", "Authorize");
|
|
441
457
|
//#endregion
|
|
442
|
-
export {
|
|
458
|
+
export { Select2faForm as C, WithInlineConsentForm as D, TermsBumpForm as E, RemoveMfaConfirmForm as S, SignupForm as T, PincodeForm as _, ConcurrencyLimitForm as a, RecoveryFactorForm as b, EnrollConfirmForm as c, InviteForm as d, LoginCredentialsForm as f, PasswordReauthForm as g, MfaCodeForm as h, ChangePasswordForm as i, EnrollPickMethodForm as l, ManageMfaForm as m, AskPhoneForm as n, EmailIdentifierForm as o, MagicLinkRequestForm as p, AuthorizeConsentForm as r, EnrollAddressForm as s, AskEmailForm as t, EnrollTotpQrForm as u, ProveControlForm as v, SetPasswordForm as w, RecoveryModeSelectForm as x, ProveControlOtpForm as y };
|
package/dist/index.d.mts
CHANGED
|
@@ -181,7 +181,7 @@ declare function authGuardInterceptor(opts?: AuthOptions): TInterceptorDef;
|
|
|
181
181
|
*/
|
|
182
182
|
declare function AuthGuarded(opts?: AuthOptions): ClassDecorator & MethodDecorator;
|
|
183
183
|
//#endregion
|
|
184
|
-
//#region ../../node_modules/.pnpm/@wooksjs+event-wf@0.7.
|
|
184
|
+
//#region ../../node_modules/.pnpm/@wooksjs+event-wf@0.7.19_@prostojs+logger@0.4.3_@wooksjs+event-core@0.7.19_@wooksjs+eve_bb34de328332c0c9e9ce8e94ed9c5cac/node_modules/@wooksjs/event-wf/dist/index.d.ts
|
|
185
185
|
interface WfFinishedResponse {
|
|
186
186
|
type: 'redirect' | 'data';
|
|
187
187
|
/** Redirect URL or response body */
|
|
@@ -946,6 +946,39 @@ declare class AuthorizeRuntime {
|
|
|
946
946
|
constructor(pending: PendingAuthorizationStore, codes: AuthCodeStore);
|
|
947
947
|
}
|
|
948
948
|
//#endregion
|
|
949
|
+
//#region src/authz/authz-binding.d.ts
|
|
950
|
+
/**
|
|
951
|
+
* Name of the double-submit cookie that binds an in-flight authorization
|
|
952
|
+
* request to the browser that started it. Set at `GET /auth/authorize` (holding
|
|
953
|
+
* the high-entropy `binding` secret recorded on the `PendingAuthorization`), and
|
|
954
|
+
* matched — constant-time, via `safeEqual` from `oauth-csrf` — at the
|
|
955
|
+
* `mint-authz-code` / `authz-consent` workflow steps before any code is minted.
|
|
956
|
+
*
|
|
957
|
+
* Why this closes the account-takeover (AUTH-SERVER.md §6): the opaque `authz`
|
|
958
|
+
* handle alone is a bearer ticket — anyone holding it can drive it to the
|
|
959
|
+
* code-minting terminal. An attacker who initiates `/auth/authorize` (to inject
|
|
960
|
+
* their own client / redirect / PKCE challenge) receives the handle AND this
|
|
961
|
+
* cookie in THEIR browser; phishing only the handle into a victim's browser
|
|
962
|
+
* fails the match here, because the victim's browser never holds the secret. So
|
|
963
|
+
* a handle can only be redeemed by the same browser that started the request.
|
|
964
|
+
*/
|
|
965
|
+
declare const AUTHZ_BINDING_COOKIE = "aooth_authz";
|
|
966
|
+
/**
|
|
967
|
+
* Cookie attributes for the authorize-binding cookie — the same httpOnly /
|
|
968
|
+
* `SameSite=Lax` shape as the OAuth CSRF cookie (Lax, NOT Strict, so the
|
|
969
|
+
* top-level GET navigation BACK from a "Continue with <provider>" detour still
|
|
970
|
+
* carries it), so it delegates to {@link oauthCsrfCookieAttrs}. `maxAgeSec` is
|
|
971
|
+
* the pending authorization's remaining lifetime — the caller derives it from
|
|
972
|
+
* the row's `expiresAt` (returned by `PendingAuthorizationStore.create`), so the
|
|
973
|
+
* cookie tracks the row's TTL even when a store is configured with a non-default
|
|
974
|
+
* `ttlMs`. `secure` is caller-controlled (off for the http test harness, on in
|
|
975
|
+
* production).
|
|
976
|
+
*/
|
|
977
|
+
declare function authzBindingCookieAttrs(opts: {
|
|
978
|
+
secure: boolean;
|
|
979
|
+
maxAgeSec: number;
|
|
980
|
+
}): TCookieAttributesInput;
|
|
981
|
+
//#endregion
|
|
949
982
|
//#region src/authz/authz-tokens.d.ts
|
|
950
983
|
/**
|
|
951
984
|
* Explicit string DI tokens for the ABSTRACT authorization-server stores
|
|
@@ -1542,6 +1575,16 @@ interface AuthWfPublicState {
|
|
|
1542
1575
|
* verify forms when a forced password change will follow.
|
|
1543
1576
|
*/
|
|
1544
1577
|
newPasswordRequired?: boolean;
|
|
1578
|
+
/**
|
|
1579
|
+
* 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).
|
|
1583
|
+
*/
|
|
1584
|
+
authz?: {
|
|
1585
|
+
clientName?: string;
|
|
1586
|
+
scope?: string;
|
|
1587
|
+
};
|
|
1545
1588
|
}
|
|
1546
1589
|
/** Unified workflow context shape — one type for all three flows. */
|
|
1547
1590
|
interface AuthWfCtx {
|
|
@@ -1612,11 +1655,17 @@ interface AuthWfCtx {
|
|
|
1612
1655
|
* input `authz` (the opaque pending-authorization handle), and `sso-callback`
|
|
1613
1656
|
* re-raises it from the federated `state.handle` when the user took a
|
|
1614
1657
|
* "Continue with <provider>" detour mid-authorize. Presence routes the login
|
|
1615
|
-
* tail to the `mint-authz-code` terminal (deliver an auth
|
|
1616
|
-
* INSTEAD of `issue`/`redirect` — no browser session is
|
|
1658
|
+
* tail to the `authz-consent` → `mint-authz-code` terminal (deliver an auth
|
|
1659
|
+
* 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.
|
|
1617
1663
|
*/
|
|
1618
1664
|
authz?: {
|
|
1619
1665
|
handle: string;
|
|
1666
|
+
clientName?: string;
|
|
1667
|
+
scope?: string;
|
|
1668
|
+
approved?: boolean;
|
|
1620
1669
|
};
|
|
1621
1670
|
/**
|
|
1622
1671
|
* FE-facing surface — the ONLY top-level ctx key whitelisted on form
|
|
@@ -1672,7 +1721,8 @@ interface AuthWorkflowOpts {
|
|
|
1672
1721
|
termsBump?: TAtscriptAnnotatedType;
|
|
1673
1722
|
concurrencyLimit?: TAtscriptAnnotatedType;
|
|
1674
1723
|
recoveryPincode?: TAtscriptAnnotatedType; /** Self-signup identifier form (`auth/signup/flow` entry pause). */
|
|
1675
|
-
signup?: TAtscriptAnnotatedType;
|
|
1724
|
+
signup?: TAtscriptAnnotatedType; /** Consent prompt shown before `mint-authz-code` mints an auth code (AUTH-SERVER.md §6). */
|
|
1725
|
+
authzConsent?: TAtscriptAnnotatedType;
|
|
1676
1726
|
};
|
|
1677
1727
|
}
|
|
1678
1728
|
/**
|
|
@@ -1722,6 +1772,7 @@ interface ResolvedAuthWorkflowOpts {
|
|
|
1722
1772
|
concurrencyLimit: TAtscriptAnnotatedType;
|
|
1723
1773
|
recoveryPincode: TAtscriptAnnotatedType;
|
|
1724
1774
|
signup: TAtscriptAnnotatedType;
|
|
1775
|
+
authzConsent: TAtscriptAnnotatedType;
|
|
1725
1776
|
};
|
|
1726
1777
|
}
|
|
1727
1778
|
//#endregion
|
|
@@ -2808,6 +2859,34 @@ declare class AuthWorkflow {
|
|
|
2808
2859
|
* (cookies from `issue` are preserved).
|
|
2809
2860
|
*/
|
|
2810
2861
|
redirect(ctx: AuthWfCtx): undefined;
|
|
2862
|
+
/**
|
|
2863
|
+
* Authorization-server consent gate (AUTH-SERVER.md §4.4 / §6). Runs BEFORE
|
|
2864
|
+
* `mint-authz-code` whenever `ctx.authz` is set. Two mandatory jobs:
|
|
2865
|
+
*
|
|
2866
|
+
* 1. **Browser binding** — verify the request carries the `aooth_authz`
|
|
2867
|
+
* cookie that constant-time-matches the secret recorded on the pending
|
|
2868
|
+
* authorization. An `authz` handle phished into a DIFFERENT browser (the
|
|
2869
|
+
* account-takeover primitive) fails here, because the secret lives only
|
|
2870
|
+
* in the browser that initiated `GET /auth/authorize`.
|
|
2871
|
+
* 2. **Explicit consent** — pause on the consent form and require the user
|
|
2872
|
+
* to press 'Authorize'. 'Deny' (or abandoning) 302s the client back with
|
|
2873
|
+
* `error=access_denied` and mints nothing. This blocks the same-browser
|
|
2874
|
+
* forced-navigation variant: a logged-in / silently-SSO-re-authenticated
|
|
2875
|
+
* victim is shown WHICH client is asking and must approve it.
|
|
2876
|
+
*
|
|
2877
|
+
* On approval it stamps `ctx.authz.approved`, which `mint-authz-code`
|
|
2878
|
+
* re-checks alongside a binding re-verification before minting.
|
|
2879
|
+
*/
|
|
2880
|
+
authzConsent(ctx: AuthWfCtx): Promise<undefined>;
|
|
2881
|
+
/**
|
|
2882
|
+
* Constant-time match of the `aooth_authz` binding cookie against the secret
|
|
2883
|
+
* recorded on the pending authorization. Fail-closed (`false`) when the cookie
|
|
2884
|
+
* is absent or differs, so a request without the browser binding can never
|
|
2885
|
+
* redeem the handle. See {@link authzConsent}.
|
|
2886
|
+
*/
|
|
2887
|
+
protected verifyAuthzBinding(req: {
|
|
2888
|
+
binding: string;
|
|
2889
|
+
}): boolean;
|
|
2811
2890
|
/**
|
|
2812
2891
|
* Authorization-server terminal (AUTH-SERVER.md §4.4). Reached INSTEAD of
|
|
2813
2892
|
* `issue`/`redirect` when this login was started from `GET /auth/authorize`
|
|
@@ -2816,6 +2895,7 @@ declare class AuthWorkflow {
|
|
|
2816
2895
|
* request's PKCE challenge / redirect / token policy, then 302s the browser to
|
|
2817
2896
|
* `redirect_uri?code&state`. It does NOT issue a session and attaches NO
|
|
2818
2897
|
* cookies — the token is minted later, off the browser, at `POST /auth/token`.
|
|
2898
|
+
* Gated by {@link authzConsent} (binding + explicit approval).
|
|
2819
2899
|
*/
|
|
2820
2900
|
mintAuthzCode(ctx: AuthWfCtx): Promise<undefined>;
|
|
2821
2901
|
/**
|
|
@@ -3106,4 +3186,4 @@ interface AuditEmitter {
|
|
|
3106
3186
|
emit(event: AuditEvent): Promise<void> | void;
|
|
3107
3187
|
}
|
|
3108
3188
|
//#endregion
|
|
3109
|
-
export { ADD_MFA_WORKFLOW, 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, buildInviteAlreadyAcceptedEnvelope, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { C as
|
|
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";
|
|
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";
|
|
5
5
|
import { HttpError, useAuthorization, useCookies, useHeaders, useRequest, useResponse, useUrlParams } from "@wooksjs/event-http";
|
|
6
6
|
import { ArbacAction, ArbacResource, getArbacMate } from "@aooth/arbac-moost";
|
|
7
|
-
import { FederatedIdentityStore, UserAuthError, UserService, generateTotpSecret, generateTotpUri, maskEmail, maskPhone, pickDefinedProfile, verifyTotpCode } from "@aooth/user";
|
|
7
|
+
import { FederatedIdentityStore, UserAuthError, UserService, generateMfaCode, generateTotpSecret, generateTotpUri, maskEmail, maskPhone, pickDefinedProfile, verifyTotpCode } from "@aooth/user";
|
|
8
8
|
import { Body, Delete, Get, HttpError as HttpError$1, Post, Query } from "@moostjs/event-http";
|
|
9
|
-
import { createHash, timingSafeEqual } from "node:crypto";
|
|
9
|
+
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";
|
|
@@ -585,6 +585,56 @@ function safeEqual(a, b) {
|
|
|
585
585
|
return timingSafeEqual(ab, bb);
|
|
586
586
|
}
|
|
587
587
|
//#endregion
|
|
588
|
+
//#region src/authz/authz-binding.ts
|
|
589
|
+
/**
|
|
590
|
+
* Name of the double-submit cookie that binds an in-flight authorization
|
|
591
|
+
* request to the browser that started it. Set at `GET /auth/authorize` (holding
|
|
592
|
+
* the high-entropy `binding` secret recorded on the `PendingAuthorization`), and
|
|
593
|
+
* matched — constant-time, via `safeEqual` from `oauth-csrf` — at the
|
|
594
|
+
* `mint-authz-code` / `authz-consent` workflow steps before any code is minted.
|
|
595
|
+
*
|
|
596
|
+
* Why this closes the account-takeover (AUTH-SERVER.md §6): the opaque `authz`
|
|
597
|
+
* handle alone is a bearer ticket — anyone holding it can drive it to the
|
|
598
|
+
* code-minting terminal. An attacker who initiates `/auth/authorize` (to inject
|
|
599
|
+
* their own client / redirect / PKCE challenge) receives the handle AND this
|
|
600
|
+
* cookie in THEIR browser; phishing only the handle into a victim's browser
|
|
601
|
+
* fails the match here, because the victim's browser never holds the secret. So
|
|
602
|
+
* a handle can only be redeemed by the same browser that started the request.
|
|
603
|
+
*/
|
|
604
|
+
const AUTHZ_BINDING_COOKIE = "aooth_authz";
|
|
605
|
+
/**
|
|
606
|
+
* Cookie attributes for the authorize-binding cookie — the same httpOnly /
|
|
607
|
+
* `SameSite=Lax` shape as the OAuth CSRF cookie (Lax, NOT Strict, so the
|
|
608
|
+
* top-level GET navigation BACK from a "Continue with <provider>" detour still
|
|
609
|
+
* carries it), so it delegates to {@link oauthCsrfCookieAttrs}. `maxAgeSec` is
|
|
610
|
+
* the pending authorization's remaining lifetime — the caller derives it from
|
|
611
|
+
* the row's `expiresAt` (returned by `PendingAuthorizationStore.create`), so the
|
|
612
|
+
* cookie tracks the row's TTL even when a store is configured with a non-default
|
|
613
|
+
* `ttlMs`. `secure` is caller-controlled (off for the http test harness, on in
|
|
614
|
+
* production).
|
|
615
|
+
*/
|
|
616
|
+
function authzBindingCookieAttrs(opts) {
|
|
617
|
+
return oauthCsrfCookieAttrs({
|
|
618
|
+
secure: opts.secure,
|
|
619
|
+
maxAgeSec: opts.maxAgeSec
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/authz/authz-redirect.ts
|
|
624
|
+
/**
|
|
625
|
+
* Build the client redirect URL for an authorization-server result (RFC 6749
|
|
626
|
+
* §4.1.2 / §4.1.2.1): append the result params (`code` on success, `error` on
|
|
627
|
+
* failure) and echo the client's `state` when it sent one. The single home for
|
|
628
|
+
* this URL contract — the controller's fail-soft redirect and the workflow's
|
|
629
|
+
* deny / code-mint terminals all build the same shape, only the transport
|
|
630
|
+
* (controller response vs `finishWf` envelope) differs.
|
|
631
|
+
*/
|
|
632
|
+
function authzRedirectUrl(redirectUri, params) {
|
|
633
|
+
const url = new URL(redirectUri);
|
|
634
|
+
for (const [key, value] of Object.entries(params)) if (value !== void 0) url.searchParams.set(key, value);
|
|
635
|
+
return url.toString();
|
|
636
|
+
}
|
|
637
|
+
//#endregion
|
|
588
638
|
//#region src/oauth/oauth-redirect.ts
|
|
589
639
|
/**
|
|
590
640
|
* Open-redirect defense for the post-login `redirect` carried across the OAuth
|
|
@@ -899,7 +949,8 @@ const DEFAULT_FORMS = {
|
|
|
899
949
|
termsBump: TermsBumpForm,
|
|
900
950
|
concurrencyLimit: ConcurrencyLimitForm,
|
|
901
951
|
recoveryPincode: PincodeForm,
|
|
902
|
-
signup: SignupForm
|
|
952
|
+
signup: SignupForm,
|
|
953
|
+
authzConsent: AuthorizeConsentForm
|
|
903
954
|
};
|
|
904
955
|
function mergeAuthWorkflowOpts(opts) {
|
|
905
956
|
return {
|
|
@@ -1699,6 +1750,10 @@ let AuthWorkflow = class AuthWorkflow {
|
|
|
1699
1750
|
if (sub) pub.proveControl = sub;
|
|
1700
1751
|
}
|
|
1701
1752
|
if (ctx.newPasswordRequired !== void 0) pub.newPasswordRequired = ctx.newPasswordRequired;
|
|
1753
|
+
if (ctx.authz) {
|
|
1754
|
+
const sub = pickDefined(ctx.authz, ["clientName", "scope"]);
|
|
1755
|
+
if (sub) pub.authz = sub;
|
|
1756
|
+
}
|
|
1702
1757
|
ctx.public = pub;
|
|
1703
1758
|
}
|
|
1704
1759
|
/**
|
|
@@ -1735,8 +1790,7 @@ let AuthWorkflow = class AuthWorkflow {
|
|
|
1735
1790
|
}
|
|
1736
1791
|
/** Mint a numeric pincode + stash it onto ctx. Returns the plain code. */
|
|
1737
1792
|
mintPin(ctx, length, ttlMs) {
|
|
1738
|
-
|
|
1739
|
-
for (let i = 0; i < length; i++) code += Math.floor(Math.random() * 10).toString();
|
|
1793
|
+
const code = generateMfaCode(length);
|
|
1740
1794
|
ctx.pin = code;
|
|
1741
1795
|
ctx.pinExpire = Date.now() + ttlMs;
|
|
1742
1796
|
delete ctx.pinAttempts;
|
|
@@ -3605,6 +3659,74 @@ let AuthWorkflow = class AuthWorkflow {
|
|
|
3605
3659
|
(ctx.completion ??= {}).redirectUrl = url;
|
|
3606
3660
|
}
|
|
3607
3661
|
/**
|
|
3662
|
+
* Authorization-server consent gate (AUTH-SERVER.md §4.4 / §6). Runs BEFORE
|
|
3663
|
+
* `mint-authz-code` whenever `ctx.authz` is set. Two mandatory jobs:
|
|
3664
|
+
*
|
|
3665
|
+
* 1. **Browser binding** — verify the request carries the `aooth_authz`
|
|
3666
|
+
* cookie that constant-time-matches the secret recorded on the pending
|
|
3667
|
+
* authorization. An `authz` handle phished into a DIFFERENT browser (the
|
|
3668
|
+
* account-takeover primitive) fails here, because the secret lives only
|
|
3669
|
+
* in the browser that initiated `GET /auth/authorize`.
|
|
3670
|
+
* 2. **Explicit consent** — pause on the consent form and require the user
|
|
3671
|
+
* to press 'Authorize'. 'Deny' (or abandoning) 302s the client back with
|
|
3672
|
+
* `error=access_denied` and mints nothing. This blocks the same-browser
|
|
3673
|
+
* forced-navigation variant: a logged-in / silently-SSO-re-authenticated
|
|
3674
|
+
* victim is shown WHICH client is asking and must approve it.
|
|
3675
|
+
*
|
|
3676
|
+
* On approval it stamps `ctx.authz.approved`, which `mint-authz-code`
|
|
3677
|
+
* re-checks alongside a binding re-verification before minting.
|
|
3678
|
+
*/
|
|
3679
|
+
async authzConsent(ctx) {
|
|
3680
|
+
this.requireSubject(ctx);
|
|
3681
|
+
const authz = ctx.authz;
|
|
3682
|
+
const { pending } = await this.authorizeRuntime();
|
|
3683
|
+
const req = authz ? await pending.get(authz.handle) : null;
|
|
3684
|
+
if (!authz || !req) {
|
|
3685
|
+
finishWf({ message: {
|
|
3686
|
+
level: "error",
|
|
3687
|
+
text: "Authorization request expired. Please try again."
|
|
3688
|
+
} });
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
if (!this.verifyAuthzBinding(req)) {
|
|
3692
|
+
await pending.delete(authz.handle);
|
|
3693
|
+
finishWf({ message: {
|
|
3694
|
+
level: "error",
|
|
3695
|
+
text: "This authorization could not be verified for your browser. Please start again."
|
|
3696
|
+
} });
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
if (req.clientId !== void 0) authz.clientName = req.clientId;
|
|
3700
|
+
if (req.scope !== void 0) authz.scope = req.scope;
|
|
3701
|
+
const wf = this.useAtscriptWfPublic(ctx, this.opts.forms.authzConsent);
|
|
3702
|
+
if (wf.resolveAction() === "deny") {
|
|
3703
|
+
await pending.delete(authz.handle);
|
|
3704
|
+
finishWf({ next: {
|
|
3705
|
+
trigger: "immediate",
|
|
3706
|
+
action: {
|
|
3707
|
+
type: "redirect",
|
|
3708
|
+
target: authzRedirectUrl(req.redirectUri, {
|
|
3709
|
+
error: "access_denied",
|
|
3710
|
+
state: req.clientState
|
|
3711
|
+
}),
|
|
3712
|
+
reason: "authz-denied"
|
|
3713
|
+
}
|
|
3714
|
+
} });
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
wf.resolveInput();
|
|
3718
|
+
authz.approved = true;
|
|
3719
|
+
}
|
|
3720
|
+
/**
|
|
3721
|
+
* Constant-time match of the `aooth_authz` binding cookie against the secret
|
|
3722
|
+
* recorded on the pending authorization. Fail-closed (`false`) when the cookie
|
|
3723
|
+
* is absent or differs, so a request without the browser binding can never
|
|
3724
|
+
* redeem the handle. See {@link authzConsent}.
|
|
3725
|
+
*/
|
|
3726
|
+
verifyAuthzBinding(req) {
|
|
3727
|
+
return safeEqual(useCookies(current()).getCookie(AUTHZ_BINDING_COOKIE), req.binding);
|
|
3728
|
+
}
|
|
3729
|
+
/**
|
|
3608
3730
|
* Authorization-server terminal (AUTH-SERVER.md §4.4). Reached INSTEAD of
|
|
3609
3731
|
* `issue`/`redirect` when this login was started from `GET /auth/authorize`
|
|
3610
3732
|
* (`ctx.authz` set by `init-login` or re-raised by `sso-callback`). Mints a
|
|
@@ -3612,6 +3734,7 @@ let AuthWorkflow = class AuthWorkflow {
|
|
|
3612
3734
|
* request's PKCE challenge / redirect / token policy, then 302s the browser to
|
|
3613
3735
|
* `redirect_uri?code&state`. It does NOT issue a session and attaches NO
|
|
3614
3736
|
* cookies — the token is minted later, off the browser, at `POST /auth/token`.
|
|
3737
|
+
* Gated by {@link authzConsent} (binding + explicit approval).
|
|
3615
3738
|
*/
|
|
3616
3739
|
async mintAuthzCode(ctx) {
|
|
3617
3740
|
this.requireSubject(ctx);
|
|
@@ -3625,6 +3748,14 @@ let AuthWorkflow = class AuthWorkflow {
|
|
|
3625
3748
|
} });
|
|
3626
3749
|
return;
|
|
3627
3750
|
}
|
|
3751
|
+
if (ctx.authz?.approved !== true || !this.verifyAuthzBinding(req)) {
|
|
3752
|
+
await pending.delete(handle);
|
|
3753
|
+
finishWf({ message: {
|
|
3754
|
+
level: "error",
|
|
3755
|
+
text: "Authorization could not be completed. Please start again."
|
|
3756
|
+
} });
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3628
3759
|
const { code } = await codes.mint({
|
|
3629
3760
|
userId: ctx.subject,
|
|
3630
3761
|
codeChallenge: req.codeChallenge,
|
|
@@ -3638,14 +3769,14 @@ let AuthWorkflow = class AuthWorkflow {
|
|
|
3638
3769
|
tokenPolicy: req.tokenPolicy
|
|
3639
3770
|
});
|
|
3640
3771
|
await pending.delete(handle);
|
|
3641
|
-
const url = new URL(req.redirectUri);
|
|
3642
|
-
url.searchParams.set("code", code);
|
|
3643
|
-
if (req.clientState !== void 0) url.searchParams.set("state", req.clientState);
|
|
3644
3772
|
finishWf({ next: {
|
|
3645
3773
|
trigger: "immediate",
|
|
3646
3774
|
action: {
|
|
3647
3775
|
type: "redirect",
|
|
3648
|
-
target:
|
|
3776
|
+
target: authzRedirectUrl(req.redirectUri, {
|
|
3777
|
+
code,
|
|
3778
|
+
state: req.clientState
|
|
3779
|
+
}),
|
|
3649
3780
|
reason: "authz-code"
|
|
3650
3781
|
}
|
|
3651
3782
|
} });
|
|
@@ -4841,6 +4972,14 @@ __decorate([
|
|
|
4841
4972
|
__decorateMetadata("design:paramtypes", [Object]),
|
|
4842
4973
|
__decorateMetadata("design:returntype", void 0)
|
|
4843
4974
|
], AuthWorkflow.prototype, "redirect", null);
|
|
4975
|
+
__decorate([
|
|
4976
|
+
Step("authz-consent"),
|
|
4977
|
+
Public(),
|
|
4978
|
+
__decorateParam(0, WorkflowParam("context")),
|
|
4979
|
+
__decorateMetadata("design:type", Function),
|
|
4980
|
+
__decorateMetadata("design:paramtypes", [Object]),
|
|
4981
|
+
__decorateMetadata("design:returntype", Promise)
|
|
4982
|
+
], AuthWorkflow.prototype, "authzConsent", null);
|
|
4844
4983
|
__decorate([
|
|
4845
4984
|
Step("mint-authz-code"),
|
|
4846
4985
|
Public(),
|
|
@@ -5033,8 +5172,12 @@ __decorate([
|
|
|
5033
5172
|
]
|
|
5034
5173
|
},
|
|
5035
5174
|
{
|
|
5036
|
-
id: "
|
|
5175
|
+
id: "authz-consent",
|
|
5037
5176
|
condition: (ctx) => !!ctx.authz
|
|
5177
|
+
},
|
|
5178
|
+
{
|
|
5179
|
+
id: "mint-authz-code",
|
|
5180
|
+
condition: (ctx) => ctx.authz?.approved === true
|
|
5038
5181
|
}
|
|
5039
5182
|
]),
|
|
5040
5183
|
__decorateMetadata("design:type", Function),
|
|
@@ -5844,7 +5987,8 @@ let AuthorizeController = class AuthorizeController {
|
|
|
5844
5987
|
return e instanceof AuthorizeError ? `invalid request: ${e.code}` : "invalid request";
|
|
5845
5988
|
}
|
|
5846
5989
|
if (responseType !== "code" || !codeChallenge || codeChallengeMethod !== "S256") return this.redirectError(resolved.redirectUri, "invalid_request", state);
|
|
5847
|
-
const
|
|
5990
|
+
const binding = randomBytes(32).toString("base64url");
|
|
5991
|
+
const { handle, expiresAt } = await this.pending.create({
|
|
5848
5992
|
...resolved.clientId !== void 0 && { clientId: resolved.clientId },
|
|
5849
5993
|
redirectUri: resolved.redirectUri,
|
|
5850
5994
|
codeChallenge,
|
|
@@ -5854,8 +5998,14 @@ let AuthorizeController = class AuthorizeController {
|
|
|
5854
5998
|
...resolved.idToken !== void 0 && { idToken: resolved.idToken },
|
|
5855
5999
|
...resolved.accessToken !== void 0 && { accessToken: resolved.accessToken },
|
|
5856
6000
|
...resolved.audience !== void 0 && { audience: resolved.audience },
|
|
5857
|
-
tokenPolicy: resolved.tokenPolicy
|
|
6001
|
+
tokenPolicy: resolved.tokenPolicy,
|
|
6002
|
+
binding
|
|
5858
6003
|
});
|
|
6004
|
+
const maxAgeSec = Math.max(1, Math.round((expiresAt - Date.now()) / 1e3));
|
|
6005
|
+
res.setCookie(AUTHZ_BINDING_COOKIE, binding, authzBindingCookieAttrs({
|
|
6006
|
+
secure: useAuth().options.cookie.secure,
|
|
6007
|
+
maxAgeSec
|
|
6008
|
+
}));
|
|
5859
6009
|
const loginPath = this.loginPath();
|
|
5860
6010
|
const target = `${loginPath}${loginPath.includes("?") ? "&" : "?"}authz=${encodeURIComponent(handle)}`;
|
|
5861
6011
|
res.status = 302;
|
|
@@ -5990,12 +6140,12 @@ let AuthorizeController = class AuthorizeController {
|
|
|
5990
6140
|
}
|
|
5991
6141
|
/** Fail soft: 302 the validated client redirect with an `?error=` (+ echoed `state`). */
|
|
5992
6142
|
redirectError(redirectUri, error, state) {
|
|
5993
|
-
const url = new URL(redirectUri);
|
|
5994
|
-
url.searchParams.set("error", error);
|
|
5995
|
-
if (state !== void 0) url.searchParams.set("state", state);
|
|
5996
6143
|
const res = useResponse(current());
|
|
5997
6144
|
res.status = 302;
|
|
5998
|
-
res.setHeader("Location",
|
|
6145
|
+
res.setHeader("Location", authzRedirectUrl(redirectUri, {
|
|
6146
|
+
error,
|
|
6147
|
+
state
|
|
6148
|
+
}));
|
|
5999
6149
|
return "";
|
|
6000
6150
|
}
|
|
6001
6151
|
};
|
|
@@ -6094,4 +6244,4 @@ function createAuthEmailOutlet(deps) {
|
|
|
6094
6244
|
});
|
|
6095
6245
|
}
|
|
6096
6246
|
//#endregion
|
|
6097
|
-
export { ADD_MFA_WORKFLOW, 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, buildInviteAlreadyAcceptedEnvelope, createAuthEmailOutlet, deriveWfStateSecret, generateMagicLinkToken, getAuthMate, isSafeRelativeRedirect, oauthCsrfCookieAttrs, parseInviteRoles, resolveOAuthRedirect, stripReservedUserKeys, useAuth };
|
|
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/auth-moost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Moost auth integration for aoothjs — AuthGuard interceptor, useAuth composable, REST endpoints, workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aoothjs",
|
|
@@ -57,33 +57,33 @@
|
|
|
57
57
|
"access": "public"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@atscript/moost-wf": "^0.1.
|
|
61
|
-
"@wooksjs/http-body": "^0.7.
|
|
62
|
-
"@aooth/arbac-moost": "^0.1.
|
|
63
|
-
"@aooth/
|
|
64
|
-
"@aooth/
|
|
65
|
-
"@aooth/
|
|
60
|
+
"@atscript/moost-wf": "^0.1.98",
|
|
61
|
+
"@wooksjs/http-body": "^0.7.19",
|
|
62
|
+
"@aooth/arbac-moost": "^0.1.17",
|
|
63
|
+
"@aooth/user": "0.1.17",
|
|
64
|
+
"@aooth/auth": "0.1.17",
|
|
65
|
+
"@aooth/idp": "0.1.17"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@atscript/core": "^0.1.
|
|
69
|
-
"@atscript/typescript": "^0.1.
|
|
70
|
-
"@atscript/ui": "^0.1.
|
|
71
|
-
"@atscript/ui-fns": "^0.1.
|
|
72
|
-
"@moostjs/event-http": "^0.6.
|
|
73
|
-
"@moostjs/event-wf": "^0.6.
|
|
74
|
-
"moost": "^0.6.
|
|
75
|
-
"unplugin-atscript": "^0.1.
|
|
76
|
-
"wooks": "^0.7.
|
|
68
|
+
"@atscript/core": "^0.1.75",
|
|
69
|
+
"@atscript/typescript": "^0.1.75",
|
|
70
|
+
"@atscript/ui": "^0.1.98",
|
|
71
|
+
"@atscript/ui-fns": "^0.1.98",
|
|
72
|
+
"@moostjs/event-http": "^0.6.26",
|
|
73
|
+
"@moostjs/event-wf": "^0.6.26",
|
|
74
|
+
"moost": "^0.6.26",
|
|
75
|
+
"unplugin-atscript": "^0.1.75",
|
|
76
|
+
"wooks": "^0.7.19"
|
|
77
77
|
},
|
|
78
78
|
"peerDependencies": {
|
|
79
|
-
"@atscript/moost-wf": "^0.1.
|
|
80
|
-
"@atscript/typescript": "^0.1.
|
|
81
|
-
"@moostjs/event-http": "^0.6.
|
|
82
|
-
"@moostjs/event-wf": "^0.6.
|
|
83
|
-
"@wooksjs/event-core": "^0.7.
|
|
84
|
-
"@wooksjs/event-http": "^0.7.
|
|
85
|
-
"@wooksjs/http-body": "^0.7.
|
|
86
|
-
"moost": "^0.6.
|
|
79
|
+
"@atscript/moost-wf": "^0.1.98",
|
|
80
|
+
"@atscript/typescript": "^0.1.75",
|
|
81
|
+
"@moostjs/event-http": "^0.6.26",
|
|
82
|
+
"@moostjs/event-wf": "^0.6.26",
|
|
83
|
+
"@wooksjs/event-core": "^0.7.19",
|
|
84
|
+
"@wooksjs/event-http": "^0.7.19",
|
|
85
|
+
"@wooksjs/http-body": "^0.7.19",
|
|
86
|
+
"moost": "^0.6.26"
|
|
87
87
|
},
|
|
88
88
|
"peerDependenciesMeta": {
|
|
89
89
|
"@atscript/typescript": {
|
|
@@ -258,7 +258,7 @@ export interface SignupForm {
|
|
|
258
258
|
* wants to abandon the flow closes / refreshes the page (the wf state token
|
|
259
259
|
* expires per the engine's TTL).
|
|
260
260
|
*/
|
|
261
|
-
@ui.form.fn.title '(_,
|
|
261
|
+
@ui.form.fn.title '(_, ctx) => ctx.public?.password?.heading || "Set your password"'
|
|
262
262
|
@wf.context.pass 'public'
|
|
263
263
|
export interface SetPasswordForm extends WithInlineConsentForm {
|
|
264
264
|
/**
|
|
@@ -537,7 +537,7 @@ export interface EnrollPickMethodForm {
|
|
|
537
537
|
* forbids backing out mid-flow). `useDifferentMethod` is hidden when the
|
|
538
538
|
* consumer has only one transport configured (nothing to switch to).
|
|
539
539
|
*/
|
|
540
|
-
@ui.form.fn.title '(_,
|
|
540
|
+
@ui.form.fn.title '(_, ctx) => ctx.public?.mfaEnroll?.method === "sms" ? "Add your phone number" : "Add your email"'
|
|
541
541
|
@meta.description 'We will send you a one-time code to confirm.'
|
|
542
542
|
@wf.context.pass 'public'
|
|
543
543
|
@ui.form.submit.text 'Send code'
|
|
@@ -849,7 +849,7 @@ export interface RecoveryFactorForm {
|
|
|
849
849
|
* `SetPasswordForm`, so the live `AsPasswordRules` renderer and dynamic copy
|
|
850
850
|
* work identically. Heading/intro are staged by `change-password-form`.
|
|
851
851
|
*/
|
|
852
|
-
@ui.form.fn.title '(_,
|
|
852
|
+
@ui.form.fn.title '(_, ctx) => ctx.public?.password?.heading || "Change your password"'
|
|
853
853
|
@ui.form.submit.text 'Change password'
|
|
854
854
|
@wf.context.pass 'public'
|
|
855
855
|
export interface ChangePasswordForm {
|
|
@@ -982,3 +982,29 @@ export interface ProveControlOtpForm {
|
|
|
982
982
|
@ui.form.pushDown
|
|
983
983
|
cancel?: ui.action
|
|
984
984
|
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Authorization-server consent prompt (AUTH-SERVER.md §4.4 / §6). Rendered by
|
|
988
|
+
* the `authz-consent` @Step once the human is authenticated, BEFORE
|
|
989
|
+
* `mint-authz-code` mints the authorization code — so a logged-in (or silently
|
|
990
|
+
* SSO-re-authenticated) browser cannot be walked into delivering a code to a
|
|
991
|
+
* client it never approved. Fieldless apart from the explanatory paragraph; the
|
|
992
|
+
* primary submit ('Authorize') records consent and proceeds to the mint, the
|
|
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).
|
|
996
|
+
*/
|
|
997
|
+
@meta.label 'Authorize access'
|
|
998
|
+
@wf.context.pass 'public'
|
|
999
|
+
@ui.form.submit.text 'Authorize'
|
|
1000
|
+
export interface AuthorizeConsentForm {
|
|
1001
|
+
@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."; }'
|
|
1003
|
+
notice: ui.paragraph
|
|
1004
|
+
|
|
1005
|
+
@ui.form.order 10
|
|
1006
|
+
@ui.form.action 'deny', 'Deny'
|
|
1007
|
+
@ui.form.attr 'align', 'center'
|
|
1008
|
+
@ui.form.pushDown
|
|
1009
|
+
deny?: ui.action
|
|
1010
|
+
}
|
|
@@ -471,4 +471,21 @@ export declare class ProveControlOtpForm {
|
|
|
471
471
|
/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
|
|
472
472
|
static toExampleData?: () => any
|
|
473
473
|
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Atscript interface **AuthorizeConsentForm**
|
|
477
|
+
* @see {@link ./forms.as:1000:18}
|
|
478
|
+
*/
|
|
479
|
+
export declare class AuthorizeConsentForm {
|
|
480
|
+
// notice: ui.paragraph
|
|
481
|
+
// deny: ui.action
|
|
482
|
+
static __is_atscript_annotated_type: true
|
|
483
|
+
static type: TAtscriptTypeObject<keyof AuthorizeConsentForm, AuthorizeConsentForm>
|
|
484
|
+
static metadata: TMetadataMap<AtscriptMetadata>
|
|
485
|
+
static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof AuthorizeConsentForm>
|
|
486
|
+
/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */
|
|
487
|
+
static toJsonSchema: () => any
|
|
488
|
+
/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */
|
|
489
|
+
static toExampleData?: () => any
|
|
490
|
+
}
|
|
474
491
|
// prettier-ignore-end
|