Repository navigation
Add OAuth account linking and harden account safety - #41
Merged
Merged
Conversation
OAuth providers could only be attached as a side effect of signing in, on a
provider-verified email that matched an existing account. Signed-in users
can now link a provider explicitly through /oauth/{provider}/link/authorize
and /link/callback. A link state is bound to the user it was issued for and
cannot be used as a sign-in state or a session token, and an identity that
belongs to another user is never moved.
Deleting an account and setting a first password now require recent
authentication: the current password, or credentials checked within
REAUTH_MAX_AGE_SECONDS. Tokens carry an auth_time claim that refresh
rotation preserves, so rotating a stolen refresh token does not count as
proving presence. The password check is rate limited in its own bucket.
Removing a sign-in method can no longer lock an account out. Unlinking a
provider and deleting a passkey share one rule, which counts only the
methods the deployment can actually serve.
Also: refresh-token pruning and the missing user_id indexes; a
PASSWORD_MAX_LENGTH enforced on every path that hashes or verifies,
including login; password-reset links invalidated by a password change;
the Redis lockout manager failing open instead of raising; Redis clients
keyed per event loop object; an unevicted passkey challenge store; a
second provider identity attached on sign-in; and several 500s turned into
401, 409 or 501.
Breaking: DELETE /me and first-password sets from sessions older than
REAUTH_MAX_AGE_SECONDS now return 403. Existing deployments need the new
indexes; the changelog carries the SQL.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Merged
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.
Summary
OAuth account linking. Signed-in users can link a provider through
GET /oauth/{provider}/link/authorizeandPOST /oauth/{provider}/link/callback. The link state is bound to the user it was issued for, cannot be used as a sign-in state or a session token, and an identity that belongs to another user is never moved.Re-authentication for destructive actions.
DELETE /meand setting a first password need the current password or credentials checked withinREAUTH_MAX_AGE_SECONDS(300). Tokens carry anauth_timeclaim that refresh rotation preserves, so rotating a stolen refresh token does not count as proving presence. The password check is rate limited in its own bucket.No more account lockout from removing a sign-in method. Unlinking a provider and deleting a passkey share one rule, which counts only the methods the deployment can actually serve.
Also:
prune_expired_refresh_tokens()and the missinguser_id/expires_atindexesPASSWORD_MAX_LENGTH, enforced on every path that hashes or verifies, including loginBreaking changes
DELETE /meand first-password sets from sessions older thanREAUTH_MAX_AGE_SECONDSreturn403. Clients should sendcurrent_passwordor re-authenticate.PASSWORD_RESET_EXPIRE_MINUTESafter deploy.Migration
Existing deployments need three new indexes:
Checks
ruff, ruff format, mypy --strict, pytest (556 passed, 9 skipped on 3.14; also run on 3.10), mkdocs build --strict. Every new security guard was mutation-tested: removing it makes its test fail.