Skip to content

feat(auth): two-factor (TOTP) login seam + enterprise-gated UI (trinity-enterprise#5)#1247

Open
dolho wants to merge 1 commit into
devfrom
feat/5-2fa-totp
Open

feat(auth): two-factor (TOTP) login seam + enterprise-gated UI (trinity-enterprise#5)#1247
dolho wants to merge 1 commit into
devfrom
feat/5-2fa-totp

Conversation

@dolho

@dolho dolho commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What

OSS side of two-factor authentication (TOTP) — enterprise issue Abilityai/trinity-enterprise#5.

Per the open-core pattern (#847), all algorithmic IP (TOTP verify, AES secret store, recovery codes, per-role policy) lives in the private trinity-enterprise submodule (separate PR). This PR adds only the edition-agnostic login seam + the entitlement-gated Vue surface.

Backend seam

  • services/mfa_gate.py — the single hook the enterprise provider registers into. No provider (OSS-only build) → gate_login() returns None → login is byte-for-byte unchanged. Fail-open on provider error.
  • dependencies.pycreate_mfa_challenge_token / decode_mfa_challenge; a challenge-scoped token is rejected as a session token in get_current_user + decode_token.
  • routers/auth.py/token and /api/auth/email/verify consult mfa_gate after the first factor; if a second factor is required they return a short-lived challenge token instead of an access token (audited mfa_challenge_issued).
  • models.pyToken gains optional mfa_required / challenge_token fields.
  • docker/backend/Dockerfilepyotp (the enterprise module runs in this image).

Frontend (gated by 2fa in GET /api/settings/feature-flags)

  • Settings → Security tab (TwoFactorPanel.vue): self-service enroll / confirm / disable / recovery codes; admin-only org policy.
  • Login.vue: second-factor step (verify + forced-enroll) after the password/email factor; QrCode.vue renders the otpauth URI with a graceful manual-key fallback.

Edition behavior

Build Security tab Login step /api/enterprise/2fa/*
OSS-only hidden none 404
Enterprise, entitled visible (all users) when enrolled/required 200

Testing

Verified end-to-end on a live stack (enroll → confirm → gated re-login → login/verify → working token; replay rejected; recovery-code login; challenge-token-as-session rejected). Enterprise unit + real-DB pytest 20/20.

Depends on the enterprise PR for the actual module; this seam is inert until the submodule registers a provider.

Related to Abilityai/trinity-enterprise#5

🤖 Generated with Claude Code

OSS side of two-factor auth (enterprise issue #5). All IP (TOTP verify,
secret store, recovery codes, policy) lives in the private trinity-enterprise
submodule; this PR adds only the edition-agnostic seam + the entitlement-gated
Vue surface, consistent with the open-core pattern (#847).

Backend seam:
- services/mfa_gate.py — single hook the enterprise provider registers into.
  No provider (OSS-only build) → gate_login() returns None → login unchanged.
  Fail-open on provider error (never lock everyone out of login).
- dependencies.py — create_mfa_challenge_token / decode_mfa_challenge; a
  challenge-scoped token is rejected as a session token in get_current_user
  and decode_token.
- routers/auth.py — /token and /api/auth/email/verify consult mfa_gate after
  the first factor; when a second factor is required they return a short-lived
  challenge token instead of an access token (audited as mfa_challenge_issued).
- models.py — Token gains optional mfa_required / challenge_token fields.
- Dockerfile — pyotp (used by the enterprise module running in this image).

Frontend (gated by `2fa` in GET /api/settings/feature-flags):
- Settings → Security tab (TwoFactorPanel): self-service enroll/confirm/
  disable/recovery; admin-only org policy.
- Login.vue: second-factor step (verify + forced-enroll) after the password/
  email factor; QrCode.vue renders the otpauth URI (graceful manual-key
  fallback). auth store carries the challenge through to the real token.

OSS-only builds: no Security tab, no login step, /api/enterprise/2fa/* → 404.

Related to Abilityai/trinity-enterprise#5

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant