add rate limiting and 2fa hardening
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
MiauInv uses signed JWT access tokens, database-backed refresh tokens, and optional TOTP-based two-factor authentication. The authentication flow is implemented in the `auth/`, `handlers/`, `storage/`, and `frontend/assets/js/` packages.
|
||||
|
||||
JWTs are signed with a symmetric secret from the `JWT_SECRET` environment variable. They are not encrypted. Claims should therefore contain identity and authorization metadata only, not secrets.
|
||||
JWTs are signed with a symmetric secret from the `JWT_SECRET` environment variable. They are not encrypted. Access tokens and normal purpose tokens should therefore contain identity and authorization metadata only, not secrets. The short-lived 2FA setup token intentionally carries the not-yet-enabled TOTP secret because the same secret is already returned to the authenticated browser for QR/manual setup and is not stored server-side until confirmation.
|
||||
|
||||
## Components
|
||||
|
||||
@@ -22,6 +22,7 @@ JWTs are signed with a symmetric secret from the `JWT_SECRET` environment variab
|
||||
| Access token | `access_token` HTTP-only secure cookie and JSON response body | 15 minutes | Authenticates normal API and page requests. |
|
||||
| Refresh token | `refresh_token` HTTP-only secure cookie and JSON response body | 7 days | Rotates sessions after access-token expiry. Stored in the database only as a hash. |
|
||||
| 2FA challenge token | JSON response from `/api/login` | 5 minutes | Allows `/api/login/2fa` to complete login after password verification. It is purpose-bound to `2fa_login`. |
|
||||
| 2FA setup token | JSON response from `/api/2fa/setup` | 10 minutes | Carries the not-yet-enabled TOTP secret until `/api/2fa/enable` validates the first code. It is purpose-bound to `2fa_setup`. |
|
||||
|
||||
## Normal Login Flow Without 2FA
|
||||
|
||||
@@ -72,27 +73,30 @@ The account settings page at `/profile/settings` exposes the UI for TOTP setup.
|
||||
|
||||
1. Authenticated user calls `POST /api/2fa/setup`.
|
||||
2. Server creates a TOTP secret using issuer `MiauInv` and the current username as the account name.
|
||||
3. Server stores the secret in `users.two_factor_secret`, but does not enable 2FA yet.
|
||||
4. Server returns:
|
||||
3. Server creates a short-lived setup token containing the not-yet-enabled TOTP secret.
|
||||
4. The secret is not written to `users.two_factor_secret` during setup.
|
||||
5. Server returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"secret": "BASE32SECRET",
|
||||
"setup_token": "...",
|
||||
"otpauth_url": "otpauth://totp/...",
|
||||
"qr_code": "data:image/png;base64,..."
|
||||
}
|
||||
```
|
||||
|
||||
5. The frontend displays the QR code and the manual setup key.
|
||||
6. User scans the QR code or enters the secret manually into an authenticator app.
|
||||
7. User submits a current TOTP code to `POST /api/2fa/enable`.
|
||||
8. Server validates the code.
|
||||
9. Server enables 2FA and generates recovery codes.
|
||||
10. Recovery codes are returned once to the client for download/copying.
|
||||
6. The frontend displays the QR code and the manual setup key.
|
||||
7. User scans the QR code or enters the secret manually into an authenticator app.
|
||||
8. User submits the setup token and a current TOTP code to `POST /api/2fa/enable`.
|
||||
9. Server validates the setup token and the TOTP code.
|
||||
10. Server stores the TOTP secret, enables 2FA, invalidates any previous recovery codes, and generates a fresh recovery-code set.
|
||||
11. Server revokes existing refresh sessions and issues a new current session.
|
||||
12. Recovery codes are returned once to the client for download/copying.
|
||||
|
||||
## Recovery Codes
|
||||
|
||||
Recovery codes are generated when 2FA is enabled and when the user regenerates them.
|
||||
Recovery codes are generated when 2FA is enabled and when the user regenerates them. Enabling 2FA deletes any old recovery-code rows before inserting the new set.
|
||||
|
||||
Properties:
|
||||
|
||||
@@ -123,7 +127,7 @@ Recovery-code login flow:
|
||||
}
|
||||
```
|
||||
|
||||
The server verifies the current password and a current TOTP code. If both are valid, all previous recovery codes are deleted and a new set is inserted. The new plaintext codes are returned once.
|
||||
The server verifies the current password and a current TOTP code. If both are valid, all previous recovery codes are deleted and a new set is inserted. The new plaintext codes are returned once. The account settings UI warns the user when the number of unused recovery codes is low.
|
||||
|
||||
## Disabling 2FA
|
||||
|
||||
@@ -183,6 +187,8 @@ Username changes require the current password.
|
||||
|
||||
Password changes require the current password, reject passwords longer than bcrypt's 72-byte effective limit, revoke existing refresh tokens, and issue a new session.
|
||||
|
||||
2FA activation also revokes existing refresh tokens and issues a new session for the current browser. Other devices must log in again and complete 2FA.
|
||||
|
||||
## Middleware Behavior
|
||||
|
||||
Protected routes use `AuthMiddleware`.
|
||||
@@ -206,7 +212,7 @@ If token validation fails:
|
||||
|
||||
The current implementation is usable for private/self-hosted deployments, but these improvements should be prioritized before exposing it to untrusted public traffic:
|
||||
|
||||
- Add rate limiting for login, 2FA, refresh, and recovery-code attempts.
|
||||
- Replace the current in-memory rate limiter with persistent or distributed rate limiting if the app is deployed across multiple instances.
|
||||
- Add audit logging for account security changes.
|
||||
- Add optional session/device management UI.
|
||||
- Consider encrypting TOTP secrets at rest if the deployment threat model includes database disclosure.
|
||||
|
||||
Reference in New Issue
Block a user