Skip to content

Commit 0d0e5bd

Browse files
authored
🐛 Fixed preview route for published post bypassing redirect to post URL (#24653)
ref https://linear.app/ghost/issue/ENG-2506/its-possible-to-bypass-paywall-on-a-post-by-constructing-a-preview There was a bug in the post preview route that bypassed the normal redirect to the published post. The preview route should only be accessible for posts that are not published. This was caused by the post's status being stripped in some cases, which bypassed the logic to redirect to the published post's normal URL, and instead allowed the preview content to render in its entirety.
1 parent e9f9e6a commit 0d0e5bd

File tree

4 files changed

+21
-1
lines changed

4 files changed

+21
-1
lines changed

ghost/core/core/server/api/endpoints/previews.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const _addMemberContextToFrame = async (frame) => {
1919
// only set apiType when given a member_status to preserve backwards compatibility
2020
// where we used to serve "Admin API" content with no gating for all previews
2121
frame.apiType = 'content';
22+
frame.isPreview = true;
2223

2324
frame.original ??= {};
2425
frame.original.context ??= {};

ghost/core/core/server/api/endpoints/utils/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,14 @@ module.exports = {
4040
*/
4141
isInternal: (frame) => {
4242
return frame.options.context && frame.options.context.internal;
43+
},
44+
45+
/**
46+
* @description Returns true if the request is a preview request.
47+
* @param {import('@tryghost/api-framework').Frame} frame
48+
* @returns {boolean}
49+
*/
50+
isPreview: (frame) => {
51+
return frame.isPreview === true;
4352
}
4453
};

ghost/core/core/server/api/endpoints/utils/serializers/output/utils/clean.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ const post = (attrs, frame) => {
8282
const fields = frame && frame.original && frame.original.query && frame.original.query.fields || null;
8383

8484
if (localUtils.isContentAPI(frame)) {
85-
delete attrs.status;
85+
if (!localUtils.isPreview(frame)) {
86+
delete attrs.status;
87+
}
8688
delete attrs.email_only;
8789
delete attrs.newsletter;
8890
delete attrs.email_segment;

ghost/core/test/e2e-frontend/preview_routes.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ describe('Frontend Routing: Preview Routes', function () {
145145
.expect(assertCorrectFrontendHeaders);
146146
});
147147

148+
it('should redirect published posts to their live url with ?member_status=paid', async function () {
149+
await request.get('/p/2ac6b4f6-e1f3-406c-9247-c94a0496d39d/?member_status=paid')
150+
.expect(301)
151+
.expect('Location', '/short-and-sweet/')
152+
.expect('Cache-Control', testUtils.cacheRules.year)
153+
.expect(assertCorrectFrontendHeaders);
154+
});
155+
148156
it('should render scheduled email-only posts', async function () {
149157
const scheduledEmail = await testUtils.fixtures.insertPosts([{
150158
title: 'test newsletter',

0 commit comments

Comments
 (0)