feat: personal API tokens
Tokens for scripts, made, listed and revoked on the account page. Each grants some of the person's own permissions at chosen places (a product of theirs, a catalog whose team they're on, or the hub for moderators); every check is the intersection with what they hold now. Tokens work only on routes that check a permission (session-only routes answer token_not_allowed), are shown once and stored as hashes, note their last use, and mark the audit entries of what they do. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
58a3e70a63
commit
2b88a52bcb
17 files changed
+4358
-98
No files matched your search
+3
-1
@@ -7,7 +7,9 @@ requests and type the website's client (Eden). This is the overview.
|
||||
- `/api/v1/*`: the hub's own routes.
|
||||
|
||||
The website reaches the API through its own origin (Next.js rewrites `/api` to it), so sessions are first-party
|
||||
cookies. T5DE's proxy calls it directly with `Authorization: Bearer <session token>`.
|
||||
cookies. T5DE's proxy calls it directly with `Authorization: Bearer <session token>`. Scripts use personal API
|
||||
tokens (`Authorization: Bearer t5de_pat_...`, docs/console.md): those work only on routes that check a permission
|
||||
(the rest answer 403 `token_not_allowed`, and a wrong, revoked or expired token 401 `bad_token`).
|
||||
|
||||
Errors are `{"error": "<code>", "message": "<text for people>"}` with a 4xx/5xx status; `message` can be shown as
|
||||
is. Codes: `invalid`, `signed_out`, `forbidden`, `not_found`, `no_such_imvu_account`, `already_linked`,
|
||||
|
||||
+19
-1
@@ -182,6 +182,24 @@ Bearer t5de_pat_<random>`; only the hash is stored; shown once. A token never ha
|
||||
check is the intersection, so losing a role takes it from tokens too. Listed and revocable in the account page;
|
||||
every use updates `last_used_at`; actions are audited as the person, marked "by token <name>".
|
||||
|
||||
Built (2026-11-05):
|
||||
- Made, listed and revoked on the account page (a website session only: a token can't make tokens). Each grant is
|
||||
a permission the person holds now at the hub (moderators and admins), a catalog whose team they're on, or a
|
||||
product of theirs (400 `not_held` otherwise). At most 20 active tokens, 50 grants, 365 days.
|
||||
- Usable on the routes that check a permission (product info, publish, locks, history; uploading new revisions of a
|
||||
product, `revision:upload`, and retracting them; a catalog's settings, key, team, members, invites and hosted
|
||||
products; blocks; assigning cases). Other routes refuse tokens (403 `token_not_allowed`), including making a new
|
||||
product, reports and the console. On those routes every check is the intersection, so a moderator's token granted
|
||||
`product:remove` hub-wide can block files but not suspend catalogs, and losing the role takes it from the token.
|
||||
- The audit log marks actions by token (`detail.token`: its name); making and revoking one is audited
|
||||
(`token.create`, `token.revoke`).
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| `GET /api/v1/account/tokens` | `{tokens: [{id, name, grants: [{permission, scope, name}], status (active, expired, revoked), createdAt, expiresAt, lastUsedAt, revokedAt}], scopes: [{scope, name, permissions}], maxDays}`: `scopes` is what a token could be given |
|
||||
| `POST /api/v1/account/tokens` | `{name, days, grants: [{permission, scope: {type: "hub"} or {type: "catalog" or "product", id}}]}` → 201 `{token, info}`; `token` is shown only here |
|
||||
| `DELETE /api/v1/account/tokens/:id` | revokes it → 204 |
|
||||
|
||||
```
|
||||
api_token(id, person, name, hash, grants jsonb, expires_at, last_used_at, created_at, revoked_at)
|
||||
```
|
||||
@@ -224,7 +242,7 @@ for meaning.
|
||||
| `POST /api/v1/cases/:id/decide` | `{decision: "resolve" or "decline", resolution}`: by those who may decide it (below); resolving carries out a rating review, removal request or deletion request; the opener is told |
|
||||
| `GET /api/v1/notifications?before=` | `{notifications: [{id, type, tags, data, at, read}], unread, before}`, newest first, 50 a page |
|
||||
| `POST /api/v1/notifications/read` | `{ids?}` (all if left out) → `{unread}` |
|
||||
| `GET/POST /api/v1/account/tokens`, `DELETE /api/v1/account/tokens/:id` | API tokens |
|
||||
| `GET/POST /api/v1/account/tokens`, `DELETE /api/v1/account/tokens/:id` | API tokens (above) |
|
||||
| `GET/PUT/DELETE /api/v1/roles/:scopeType/:scopeId/:person` | role assignments at a scope |
|
||||
| `POST /api/v1/console/view-as/:person` | (admins) start viewing as someone, read-only |
|
||||
|
||||
|
||||
+2
-2
@@ -478,8 +478,8 @@ Phases for products, catalogs and imports (products.md, console.md, imports.md):
|
||||
(to look into with the importer spike).
|
||||
3. Cases (reports, appeals, reviews), file blocks and the catalogs' changes feed; notifications; API tokens;
|
||||
two-factor sign-in for sensitive permissions. Cases and notifications **done** (2026-11-05), without email, canned
|
||||
reasons, moderator notes or second approval. File blocks **done** (2026-11-05); API tokens and two-factor sign-in
|
||||
next.
|
||||
reasons, moderator notes or second approval. File blocks and API tokens **done** (2026-11-05); two-factor
|
||||
sign-in next.
|
||||
4. Imports: grants, recorded grants, requests, unclaimed creators, the job queue, `t5de-importer` (after its spike).
|
||||
5. Charts and stats; mirroring between catalogs.
|
||||
|
||||
|
||||
Reference in new issue
Block a user