feat(api): invite friends and manage accounts #9

Merged
thibault merged 1 commit from feat/invitations into 0.3 2026-10-08 20:05:01 +02:00
Owner

Backend of 0.3 "accounts for friends" (the web part comes in a second PR).

What changes

  • Migration 0007_invitations: invitations (kind invite/reset, SHA-256 of the token only, expires_at, used_at) and torrents.added_by (ON DELETE SET NULL, not exposed).
  • Roles: require_admin on /api/admin/* and /api/indexers* → 403 {"error":"admins only"}. Users keep everything on downloads.
  • Admin: POST/GET /api/admin/invitations, DELETE /api/admin/invitations/{id}, GET /api/admin/users, DELETE /api/admin/users/{id} (not oneself, never the last admin: 409), POST /api/admin/users/{id}/reset.
  • Public: GET/POST /api/invitations/{token}. Same 404 for unknown/used/expired links, own per-address limiter (5 in 15 min → 429), failed-login delay. An invite creates a user and logs in; a reset sets the password, closes all sessions, opens a new one. Links are single use (7 days / 24 h; a new reset link replaces the older ones).
  • POST /api/auth/password {current, new} → 204, other sessions closed; a wrong current is 403 and counts as a failed login.
  • Usernames: 1–32 ASCII letters, digits, -, _, ., unique whatever the case (400/409). New passwords ≥ 10 characters (CLI too).
  • Logs: the http span gets a user field once the session is checked, so download added/updated/removed carry who did it. Invitation tokens are replaced by … in the logged URI.
  • README (API table, Accounts), ROADMAP phase 8 progress, Bruno admin/ folder.

Tests

142 tests (13 new): admin vs user access, invite lifecycle (reuse/expired/revoked), validation and case-insensitive uniqueness, reset lifecycle, delete rules, password change, rate limiting, added_by, username in logs.

🤖 Generated with Claude Code

Backend of 0.3 "accounts for friends" (the web part comes in a second PR). ## What changes - **Migration `0007_invitations`**: `invitations` (kind `invite`/`reset`, SHA-256 of the token only, `expires_at`, `used_at`) and `torrents.added_by` (`ON DELETE SET NULL`, not exposed). - **Roles**: `require_admin` on `/api/admin/*` and `/api/indexers*` → `403 {"error":"admins only"}`. Users keep everything on downloads. - **Admin**: `POST/GET /api/admin/invitations`, `DELETE /api/admin/invitations/{id}`, `GET /api/admin/users`, `DELETE /api/admin/users/{id}` (not oneself, never the last admin: `409`), `POST /api/admin/users/{id}/reset`. - **Public**: `GET/POST /api/invitations/{token}`. Same `404` for unknown/used/expired links, own per-address limiter (5 in 15 min → `429`), failed-login delay. An invite creates a `user` and logs in; a reset sets the password, closes all sessions, opens a new one. Links are single use (7 days / 24 h; a new reset link replaces the older ones). - **`POST /api/auth/password`** `{current, new}` → `204`, other sessions closed; a wrong `current` is `403` and counts as a failed login. - Usernames: 1–32 ASCII letters, digits, `-`, `_`, `.`, unique whatever the case (`400`/`409`). New passwords ≥ 10 characters (CLI too). - **Logs**: the `http` span gets a `user` field once the session is checked, so `download added/updated/removed` carry who did it. Invitation tokens are replaced by `…` in the logged URI. - README (API table, Accounts), ROADMAP phase 8 progress, Bruno `admin/` folder. ## Tests 142 tests (13 new): admin vs user access, invite lifecycle (reuse/expired/revoked), validation and case-insensitive uniqueness, reset lifecycle, delete rules, password change, rate limiting, `added_by`, username in logs. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(api): invite friends and manage accounts
All checks were successful
CI / web (pull_request) Successful in 9s
CI / test (pull_request) Successful in 33s
f7fd9fa358
Accounts for friends (0.3), backend part:

- Migration 0007: invitations (invite and reset links, only the SHA-256 of
  their token stored) and torrents.added_by.
- require_admin on /api/admin/* and /api/indexers*: 403 for users.
- Admin: create, list and revoke invitations; list and delete users (not
  oneself, never the last admin); password reset links.
- Public GET/POST /api/invitations/{token}: same 404 for unknown, used or
  expired links, per-address limiter and failed-login delay, tokens kept
  out of the request logs. Using a link logs in.
- POST /api/auth/password closes the other sessions.
- Usernames: 1 to 32 ASCII letters, digits, '-', '_', '.'; new passwords
  at least 10 characters.
- The request span carries the username, so download changes are logged
  with who made them; added_by is stored on add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01947SGTYxD1CJLsk2PcULRA
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
thibault/plankton!9
No description provided.