-
Notifications
You must be signed in to change notification settings - Fork 1
Two Factor Auth
Sia edited this page May 31, 2026
·
3 revisions
RFC 6238 implementation with zero external dependencies — only
javax.crypto.Mac (HmacSHA1) + a tiny Base32 encoder. Compatible with
Google Authenticator, 1Password, Authy, etc.
Enable at /2fa (left nav: "2단계 인증").
- Password-only login was OK for a LAN-only single-operator tool, but
vibe-coder-serveris now occasionally exposed over SSH tunnels and reverse proxies. A second factor cuts the blast radius of a leaked password. - TOTP is offline (no SMS / push provider), free, and supported by every major authenticator app.
- Implementing RFC 6238 from scratch (about 150 LOC including Base32) was
cheaper than adding a
com.warrenstrange:googleauthordev.samstevens.totpdependency.
- Sign in normally → click 2단계 인증 in the left navigation.
- The page shows your
otpauth://…URI plus the Base32 secret in 4-character groups. Either:- Convert the URI to a QR code (
qrencode -t ANSI '<uri>'in a terminal, or a browser-based QR generator) and scan with Authenticator, or - Tap "Enter a setup key" in Authenticator and paste the Base32 secret.
- Convert the URI to a QR code (
- Authenticator shows a 6-digit code that rotates every 30 s. Enter the current code in the form and submit.
- The server verifies the code (window
±1to absorb clock drift) and stores the secret inadmin_users.totp_secret. The setup page now shows "✓ 현재 활성" with the enablement timestamp.
-
POST /api/auth/login(or the SSR form) with{username, password}. - Server returns
401 totp_required(audited as such — not counted as a failed login). - Client prompts for the 6-digit code, resubmits with
totpCodefield added. SSR keeps the username + password in hidden inputs for round 2. - Server verifies the code; on success issues the token + cookie as
usual. On failure returns
401 invalid_totp(counted against the account / IP brute-force limits).
LoginRequestDto.totpCode defaults to null so clients continue to work
for accounts without 2FA. Clients should handle totp_required to surface
a second-step UI.
/2fa shows a destructive form requiring a current code. On submit the
totp_secret row is cleared. Future logins skip step 2.
If you lose access to the Authenticator (phone wiped, app deleted), the only recovery is to:
- Connect to the host with shell access.
docker exec -it vibe-coder-postgres psql -U vibecoder vibecoderUPDATE admin_users SET totp_secret = NULL, totp_enabled_at = NULL WHERE username = 'admin';
Single-admin tool, no recovery codes / SMS backup. Treat the secret like a keystore.
| Attack | Mitigation |
|---|---|
| Password leak | TOTP code required as second factor |
| TOTP secret leak | Treat as critical — manual UPDATE to rotate; investigate audit log |
| Clock drift |
verify() accepts ±1 slot (±30 s) |
| Replay of a code within the same slot | RFC 6238 doesn't prevent this; rate limit + 30 s slot makes practical replay unlikely |
| Brute force the 6-digit code | Combined with existing brute-force limits (10 fails/account/15 min, 30 fails/IP/24 h) |
| Action | Logged as |
|---|---|
| Enable | auth.2fa.enable |
| Disable | auth.2fa.disable |
| Successful login with 2FA |
auth.login OK |
| Wrong TOTP code |
auth.login FAIL (reason=invalid_totp) |
| Missing TOTP code | (intentional — totp_required is a normal step, not a failure) |
- WebAuthn / passkey — see WebAuthn (Passkey). Both methods are available concurrently; the login page exposes a "🔑 Passkey 로 로그인" button next to the password form.