Repository navigation
feat(auth): optional two-factor sign-in with TOTP and recovery codes (#105) - #108
Merged
Merged
Conversation
…105) The master password derives both the login key and the vault key, so a phished or reused password was the whole account. An account can now turn on a second factor the server checks before it hands out a session or the wrapped secrets. Login for such an account answers 428 TWO_FACTOR_REQUIRED with a five-minute pending token, and POST /v1/auth/login/2fa trades it plus a code or a recovery code for the usual response. Enrolment is setup (seed + otpauth URI, needs the auth key) then enable (first code), which returns ten recovery codes and ends every earlier session. Disabling, regenerating the codes and changing the master password all take a code. Seeds are sealed with TOTP_ENC_KEY. Without the key the server reports two_factor: false in /v1/meta and refuses enrolment, but an account already enrolled is still stopped at the second step and passes it only with a recovery code. Five wrong codes lock that step for fifteen minutes, counted in Postgres, and a code is accepted once. POST /v1/admin/users/:id/2fa/clear is the way back for someone who lost the authenticator and the codes; it is audited and ends their sessions. update_email and update_password shared a fetch-and-verify of the auth key with the new handlers, now require_auth_key, and the AuthResponse built in four places comes from one auth_response.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #105. Builds on #107.
Two-factor sign-in is opt-in per account, off by default, free on every tier, and the same on a self-hosted server.
Endpoints
POST /v1/auth/login428 {error: "TWO_FACTOR_REQUIRED", pending_token, methods}, no tokens, nowrapped_user_secrets. Unchanged otherwise.POST /v1/auth/login/2fapending_token+codeorrecovery_codePOST /v1/auth/2fa/setupauth_keysecretandotpauth_uri.POST /v1/auth/2fa/enablecodePOST /v1/auth/2fa/disableauth_key+ code or recovery codePOST /v1/auth/2fa/recovery-codesPUT /v1/auth/passwordPOST /v1/admin/users/:id/2fa/clearclear_two_factor, ends the account's sessions.GET /v1/auth/megainstwo_factor_enabled,GET /v1/metagainstwo_factor, and the admin user detail gainstwo_factor_enabled.Error bodies:
TWO_FACTOR_REQUIRED(428, nothing was sent),TWO_FACTOR_INVALID(401),TWO_FACTOR_LOCKED(429),TWO_FACTOR_UNAVAILABLE(503),TWO_FACTOR_ALREADY_ENABLED/TWO_FACTOR_NOT_ENABLED(409).Operator notes
TOTP_ENC_KEY(32 bytes, base64:openssl rand -base64 32). A malformed value stops the server at boot. Unset means the feature is not offered..env.example, both compose files and the bootstrap runbook say so.Behaviour worth knowing
loginas a wrong password and anything else as a server error with its status.usersrow, not in process memory. Reaching the second step already takes the master password, so the lock cannot be used to shut a stranger out.TWO_FACTOR_REQUIREDand does not count as a wrong guess, so a client that predates the feature cannot lock its user out by changing a password.token_versionmoves.Refactor
require_auth_keyreplaces the fetch-and-verify block thatupdate_emailandupdate_passwordeach carried and the new handlers needed.auth_responsebuilds the login response for register, login, the second step and password change.bare_tokenis the shared body of the refresh and pending tokens.Tests
19 new: RFC 6238 vectors, step window, seed sealing, key parsing, recovery code normalisation, and DB-backed handler tests for sign-in, replay, lockout, enrolment, disable, regenerate, missing key, pending token expiry by version, password change, admin clear and the meta flag.
cargo test --all-targets(716 passed) and clippy clean on Postgres 16. Also driven over HTTP against a local server: register, setup, enable, 428, both second-step proofs, password change, regenerate, disable.Not in this PR: passkeys (#106), the client (VoltiusApp/voltius#615), the portal sign-in page and the dashboard button for the admin clear.