Uh oh!
There was an error while loading. Please reload this page.
feat(auth): password expiry via the session-validation gate (ADR-0069 D1, P1) - #2388
Conversation
… D1, P1)
Builds the authentication-policy session gate ADR-0069 needs (the seam shared
by password expiry and enforced MFA) and wires it for password expiry.
- core: pure evaluateAuthGate / isAuthGateAllowlisted helper — one source of
truth for the allow-list (auth + remediation + health + UI-bootstrap reads).
- plugin-auth: customSession computes posture once -> user.authGate;
computeAuthGate compares sys_user.password_changed_at vs password_expiry_days;
password_changed_at stamped on sign-up / change / reset; isAuthGateActive()
keeps it zero-overhead when off.
- platform-objects: sys_user.password_changed_at column.
- rest: resolveExecCtx carries authGate; enforceAuth blocks gated sessions
(independent of requireAuth) with 403 { code: PASSWORD_EXPIRED }, allow-list
keeps auth/remediation reachable.
- service-settings: password_expiry_days setting (0 = off).
Default-off / additive; null password_changed_at never expires (upgrade-safe);
ADR-0049 (enforcement ships with the setting); Date timestamps (ADR-0074).
Verified live (dogfood): expiry=30 + backdated password -> data GET/POST 403
PASSWORD_EXPIRED while change-password (allow-listed) works; admin + within-
window users NOT gated; after change-password the gate lifts (normal RLS
resumes) -> remediation loop closes. Unit: core 299 + rest 140 + plugin-auth
182 + service-settings 129 + platform-objects 63 green; full 60-pkg build
(incl. strict DTS) green.
The dispatcher/MCP path is a follow-up (#2375); the Console's REST surface is
fully gated here. Enforced MFA (D3) reuses this seam next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>The latest updates on your projects. Learn more about Vercel for GitHub.
|
📓 Docs Drift CheckThis PR changes 5 package(s): 30 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- core/auth-gate: replace the trailing-slash regex (`/\/+$/`, flagged as polynomial ReDoS on uncontrolled path input) with an O(n) char-scan strip. - plugin-auth: declare `succeeded`/`signupOk` without the always-overwritten initial value (useless-assignment warnings). - rest-server: drop the redundant `authService &&` (already non-null here). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Uh oh!
There was an error while loading. Please reload this page.
…, P1) (#2390) Completes the session gate (after password expiry #2388) by reusing its authGate seam for enforced MFA. - plugin-auth: computeAuthGate gains an MFA branch — when mfaRequired and the user has no TOTP enrolled (sys_user.two_factor_enabled), block with MFA_REQUIRED once the grace window (mfa_required_at + mfaGracePeriodDays) elapses. mfa_required_at is stamped lazily on first required-but-unenrolled session. isAuthGateActive() also trips on mfaRequired. - bindAuthSettings: mfa_required + mfa_grace_period_days; enabling mfa_required force-enables the twoFactor plugin so /two-factor/* enrollment exists. - platform-objects: sys_user.mfa_required_at column. - service-settings: Multi-factor settings group. Default-off / additive; ADR-0049 (enforcement ships with the setting). Verified live (dogfood): mfa_required + grace 7, un-enrolled user — within grace NOT gated (mfa_required_at stamped); past grace -> 403 MFA_REQUIRED; /two-factor/enable reachable (200, totpURI) while blocked; after enrollment the gate lifts (native 2FA verification takes over); admin within grace NOT blocked. Unit: plugin-auth 191 + service-settings 129 + platform-objects 63 + core 299 + rest 140 green; full build (incl. strict DTS) green. objectui follow-up: Console must render a TOTP-enroll prompt on 403 MFA_REQUIRED. Per-org require_mfa + dispatcher/MCP gate remain follow-ups (#2375). Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ADR-0069 P1 (tracking #2281, follow-up #2375): builds the authentication-policy session gate and wires it for password expiry (D1). This is the shared seam that enforced MFA (D3) will reuse.
What
When
password_expiry_days(newauthsetting, 0 = off) is exceeded, an authenticated user is blocked from protected REST resources with403 PASSWORD_EXPIREDuntil they change their password — while auth + remediation paths stay reachable so they can recover.@objectstack/coreevaluateAuthGate/isAuthGateAllowlisted— one source of truth for the allow-list (auth endpoints, change-password, health, UI-bootstrap reads)@objectstack/plugin-authcustomSessioncomputes the gate posture once →user.authGate;computeAuthGatecomparessys_user.password_changed_atvs the window;password_changed_atstamped on sign-up / change / reset;isAuthGateActive()keeps it zero-overhead when off@objectstack/platform-objectssys_user.password_changed_atcolumn@objectstack/restresolveExecCtxcarriesauthGate;enforceAuthblocks gated sessions (independent ofrequireAuth)@objectstack/service-settingspassword_expiry_dayssettingDesign notes
@objectstack/rest'srest-server(not the hono-plugin CRUD routes nor the dispatcher). The gate hooksenforceAuth, which 39 protected routes already call. Metadata / discovery /me/*stay reachable so the Console can still render the remediation screen — blocking those would brick the UI; data (read + write) is the protected resource and is gated.password_changed_atnever expires (upgrade-safe). Default-off / additive. ADR-0049 (enforcement ships with the setting);Datetimestamps (ADR-0074).Verification
enforceAuthgate,computeAuthGate/stampPasswordChangedAt, and the settings bind.password_expiry_days=30, backdated password):GETandPOST /api/v1/data/*→403 PASSWORD_EXPIRED;change-password(allow-listed) → 200Next
Enforced MFA (D3) reuses this
authGateseam — a second posture branch (mfa_required+ grace + per-org) + an objectui follow-up for the enrollment prompt.🤖 Generated with Claude Code