84 lines
3.7 KiB
Markdown
84 lines
3.7 KiB
Markdown
# Security Notes
|
|
|
|
This document summarizes the current security-relevant behavior of MiauInv. It is intended as implementation documentation, not as a guarantee that the application is production-ready for untrusted public deployments.
|
|
|
|
## Authentication
|
|
|
|
MiauInv uses signed JWT access tokens, database-backed refresh tokens, and optional TOTP-based two-factor authentication.
|
|
|
|
JWTs are signed with `JWT_SECRET`. They are not encrypted. Normal access tokens and purpose tokens should therefore contain only identity and authorization metadata.
|
|
|
|
The short-lived 2FA setup token is a narrow exception: it carries the not-yet-enabled TOTP secret until the first authenticator code is validated. This avoids storing the setup secret in the database before 2FA is confirmed.
|
|
|
|
## Passwords
|
|
|
|
Passwords are hashed with bcrypt. Password updates require the current password. New passwords longer than bcrypt's effective 72-byte limit are rejected.
|
|
|
|
When a password is changed:
|
|
|
|
1. The new password is bcrypt-hashed.
|
|
2. The stored password hash is updated.
|
|
3. Existing refresh tokens for the user are revoked.
|
|
4. A new session is issued for the current browser.
|
|
|
|
## Sessions
|
|
|
|
Access tokens expire after 15 minutes. Refresh tokens expire after 7 days.
|
|
|
|
Refresh tokens are stored only as hashes in the database. Refresh-token rotation revokes the used refresh token and inserts a new token hash.
|
|
|
|
Security-sensitive account changes revoke existing refresh-token sessions.
|
|
|
|
## Cookies
|
|
|
|
Authentication cookies are set as HTTP-only secure cookies using `SameSite=Lax`.
|
|
|
|
Because the cookies are marked `Secure`, local development should use HTTPS. If the application is placed behind a reverse proxy, the deployment should preserve HTTPS semantics between the user and the proxy.
|
|
|
|
## Two-Factor Authentication
|
|
|
|
TOTP 2FA is optional per account.
|
|
|
|
The setup flow returns a QR code, a manual setup key, and a short-lived setup token. The TOTP secret is stored only after the user submits a valid code from their authenticator app.
|
|
|
|
When 2FA is enabled:
|
|
|
|
1. Any previous recovery codes are deleted.
|
|
2. A new recovery-code set is generated.
|
|
3. The TOTP secret is stored.
|
|
4. Existing refresh sessions are revoked.
|
|
5. A new current session is issued.
|
|
|
|
When 2FA is disabled:
|
|
|
|
1. The TOTP secret is cleared.
|
|
2. Recovery codes are deleted.
|
|
3. Existing refresh sessions are revoked.
|
|
4. Authentication cookies are cleared.
|
|
|
|
## Recovery Codes
|
|
|
|
Recovery codes are generated with cryptographically secure randomness and stored only as hashes.
|
|
|
|
They are displayed only immediately after generation or regeneration. They cannot be recovered later because the plaintext values are not stored.
|
|
|
|
Recovery codes are single-use. During login, a submitted value is first checked as a TOTP code. If that fails, the value is normalized, hashed, and matched against unused recovery-code hashes.
|
|
|
|
The account settings UI warns the user when the remaining unused recovery-code count is low.
|
|
|
|
## Rate Limiting
|
|
|
|
Basic in-memory rate limiting protects login, 2FA, refresh, registration, and sensitive account endpoints.
|
|
|
|
This is suitable for a single-instance private deployment. It is not sufficient for multi-instance deployments because limiter state is process-local. A public or multi-instance deployment should use persistent or distributed rate limiting at the application, reverse proxy, or infrastructure layer.
|
|
|
|
## Known Limitations
|
|
|
|
- Automated testing is currently limited.
|
|
- TOTP secrets are stored in the database after confirmation because the server must validate future codes.
|
|
- TOTP secrets are not encrypted at rest.
|
|
- There is no dedicated session/device management UI yet.
|
|
- There is no audit log for account security changes yet.
|
|
- The current rate limiter is process-local and memory-only.
|
|
- Passkeys/WebAuthn are intentionally not implemented yet.
|