Follow-up to #7, which added authentication and gave every user a required, unique email column (packages/fleet-bridge/src/auth/auth-database.ts). Nothing consumes that column yet — it exists so this can be built on top of it.
What this is
A second factor on POST /auth/login: after a username and password verify, the bridge emails a short-lived code and issues a session only once that code is returned.
Decisions this needs before implementation
Who is covered. Opt-in per user, enforced per role (all admins, say), or fleet-wide? Opt-in is friendlier; enforced-for-admins matches where the privilege actually is, now that registering a ship is admin-only.
Delivery. The bridge has no SMTP configuration and no outbound mail of any kind today, so this is new infrastructure: where the credentials live, what happens when sending fails, and whether a send failure is fail-open or fail-closed. Fail-closed on a broken mail server locks everyone out of their own fleet; fail-open makes the second factor advisory.
Where it slots in. AuthService.login currently returns { token, user } in one call. A second factor means a two-step exchange — a short-lived challenge, then the code — which changes the shape of POST /auth/login and therefore both clients (packages/fleet-client/src/data/auth.ts, apps/cli/src/session.ts).
Recovery codes. Without them, a lost mailbox is a lost fleet. With them, they are one more secret to store and hash.
Rate limiting. Deliberately out of scope in #7. A mailed numeric code is weak enough that it probably cannot ship without a throttle on both login attempts and code submissions.
Explicitly exempt
Machine credentials must not be covered: the ship, ship-agent, and workspace-agent tokens all authenticate processes with no human at a keyboard. Only the interactive user login gains a second factor.
Prior art in the repo
packages/fleet-bridge/src/auth/auth-service.ts — sessions, argon2id passwords, and the in-memory single-use WebSocket tickets, which are a reasonable model for a short-lived challenge.
packages/fleet-bridge/src/auth/bootstrap.ts — the env-vars-then-TTY-then-fail-fast pattern, worth mirroring for any new required configuration.
apps/docs/src/content/docs/guides/authentication.md — the current model, which this would extend.
Follow-up to #7, which added authentication and gave every user a required, unique
emailcolumn (packages/fleet-bridge/src/auth/auth-database.ts). Nothing consumes that column yet — it exists so this can be built on top of it.What this is
A second factor on
POST /auth/login: after a username and password verify, the bridge emails a short-lived code and issues a session only once that code is returned.Decisions this needs before implementation
Who is covered. Opt-in per user, enforced per role (all admins, say), or fleet-wide? Opt-in is friendlier; enforced-for-admins matches where the privilege actually is, now that registering a ship is admin-only.
Delivery. The bridge has no SMTP configuration and no outbound mail of any kind today, so this is new infrastructure: where the credentials live, what happens when sending fails, and whether a send failure is fail-open or fail-closed. Fail-closed on a broken mail server locks everyone out of their own fleet; fail-open makes the second factor advisory.
Where it slots in.
AuthService.logincurrently returns{ token, user }in one call. A second factor means a two-step exchange — a short-lived challenge, then the code — which changes the shape ofPOST /auth/loginand therefore both clients (packages/fleet-client/src/data/auth.ts,apps/cli/src/session.ts).Recovery codes. Without them, a lost mailbox is a lost fleet. With them, they are one more secret to store and hash.
Rate limiting. Deliberately out of scope in #7. A mailed numeric code is weak enough that it probably cannot ship without a throttle on both login attempts and code submissions.
Explicitly exempt
Machine credentials must not be covered: the ship, ship-agent, and workspace-agent tokens all authenticate processes with no human at a keyboard. Only the interactive user login gains a second factor.
Prior art in the repo
packages/fleet-bridge/src/auth/auth-service.ts— sessions, argon2id passwords, and the in-memory single-use WebSocket tickets, which are a reasonable model for a short-lived challenge.packages/fleet-bridge/src/auth/bootstrap.ts— the env-vars-then-TTY-then-fail-fast pattern, worth mirroring for any new required configuration.apps/docs/src/content/docs/guides/authentication.md— the current model, which this would extend.