Files
MiauInv/docs/SECURITY.md
miaurizius fb3be56959
All checks were successful
test-and-lint / test-and-lint (pull_request) Successful in 2m50s
added passkey support (closes #6)
2026-06-10 03:24:31 +02:00

98 lines
5.1 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, optional TOTP-based two-factor authentication, and optional WebAuthn passkeys.
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.
## Passkeys
Passkeys use WebAuthn public-key credentials. The server stores credential metadata and public-key material, but not private keys. Private keys remain controlled by the authenticator, browser, operating system, or security key.
Passkey registration requires the current account password. Registration uses a server-side challenge stored in `passkey_challenges` and returned to the browser only as an opaque challenge token. The browser response is verified before the credential is stored.
Passkey login creates a one-time server-side challenge. The login flow uses discoverable passkeys and does not require entering a username first. User verification is required for passkey registration and login.
Passkey login is treated as a complete phishing-resistant sign-in method. Because passkey registration and login require WebAuthn user verification, the server issues a normal session after a valid passkey assertion instead of asking for an additional TOTP code.
When passkeys are added, removed, or disabled, existing refresh sessions are revoked and a fresh current session is issued.
Passkey ceremonies require HTTPS except for localhost. Reverse proxy deployments must preserve the correct public `Host`, `X-Forwarded-Host`, and `X-Forwarded-Proto` information so that the relying party origin and ID match the browser-visible origin.
## Rate Limiting
Basic in-memory rate limiting protects login, passkey ceremonies, 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.
- Passkey credential metadata and public-key data are stored in the database after registration.
- 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.