refactor(auth): remove web_read/web_write scope dependency#11148
Open
refactor(auth): remove web_read/web_write scope dependency#11148
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
OpenAPI ChangesNo changes detected in the OpenAPI schema. |
Contributor
|
Preview Environment |
fdf26d2 to
7294f66
Compare
8ee8a33 to
3f83453
Compare
3f83453 to
26db9b1
Compare
7294f66 to
5ad420e
Compare
26db9b1 to
e921b44
Compare
5ad420e to
038b3c6
Compare
038b3c6 to
b5af1d7
Compare
e921b44 to
64ed93c
Compare
b5af1d7 to
88392b6
Compare
64ed93c to
29229dd
Compare
88392b6 to
e1ded23
Compare
29229dd to
197bb0b
Compare
2afdeae to
592b0c1
Compare
cce19fd to
d715452
Compare
d715452 to
e408a73
Compare
592b0c1 to
8247ac1
Compare
8247ac1 to
883a748
Compare
883a748 to
a1b05b6
Compare
e234b84 to
b2b1d3c
Compare
- AuthorizeFinanceRead/Write: use finance-specific scopes - AccountPolicyGuard/PayoutAccountPolicyGuard: explicit scope requirements - Blocked org filtering in get_by_account/get_by_payout_account - Write endpoints re-fetch entities on write session - Blocked org test
Web sessions now have all scopes. Test fixtures should match to avoid false 403s from scope checks on finance endpoints.
Web sessions get all scopes at login. Remove web_read/web_write from all auth.py required_scopes. Legacy sessions transparently upgraded in middleware. Search and impersonation references cleaned up.
- is_web_session: check isinstance(session, UserSession)
- Legacy session upgrade: narrow to exact {web_read, web_write} match
- Impersonation sessions: use read-only scopes
WebUserRead/WebUserWrite/WebUserOrAnonymous now check is_web_session() to reject API tokens (PATs, OATs). Previously these depended on web_read/web_write reserved scopes for this gate, but with those scopes removed, any token could access web-only endpoints (OAuth consent, email change, PAT management, etc.). Also removes web_read/web_write remnants from OrgPolicyGuard defaults and AuthorizeMembersManage/AuthorizeOrgDelete scope requirements. Test fixture updated to provide mock UserSession for User subjects so is_web_session() returns True in tests.
Keep the original _WebUserOrAnonymous name — the underscore prefix already distinguishes it from the public WebUserOrAnonymous. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace runtime filtering (`s for s in Scope if s.value.endswith(":read")`)
with an explicit READ_ONLY_SCOPES set on the Scope module. Used for
impersonation sessions that should only have read access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove web_read/web_write from e2e test fixtures and OAT test scopes —
these scopes no longer gate anything.
Add explicit tests for the legacy session upgrade logic:
- Legacy {web_read, web_write} sessions are upgraded to all scopes
- Read-only impersonation sessions are NOT upgraded
- Partial scope sets are NOT upgraded
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AuthSubjectFixture defaults to set(Scope), so explicitly passing it is redundant. In multi-parametrize entries, an all-scopes variant alongside a specific-scope variant adds no coverage — removed the all-scopes entry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
_EventTypeWrite had required_scopes=set() after web_write removal, meaning any token could write event types without scope checks. Should require events_write. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Yopi
approved these changes
Apr 23, 2026
| return json_schema | ||
|
|
||
|
|
||
| READ_ONLY_SCOPES: set[Scope] = { |
frankie567
reviewed
Apr 24, 2026
Member
frankie567
left a comment
There was a problem hiding this comment.
A few nitpicks, but otherwise good to go :)
| ) -> AuthSubject[Anonymous | User]: | ||
| """Allow anonymous or web-session users. Reject API tokens.""" | ||
| if not is_anonymous(auth_subject) and not is_web_session(auth_subject): | ||
| raise Unauthorized() |
Member
There was a problem hiding this comment.
I would say to raise NotPermitted (403), since the user is technically authenticated, but can't access. This was the previous behavior IIRC.
| ) -> AuthSubject[User]: | ||
| """Allow web-session users only. Reject API tokens.""" | ||
| if not is_web_session(auth_subject): | ||
| raise Unauthorized() |
| Scope.organization_access_tokens_read, | ||
| } | ||
|
|
||
| RESERVED_SCOPES: set[Scope] = set() |
Member
There was a problem hiding this comment.
We can probably get rid of this one then.
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
Related Issue: #11126
Remove
web_read/web_writefrom all authorization scope checks and enforce web-session-only access onWebUser*dependencies.What
web_read/web_writeremoved from all 33auth.pyrequired_scopessetslist(Scope)) instead of{web_read, web_write}WebUserRead/WebUserWrite/WebUserOrAnonymousenforce web-session-only access viais_web_session()— API tokens are rejectedis_web_session()checksisinstance(session, UserSession){web_read, web_write}match — prevents upgrading read-only impersonation sessions*:read)web_writeremoved fromOrgPolicyGuarddefaults andAuthorizeMembersManage/AuthorizeOrgDeleteAuthorizeFinanceRead/AuthorizeFinanceWrite) use finance-specific scopes instead of defaultsset(Scope), test User subjects get mockUserSessionWhy
web_read/web_writewere never authorization scopes — they were a proxy for "is this a web session?" This conflated authentication method with authorization, and required everyauth.pyto include them as fallbacks for web sessions to pass scope checks.How
WebUserRead/WebUserWritenow wrap the authenticator with anis_web_session()check that rejects API tokensweb_read/web_writekept inScopeenum for backward compat with in-flight sessions (safe to remove after 2026-05-22){web_read, web_write}transparently upgraded in middlewareChecklist
uv run task lint && uv run task lint_types)