feat: add TOTP multifactor authentication
This commit is contained in:
+42
-6
@@ -9,8 +9,8 @@ The team builds the project in vertical slices.
|
||||
data
|
||||
- Auth.js-compatible server authentication with Discord OAuth and credentials
|
||||
providers
|
||||
- Argon2id password hashing, server-side sessions, CSRF protection, and role
|
||||
checks
|
||||
- Argon2id password hashing, TOTP MFA with encrypted secrets, server-side
|
||||
sessions, CSRF protection, and role checks
|
||||
- Browser `localStorage` only for anonymous build drafts
|
||||
|
||||
## Product boundaries
|
||||
@@ -41,6 +41,43 @@ Local credentials use Argon2id PHC strings with a unique random salt per
|
||||
password. Current work factors meet OWASP's 19 MiB / 2 iteration / single-lane
|
||||
minimum profile. Successful login rehashes credentials with outdated parameters.
|
||||
|
||||
## Authentication and MFA
|
||||
|
||||
Sign-in never grants administrator authority. The first full administrator is
|
||||
created through an offline database operation after the target account has
|
||||
enrolled TOTP. The local command is:
|
||||
|
||||
```sh
|
||||
npm run admin:bootstrap -- <verified-email>
|
||||
```
|
||||
|
||||
Staging and production use the stopped-container Compose workflow documented in
|
||||
[`DEPLOYMENT.md`](DEPLOYMENT.md), not a host-side npm command. The bootstrap
|
||||
operation accepts exactly one active, unblocked account with a verified email,
|
||||
requires an active TOTP factor and an unused recovery code, requires that no
|
||||
active full administrator exists, assigns the canonical `role-admin`
|
||||
relationship, revokes the target's sessions, and records
|
||||
`role_management / administrator.bootstrapped`. Administrators must enroll TOTP
|
||||
before offline elevation.
|
||||
|
||||
`MFA_TOTP_ENCRYPTION_KEYS` is a required app-only secret. Its value contains one
|
||||
to three comma-separated, unique `key-id:canonical-base64url-32-byte-key`
|
||||
entries. The first entry is the active encryption key for new factors; later
|
||||
entries retain decryption access to older factors during rotation. There is no
|
||||
generated-key fallback. Rotate by prepending a newly generated 32-byte key with
|
||||
a new ID, deploying it with the old entries retained, and verifying existing
|
||||
TOTP authentication. A successful verification rewraps that factor under the
|
||||
first key. Do not remove an old entry until every factor encrypted with it has
|
||||
been rewrapped and verified. Keep key material in the secret manager and never
|
||||
place it in source, logs, audit snapshots, or documentation.
|
||||
|
||||
TOTP enrollment issues ten one-time recovery codes. The interface displays them
|
||||
only once; the database stores only domain-separated hashes. Save the codes in
|
||||
an offline password manager or another protected offline record. Each code is
|
||||
consumed once. Regenerating codes immediately revokes all unused previous codes
|
||||
and displays a new set once. Disabling TOTP revokes its recovery codes, and a
|
||||
full administrator cannot disable TOTP.
|
||||
|
||||
## Database runtime
|
||||
|
||||
The application uses Turso Database in-process through its native Node package.
|
||||
@@ -244,10 +281,9 @@ does not become part of the canonical action replay.
|
||||
|
||||
# Administrator bootstrap
|
||||
|
||||
The authentication service promotes the first authenticated user to `admin` if
|
||||
the database does not contain an administrator. Promotion uses one conditional
|
||||
database update so concurrent first sign-ins cannot both become administrators.
|
||||
Once an administrator exists, later sign-ins never alter roles.
|
||||
The first full administrator is created only through the offline flow documented
|
||||
in [Authentication and MFA](#authentication-and-mfa). Sign-in never grants
|
||||
administrator authority or changes an account's roles.
|
||||
|
||||
# Database backup and restore
|
||||
|
||||
|
||||
Reference in New Issue
Block a user