feat: three-token auth flow (session_id + user_code + device_code)
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

- session_id (clm_sess_...) in browser URL — identifies login attempt
- user_code (ABCD-EFGH) visual confirmation — shown in both terminal and browser
- device_code (secret) — CLI polls with this, never displayed
- CLI accepts stdin paste of JWT token while polling (race)
- Web page handles both ?session= and ?code= params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-13 12:19:08 +01:00
parent bb1310167e
commit d07cff788c
6 changed files with 35 additions and 22 deletions

View File

@@ -1125,10 +1125,12 @@ export const deviceCodeStatusEnum = meshSchema.enum("device_code_status", [
*/
export const deviceCode = meshSchema.table("device_code", {
id: text().primaryKey().notNull().$defaultFn(generateId),
/** Random 16-char code used by CLI to poll. */
/** Random 16-char code used by CLI to poll (secret, never shown to user). */
deviceCode: text().notNull().unique(),
/** Human-readable code shown in browser (ABCD-EFGH). */
/** Human-readable code shown in both terminal and browser for visual confirmation. */
userCode: text().notNull(),
/** URL-safe session identifier (clm_sess_..., 32 chars). Not secret — appears in browser URL. */
sessionId: text().notNull().unique(),
status: deviceCodeStatusEnum().notNull().default("pending"),
/** Filled on approve — the authenticated user. */
userId: text().references(() => user.id, { onDelete: "cascade" }),