Skip to content

feat(server): device flow stubs (Axum + Python) + NestJS controllers#530

Closed
DorianZheng wants to merge 1 commit into
mainfrom
feat/auth-server-stubs
Closed

feat(server): device flow stubs (Axum + Python) + NestJS controllers#530
DorianZheng wants to merge 1 commit into
mainfrom
feat/auth-server-stubs

Conversation

@DorianZheng

@DorianZheng DorianZheng commented May 14, 2026

Copy link
Copy Markdown
Member

Summary

Server-side scaffolding for the bearer auth contract + RFC 8628 device flow. Three independent stacks land in one PR because they share only the OpenAPI surface and don't touch each other.

Axum reference server (boxlite serve)

  • src/cli/src/commands/serve/handlers/auth.rsdevice_code / token / revoke stubs that auto-complete every poll. Implements RFC 8628 server-side, lets the CLI flow be exercised end-to-end against a local target without standing up the NestJS gateway.
  • src/cli/src/commands/serve/handlers/me.rsGET /v1/me returns a fixed local-anonymous Principal.
  • src/cli/src/commands/serve/{mod,types}.rs, handlers/mod.rs — routing + wire-type cleanup.

Python reference server (openapi/reference-server/server.py)

Mirrors the Axum stubs for the FastAPI-based reference target.

NestJS gateway (apps/api/src/boxlite-rest/)

  • BoxliteMeController — maps OrganizationAuthContextPrincipalDto.
  • dto/principal.dto.ts — DTO matching the OpenAPI schema.
  • Deletes the old BoxliteAuthController (the client_credentials handler, never reached production).

No NestJS OAuth controller in this PR. The device-flow wire endpoints (/v1/oauth/*) are not in the OpenAPI spec (dropped in #531 follow-up to #527), so the gateway doesn't need to scaffold them. When the real @node-oauth/node-oauth2-server backend lands, that work will add the controller + the spec paths together in one coherent commit. Until then, spec and gateway stay in sync.

The local-dev path still works end-to-end: the Axum reference server's RFC 8628 stubs absorb the CLI's device-flow client, so boxlite auth login --web --url http://localhost:8080 is fully exercisable without the real backend.

Dashboard

  • apps/dashboard/src/pages/Onboarding.tsx — minor copy update for the API key path.

Stacked PRs

Independent of the SDK / CLI PRs. Base is main.

Test plan

  • cargo check -p boxlite-cli (Axum stubs compile)
  • cd apps/api && pnpm build (gateway compiles without BoxliteOAuthController)
  • boxlite serve --port 8080 + boxlite auth login --web --url http://localhost:8080 end-to-end against Axum stubs
  • Python: python openapi/reference-server/server.py + curl -X POST localhost:8000/v1/oauth/device_code
  • NestJS: GET /v1/me returns a valid Principal for an authenticated request

…trollers

Server-side scaffolding for the dual-bearer + device-flow contract.
Three independent stacks land in one PR because they share only the
OpenAPI surface and don't touch each other:

Axum reference server (boxlite serve, src/cli/src/commands/serve/):
- handlers/auth.rs        device_code / token / revoke stubs that
                          auto-complete every poll. Lets the CLI flow be
                          exercised end-to-end against a local target
                          without standing up the NestJS gateway.
- handlers/me.rs          GET /v1/me — fixed local-anonymous Principal.
- handlers/mod.rs         register auth + me modules.
- mod.rs / types.rs       routing + small wire-type cleanup.

Python reference server (openapi/reference-server/server.py):
- Mirrors the Axum stubs for the FastAPI-based reference target.

NestJS gateway (apps/api/src/boxlite-rest/):
- BoxliteMeController     maps OrganizationAuthContext → PrincipalDto.
- BoxliteOAuthController  returns 503 temporarily_unavailable on all
                          /v1/oauth/* until @node-oauth/node-oauth2-server
                          + Postgres entities + consent UI land (backend
                          follow-up). Keeps the spec contract honoured
                          while signalling "not ready" to clients.
- dto/principal.dto.ts    Principal DTO matching the OpenAPI schema.
- Deletes boxlite-auth.controller.ts — the old client_credentials grant
  handler that never reached production.

Dashboard (apps/dashboard/src/pages/Onboarding.tsx):
- Minor onboarding copy update to mention the new API key path.

Wire protocol on feat/auth-single-bearer-impl.
SDK / CLI consumers on feat/auth-rest-credential + feat/auth-cli-device-flow.
@app.post("/v1/oauth/device_code")
async def device_code(request: Request):
"""RFC 8628 §3.1 — start a device authorization flow. Auto-completes."""
_body = await request.form()
@app.post("/v1/oauth/revoke")
async def oauth_revoke(request: Request):
"""RFC 7009 §2.2 — always 200, idempotent."""
_body = await request.form()
DorianZheng added a commit that referenced this pull request May 15, 2026
PR #527 added /v1/oauth/device_code, /v1/oauth/token, /v1/oauth/revoke
endpoints + their request/response schemas to the spec. On review, those
spec entries turn out to be redundant and contract-drifting:

Zero code consumers in the BoxLite tree read the OAuth schemas:
- Python/Node SDKs expose only api_key (PyBoxliteRestOptions /
  JsBoxliteRestOptions). Go/C SDKs have no REST surface at all.
- The Rust SDK's refresh_oauth() in src/boxlite/src/rest/client.rs
  posts hand-coded RFC 8628 forms — never reads the spec.
- The CLI's commands::auth::device::login() hand-codes RFC 8628 forms.

Industry alignment — none of these put OAuth endpoints in their spec:
Stripe, GitHub, DigitalOcean, Anthropic, OpenAI. Only identity-provider
products (Auth0, Okta) co-locate. Their business is auth; ours isn't.

The wire format is fully defined by published IETF RFCs:
  - RFC 8628 §3.1 — device authorization request/response
  - RFC 6749 §4.4 / §6 — token exchange + refresh
  - RFC 7009         — token revocation

Restating those in OpenAPI adds maintenance churn with no precision
gain — implementers follow the RFC either way.

Contract-drift fix: PR #530's BoxliteOAuthController in apps/api returns
503 temporarily_unavailable on every /v1/oauth/* route because the real
OAuth server isn't built. With the endpoints out of the spec, the
gateway no longer promises something it can't deliver. When the
@node-oauth/node-oauth2-server backend lands, that work will add both
the controller and the spec paths in one coherent commit.

Spec changes:
- Remove /v1/oauth/device_code, /v1/oauth/token, /v1/oauth/revoke paths
- Remove DeviceAuthorizationRequest/Response, OAuthTokenRequest/Response,
  OAuthRevokeRequest, OAuthError schemas
- Update info.description to point at the IETF RFCs for the wire format
- BearerAuth.description: keep the four-token-source list; soften
  blo_/blr_ prefix descriptions to reference the RFCs not specific paths
@DorianZheng

Copy link
Copy Markdown
Member Author

Superseded by #532 — three logical commits (SDK + CLI + server) consolidated into one PR. BoxliteOAuthController is dropped in the consolidated version (per #531 spec cleanup).

@DorianZheng DorianZheng deleted the feat/auth-server-stubs branch May 15, 2026 01:52
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.

1 participant