Two-factor authentication (2FA)
LeonAI supports TOTP (RFC 6238) two-factor authentication for account logins. The
TOTP secret is stored AES-256-GCM encrypted server-side, and enrollment yields ten
single-use recovery codes. All 2FA management endpoints require a JWT; the final
verify step during login is public (it is the second half of the login handshake).
2FA enrollment needs a TOTP_ENCRYPTION_KEY configured on the server. Without it,
POST /auth/2fa/setup returns 503.
Enroll
1. Setup — get a secret
curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/setup \
-H "Authorization: Bearer <jwt>"
REST: POST /api/v1/auth/2fa/setup → { success, data: { secret, otpauthUrl } }. The
plaintext secret (and the otpauthUrl for a QR code) is revealed only here. Load
it into an authenticator app (Google Authenticator, 1Password, Authy, …).
2. Enable — confirm a code
curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/enable \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{ "token": "123456" }'
REST: POST /api/v1/auth/2fa/enable. token is the current 6-digit code
(^\d{6}$). On success:
{ "success": true, "data": { "enabled": true, "recoveryCodes": ["…", "…"] } }
The ten recovery codes are shown only once — store them somewhere safe. Each is single-use.
Login with 2FA
When 2FA is enabled, POST /api/v1/auth/login no longer returns tokens. Instead it
returns a short-lived (5-minute) pending token:
{ "success": true, "data": { "requiresTwoFactor": true, "pendingToken": "…" } }
Complete the login by verifying a factor:
curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/verify \
-H "Content-Type: application/json" \
-d '{ "pendingToken": "…", "token": "123456" }'
REST: POST /api/v1/auth/2fa/verify (no auth header). Send either token (6-digit
TOTP) or recoveryCode (a stored recovery code, single-use). On success it returns
the normal login payload — { user, tokens: { accessToken, refreshToken, expiresIn } }.
The pending token is single-use. This endpoint is rate limited (20/5 min per IP, plus a
per-user failure counter).
Status & disable
# Is 2FA on?
curl https://api.short.leonai.io/api/v1/auth/2fa/status -H "Authorization: Bearer <jwt>"
# Turn it off (provide a current TOTP code OR a recovery code)
curl -X POST https://api.short.leonai.io/api/v1/auth/2fa/disable \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{ "token": "123456" }'
GET /api/v1/auth/2fa/status→{ success, data: { enabled: boolean } }.POST /api/v1/auth/2fa/disablerequires at least one oftokenorrecoveryCode.
Related
- Authentication — the JWT login/refresh flow this builds on