Moved gift-link verification into the posts/pages Content API#29078
Moved gift-link verification into the posts/pages Content API#29078kevinansfield wants to merge 1 commit into
Conversation
ref #29064 - the entry controller previously verified gift tokens itself and re-read the entry as a paid-member shim, giving the frontend gift-service knowledge through the proxy seam and costing a second lookup per gift view - the frontend now passes the raw ?gift token through the single entry lookup as read context; the posts/pages read endpoints resolve it, verify it against the entry being read and unlock its gated content via the same shim previews use, or reject with INVALID_GIFT_TOKEN for the frontend to strip-and-301 - the token resolves to its post id in generateCacheKeyData, before the response cache is consulted: unlocked variants are cached by resolved post id, revocations take effect immediately, and an unresolvable token can never share a cache key with a plain read - ?gift stays intentionally a site-frontend concern: the token travels as internal read context only, so the public Content API ignores the param
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run ghost:test:ci:integration |
✅ Succeeded | 2m 35s | View ↗ |
nx run ghost:test:integration |
✅ Succeeded | 2m 42s | View ↗ |
nx run ghost:test:legacy |
✅ Succeeded | 2m 53s | View ↗ |
nx run ghost:test:e2e |
✅ Succeeded | 2m 28s | View ↗ |
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | <1s | View ↗ |
nx run-many -t lint -p ghost |
✅ Succeeded | 33s | View ↗ |
nx run-many -t test:unit -p ghost |
✅ Succeeded | 28s | View ↗ |
nx run @tryghost/admin:build |
✅ Succeeded | 7s | View ↗ |
Additional runs (2) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-07-03 15:28:03 UTC
rob-ghost
left a comment
There was a problem hiding this comment.
👍🏻 best approach yet.
Not blocking but we could add a test for gift link rendering of a post which contains a kg-gated-block like:
<!--kg-gated-block:begin nonMember:true-->
<p>Anonymous gated content</p>
<!--kg-gated-block:end-->
which would enshrine the need to have a member shim when rendering a post with a gift link. This is currently missing,

ref #29064
Draft demonstrating an alternative to the public
GET /content/gift_links/:tokenendpoint proposed in #29064 — not intended to merge as-is until we've settled on a direction.The entry controller currently verifies gift tokens itself and re-reads the entry as a paid-member shim. That gives the frontend gift-service knowledge through the proxy seam (
proxy.giftLinks,proxy.createPaidMemberShim), costs a second entry lookup on every gift view, and any future consumer of gift access has to reimplement the same verification dance.This moves verification inside the posts/pages Content API read, where content gating already lives:
?gifttoken through the single entry lookup as read context — pure URL knowledge, no service access. Its remaining gift handling is reactive: an unverifiable token rejects the lookup withINVALID_GIFT_TOKEN, which triggers the existing strip-?gift-and-301; a verified render gets the noindex/no-referrer headers and toast/analytics flag.generateCacheKeyData, before the response cache is consulted, mirroring how member auth is resolved per-request ahead of the cache: unlocked variants are cached by resolved post id, revoking or resetting a link takes effect immediately, and an unresolvable token can never share a cache key with a plain read — a cache hit can never serve a gated 200 where a miss would 403.?giftstays intentionally a site-frontend concern: the token travels as internal read context only, so the public Content API ignores the param entirely (pinned by a canary test).The existing
test/e2e-frontend/gift-links.test.tssuite passes unchanged.