Tokens can write but never read: no token auth on the read API #156

Open
opened 2026-09-02 21:36:31 +02:00 by qwc · 0 comments
Owner

Noticed while mapping token scoping for #155. Not urgent — filing it so it is written down.

What is the case today

Bearer tokens are wired into exactly three routes, all of them writes:

  • POST /api/projects
  • POST /api/project/{slug}/upload
  • POST /api/upload

Everything else, including the read API, goes through withSession, which reads the session cookie and nothing else (SessionManager.GetUserFromRequest). So these return the signed-out view to a token holder:

  • GET /api/projects
  • GET /api/project/{slug}/versions
  • GET /api/search

A CI job can push a version and then cannot ask which versions exist. Scripts that want to know "is v1.2.0 already uploaded" have to guess from the upload's response, or carry a session cookie, which is not a thing tokens were meant to need.

Why it is worth doing deliberately rather than quickly

Reading is where a token's scope actually gets interesting, and the answers are not obvious:

  • A project-scoped token asking GET /api/projects — one project, or an error? One project seems right: the token says which project it is for.
  • A global token — everything its robot is granted, filtered by the same resolver the front page uses, not everything that exists.
  • GET /api/search across projects the robot cannot view would be an information leak, so it has to run through FilterAccessible like the web search does.
  • Anonymous reads of public projects: today the read API is cookie-only, so a public project's version list is already readable by a signed-out visitor. Adding token auth must not change that either way by accident.

The shape that fits what #155 settled is a middleware that tries the session first and the token second, putting the resolved user in the context the same way, so the handlers stay unaware — with the token's project scope applied to the listing, not just to writes.

Also worth deciding at the same time

Whether reading needs its own scope value. api_tokens.scopes now carries upload and create (#155); a read-only token would be a genuinely useful thing to hand a dashboard, and it only makes sense to add the value when something reads it.

Assisted-by: Claude Opus 5

Noticed while mapping token scoping for #155. Not urgent — filing it so it is written down. ## What is the case today Bearer tokens are wired into exactly three routes, all of them writes: - `POST /api/projects` - `POST /api/project/{slug}/upload` - `POST /api/upload` Everything else, including the read API, goes through `withSession`, which reads the session cookie and nothing else (`SessionManager.GetUserFromRequest`). So these return the signed-out view to a token holder: - `GET /api/projects` - `GET /api/project/{slug}/versions` - `GET /api/search` A CI job can push a version and then cannot ask which versions exist. Scripts that want to know "is v1.2.0 already uploaded" have to guess from the upload's response, or carry a session cookie, which is not a thing tokens were meant to need. ## Why it is worth doing deliberately rather than quickly Reading is where a token's scope actually gets interesting, and the answers are not obvious: - A **project-scoped** token asking `GET /api/projects` — one project, or an error? One project seems right: the token says which project it is for. - A **global** token — everything its robot is granted, filtered by the same resolver the front page uses, not everything that exists. - `GET /api/search` across projects the robot cannot view would be an information leak, so it has to run through `FilterAccessible` like the web search does. - Anonymous reads of `public` projects: today the read API is cookie-only, so a public project's version list is already readable by a signed-out visitor. Adding token auth must not change that either way by accident. The shape that fits what #155 settled is a middleware that tries the session first and the token second, putting the resolved user in the context the same way, so the handlers stay unaware — with the token's project scope applied to the listing, not just to writes. ## Also worth deciding at the same time Whether reading needs its own scope value. `api_tokens.scopes` now carries `upload` and `create` (#155); a read-only token would be a genuinely useful thing to hand a dashboard, and it only makes sense to add the value when something reads it. Assisted-by: Claude Opus 5
Sign in to join this conversation.
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
qwc-open/asiakirjat#156
No description provided.