feat(openapi): redesign auth pipeline with per-token-type routing#36693
Merged
Conversation
…ith PipelineRouter Replace the single mutable-context Pipeline with a two-phase, condition-driven system dispatched by token type. New architecture: - TokenType(StrEnum) replaces source: str on AuthContext / TokenKind - AuthPipeline: pure prepare→auth step runner; no guard() - PipelineRoute: binds AuthPipeline to an optional required_edition gate - PipelineRouter: single guard() entry point; runs edition/license/token-type pre-gates then dispatches to the registered pipeline for the token type - Cond / When: composable predicates for conditional step dispatch - AuthData: frozen Pydantic model produced by the prepare phase; carries token_id so endpoints don't need to call get_auth_ctx() for identity fields - Edition enum + current_edition(): CE / EE / SAAS discriminator Two pipelines in composition.py: - account_pipeline — OAUTH_ACCOUNT tokens - external_sso_pipeline — OAUTH_EXTERNAL_SSO tokens (EE enforced at route level) All /openapi/v1 endpoints migrated to auth_router.guard(). Old context.py, steps.py, strategies.py, surface_gate.py deleted. WORKSPACE_READ scope added; cached_verdicts renamed to membership_cache.
…ce stacked null guards
Contributor
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-05-27 11:22:24.013629207 +0000
+++ /tmp/pyrefly_pr.txt 2026-05-27 11:22:09.885514490 +0000
@@ -2189,58 +2189,6 @@
--> tests/unit_tests/controllers/inner_api/plugin/test_plugin_wraps.py:342:35
ERROR Missing argument `payload` in function `protected_view` [missing-argument]
--> tests/unit_tests/controllers/inner_api/plugin/test_plugin_wraps.py:348:36
-ERROR Object of class `Step` has no attribute `_mounters` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/auth/test_composition.py:52:31
-ERROR Argument `Literal['apps:run']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_context.py:5:34
-ERROR Argument `Literal['apps:run']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_context.py:19:34
-ERROR `frozenset[str]` is not assignable to attribute `scopes` with type `frozenset[Scope]` [bad-assignment]
- --> tests/unit_tests/controllers/openapi/auth/test_context.py:20:18
-ERROR Argument `Literal['x']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_pipeline.py:18:65
-ERROR Argument `Literal['x']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_pipeline.py:34:61
-ERROR Argument `Literal['apps:run']` is not assignable to parameter `scope` with type `Scope` in function `controllers.openapi.auth.pipeline.Pipeline.guard` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_pipeline.py:49:27
-ERROR Argument `Literal['apps:run']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_app_resolver.py:13:35
-ERROR Object of class `NoneType` has no attribute `id` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/auth/test_step_app_resolver.py:63:12
-ERROR Object of class `NoneType` has no attribute `id` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/auth/test_step_app_resolver.py:64:12
-ERROR Argument `Literal['apps:run']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_authz.py:14:32
-ERROR `str | Unknown` is not assignable to attribute `account_id` with type `UUID | None` [bad-assignment]
- --> tests/unit_tests/controllers/openapi/auth/test_step_authz.py:17:20
-ERROR `SimpleNamespace` is not assignable to attribute `app` with type `App | None` [bad-assignment]
- --> tests/unit_tests/controllers/openapi/auth/test_step_authz.py:18:13
-ERROR `SimpleNamespace` is not assignable to attribute `tenant` with type `Tenant | None` [bad-assignment]
- --> tests/unit_tests/controllers/openapi/auth/test_step_authz.py:19:16
-ERROR Argument `() -> SimpleNamespace` is not assignable to parameter `resolve_strategy` with type `() -> AppAuthzStrategy` in function `controllers.openapi.auth.steps.AppAuthzCheck.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_authz.py:70:23
-ERROR Argument `() -> SimpleNamespace` is not assignable to parameter `resolve_strategy` with type `() -> AppAuthzStrategy` in function `controllers.openapi.auth.steps.AppAuthzCheck.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_authz.py:76:19
-ERROR Argument `Literal['apps:run']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_bearer.py:22:35
-ERROR Argument `Literal['apps:read']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_layer0.py:18:32
-ERROR `SimpleNamespace | None` is not assignable to attribute `tenant` with type `Tenant | None` [bad-assignment]
- --> tests/unit_tests/controllers/openapi/auth/test_step_layer0.py:21:16
-ERROR Argument `Literal['apps:run']` is not assignable to parameter `required_scope` with type `Scope` in function `controllers.openapi.auth.context.Context.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_mount.py:15:32
-ERROR `SimpleNamespace` is not assignable to attribute `app` with type `App | None` [bad-assignment]
- --> tests/unit_tests/controllers/openapi/auth/test_step_mount.py:19:13
-ERROR `SimpleNamespace` is not assignable to attribute `tenant` with type `Tenant | None` [bad-assignment]
- --> tests/unit_tests/controllers/openapi/auth/test_step_mount.py:20:16
-ERROR Argument `test_caller_mount_dispatches_by_subject_type.Fake` is not assignable to parameter `*mounters` with type `CallerMounter` in function `controllers.openapi.auth.steps.CallerMount.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_mount.py:68:9
-ERROR Argument `test_caller_mount_dispatches_by_subject_type.Fake` is not assignable to parameter `*mounters` with type `CallerMounter` in function `controllers.openapi.auth.steps.CallerMount.__init__` [bad-argument-type]
- --> tests/unit_tests/controllers/openapi/auth/test_step_mount.py:69:9
-ERROR `in` is not supported between `Literal['wrong_surface']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/auth/test_surface_gate.py:93:20
-ERROR `in` is not supported between `Literal['/openapi/v1/apps']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/auth/test_surface_gate.py:96:20
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
--> tests/unit_tests/controllers/openapi/test_account.py:40:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
@@ -2297,24 +2245,26 @@
--> tests/unit_tests/controllers/openapi/test_workspaces.py:49:12
ERROR `in` is not supported between `Literal['GET']` and `None` [not-iterable]
--> tests/unit_tests/controllers/openapi/test_workspaces.py:50:12
+ERROR Could not find name `AuthData` [unknown-name]
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:107:42
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:164:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:176:12
ERROR `in` is not supported between `Literal['POST']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:165:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:177:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:170:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:182:12
ERROR `in` is not supported between `Literal['GET']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:171:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:183:12
ERROR `in` is not supported between `Literal['POST']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:172:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:184:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:177:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:189:12
ERROR `in` is not supported between `Literal['DELETE']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:178:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:190:12
ERROR Object of class `FunctionType` has no attribute `view_class` [missing-attribute]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:183:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:195:12
ERROR `in` is not supported between `Literal['PUT']` and `None` [not-iterable]
- --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:184:12
+ --> tests/unit_tests/controllers/openapi/test_workspaces_members.py:196:12
ERROR Object of class `NoneType` has no attribute `json`
ERROR Object of class `NoneType` has no attribute `json`
ERROR Cannot index into `Iterable[bytes]` [bad-index]
@@ -6129,13 +6079,13 @@
ERROR `SimpleNamespace` is not assignable to attribute `_current_tenant` with type `Tenant | None` [bad-assignment]
--> tests/unit_tests/libs/test_login.py:247:35
ERROR Argument `Literal['apps:read']` is not assignable to parameter `scope` with type `Scope` in function `libs.oauth_bearer.require_scope` [bad-argument-type]
- --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:61:20
+ --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:62:20
ERROR Argument `Literal['apps:write']` is not assignable to parameter `scope` with type `Scope` in function `libs.oauth_bearer.require_scope` [bad-argument-type]
- --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:70:20
+ --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:71:20
ERROR Argument `Literal['apps:write']` is not assignable to parameter `scope` with type `Scope` in function `libs.oauth_bearer.require_scope` [bad-argument-type]
- --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:81:20
+ --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:82:20
ERROR Argument `Literal['apps:read']` is not assignable to parameter `scope` with type `Scope` in function `libs.oauth_bearer.require_scope` [bad-argument-type]
- --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:90:20
+ --> tests/unit_tests/libs/test_oauth_bearer_require_scope.py:91:20
ERROR Argument `_FakeRedis` is not assignable to parameter `redis_client` with type `_RateLimiterRedisClient` in function `libs.helper.RateLimiter.__init__` [bad-argument-type]
--> tests/unit_tests/libs/test_rate_limiter.py:44:22
ERROR Argument `dict[@_, @_]` is not assignable to parameter `headers` with type `Message` in function `python_http_client.exceptions.HTTPError.__init__` [bad-argument-type]
|
Contributor
Pyrefly Type Coverage
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #36693 +/- ##
==========================================
- Coverage 85.85% 85.84% -0.02%
==========================================
Files 4534 4536 +2
Lines 220457 220490 +33
Branches 40660 40666 +6
==========================================
+ Hits 189284 189288 +4
- Misses 27601 27636 +35
+ Partials 3572 3566 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
wylswz
reviewed
May 27, 2026
Contributor
|
There are some conflicts. |
…rier - Remove frozen=True from AuthData; add path_params field - Construct AuthData directly in _run(), inline ExternalIdentity from identity.subject_email - Delete _init_builder, pop() calls, and AuthData(**builder) splat - Prepare steps take (data: AuthData) instead of (builder: dict) - Remove build_external_identity step; composition and tests updated - Add InternalServerError guard in load_tenant when app not loaded - Use explicit None checks in resolve_external_user for type narrowing
…source→token_type in tests
… WORKSPACE_WRITE scope
wylswz
approved these changes
May 27, 2026
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
TokenTypediscriminator (oauth_account,oauth_external_sso) on bearer tokens, enabling correct routing for future token kinds (e.g. PATs) that share the same subject typeWORKSPACE_READscope used by the/workspacesendpointTest plan
pytest api/tests/unit_tests/controllers/openapi/— all pipeline, condition, and endpoint testspytest api/tests/unit_tests/libs/test_oauth_bearer_rate_limit_ordering.py api/tests/unit_tests/libs/test_workspace_member_helper.pyGET /openapi/v1/accountreturns account info403 unsupported_token_type403 external_sso_requires_ee