diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..900c84c --- /dev/null +++ b/TODO.md @@ -0,0 +1,33 @@ +# ken — next up + +## multi-account + settings + profile record + +design notes: [notes/multi-account-and-settings.md](notes/multi-account-and-settings.md) + +### what + +- write a `tech.waow.ken.profile/self` record on first sign-in so indexers can discover ken users +- consolidate all PDS record management (profile, pack, scopes) into a settings panel +- multi-account support: link accounts, switch without re-auth, per-account indexes +- scope management: user-selectable at login, progressive escalation (pdsls pattern) + +### reference + +- **pdsls** (tangled.org/pds.ls/pdsls): scope selector UI, PermissionButton pattern, account manager modal +- **plyr.fm** (sibling repo): group_id session linking, server-side account switching, logout modal + +### order of work + +1. lexicons: `tech.waow.ken.profile`, `tech.waow.ken.optout` +2. backend: profile read/write on sign-in, settings endpoints, scope storage +3. frontend: settings panel, move pack actions out of meta line +4. backend: group_id on sessions, add-account/switch-account endpoints +5. frontend: account menu in nav, multi-account switching + +### context the next engineer needs + +- ken is a zig HTTP server (0.16-dev) with a vanilla JS SPA (single main.js, no framework) +- all state is in-memory (state.zig) — no database, PDS is the only persistence +- session cookies are now random tokens mapped to DIDs (shipped this session, `921fefe`) +- OAuth uses zat for atproto primitives, DPoP-bound tokens, PKCE +- the session token fix also needs to be propagated to pollz (same vulnerability) diff --git a/notes/multi-account-and-settings.md b/notes/multi-account-and-settings.md new file mode 100644 index 0000000..f6c5a2c --- /dev/null +++ b/notes/multi-account-and-settings.md @@ -0,0 +1,111 @@ +# multi-account support + settings panel + +status: design notes. not started. + +## what we want + +ken currently has single-account OAuth with a fixed scope, inline pack +management in the meta line, and no profile record. this should become: + +1. **profile record** — `tech.waow.ken.profile/self`, written on first + sign-in (idempotent). lets indexers discover who has used ken. users + can delete it from settings; ken respects the delete by checking for + an opt-out record (`tech.waow.ken.optout/self`) before rewriting. + +2. **settings panel** — replaces the inline disclosure in the meta line. + one place to manage all ken-related records on the user's PDS: + - profile record: exists / doesn't, toggle create/delete + - search index (pack): saved / not saved, save / delete / view on PDS + - oauth scopes: what was granted, option to re-auth with different scopes + +3. **multi-account** — link multiple atproto accounts, switch between + them without re-authing. each account gets its own index/pack. + +4. **scope management** — user-selectable scopes at login (like pdsls), + progressive permission escalation (PermissionButton pattern wraps + actions and prompts for re-auth if the scope wasn't granted). + +## reference implementations + +### pdsls (tangled.org/pds.ls/pdsls) + +- scopes: user-selectable checkboxes at login. 4 granular scopes + (create, update, delete, blob) on top of base `atproto` scope. + `PermissionButton` component wraps every write action — if scope + wasn't granted, shows a modal offering to re-auth with the needed + scope. progressive escalation, no silent failures. +- multi-account: sessions keyed by DID in localStorage (client-side). + account manager modal with avatar + handle, click to switch via + `resumeSession(did)`. expired sessions prompt re-auth automatically. +- settings: minimal — PLC directory URL, theme, version. +- stack: vite + typescript SPA, cloudflare pages. + +### plyr.fm (/Users/nate/tangled.sh/@zzstoatzz.io/plyr.fm/) + +- multi-account: `group_id` column on `UserSession` table links sessions + across accounts server-side. active account = which session_id the + cookie points to. switching = `POST /auth/switch-account` → server + validates both sessions share a group_id → swaps the cookie. +- add account: dedicated `/auth/add-account/start` endpoint. initiates + OAuth with `prompt=login`, links new session to existing group_id. + `PendingAddAccount` record ties the OAuth state to the group. +- logout: modal when multi-account — "switch to @handle" or "sign out + of all." single-account just logs out. +- scope upgrade: `PendingScopeUpgrade` flow — re-auth with new scopes, + update existing session rather than creating a new one. +- session storage: postgres + redis (60s cache TTL) + fernet encryption + on OAuth tokens at rest. random session_id (secrets.token_urlsafe(32)) + as cookie, never the DID. +- stack: fastapi + sveltekit, postgres, redis. + +## proposed design for ken + +**backend (zig, in-memory):** +- add `group_id: ?[]u8` to `StoredSession` in state.zig +- `createSessionToken` already generates random tokens (just shipped) +- new endpoints: + - `POST /oauth/add-account` — initiates OAuth, links to existing group + - `POST /oauth/switch-account` — validates group membership, swaps cookie + - `GET /api/me` returns `linked_accounts: [{did, handle}]` for the group +- profile record: check for `tech.waow.ken.profile/self` on sign-in, + write if absent and no `tech.waow.ken.optout/self` exists +- scope: store granted scopes in session, expose via `/api/me` + +**frontend (vanilla JS, single file):** +- account menu in nav (avatar + handle button → dropdown): + - list linked accounts with handles + - click to switch (POST to switch-account) + - "add account" button + - per-account: remove, edit permissions, view repo on pdsls +- settings panel (gear icon or link from account menu): + - profile record: status + toggle + - pack: status + save/delete/view + - scopes: what was granted + re-auth button +- PermissionButton pattern: wrap save/delete with scope check, + show prompt if scope wasn't granted + +**lexicons needed:** +- `tech.waow.ken.profile` — marker record, rkey `self` +- `tech.waow.ken.optout` — opt-out marker, rkey `self` +- `tech.waow.ken.pack` — already exists + +**migration from current state:** +- the inline "saved ▾" disclosure in the meta line moves to settings +- meta line becomes pure stats again +- existing single-account sessions continue to work (group_id = null + means "no linked accounts") + +## open questions + +- should the profile record carry any data beyond a marker? handle, + sign-in timestamp, ken version? or keep it empty? +- should we write the profile on first sign-in automatically, or + require the user to opt in via settings? auto-write is more useful + for indexers but less respectful of user agency. +- ken is a vanilla JS SPA — the PermissionButton pattern assumes + components. may need a simpler wrapper (function that checks scope + before calling the action, shows a confirm dialog if scope is missing). +- in-memory sessions mean group_id doesn't survive restarts. is that + acceptable? users re-auth on restart anyway, but they'd lose their + linked-account associations. could persist group mappings to... the + PDS? a separate record? or just accept the UX cost.