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.
Harden auth recovery flow and disable perpetual admin tokens by default #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Harden auth recovery flow and disable perpetual admin tokens by default #6
Changes from all commits
6303fdcFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚨 issue (security): Defaulting a falsy scope to 'admin' is risky and may accidentally elevate recovery privileges.
scope or "admin"makes any falsy or empty scope (e.g. missing field, empty string) behave as an admin recovery request, so validation failures can silently become admin flows. Consider either defaulting to a non-privileged scope like"user"or requiring a non-empty scope and raising if it’s falsy.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider normalizing the incoming reset token before constant-time comparison to reduce UX friction.
payload.reset_tokenis compared verbatim to a normalized session token. If the client sends the correct token with extra whitespace (e.g. from copy‑paste),compare_digestwill fail. Trimming or otherwise normalizingpayload.reset_token(for example,payload.reset_token.strip()) before comparison would handle these cases without affecting security.Suggested implementation:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Add coverage for purging expired password recovery sessions before start/verify/reset
Since
_purge_expired_password_recovery_sessionsis now invoked by all three recovery endpoints, it’d be helpful to add tests that:_password_recovery_storewith both an expired and a valid session.start_password_recovery,verify_password_recovery_identity, orreset_password_via_recovery.This will keep the purge behavior verified as part of the recovery flow.
Suggested implementation:
The above tests assume:
backend.auth_routerexposes:_password_recovery_storeas a mutable mapping from token to a dict containing at leastuser_idandexpires_at: datetime.start_password_recovery,verify_password_recovery_identity, andreset_password_via_recoverycallable as shown.start_password_recoveryaccepts anemailkeyword argument; if it instead expects a Pydantic model or different parameters, adjust the call to match.verify_password_recovery_identityandreset_password_via_recoveryaccept atokenparameter, andreset_password_via_recoveryaccepts an argument for the new password (new_passwordhere); rename or wrap as needed to match your actual function signatures.HTTPExceptionwith 404 or 410 for expired sessions. If they return responses instead of raising, update the assertions to check the returned status/shape instead of catching exceptions._password_recovery_storeon success), tighten theassert valid_token in store or valid_token not in storeline to reflect the exact expected behavior (e.g.assert valid_token not in store).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Add parametrized tests for all supported ALLOW_NON_EXPIRING_ADMIN_TOKENS values and non-admin users
This test only covers the default and a single truthy value. Since
_should_issue_non_expiring_admin_tokenaccepts multiple truthy strings and depends on admin/superuser flags, please parameterize it to cover:"1","true","yes","on", plus casing/whitespace variants) returningTruefor an admin user.Falseeven when the env var is a truthy value."false","0","random") returningFalsefor an admin user.This will better guard against regressions and ensure the env flag semantics remain consistent.
Suggested implementation:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Cover zero-padding and multi-session behavior of the recovery verification code
To strengthen this test, consider adding:
randbelowto return a small value (e.g.42) and assertverification_code == "000042"to cover the formatting logic.randbelowoutputs and assert their verification codes differ, confirming per-session randomness.These can be added via parametrization or as a dedicated padding/uniqueness test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Add a positive-path test for identity verification, including reset of attempt counter and reset-token issuance
This test covers the failure path and 401/429 behavior well. To fully cover
verify_password_recovery_identity, please also add a positive-path test that:verification_attempts> 0,verification_code,session_state["verified"]becomesTrue,session_state["verification_attempts"]is reset to0,identity_session_token,reset_token, andreset_expires_atare correctly set,reset_token.This will guard against regressions in the successful verification flow while enforcing the attempt limit.
Suggested implementation:
To wire this in cleanly you may need to:
verify_password_recovery_identity(db, recovery_session_token, verification_code)or an HTTP route tested via a test client), adjust theresponse = ...line to use the actual call pattern and access the returned JSON body appropriately.dbfixture is available in this test module (other tests in the file likely already use it; if not, add or import it).test_password_recovery_verify_identity_limits_failed_attemptsso both success and failure paths live together."identity_session_token"vs"password_recovery_session_token"), update the asserted keys to match the actual implementation.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Distinguish between unverified and missing identity session token in reset-password tests
This test covers the
verified=False→ 403 behavior. The implementation also requires a non-emptyidentity_session_token. Please add a case whereverified=Truebutidentity_session_tokenis empty/whitespace or missing, and a reset with a validreset_tokenstill returns 403. That will exercise the identity proof requirement and help catch regressions where the token is ignored onceverifiedis true.Suggested implementation:
auth_router.reset_password(...)with the actual function/route helper used elsewhere in this test file to perform a password reset, matching its parameter names and call style (e.g. usingclient.post("/reset-password", json=...)or similar).recovery_session_token,reset_token,new_password) with your actual reset handler signature.identity_session_token, you may also want to add a third subcase where the key is completely omitted from the store entry.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (testing): Add tests for scope normalization and invalid scopes in the recovery flow
The current test only covers the happy path for an
adminscope, but the implementation now normalizes and validates scopes in bothstart_password_recoveryandreset_password_via_recovery. Please add tests that:" Admin ","USER") for both start and reset, asserting that recovery succeeds, confirming consistent normalization."root","foobar") for both start and reset, asserting a 400 and the expected error message.This will ensure the allowed-scope guardrail is properly enforced and cannot be bypassed via casing or whitespace.
Suggested implementation:
The above tests assume the following, based on typical patterns in this file:
start_password_recovery(request, db)accepts arequestobject withemailandscopeattributes and raisesHTTPExceptionon invalid scope, with a message that includes "invalid scope".reset_password_via_recovery(request, db)will:scopefrom the request (trimming whitespace and normalizing case) and compare it against allowed scopes.HTTPException(status_code=400, detail="...invalid scope...")when the scope is invalid.auth_router._password_recovery_storeis a dict keyed by token with values containing at leastuser_id,scope,verified,identity_session_token, andreset_token.If your actual request/DB interfaces or error messages differ, adjust:
SimpleNamespacefields used to construct therequestobjects."invalid scope").Uh oh!
There was an error while loading. Please reload this page.