Skip to content

Integration Admin API keys get 403 on custom field definition routes since 6.64.0 #30922

Description

@OutpostRyan

Issue Summary

Since 6.64.0, a custom integration's Admin API key can no longer read or manage member custom field definitions. Every request under /ghost/api/admin/members/metafields/ (browse, read, add, reorder, edit, destroy) returns 403 NoPermissionError: API tokens do not have permission to access this endpoint. On 6.63.0 the same key could list and create definitions. Staff access tokens still work, and integration keys can still read and write member values through /members/:id/.

This reopens the gap #30497 closed in 6.63: an integration can read members' values but not the definitions that describe them.

The cause is #30512, which moved these routes onto a router mounted at /members/metafields and runs mw.authAdminApi inside it. For integration tokens, tokenPermissionCheck takes the resource from the first segment of req.url and checks it against its allowlist, where members is allowed. Inside a mounted router Express strips the mount path from req.url, so the check sees custom instead of members and refuses the request. Logged from a running 6.64.0:

  • GET /ghost/api/admin/members/: req.url is /members/, req.baseUrl is /ghost/api/admin
  • GET /ghost/api/admin/members/metafields/custom/: req.url is /custom/, req.baseUrl is /ghost/api/admin/members/metafields

test/e2e-api/admin/member-custom-fields.test.ts calls these routes only with staff sessions, so the change went unnoticed. It is still present on main at a2c06fe.

Suggested fix: run mw.authAdminApi on the main router before the mount, where req.url is still /members/metafields/..., and keep the sub-router and its guards as they are. A PR with this change and e2e tests follows.

Steps to Reproduce

  1. On 6.63.0 with labs.membersCustomFields enabled, create a custom integration and use its Admin API key.
  2. GET /ghost/api/admin/members/metafields/custom/ returns 200 with the definitions, and a POST to the same path creates one (201).
  3. Upgrade to 6.64.0 and repeat both requests. Each returns 403 with "API tokens do not have permission to access this endpoint".
  4. The same GET with a staff access token still returns 200.

Expected: an integration's Admin API key can read and manage custom field definitions, as it could in 6.63.0 and as #30497 intended.

Ghost Version

6.64.0 (also main at a2c06fe)

Node.js Version

22.23.1

How did you install Ghost?

npm package in production mode on macOS, which uses the same routing as the official Docker image

Database type

Other: MySQL 9.6. The refusal comes from request routing, so the database doesn't matter.

Browser & OS version

Not applicable (Admin API)

Relevant log / error output

HTTP/1.1 403 Forbidden
{"errors":[{"message":"API tokens do not have permission to access this endpoint","type":"NoPermissionError"}]}

Code of Conduct

  • I agree to be friendly and polite to people in this repository

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs:triage[triage] this needs to be triaged by the Ghost team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions