Skip to content

Sign Ghost Admin API tokens per request from the Admin API key - #1615

Merged
helpcodeai merged 3 commits into
mainfrom
fix/ghost-admin-jwt
Nov 9, 2026
Merged

helpcodeai merged 3 commits into
mainfrom
fix/ghost-admin-jwt

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

What and why

The Ghost adapter asked for GHOST_ADMIN_JWT, a token the user had to generate by hand. Ghost refuses Admin API tokens older than 5 minutes, so every Ghost connector stopped working a few minutes after setup. A Cloud customer hit it today: three calls worked, the next one failed with INVALID_JWT, he pasted a new token, and then he asked how to cancel.

  • authType: "HMAC" signatures can now carry a jwt block (hmac-jwt.util.ts). The engine signs a new HS256/384/512 token for every request and every retry and sends it in a configurable header. The connector stores the key only.
  • Ghost now uses it: kid = the id half of the id:secret Admin API key, signed with the hex-decoded secret, aud: "/admin/", 5-minute lifetime, Authorization: Ghost <token>.
  • The adapter asks for GHOST_ADMIN_API_KEY. It has a format check that turns away a Content API key, a URL pattern that requires /ghost/api/admin, verify hints for 401/403/404, and a probe on ghost_list_posts: /site/ answers without authentication, so it passed any key.
  • Signing errors name the field that is wrong (no colon, a secret that isn't hex, an unset variable) and never include the key.
  • docs/tool-definition.md documents the jwt block.

Installed connectors

Resync doesn't touch authType/authConfig, so the two installed Ghost connectors on Cloud need their auth moved to the new config after deploy (targeted update), and their owners need to paste the Admin API key. The old GHOST_ADMIN_JWT never stayed valid anyway.

Cloud and self-hosted

Same in both: the engine change and the adapter JSON are shared. No schema change and no new auth type: it is an option on the existing HMAC type.

Tests

Working directory packages/backend, after npx prisma generate:

  • npx jest --maxWorkers=4 src/connectors/engines/hmac-jwt.util.spec.ts src/connectors/engines/rest.engine.hmac-jwt.spec.ts src/adapters/intl/ghost.live.spec.ts src/connectors/engines/rest.engine.hmac-pin.spec.ts: 26 passed, 1 skipped (live, gated by RUN_GHOST_LIVE=1).
  • npx jest --maxWorkers=4 (full suite): 1062 suites passed. Two suites failed:
    • rest.engine.shopee.spec.ts: a list of all HMAC adapters didn't include Ghost. Fixed in this PR to leave JWT-signed adapters out; 8 of 8 pass now.
    • auth/jwt-or-management-key.guard.spec.ts: it needs a live PostgreSQL, which my local worktree didn't have. Environment, not this change; CI runs it with its database.
  • npm run lint: 0 errors (11 warnings, all in files this PR doesn't touch). npx tsc --noEmit -p tsconfig.json: clean.
  • Repository root: node scripts/validate-adapters.mjs --warn: the Ghost warnings are the existing short tool descriptions, none new. node scripts/regenerate-catalog.mjs: no catalog diff.

Not tested against a real Ghost site (no Ghost instance at hand). The token format is checked with jsonwebtoken.verify against Ghost's documented claims.

The Ghost adapter asked users for a JWT they generated by hand. Ghost
expires those after 5 minutes, so every Ghost connector stopped working
minutes after setup. HMAC signatures can now carry a jwt block: the
engine signs a fresh HS256 token per request (kid from the id:secret
key, hex-decoded secret, aud /admin/) and sends Authorization: Ghost.
The adapter now asks for GHOST_ADMIN_API_KEY with a format check and
probes an authenticated endpoint.
@helpcodeai
helpcodeai requested a review from D3nisty as a code owner November 9, 2026 17:23
@helpcodeai
helpcodeai enabled auto-merge (squash) November 9, 2026 17:23
@helpcodeai
helpcodeai merged commit b8f7551 into main Nov 9, 2026
14 checks passed
@helpcodeai
helpcodeai deleted the fix/ghost-admin-jwt branch November 9, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant