adding claude file and some plans
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
# 05 — Auth, Sessions, OAuth & Tokens
|
||||
|
||||
Two credential paths, one identity: browser SPA uses **server-side sessions in HttpOnly cookies** (alexedwards/scs, Postgres store); scripts/CLI use **personal access tokens**. Local email/password and GitHub/Gitea OAuth all resolve to the same `users` row via `oauth_accounts`.
|
||||
|
||||
## Registration & login
|
||||
|
||||
- [ ] `POST /auth/register`: validate email format, username `[a-z0-9-_]{3,32}` lowercase-unique (citext), password ≥ 10 chars; bcrypt cost 12
|
||||
- [ ] **First registered user gets `is_admin = true`** (instance bootstrap; ids 1–2 are reserved seeds, so the first human is id 3)
|
||||
- [ ] `POST /auth/login`: verify bcrypt; on success `scs.RenewToken` (session fixation) then store user id; emit `auth.login_succeeded` / `auth.login_failed` (failed: no user enumeration in the response — generic message)
|
||||
- [ ] Rate-limit register/login/password endpoints per IP (10/min)
|
||||
- [ ] `POST /auth/logout`: destroy session
|
||||
- [ ] Session config: Postgres store (`sessions` table), 30-day lifetime with idle-timeout 7 days, cookie `HttpOnly`, `SameSite=Lax`, `Secure` when BASE_URL is https
|
||||
|
||||
## OAuth (GitHub + Gitea)
|
||||
|
||||
- [ ] Standard authorization-code flow; `state` parameter stored in session, verified at callback (CSRF)
|
||||
- [ ] GitHub: fixed endpoints; Gitea: endpoints derived from `GITEA_URL` (self-hosted friendly)
|
||||
- [ ] Provider buttons hidden in the SPA when the corresponding env vars are unset (`/api/v1/me`-adjacent bootstrap config endpoint or injected at index render)
|
||||
- [ ] Callback logic, in order:
|
||||
- [ ] Existing `oauth_accounts(provider, provider_user_id)` row → log that user in
|
||||
- [ ] Else: fetch the provider's **verified** primary email; if a user with that email exists → **link**: insert oauth_accounts row for that user, log in
|
||||
- [ ] Else: create a new user (username derived from provider login, de-duplicated with numeric suffix; `password_hash` NULL) + oauth_accounts row, log in
|
||||
- [ ] A linked user may later set a password via account settings (password change with no current password required only when `password_hash` IS NULL)
|
||||
- [ ] Never auto-link on an **unverified** provider email (account-takeover vector) — fall through to create-new-user
|
||||
|
||||
## Personal access tokens
|
||||
|
||||
- [ ] Format `solopm_<40 chars base62>`; store SHA-256 hex in `token_hash`; show plaintext exactly once at creation
|
||||
- [ ] `prefix` (first 12 chars) stored for display in the token list
|
||||
- [ ] Bearer auth middleware: constant-time hash lookup; reject revoked/expired; update `last_used_at` (throttled to once/minute per token)
|
||||
- [ ] Tokens act as the full user (no scopes in v1 — recorded in [17-decisions.md](17-decisions.md))
|
||||
- [ ] Emit `auth.token_created` / `auth.token_revoked`
|
||||
|
||||
## Authorization model
|
||||
|
||||
- [ ] `is_admin` (system-wide): manage users, run cull/reindex, see admin endpoints. Admins are **not** implicit members of every project — they see only their own projects in normal UI (admin endpoints are separate)
|
||||
- [ ] Project `owner`: everything a member can, plus: edit project settings, manage members/labels/webhooks, transfer ownership, delete project
|
||||
- [ ] Project `member`: full read/write on the project's issues, epics, comments, wiki, attachments, links; manage own watches/favorites
|
||||
- [ ] Non-member: **404** on all project-scoped routes
|
||||
- [ ] System user (id 1) and zombie user (id 2): cannot log in (no password, no oauth rows, login explicitly rejects ids 1–2), never listed in pickers
|
||||
|
||||
## User deletion edge cases
|
||||
|
||||
- [ ] A user who is the sole owner of any project cannot be deleted (409 `conflict`, message lists the projects) — transfer ownership first
|
||||
- [ ] Deletion is a soft delete; sessions destroyed and PATs revoked immediately
|
||||
- [ ] At cull time the zombie user inherits authored records ([12-retention.md](12-retention.md)); audit_log keeps the original user id (immutable)
|
||||
- [ ] Self-service account deletion follows the same rules as admin deletion
|
||||
Reference in New Issue
Block a user