Repository navigation
feat: first-party role guard, optional user and organization on User - #31
Merged
Merged
Conversation
RequireUser(adapter, roles=[...]) and @require_auth(roles=[...]) admit a session when any of its roles grants any listed role by role_grants_access, and answer 403 insufficient_role otherwise without naming the roles. The roles come from the verified access token. An empty roles list raises at setup. The core check is Adapter.authenticate_request(roles=...), raising InsufficientRole, with has_any_role for the match on its own. User.organization_id comes from the verified access token's org_id claim, never from the cookie copy. FastAPI gains OptionalUser(adapter), which returns the User exactly when RequireUser with its defaults would and None otherwise, backed by Adapter.optional_user. Django exports user_of, which stays an accessor for guarded views. Refs #28
Bearer transport reads no cookies, so a cross-site request has no session to ride on. The JS, Go and Rust adapters apply the check to cookie sessions only; this matches them. Cookie sessions on the guard keep it. Closes #28
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 #28.
What
roles=[...]on FastAPIRequireUser(adapter, roles=...), Django@require_auth(roles=...), and the coreAdapter.authenticate/authenticate_request. Any listed role granted by any of the session's roles passes, matched with the existingrole_grants_access(the port ofroleGrantsAccessin@seamless-auth/types). Roles come from the verified access token only. A refusal raises the newInsufficientRoleand answers 403insufficient_role(FastAPI underdetail, Django undererror, as the existing guard codes are), without naming any roles. An empty list, a bare string or an empty entry is refused when the guard is built. Checks run in order: session (401), recovery (403), cross-site for cookie sessions (403), role (403).has_any_role(user, roles)is exported for checks inside a view.User.organization_id: theorg_idclaim of the verified access token, for cookie and bearer sessions alike. The cookie's unsignedorganizationIdcopy is not read.Adapter.optional_user(method, headers)returns the user exactly whenauthenticate_requestwith its defaults would, otherwiseNone(recovery sessions included). FastAPIOptionalUser(adapter)wraps it. Djangouser_ofis now in__all__; it reads whatrequire_authattached and is documented as such./authrequests. Bearer transport reads no cookies, so there is no ambient session for a cross-site request to use, and the custom transport header needs a CORS preflight. This matches the JS, Go and Rust adapters. Cookie transport and cookie sessions on the guard keep the check.OptionalUser,user_of,organization_id, and the bearer origin rule.The same guard and field are landing in the Go and Rust adapters and in
@seamless-auth/nextjs, with the same responses.Checks
uv run ruff check .: all checks passed.uv run ruff format --check .: 39 files already formatted.uv run mypy: no issues in 14 source files.uv run pytest: 526 passed.scripts/check-manifest.shpasses.