From a3dee83ac2a4546ce2eb3b325e2e000ae6de8e4a Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 11 Sep 2026 02:42:07 +0200 Subject: [PATCH] docs: record the sign-in code and preview behaviour still to build Both came out of using the app: the preview works but is all-or-nothing, and signing in should be possible without a password without replacing it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU --- TODO.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/TODO.md b/TODO.md index 652860ab..faed4bc2 100644 --- a/TODO.md +++ b/TODO.md @@ -145,3 +145,37 @@ Worth doing before Kubernetes, roughly in order: them and the compose should stay reviewable. Co-Authored-By: Claude Opus 5 + +## Sign-in by one-time code, alongside passwords + +An option, not a replacement. An account keeps its password; the sign-in screen +offers "email me a code" as a second way in, and an account can use either. + +- Reuse the invite machinery rather than inventing a second one: codes are + hashed at rest, single-use, short-lived, and claimed by one atomic UPDATE + carrying every condition, so two attempts at the same code cannot both win. +- Much shorter TTL than an invite — minutes, not days — and rate limited per + account and per IP, because a login code is a credential and an attacker can + ask for one without knowing the password. +- A code must never reveal whether the address has an account. The response is + the same either way. +- 2FA still applies on top where enabled: a code replaces the password step, not + the second factor. + +## The signed-out preview, as it should behave + +The preview works now, but it is currently all-or-nothing. Intended shape: + +- Signed out with preview on, the assistant chat is visible and usable. +- Anything else — saved chats, images, settings — prompts sign-in rather than + failing. Today those simply 401. +- A cap of three questions per visitor, then an invitation to sign in. The cap + has to be enforced on the server: a preview visitor has no identity, so it + cannot be per-account, and a purely client-side count is worth nothing. +- Nothing is persisted for a preview visitor. That is already true and must + stay true — PREVIEW_USER has id: null precisely so nothing can be owned. +- If the visitor then signs in **in the same browser**, the preview + conversation is adopted into the new account and saved. This is the + interesting part: because preview deliberately has no identity, the transcript + has to be held client-side and replayed on the first authenticated request, + not looked up server-side. Size-cap it and drop it on sign-out.