Skip to content

queueserver: port the tiled-v0.2.12 auth stack (upstream PR #81) + three upstream fixes - #113

Merged
Anthony Sligar (sligara7) merged 5 commits into
NSLS2:mainfrom
sligara7:port/qs-tiled-auth
Aug 3, 2026
Merged

queueserver: port the tiled-v0.2.12 auth stack (upstream PR #81) + three upstream fixes#113
Anthony Sligar (sligara7) merged 5 commits into
NSLS2:mainfrom
sligara7:port/qs-tiled-auth

Conversation

@sligara7

Copy link
Copy Markdown
Collaborator

Ports bluesky/bluesky-httpserver#81 (merged 2026-08-03), which re-aligns the auth stack with tiled v0.2.12 after ~3 years of divergence, keeping this fork wire-compatible with the OIDC login workflows bluesky-queueserver-api is adding (bluesky/bluesky-queueserver-api#62).

Adopted from upstream (imports rewritten to queueserver_service.http):

  • authenticators.py: OIDC incl. Entra, proxied OIDC, and the device-code flow for CLI/headless clients; the per-instance mode flag is gone.
  • protocols.py (new): InternalAuthenticator/ExternalAuthenticator drive route wiring by class type.
  • database core/orm + pending-sessions migration, with automatic alembic upgrade at startup; robust shutdown; DeviceCode schemas; WebSocket first-message auth handshake (ported into the fork's split console router).

Fork behavior re-applied on top: case-insensitive WS auth schemes, WS query-param fallback, OpenAPI docs on auth routes, and the async IdP token exchange (upstream's new exchange_code is async but still calls blocking httpx.post — worth offering back upstream). Contract note: Bearer tokens are now accepted on WebSockets, matching upstream; the committed OpenAPI schema change is additive only.

Also ports the three functional fixes this fork was missing from bluesky-queueserver v0.0.25: the loop-mode UID-dict memory leak, the Python >= 3.13 sys.modules eviction guard in profile_ops, and a shadowed loop variable in annotation processing. An audit of all other upstream changes since April found them already present or not applicable.

Test-infra: the HTTP test-server port is overridable via QSERVER_TEST_HTTP_PORT; new OIDC test fixtures + upstream's authenticator/OIDC/database test modules; dev deps for local runs added as a uv dependency group.

Tests: 28 authenticator + 14 database/OIDC + 12 WebSocket-auth tests green; API-compat tests green against the real bluesky-queueserver-api client; response-model/shim/OpenAPI-drift sentinels 64 passed; 73 profile-ops + 36 queue-ops tests green. LDAP authenticator tests run in CI (need the LDAP container).

Upstream merged 'Updating authenticators from latest in Tiled' (PR NSLS2#81,
2026-08-03), re-aligning bluesky-httpserver's auth with tiled v0.2.12
after ~3 years of divergence. This port keeps the fork wire-compatible
with the OIDC login workflows the bluesky-queueserver-api client is
adding (its PR NSLS2#62).

Adopted wholesale (imports rewritten to queueserver_service.http):
authenticators.py (OIDC incl. Entra + proxied + device-code flow, mode
flag removed), protocols.py (new; class-type route wiring), database
core/orm + pending-sessions migration, schemas additions. app.py wiring,
DB auto-upgrade, robust shutdown, and the WebSocket first-message auth
handshake are hand-ported into the fork's build_app and split routers.

Fork-local behavior re-applied on top: case-insensitive WS auth schemes,
WS query-param fallback, async IdP token exchange (upstream's new
exchange_code still blocks the event loop), OpenAPI docs on auth routes.
Bearer tokens are now accepted on WebSockets (new upstream contract);
the WS test asserting they were rejected is updated accordingly.

Tests: upstream's new authenticator/OIDC/database test modules ported;
OIDC fixtures added to conftest; test server HTTP port overridable via
QSERVER_TEST_HTTP_PORT (a foreign container on 60610 otherwise absorbs
the whole suite). OpenAPI schema regenerated (additive only).

Auth suites green: 28 authenticator + 14 database/OIDC + 12 WS auth +
Side-C auth; response-model/shim/drift sentinels 64 passed.
- plan_queue_ops: remove the completed item's UID before registering the
  re-queued copy in loop mode; the UID dict otherwise grows by one entry
  per cycle (upstream memory-leak fix).
- profile_ops: only evict script-local modules from sys.modules after a
  startup-script load; unconditional eviction breaks common library
  modules on Python >= 3.13. Also drops a stray debug print.
- profile_ops: fix a shadowed loop variable in annotation processing
  (type_patterns reused as the loop variable).
The Side-B CI job collects tests/http for the OpenAPI drift test with
only the base install present; module-level cryptography/jose/respx
imports in conftest broke that collection. The auth/OIDC fixtures now
import their deps inside the fixture bodies.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

Verified issues in the new WebSocket/Bearer auth path and some schema/protocol defaults would break or mis-handle authentication at runtime unless corrected.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Ports the tiled v0.2.12-aligned authentication stack (OIDC incl. Entra/proxied/device-code), plus associated DB/schema and WebSocket auth changes, into queueserver_service to keep this fork wire-compatible with upstream and upcoming bluesky-queueserver-api OIDC workflows.

Changes:

  • Add tiled-aligned authenticator protocols and expanded OIDC/Entra/proxied-auth flows (incl. device-code) with new DB persistence for pending sessions + per-session state.
  • Update HTTP/WebSocket auth handling (including first-message WS auth handshake) and refresh OpenAPI/schema/docs accordingly.
  • Port three upstream functional fixes (loop-mode UID dict leak, Py>=3.13 sys.modules eviction guard, annotation loop var shadowing) and extend test/fixture/dev-deps coverage.
File summaries
File Description
shared-schema/queueserver_service.openapi.json OpenAPI schema updates for new auth/session fields.
backend/queueserver_service/tests/http/test_oidc_proxied_authenticators.py New tests for proxied OIDC OAuth2 schema/token extraction.
backend/queueserver_service/tests/http/test_oidc_authenticators.py New tests for OIDC authenticator caching/decoding/properties.
backend/queueserver_service/tests/http/test_database.py New tests for new Principal/Session DB fields and helpers.
backend/queueserver_service/tests/http/test_authenticators.py Expanded auth tests (LDAP/OIDC/Entra/token decoding/scope helpers).
backend/queueserver_service/tests/http/test_auth_for_websockets.py Extended WS auth tests including Bearer forwarding + first-message handshake.
backend/queueserver_service/tests/http/conftest.py New OIDC + SQLite fixtures; configurable HTTP test server port.
backend/queueserver_service/requirements-dev.txt Add dev deps needed for new auth tests (cryptography/respx).
backend/queueserver_service/queueserver_service/manager/profile_ops.py Py>=3.13 sys.modules eviction guard + annotation loop var fix.
backend/queueserver_service/queueserver_service/manager/plan_queue_ops.py Fix loop-mode UID dict growth by removing completed UID before re-queue.
backend/queueserver_service/queueserver_service/http/server.py Cache lazily-created app to avoid repeated factory calls.
backend/queueserver_service/queueserver_service/http/schemas.py Add device-code schemas + new Principal/Session fields.
backend/queueserver_service/queueserver_service/http/routers/console.py Add WS first-message auth handshake and consistent close codes.
backend/queueserver_service/queueserver_service/http/protocols.py New authenticator protocol base classes + session state DTO.
backend/queueserver_service/queueserver_service/http/database/orm.py Add session state JSON + PendingSession ORM model.
backend/queueserver_service/queueserver_service/http/database/migrations/versions/a1b2c3d4e5f6_add_pending_sessions.py New migration for pending sessions + session state column.
backend/queueserver_service/queueserver_service/http/database/core.py Add pending-session lookups + principal creation helper; bump required revision.
backend/queueserver_service/queueserver_service/http/config_schemas/service_configuration.yml Update authenticator import-path documentation/examples.
backend/queueserver_service/queueserver_service/http/authenticators.py Major port of tiled-aligned OIDC/Entra/proxied auth + async token exchange.
backend/queueserver_service/queueserver_service/http/authentication.py Major refactor: token decoding, device-code routes, WS auth support, session state embedding.
backend/queueserver_service/queueserver_service/http/app.py Route wiring driven by authenticator class type; automatic DB upgrade; robust shutdown.
backend/queueserver_service/pyproject.toml Add runtime dep (cachetools) + uv dev dependency group.
backend/queueserver_service/docs/source/http/usage.rst Document OIDC browser/device flows; test-port override note.
backend/queueserver_service/docs/source/http/configuration.rst Document OIDC/Entra authenticator configuration examples.
backend/queueserver_service/bluesky-httpserver/bluesky_httpserver/init.py Shim mapping updated to include protocols.
.gitignore Ignore local .reflow2/ directory.
Review details

Suppressed comments (2)

backend/queueserver_service/queueserver_service/http/authentication.py:574

  • authenticate_websocket_first_message() calls get_current_principal() directly but does not decode access_token into decoded_access_token, so Bearer-token auth via the first-message handshake will not work (and the default Depends(...) value can cause type errors).
    security_scopes = SecurityScopes(scopes=[])
    try:
        return get_current_principal(
            request=websocket,
            security_scopes=security_scopes,
            access_token=access_token,
            api_key=api_key,
            settings=settings,
            authenticators=authenticators,
            api_access_manager=api_access_manager,
        )

backend/queueserver_service/docs/source/http/configuration.rst:369

  • This configuration example uses the shim import path bluesky_httpserver.authenticators:EntraAuthenticator, but the rest of this repo’s docs/config schema use queueserver_service.http.authenticators:.... Using the canonical in-repo path here would avoid confusing users and keep examples consistent.
        - provider: entra
          authenticator: bluesky_httpserver.authenticators:EntraAuthenticator
          args:
  • Files reviewed: 25/27 changed files
  • Comments generated: 7
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread backend/queueserver_service/queueserver_service/http/authentication.py Outdated
Comment thread backend/queueserver_service/queueserver_service/http/protocols.py Outdated
Comment thread backend/queueserver_service/queueserver_service/http/schemas.py Outdated
Comment thread backend/queueserver_service/docs/source/http/usage.rst Outdated
Comment thread backend/queueserver_service/docs/source/http/configuration.rst
- Decode Bearer tokens explicitly on both direct WebSocket auth paths:
  calling get_current_principal() outside FastAPI left
  decoded_access_token at its Depends(...) default (a truthy sentinel),
  which broke WS Bearer auth. Invalid/expired tokens now fail closed;
  regression asserts added to the WS precedence test.
- Fix decoded_access_token annotation (Optional[dict]) and the invalid
  'Principal or None' return annotations (Optional[...], 3 sites).
- Replace mutable defaults: UserSessionState.state and Session.state
  use default_factory; schema regenerated (default no longer emitted).
- Docs: use the canonical queueserver_service.http.authenticators paths
  in configuration examples (3 sites); fix 'acheived' typo.
@sligara7

Copy link
Copy Markdown
Collaborator Author

Also addressed the two suppressed review comments in c7280ce:

  • authenticate_websocket_first_message() had the same missing-decoded_access_token defect as the header/query path; both direct WebSocket auth paths now decode via the shared _decode_ws_access_token() helper, and invalid tokens fail closed.
  • configuration.rst line 369 (entra example) used the shim import path; all provider examples now use the canonical queueserver_service.http.authenticators:... path.

@sligara7
Anthony Sligar (sligara7) merged commit e07b96c into NSLS2:main Aug 3, 2026
17 checks passed
@sligara7
Anthony Sligar (sligara7) deleted the port/qs-tiled-auth branch August 3, 2026 16:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants