Skip to content

feat(auth): per-org MFA + dispatcher/MCP gate (ADR-0069 D3)#2395

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

feat(auth): per-org MFA + dispatcher/MCP gate (ADR-0069 D3)#2395
os-zhuang merged 1 commit into
mainfrom
auth/adr-0069-mfa-tails

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

ADR-0069 (tracking #2281, follow-up #2375): the two remaining tails that make enforced MFA total.

What

  • Per-org sys_organization.require_mfa — an org can require MFA above the global floor. computeAuthGate now treats the active org's require_mfa as an effective MFA requirement even when global mfa_required is off. isAuthGateActive() stays cheap (synchronous) via a 60s-TTL "any org requires MFA" cache with a lazy background refresh — a brand-new per-org requirement activates the gate on the next request, with no per-request org query when the feature is off.
  • Dispatcher / MCP gate — the auth-policy gate now also runs in the runtime dispatcher (after resolveExecutionContext), so MCP / GraphQL / embedded data paths enforce PASSWORD_EXPIRED / MFA_REQUIRED consistently with the REST seam (feat(auth): password expiry via the session-validation gate (ADR-0069 D1, P1) #2388). Reuses the shared core evaluateAuthGate + allow-list. Previously only the REST surface (the Console) was gated.

Default-off / additive; ADR-0049 (enforcement ships with the setting).

Verification

  • Unit: plugin-auth 195 + runtime 444 + core 299 + platform-objects 63 + service-settings 129 green; full build incl. strict DTS green. New tests: per-org computeAuthGate (active-org require_mfa → MFA_REQUIRED with global off; not-requiring org → pass; cache trips isAuthGateActive).
  • Live dogfood:
    • Per-org: global MFA off; an org with require_mfa=true → its member's data request is 403 MFA_REQUIRED, while an admin with no org is 200 (correct scoping).
    • Dispatcher: global MFA on → POST /api/v1/mcp returns 403 { error: { details: { code: 'MFA_REQUIRED' } } } (the dispatcher seam fires, matching the REST gate).

ADR-0069 enforced MFA — now complete across all surfaces

✅ global + per-org enforcement · ✅ REST (Console) gate (#2388) · ✅ dispatcher/MCP gate (this PR) · ✅ Console remediation UI (objectstack-ai/objectui#2037). Remaining on #2375 is P2/P3 only.

🤖 Generated with Claude Code

Completes enforced MFA with the two remaining tails.

- platform-objects: sys_organization.require_mfa (per-org tightening above the
  global floor).
- plugin-auth: computeAuthGate treats the active org's require_mfa as an
  effective MFA requirement even when global mfaRequired is off; isAuthGateActive
  consults a 60s-TTL cached "any org requires MFA" flag (lazy background refresh)
  so the cheap sync check stays honest without per-request org queries.
- runtime: enforceAuthGate runs in the dispatcher after resolveExecutionContext,
  gating MCP/GraphQL/embedded data paths the same way the REST seam gates the
  Console — reusing the shared core evaluateAuthGate + allow-list.

Default-off / additive; ADR-0049.

Verified live (dogfood): with GLOBAL mfa off, an org member whose org has
require_mfa=true is gated 403 MFA_REQUIRED while an admin with no org is not
(per-org scoping). With global mfa on, POST /api/v1/mcp returns 403 with
details.code=MFA_REQUIRED (the dispatcher seam). Unit: plugin-auth 195 +
runtime 444 + core 299 + platform-objects 63 + service-settings 129 green;
full build incl. strict DTS green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 27, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 27, 2026 5:21pm

Request Review

@github-actions github-actions Bot 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, @objectstack/runtime.

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

  • content/docs/concepts/cloud-artifact-api.mdx (via packages/runtime)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/concepts/packages.mdx (via @objectstack/platform-objects, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/concepts/setup-app.mdx (via @objectstack/platform-objects)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/api-reference.mdx (via @objectstack/runtime)
  • content/docs/guides/auth-sso.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/authentication.mdx (via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/guides/cloud-deployment.mdx (via @objectstack/runtime)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/runtime)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/runtime)
  • content/docs/guides/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/guides/kernel-services.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/packages.mdx (via @objectstack/platform-objects, @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/guides/plugin-chatbot-integration.mdx (via @objectstack/runtime)
  • content/docs/guides/plugins.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/production-readiness.mdx (via @objectstack/plugin-auth, @objectstack/runtime)
  • content/docs/guides/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/runtime)
  • 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant