Skip to content

Every write through adminApi.js fails CSRF — review submit, both moderation queues, federation and EOL sync were unreachable from the UI #481

Description

@rubenvdlinde

Summary

src/utils/adminApi.js builds every request with only Content-Type and
X-Requested-With: XMLHttpRequest and never sends the Nextcloud request
token
. Nextcloud's CSRF middleware rejects any non-GET, cookie-authenticated
request that lacks it, and none of the target controllers declares
#[NoCSRFRequired].

The result: every write action routed through this helper was unreachable
from the UI.
GET is exempt from the CSRF check, so each settings section
rendered perfectly and only its buttons were dead — the failure mode looks
like "the button does nothing", which is why it survived.

Affected actions (all POST/DELETE through apiRequest)

Surface Call
src/modals/SubmitReviewModal.vue:206 POST reviews — submit a review
src/views/settings/sections/ModerationQueue.vue:241 POST moderation/{uuid}/approve and /rejectboth moderated types
src/views/settings/sections/FederationSettings.vue:234 POST federation/peers
src/views/settings/sections/FederationSettings.vue:255 DELETE federation/peers
src/views/settings/sections/FederationSettings.vue:275 POST federation/pull
src/views/settings/sections/EolSyncSettings.vue:306 POST eol-sync/config
src/views/settings/sections/EolSyncSettings.vue:328 POST eol-sync/trigger ("Sync now")

Evidence (measured, not inferred)

A Playwright test driving the real "Write a review" modal failed with the
submit dialog still open. The failure page snapshot contains, verbatim:

- alert: CSRF check failed
- button "Cancel"
- button "Submit review"

Adding requesttoken: getRequestToken() to the helper's headers and rebuilding
turned the same, unchanged test green — the fix is the treatment and the
test is the assay.

Fix

Shipped in the same PR as the new gate-19 e2e coverage
(tests/e2e/spec-coverage/catalog-ratings.spec.ts), because the coverage could
not exist without it: src/utils/adminApi.js now imports getRequestToken
from @nextcloud/auth and sends it on every request.

@nextcloud/auth keeps that value in sync with the data-requesttoken head
meta, so it survives Nextcloud's token rotation. This is the same source
@nextcloud/axios uses — and src/store/modules/facets.js already went
through axios and was never affected, so the repo contained a working example
of the correct pattern the whole time.

Follow-up worth doing separately

There is no regression test that would catch a future helper regressing this
way. The e2e tests added in this PR cover the review-submit path only. A
vitest assertion on apiRequest's constructed headers would cover all seven
call sites at once and is cheap.

Related

Found while closing gate-19 (e2e-coverage) for softwarecatalog. See
tests/e2e/spec-coverage/catalog-ratings.spec.ts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions