Add optional TOTP 2FA to admin login#36
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds optional TOTP-based two-factor authentication to the single-admin login. When enabled, login requires a 6-digit TOTP code (or a one-time recovery code) on top of the existing admin password. Implemented with
github.com/pquerna/otp, stored in the existingsettingskey-value table (no schema migration), and recoverable via backup codes or adocker execCLI command so a lost authenticator never permanently locks the admin out.Enforcement is login-only — the session is already 2FA-backed, so the withdraw handler keeps its existing password re-prompt (no 30s-code friction during a payout).
Design spec:
docs/superpowers/specs/2026-06-15-admin-totp-2fa-design.mdImplementation plan:
docs/superpowers/plans/2026-06-15-admin-totp-2fa.mdWhat's included
Backend
web/totp.go— TOTP generate/validate (±1 step skew) + recovery-code generation (10 codes, 64-bit, bcrypt-hashed, shown once).web/admin_api_2fa.go— settings helpers +GET status,POST setup,POST enable,POST disableendpoints (all session-protected).web/admin_api.go—adminApiLoginnow requires a valid TOTP or single-use recovery code before issuing a session when 2FA is enabled.web/admin_api_settings.go— settings list redacts the_secretsuffix (the TOTP secret never leaks).cli.go—DisableAdmin2FArecovery command (docker exec -it card ./app DisableAdmin2FA).Caddyfile—/2fa/enableand/2fa/disableadded to the auth-endpoint rate-limit zone.Frontend (admin SPA)
hooks/use-auth.tsx—login(password, code?)+TotpRequiredError; surfaces a rejected-code error.pages/login.tsx— reveals a code field (with an authenticator/recovery-code toggle) when a second factor is required.components/two-factor-card.tsx— Settings-page card driving enrollment (QR + confirm → recovery codes shown once) and disable (password-confirmed).Storage (settings table, no migration):
admin_totp_enabled("Y"/"N", gates enforcement),admin_totp_secret(base32, redacted),admin_totp_recovery_hash(JSON array of bcrypt-hashed single-use codes, redacted).Versioning: bumped
0.19.9 → 0.20.0(SemVer minor — new backward-compatible feature); CLAUDE.md updated to adopt SemVer and document the feature.Test Plan
Automated (all green):
go test -race -count=1 ./...and frontendnpm run build.totpRequired(no session issued); valid TOTP → session; invalid code → 401; recovery code single-use (then rejected); wrong password + valid code → still rejected; 2FA-off password-only still works.Manual (recommended on the test hub after deploy):
docker exec -it card ./app DisableAdmin2FA→ next login is password-only.admin_totp_secretas REDACTED.🤖 Generated with Claude Code