Fix inline authz drift (H-1, H-2) and project-scoped tokens creating projects (H-3) #106

Merged
qwc merged 1 commit from fix/inline-authz-and-token-scope into main 2026-05-18 20:27:19 +02:00
Owner

Summary

Workstream item #11. Three small fixes the access.Checker refactor (#105) unblocked.

H-1 — handleDeleteVersion ignored global-access grants

The inline check (admin/editor OR per-project ProjectAccess) didn't honor GlobalAccessGrant. A user with a global editor grant could canUpload to a private project but not delete versions of it. Replaced with h.canUpload(...).

H-2 — handleProjectDetail CanUpload/CanDelete flag drift

Recomputed the same inline rule for the template flag, so the UI hid affordances from users who could in fact POST to the underlying routes. Replaced with h.canUpload(...).

H-3 — project-scoped tokens could create unrelated projects

handleAPICreateProject called tokenAuth.AuthenticateRequest, which silently discards the token's ProjectID scope. A project-scoped editor token (created via /project/{slug}/tokens) could therefore hit POST /api/projects and spawn new top-level projects. Added TokenAuthenticator.AuthenticateRequestWithToken(r) (*User, *APIToken) and the handler now rejects tokens with non-nil ProjectID with 403.

Changes

  • internal/auth/token.go — new AuthenticateRequestWithToken.
  • internal/handler/project.gohandleDeleteVersion and the CanUpload flag in handleProjectDetail both call h.canUpload(...).
  • internal/handler/api.gohandleAPICreateProject uses the new auth method and rejects scoped tokens.
  • internal/handler/handler_test.gosetupTestApp was missing GlobalAccess in Deps, so the test handler had a nil globalAccess store. Added (needed for the H-1/H-2 regression tests).
  • Four new regression tests in inline_authz_test.go.

Test plan

  • go test ./... passes
  • H-1: viewer with global editor grant can delete versions of a private project
  • H-2: same user sees the upload affordance on the project detail page
  • H-3: scoped editor token gets 403 trying to create an unrelated project; the project is not created
  • Sanity: global (unscoped) editor token still creates projects (201)
  • Manual: log in as a viewer + flip on a global editor grant in the admin UI, confirm the upload + delete buttons appear and work

🤖 Generated with Claude Code

## Summary Workstream item #11. Three small fixes the `access.Checker` refactor (#105) unblocked. ### H-1 — `handleDeleteVersion` ignored global-access grants The inline check (admin/editor OR per-project ProjectAccess) didn't honor `GlobalAccessGrant`. A user with a global editor grant could `canUpload` to a private project but not delete versions of it. Replaced with `h.canUpload(...)`. ### H-2 — `handleProjectDetail` `CanUpload`/`CanDelete` flag drift Recomputed the same inline rule for the template flag, so the UI hid affordances from users who could in fact POST to the underlying routes. Replaced with `h.canUpload(...)`. ### H-3 — project-scoped tokens could create unrelated projects `handleAPICreateProject` called `tokenAuth.AuthenticateRequest`, which silently discards the token's `ProjectID` scope. A project-scoped editor token (created via `/project/{slug}/tokens`) could therefore hit `POST /api/projects` and spawn new top-level projects. Added `TokenAuthenticator.AuthenticateRequestWithToken(r) (*User, *APIToken)` and the handler now rejects tokens with non-nil `ProjectID` with **403**. ## Changes - `internal/auth/token.go` — new `AuthenticateRequestWithToken`. - `internal/handler/project.go` — `handleDeleteVersion` and the `CanUpload` flag in `handleProjectDetail` both call `h.canUpload(...)`. - `internal/handler/api.go` — `handleAPICreateProject` uses the new auth method and rejects scoped tokens. - `internal/handler/handler_test.go` — `setupTestApp` was missing `GlobalAccess` in `Deps`, so the test handler had a nil `globalAccess` store. Added (needed for the H-1/H-2 regression tests). - Four new regression tests in `inline_authz_test.go`. ## Test plan - [x] `go test ./...` passes - [x] H-1: viewer with global editor grant can delete versions of a private project - [x] H-2: same user sees the upload affordance on the project detail page - [x] H-3: scoped editor token gets 403 trying to create an unrelated project; the project is not created - [x] Sanity: global (unscoped) editor token still creates projects (201) - [ ] Manual: log in as a viewer + flip on a global editor grant in the admin UI, confirm the upload + delete buttons appear and work 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Fix H-1/H-2 inline authz copies; H-3 reject project-scoped tokens on POST /api/projects
All checks were successful
CI / test (pull_request) Successful in 1m21s
CI / build (pull_request) Successful in 55s
CI / docker (pull_request) Has been skipped
dcd1900e76
Three small fixes the access.Checker refactor (H-11, PR #105) unblocked.

H-1: handleDeleteVersion had an inline check (admin/editor or per-project
ProjectAccess) that ignored GlobalAccessGrant editor grants. A user with
a global editor grant could upload to a private project via canUpload but
not delete versions of it. Replace with a single h.canUpload(...) call.

H-2: handleProjectDetail recomputed CanUpload / CanDelete with the same
inline rule, drifting from the real authz that the upload/delete routes
enforced. UI hid the upload affordance from users who could in fact POST
to the underlying routes — confusing rather than broken, but a separate
copy of the rule that we wanted to eliminate. Replace with the same
h.canUpload(...) call.

H-3: handleAPICreateProject called tokenAuth.AuthenticateRequest, which
silently discards the token's ProjectID scope. A project-scoped editor
token (created via /project/{slug}/tokens) could therefore hit
POST /api/projects and create new, unrelated projects. Add
AuthenticateRequestWithToken to TokenAuthenticator and reject tokens
with non-nil ProjectID in the create-project handler. 403 with a clear
message.

setupTestApp was missing GlobalAccess in Deps so the test handler had a
nil globalAccess store — the H-1/H-2 regression tests need it. Added.

Four new regression tests:
  - TestDeleteVersionHonorsGlobalAccessGrant
  - TestProjectDetailCanUploadHonorsGlobalAccess
  - TestAPICreateProjectRejectsProjectScopedToken
  - TestAPICreateProjectAllowsGlobalToken (sanity: global token still OK)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
qwc merged commit 70dcb63a6a into main 2026-05-18 20:27:19 +02:00
qwc deleted branch fix/inline-authz-and-token-scope 2026-05-18 20:27:19 +02:00
Sign in to join this conversation.
No reviewers
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!106
No description provided.