Skip to content

feat(auth): enforced MFA via the session-validation gate (ADR-0069 D3, P1) - #2390

Merged
os-zhuang merged 1 commit into
mainfrom
auth/adr-0069-enforced-mfa
Jun 27, 2026
Merged

feat(auth): enforced MFA via the session-validation gate (ADR-0069 D3, P1)#2390
os-zhuang merged 1 commit into
mainfrom
auth/adr-0069-enforced-mfa

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

ADR-0069 P1 (tracking #2281, #2375): enforced MFA (D3) — completes the session-validation gate, reusing the authGate seam shipped in #2388 (password expiry).

What

When mfa_required (new auth toggle) is on, an authenticated user without TOTP enrolled is blocked from protected REST resources with 403 MFA_REQUIRED once their mfa_grace_period_days (default 7) window elapses — while /two-factor/* enrollment stays reachable so they can comply.

  • plugin-auth: computeAuthGate gains an MFA branch (un-enrolled + grace elapsed → MFA_REQUIRED); mfa_required_at (the grace clock) is stamped lazily on the first required-but-unenrolled session; isAuthGateActive() also trips on mfa_required.
  • bind: mfa_required + mfa_grace_period_days; enabling mfa_requiredforce-enables the twoFactor plugin so the enrollment endpoints exist (otherwise gated users couldn't comply).
  • platform-objects: new sys_user.mfa_required_at column.
  • service-settings: new Multi-factor settings group.

Default-off / additive; ADR-0049 (enforcement ships with the setting). The /two-factor/* + change-password paths are already allow-listed by the core gate helper.

Verification

  • Unit: plugin-auth 191 + service-settings 129 + platform-objects 63 + core 299 + rest 140 green; full build incl. strict DTS green. New tests: MFA gate (grace elapsed/within/null-stamp/enrolled/grace-0) + bind (force-twoFactor, clamp).
  • 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 — remediation open
    • after enrollment (two_factor_enabled) the gate lifts; native better-auth 2FA verification takes over
    • admin within grace → not falsely blocked

Follow-ups (tracked in #2375)

  • objectui: Console must render a TOTP-enroll prompt on 403 MFA_REQUIRED (otherwise flipping the toggle on shows errors instead of a guided enroll). Backend is default-off so existing envs are unaffected.
  • Per-org sys_organization.require_mfa; the dispatcher/MCP gate path.

ADR-0069 P1 — now complete

✅ D1 HIBP (#2361) · ✅ D2 lockout+rate-limit (#2365) · ✅ D1 complexity (#2368) · ✅ D1 history (#2371) · ✅ D1 expiry + gate (#2388) · ✅ D3 enforced MFA (this PR).

🤖 Generated with Claude Code

…, P1)
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: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercelBot commented Jun 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specBuildingBuildingPreview, CommentJun 27, 2026 4:25pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/m labels Jun 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/platform-objects, @objectstack/plugin-auth, packages/services.

15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/implementation-status.mdx(via @objectstack/plugin-auth)
  • content/docs/concepts/packages.mdx(via @objectstack/platform-objects, @objectstack/plugin-auth)
  • content/docs/concepts/setup-app.mdx(via @objectstack/platform-objects)
  • content/docs/getting-started/cli.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/auth-sso.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/authentication.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/kernel-services.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/packages.mdx(via @objectstack/platform-objects, @objectstack/plugin-auth, packages/services)
  • content/docs/guides/plugins.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/production-readiness.mdx(via @objectstack/plugin-auth)
  • content/docs/guides/runtime-services/audit-service.mdx(via packages/services)
  • content/docs/guides/runtime-services/index.mdx(via packages/services)
  • content/docs/guides/runtime-services/settings-service.mdx(via packages/services)
  • content/docs/protocol/objectos/i18n-standard.mdx(via packages/services)
  • content/docs/releases/v9.mdx(via @objectstack/plugin-auth)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang