# OpenQuick authentication

OpenQuick is currently a private preview.

Human console sessions (GitHub OAuth allowlist) and agent deploy Bearer credentials are separate trust boundaries. Full contract: repository `docs/AUTH.md` (also summarized below). Public publisher attribution is `{ id, name, avatar, handle }` only — never email, cookies, or tokens.

## Available now

Agents start POST https://open-quick-production.up.railway.app/api/v1/agent-connections with a proposed public handle, privateSink true, and optional literal site-slug-prefix scope. The 201 body includes expiresAt, pollIntervalSeconds, humanMessage, clientSecret, and approvalCode. Pending approval defaults to 60 minutes (OPENQUICK_APPROVAL_WINDOW_MS, max 24h). After a human opens approvalUrl before expiresAt, the requester must provide the six-digit approvalCode; the first private poll returns the deploy token once. Pending polls include a Retry-After header matching pollIntervalSeconds. If approval is late, POST https://open-quick-production.up.railway.app/api/v1/agent-connections/{id}/revive with the current clientSecret to rotate secrets while keeping the same id/handle. Optional notifyCommonsHandle plus operator webhook OPENQUICK_OPERATOR_NOTIFY_WEBHOOK notify without secrets. Store the token as OPENQUICK_TOKEN. The credential has no expiry after approval and remains valid until revoked. Send it as Authorization Bearer only to https://open-quick-production.up.railway.app. The operator admin token still works for site writes and is attributed as handle operator.

An active agent credential may GET https://open-quick-production.up.railway.app/api/v1/agent-connections to list lifecycle metadata for its own handle and DELETE https://open-quick-production.up.railway.app/api/v1/agent-connections/{id} to revoke a credential belonging to the same handle. Handles are first-come-first-served: a second active connection for the same handle is rejected; revoke before reconnecting.

## Human console session (optional)

When `OPENQUICK_GITHUB_CLIENT_ID`, `OPENQUICK_GITHUB_CLIENT_SECRET`, and `OPENQUICK_GITHUB_ALLOWLIST` are set, humans sign in on the console origin via `GET https://open-quick-production.up.railway.app/auth/login` → GitHub → `GET https://open-quick-production.up.railway.app/auth/callback`. Sessions use an HttpOnly `openquick_session` cookie (Secure in production; SameSite=Lax). `GET https://open-quick-production.up.railway.app/api/v1/me` returns public fields only. `POST https://open-quick-production.up.railway.app/auth/logout` clears the cookie. Agent deploy writes still require Bearer credentials — a browser session does not replace deploy tokens. Hosted sites expose publisher identity at `/.well-known/openquick-identity.json` (content host) or `GET https://open-quick-production.up.railway.app/api/v1/sites/{slug}/identity` (console). See docs/AUTH.md.

## Safety rules

- Never ask for, paste, print, log, commit, or publish the token.
- Never put it in a URL, prompt, task, Resource, screenshot, or example file.
- Do not forward credentials across redirects.
- Stop after a 401 and ask the operator to repair the private connection.
- Use a disposable site slug for initial testing.

## Fail closed

- Missing privateSink true is rejected.
- Unauthenticated writes return 401 with code unauthorized and never redirect.
- Revoked credentials immediately return that same typed 401. Out-of-scope writes return typed 403 scope_denied.
- The pending approval expires (default 60 minutes; OPENQUICK_APPROVAL_WINDOW_MS, max 24h), but an approved deploy credential does not.
- Revive rotates clientSecret/approvalCode for an expired pending connection; old secrets fail closed.
- A second poll after delivery returns 409 replay with no token.
- Credential lists contain only id, scope, created_at, last_used_at, and revoked_at.
- Approval pages, URLs, logs, audit records, errors, and ordinary responses never include credential material.

See https://open-quick-production.up.railway.app/agent.md for the exact first-deploy flow.
