test(authz): bearer-authed admin cannot edit/cover NULL-uploader mangas (0.87.19)

0.87.10 followup from the rereview. The fix lacked a test for the
specific axis it changed (admin authority via cookie only). New test
mints a bearer for a promoted admin and asserts PATCH/PUT-cover/DELETE-
cover all 403 on NULL-uploader rows. Mutation-confirmed.

Also: `MultipartBuilder::finalize` now `pub` for bearer-multipart use;
`admin_csrf_guard` rustdoc updated to describe the post-0.87.10
cookie-precedence rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-23 20:36:50 +02:00
parent d6a4fd668c
commit 0f9254b054
6 changed files with 146 additions and 8 deletions

View File

@@ -1058,9 +1058,17 @@ const ADMIN_PATH_PREFIX: &str = "/api/v1/admin/";
/// the configured allowlist. Safe methods (`GET`/`HEAD`/`OPTIONS`) are
/// always allowed.
///
/// **Bearer-token requests** (`Authorization: Bearer …`) are bot API
/// callers — they can't be a CSRF vector because the browser never
/// attaches that header automatically. Skip the check for them.
/// **Bearer-token-only requests** (`Authorization: Bearer …` with NO
/// session cookie) are bot API callers — they can't be a CSRF vector
/// because the browser never attaches the Authorization header
/// automatically. Skip the check for them.
///
/// **Bearer + cookie (the "cookie-ride")** is treated as cookie-auth.
/// An attacker page can mint `Authorization: Bearer junk` on a
/// credentialed cross-site POST; the cookie carries the actual
/// authority. Closing this hole means cookie precedence: as soon as a
/// session cookie is present, the CSRF gate fires regardless of the
/// Authorization header. (0.87.10 closed a 0.87.2 regression here.)
///
/// **Cookie-auth requests** must:
/// * be from an allowed origin (`Origin` then `Referer`), AND
@@ -1073,7 +1081,11 @@ const ADMIN_PATH_PREFIX: &str = "/api/v1/admin/";
/// — this used to silently let everything through, so an operator who
/// forgot to set `ADMIN_ALLOWED_ORIGINS` shipped an unguarded admin
/// surface. Operators on a pure-bot-token deploy aren't impacted (their
/// requests carry `Authorization: Bearer …` and skip the gate above).
/// requests carry only `Authorization: Bearer …` with no session cookie
/// and skip the gate via the bearer-only branch above).
///
/// **No auth at all** (no cookie, no bearer): bypass the CSRF gate so
/// the auth extractor returns a clean 401 instead of a confusing 403.
async fn admin_csrf_guard(
State(state): State<AppState>,
req: Request,