Skip to content

feat(auth)!: ready-made interactive auth layer with real enforcement#81

Merged
antfu merged 1 commit into
mainfrom
feat/anonymous-auth-recipe-and-server
Jul 9, 2026
Merged

feat(auth)!: ready-made interactive auth layer with real enforcement#81
antfu merged 1 commit into
mainfrom
feat/anonymous-auth-recipe-and-server

Conversation

@antfubot

@antfubot antfubot commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the auth-side upstream proposals from the Vite DevTools 0.6 migration writeup (proposals #5, #1, #2, #4, and the terminals doc clarification #6), landed together per the stated dependency order (#5#1#2; #4/#6 independent).

#5anonymous: as the single source of truth for pre-auth RPC methods (breaking, wire-level, @internal)

  • devframe/constants exports ANONYMOUS_RPC_PREFIX ('anonymous:') and isAnonymousRpcMethod(name).
  • Renamed the two handshake methods so the prefix rule has no exceptions:
    • devframe:anonymous:authanonymous:devframe:auth
    • devframe:auth:exchangeanonymous:devframe:auth:exchange
  • devframe:auth:revoked (server→client) is untouched — it's an event, never gated.

#1devframe/recipes/interactive-auth

createInteractiveAuth(ctx, options) packages the OTP protocol devframe's primitives already implement (exchangeTempAuthCode, verifyAuthToken, revokeAuthToken, getTempAuthCode, buildOtpAuthUrl) into a DevframeAuthLayer:

  • rpcFunctions — the handshake pair plus the new self-revoke method (feat!: migrate to h3 v2 #4).
  • authorize(methodName, session) — the resolver gate: anonymous: methods always pass, everything else needs session.meta.isTrusted.
  • onConnect(peer, session) — connect-time trust: reads a bearer off the WS upgrade URL's ?devframe_auth_token= and trusts the session before the client's own handshake call arrives.
  • printBanner() — prints the code + magic-link URL once per code. Devframe stays headless; nothing prints until the host calls this.

The recipe closes over the auth storage internally — no devframe/node/hub-internals reach from the host.

#2startHttpAndWs owns the authenticated server

  • auth?: boolean | DevframeAuthLayer — passing a layer registers its rpcFunctions, wires authorize as the resolver gate, and wires onConnect on every new peer.
  • authorize? / onPeerConnect? — lower-level escape hatches for a custom policy without a full layer.
  • The resolver now actually enforces the gate (previously session.meta.isTrusted was written but never read) — a rejected call throws the new DF0035.
  • StartedServer.connectionMeta() returns the __connection.json shape ({ backend, websocket, jsonSerializableMethods }) a host would otherwise hand-roll.
  • auth: boolean preserves today's behavior exactly (verified — existing dedicated-port / auth:false tests pass unchanged).

#4 — Client self-revoke + forgetAuthToken()

  • New trusted-only server method devframe:auth:revoke (self-revoke), registered by the interactive-auth layer.
  • DevframeRpcClient.revokeSelf() — calls it, forgets the persisted token, and broadcasts to sibling tabs over the existing devframe-auth channel so they forget too.
  • Standalone forgetAuthToken() export for integrations managing their own auth UI.

#6 — Terminals hub-aggregation model

Documents (in docs/plugins/terminals.md, cross-linked from docs/guide/hub.md) that ctx.terminals is the single source of truth for terminal sessions, and the terminals plugin is the PTY-capable renderer that mirrors into it — a session started via ctx.terminals.startChildProcess directly and one started from the plugin UI land in the same list.

Testing

  • New unit + integration tests in packages/devframe/src/recipes/__tests__/interactive-auth.test.ts, including a full round-trip over a real WS connection (untrusted connect → rejected call → handshake → code exchange → trusted → reconnect with the returned bearer, no new code) and a self-revoke round-trip.
  • pnpm lint && pnpm test && pnpm typecheck && pnpm build all pass. The only pre-existing failures (8 tsnapi snapshot mismatches from import("pkg").X vs a rolldown-namespace alias formatting difference, unrelated to this change) reproduce identically on unmodified main with a fresh pnpm install.

Generated with the help of an agent.

@netlify

netlify Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit 6594ff5
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a4f62352861190009821cfd
😎 Deploy Preview https://deploy-preview-81--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@antfubot antfubot force-pushed the feat/anonymous-auth-recipe-and-server branch 2 times, most recently from 638323b to d413ef4 Compare July 9, 2026 08:28
Implements the auth-side upstream proposals surfaced by the Vite DevTools
0.6 migration (devframes/devframe issues): a single pre-trust rule, a
packaged OTP auth handler, and an authenticated `startHttpAndWs`.

BREAKING CHANGE (wire-level, `@internal` methods): the two pre-auth
handshake methods are renamed to carry the `anonymous:` prefix so the
pre-trust gate has one rule to apply, not a hand-maintained allowlist:

  - `devframe:anonymous:auth` -> `anonymous:devframe:auth`
  - `devframe:auth:exchange` -> `anonymous:devframe:auth:exchange`

- `devframe/constants` exports `ANONYMOUS_RPC_PREFIX` / `isAnonymousRpcMethod`,
  the single source of truth for what's callable before trust.
- `devframe/recipes/interactive-auth`'s `createInteractiveAuth(ctx, options)`
  packages the OTP protocol devframe's primitives already implement
  (`exchangeTempAuthCode`, `verifyAuthToken`, `revokeAuthToken`,
  `getTempAuthCode`, `buildOtpAuthUrl`) into a `DevframeAuthHandler` —
  handshake RPC functions, the resolver gate, a connect-time trust hook
  that reads a bearer off the WS upgrade URL, and the code/link banner.
  The auth storage stays internal to the handler.
- `startHttpAndWs` accepts that handler (or a lower-level `authorize` /
  `onPeerConnect` pair) directly via `auth`, and now actually enforces
  it: an untrusted call to a non-`anonymous:` method throws `DF0036`.
  `StartedServer.connectionMeta()` returns the `__connection.json` shape
  a host would otherwise hand-roll.
- A trusted-only `devframe:auth:revoke` server method lets a client give
  up its own session token.
- Docs: rewrite the Security guide's auth-methods table for the new
  names and the ready-made handler, add the `DF0036` error page, and add
  an Interactive Auth helper page.

Also clarifies the terminals hub-aggregation model (`ctx.terminals` as
the single source of truth for sessions, the plugin as the PTY-capable
renderer that mirrors into it) in the terminals plugin docs, closing out
the last of the five proposals.

Generated with the help of an agent.
@antfubot antfubot force-pushed the feat/anonymous-auth-recipe-and-server branch from d413ef4 to 6594ff5 Compare July 9, 2026 08:56
@antfu antfu merged commit 1e17723 into main Jul 9, 2026
11 of 12 checks passed
@antfu antfu deleted the feat/anonymous-auth-recipe-and-server branch July 9, 2026 09: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