feat(auth): guest login with random telegraph operator name

No email required — players pick male/female/mystery, get assigned
a humorous period-appropriate operator name (e.g. "Dusty Clicksworth",
"Ada Dottsworth", "The Phantom Sender"), can reroll, then hit
"Begin Transmitting!" for instant play. Email/magic link moved to
a collapsible section for cross-device access only.

- server: POST /api/auth/guest creates guest profile + session
- server/db: profiles.email now nullable, is_guest column added,
  migration recreates table for existing installs
- client: names.ts with 15 names per gender category
- client: LoginPage redesigned — gender picker → name display → play
- client: useAuth gains setAuth() for direct session injection
- 55 server + 44 client tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 04:22:05 +00:00
parent 8232e75fa9
commit fe5cbaec2b
7 changed files with 341 additions and 39 deletions
+17
View File
@@ -62,6 +62,22 @@ function createServer(db) {
// ── Auth routes ──────────────────────────────────────────────────────────
if (pathname === '/api/auth/guest' && method === 'POST') {
const body = await readBody(req)
const name = body.display_name
if (!name || typeof name !== 'string' || name.trim().length < 2 || name.trim().length > 80) {
return send(res, 400, { error: 'display_name required (2-80 chars)' })
}
const profile = db.createGuestProfile(name.trim())
db.getOrCreateProgress(profile.id)
const sessionToken = crypto.randomBytes(32).toString('hex')
db.createSession(sessionToken, profile.id)
return send(res, 200, {
token: sessionToken,
profile: { id: profile.id, email: null, display_name: profile.display_name, is_guest: true },
})
}
if (pathname === '/api/auth/request' && method === 'POST') {
const body = await readBody(req)
const email = body.email
@@ -102,6 +118,7 @@ function createServer(db) {
id: profile.id,
email: profile.email,
display_name: profile.display_name,
is_guest: profile.is_guest === 1,
})
}