diff --git a/CLAUDE.md b/CLAUDE.md index 414a8f9..ff8c7fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -235,6 +235,23 @@ Key facts: `fetch('/api/me')` auth wall; `/login/` and `/register/` have `public={true}` to skip it - Incremental rebuild: `POST /api/activity/{id}` triggers `bincio render --handle {user}` as a fire-and-forget subprocess (only if `--site-dir` was passed to `bincio serve`) + +### Password reset (no email — out-of-band code) + +There is no email infrastructure. Password resets work via admin-generated one-time codes: + +1. **Admin** opens `/admin/` → clicks **"Reset pwd"** next to the user → a code appears + inline (monospace, click to copy). Valid for **24 hours**, tied to that handle. +2. **Admin** sends the code out-of-band (Signal, Telegram, etc.). +3. **User** goes to `/reset-password/`, enters handle + code + new password → done. + +API: +- `POST /api/admin/users/{handle}/reset-password-code` (admin) → `{code, expires_in_hours: 24}` +- `POST /api/auth/reset-password` (public) → body `{handle, code, password}` + +DB: `reset_codes` table `(code, handle, created_by, created_at, expires_at, used_at)`. +Generating a new code invalidates any prior unused code for the same handle. +Used codes are kept for audit. `change_password()` in `db.py` updates the bcrypt hash. - Write API in `bincio serve` delegates to `bincio.edit.server._apply_sidecar_edit`; the Strava sync delegates to `bincio.edit.server.strava_sync` with a temporary data_dir swap diff --git a/site/src/pages/reset-password/index.astro b/site/src/pages/reset-password/index.astro index 8051850..1379dad 100644 --- a/site/src/pages/reset-password/index.astro +++ b/site/src/pages/reset-password/index.astro @@ -4,7 +4,8 @@ import Base from '../../layouts/Base.astro';

Reset password

-

Enter the reset code you received from the admin.

+

Enter the reset code you received from the admin.

+

Don't have a code? Contact the instance admin — they can generate one for you from the admin panel. Codes expire after 24 hours.