Skip to content

feat(cli): browser session login + session auth on most routes#189

Merged
saltyskip merged 2 commits into
mainfrom
feat/cli-session-auth
Jun 4, 2026
Merged

feat(cli): browser session login + session auth on most routes#189
saltyskip merged 2 commits into
mainfrom
feat/cli-session-auth

Conversation

@saltyskip
Copy link
Copy Markdown
Owner

What & why

Today the CLI authenticates only with a pasted rl_live_ secret key. This lets a human run rift login, authenticate in the browser (reusing the existing magic-link and OAuth sign-in), and have the CLI use a session token — so most routes accept a session token OR an API key. API keys stay the path for CI/automation via rift login --api-key.

Builds on #185's composable credential-flags engine (require_auth(SESSION | SECRET | …)), so enabling sessions per route is a one-flag policy edit.

Server

  • SESSION added to require_auth(...) on: links, domains (both groups), apps, webhooks, analytics, affiliates, sources CRUD, publishable-keys, users. x402/anon flags untouched.
  • secret-keys create/confirm intentionally left key-only — documented inline as the email-code "provision a key for a teammate" flow; sessions already have /v1/auth/secret-keys/issue.
  • New endpoints (api/auth/sessions/):
    • GET /v1/auth/cli/start — public; validates a loopback redirect_uri, bounces the browser to the dashboard /cli/authorize.
    • POST /v1/auth/cli/authorizerequire_auth(SESSION); mints a rift-cli-tagged session via issue_session, returns { token }. CORS restricts who can read it.

Dashboard (marketing/)

  • New /cli/authorize page + cli-authorize.tsx: reuses /signin?next= (magic-link + OAuth) for not-signed-in users, then an Approve button POSTs to mint and navigates the browser to the CLI's loopback listener with the token.

CLI

  • rift login → browser flow: one-shot 127.0.0.1 listener + state nonce, opens /v1/auth/cli/start, captures the token, confirms via /v1/auth/me, persists.
  • rift login --api-key → preserves today's paste-and-verify flow (headless/CI).
  • New SessionToken credential; backward-compatible config (secret_key/session_token both optional, skip_serializing_if); logout best-effort revokes the session; credential-agnostic 401 messaging.

Metering

No change. Billable quota is enforced in the service-layer QuotaService (shared by all transports), so session/CLI calls are already counted. The middleware usage-log stays key/anon-only by design (its only consumer is the anonymous IP limit).

Security

  • redirect_uri is loopback-only (validated on both endpoints) — the token can only ever be handed to 127.0.0.1/localhost/[::1].
  • state nonce binds the browser round-trip to the CLI instance.
  • A fresh rift-cli session is minted (independently revocable); the browser's cookie token is never exposed; the token only ever appears in a loopback URL.

Tests / checks

  • Server: fmt + clippy --all-targets -D warnings + tests (incl. architecture tests and new is_loopback_redirect cases).
  • Core + CLI: fmt + clippy + tests (new session-config round-trip + legacy-config back-compat).
  • Marketing: tsc --noEmit + eslint clean on touched/new files.

Try it (local)

Set MARKETING_URL to your Next dev URL, run the server + npm run dev in marketing/, then cargo run -- login --base-url http://localhost:3000 → sign in → Approve → terminal unblocks. rift logout revokes.

🤖 Generated with Claude Code

Let `rift login` authenticate via the browser (reusing the existing
magic-link + OAuth sign-in) and accept a session token OR an API key on
most routes — so humans don't need to paste a long-lived `rl_live_` key.
API keys remain the path for CI/automation (`rift login --api-key`).

Server (builds on #185's composable credential flags):
- Add SESSION to require_auth(...) on links, domains, apps, webhooks,
  analytics, affiliates, sources CRUD, publishable-keys, users. x402/anon
  untouched. secret-keys create/confirm stay key-only by design (the
  email-code teammate-provisioning flow; sessions use /secret-keys/issue).
- New GET /v1/auth/cli/start (public, loopback-validated → bounces the
  browser to the dashboard) and POST /v1/auth/cli/authorize
  (session-gated; mints a `rift-cli`-tagged session, returns the token).

Dashboard: new /cli/authorize page reuses /signin?next= (magic-link +
OAuth), then POSTs to mint and hands the token to the CLI's loopback
listener.

CLI: `rift login` opens the browser with a one-shot loopback listener +
state nonce; `--api-key` preserves the paste flow. New SessionToken
credential, backward-compatible config (secret_key/session_token both
optional), logout revokes the session, credential-agnostic 401 message.

Metering unchanged: session calls are already billed via the
service-layer QuotaService; the middleware usage-log stays key/anon-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jun 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rift Ready Ready Preview, Comment Jun 4, 2026 6:40pm

Request Review

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@saltyskip saltyskip merged commit 40e5437 into main Jun 4, 2026
8 checks passed
@saltyskip saltyskip deleted the feat/cli-session-auth branch June 4, 2026 18:46
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