Skip to content

🎨 Improved sign-in to return you to the comment you were replying to#28439

Open
luissazevedo wants to merge 5 commits into
TryGhost:mainfrom
luissazevedo:BER-3648/reply-sign-in-scroll-position
Open

🎨 Improved sign-in to return you to the comment you were replying to#28439
luissazevedo wants to merge 5 commits into
TryGhost:mainfrom
luissazevedo:BER-3648/reply-sign-in-scroll-position

Conversation

@luissazevedo

Copy link
Copy Markdown
Contributor

ref https://linear.app/tryghost/issue/BER-3648/

What

When a logged-out reader clicks Reply on a comment and signs in, the page used to reload and drop them at the top of the post. Now it returns them to the comment they were replying to (scrolled + highlighted).

How

  • The reply CTA's Sign in opens Portal with a same-site redirect: #/portal/signin?redirect=<comment permalink>.
  • Portal already threads pageData.redirect through the magic-link round-trip and core preserves the URL fragment, so the existing permalink path does the scroll/highlight — no client-side storage, no new restore logic.
  • Scope: sign-in only. Free/paid sign-up is deferred (same mechanism — see follow-up).

🔍 Reviewer notes — please weigh in on these two

1. closePopup clears a one-shot redirect (shared Portal action)apps/portal/src/actions.js
To stop a dismissed reply-sign-in from leaking its pageData.redirect into a later, unrelated sign-in on the same page, closePopup now strips redirect from pageData (other keys preserved). This runs on every popup close across Portal — please confirm no existing flow relies on pageData.redirect surviving a close. Account-page / feedback redirects consume their redirect during sign-in (not after a close), so I believe it's safe; covered by a new actions.test.ts test, and existing signin-flow tests still pass.

2. getPageUrl strips a generic action paramapps/comments-ui/src/index.tsx
We strip success/action so they don't leak into comment permalinks (and the redirect built from the page URL). success is unambiguously a Ghost auth marker, but action is generic — a publisher could legitimately use ?action=… and we'd silently drop it. Options: only strip action when success is present, or only strip action=signin or not implement this

Security

The sign-in link can now carry a redirect (where to send the reader after they log in). It's validated same-site only — via a new isSameOriginUrl() helper that compares URL origins rather than a string prefix, so it can't be fooled by look-alike hosts or a missing trailing slash — meaning it can't bounce anyone to an external site. This makes "redirect after sign-in" usable by any page on the site, not just comments, and Ghost core has an older, looser version of the same check elsewhere. Neither is exploitable in this PR, but flagging in case we want to tighten or standardise it.

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR implements a feature enabling users to be redirected to a specific comment after signing in from comment CTAs. Changes span comments-ui and portal: ReplyButton and Like/Dislike include commentId when opening the CTA popup; CTAPopup/CTABox forward commentId and CTABox builds and encodes a comment permalink into the Portal signin redirect; getPageUrl and cleanPageUrl remove transient auth markers from permalinks; Portal adds isSameOriginUrl and merges a validated redirect into signin pageData; closePopup clears one-shot redirects. Tests (unit and E2E) exercise these paths.

Suggested labels

ok to merge for me

Suggested reviewers

  • EvanHahn
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Improved sign-in to return you to the comment you were replying to' clearly and concisely summarizes the main objective of the changeset.
Description check ✅ Passed The description thoroughly explains the feature, implementation approach, reviewer notes, and security considerations, all of which align with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/comments-ui/src/index.tsx`:
- Around line 51-54: The code unconditionally deletes the 'action' query param
in apps/comments-ui/src/index.tsx which strips publisher-specific values; change
this to only remove Ghost auth markers by checking
url.searchParams.get('action') and deleting it only when it equals known Ghost
values (e.g., 'signin', 'signup', 'giftRedeem' or whatever markers your Ghost
install uses); leave 'success' handling as-is if it should always be removed,
and ensure this conditional targets the same location where you build permalinks
(referencing index.tsx and the buildCommentPermalink flow) so other publisher
query params are preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8a9012cd-e0cb-4b32-9d70-83806e884f4a

📥 Commits

Reviewing files that changed from the base of the PR and between 1d1085f and c7357a4.

📒 Files selected for processing (11)
  • apps/comments-ui/src/components/content/buttons/reply-button.tsx
  • apps/comments-ui/src/components/content/comment.tsx
  • apps/comments-ui/src/components/content/cta-box.tsx
  • apps/comments-ui/src/components/popups/cta-popup.tsx
  • apps/comments-ui/src/index.tsx
  • apps/comments-ui/test/e2e/reply-signin-redirect.test.ts
  • apps/portal/src/actions.js
  • apps/portal/src/app.js
  • apps/portal/src/utils/helpers.js
  • apps/portal/test/actions.test.ts
  • apps/portal/test/utils/helpers.test.js

Comment thread apps/comments-ui/src/index.tsx Outdated
@luissazevedo luissazevedo force-pushed the BER-3648/reply-sign-in-scroll-position branch from c7357a4 to 8c71734 Compare June 9, 2026 13:52
ref https://linear.app/tryghost/issue/BER-3648/

- clicking Reply while logged out, then signing in, used to reload the post and
  drop the reader at the top; now it returns them to the comment they wanted to
  reply to (scrolled + highlighted)
- the reply CTA's "Sign in" asks Portal to return to that comment via a
  same-site redirect (#/portal/signin?redirect=<comment permalink>); Portal
  threads pageData.redirect through the magic-link round trip and core preserves
  the fragment, so the existing permalink path does the scroll/highlight — no
  client-side storage needed
- portal: the signin route reads a redirect from its hash query, validated
  same-origin (isSameOriginUrl) to prevent open redirects
- portal: closePopup clears a one-shot redirect from pageData so a dismissed
  sign-in can't leak its redirect into a later, unrelated sign-in on the same page
- comments-ui: getPageUrl strips success/action auth markers so they don't leak
  into comment permalinks (or the redirect built from the page URL)
- scope: sign-in only; free/paid sign-up deferred (same redirect mechanism)
@luissazevedo luissazevedo force-pushed the BER-3648/reply-sign-in-scroll-position branch from 8c71734 to db53d44 Compare June 9, 2026 14:02
ref https://linear.app/tryghost/issue/BER-3648/

- review follow-up: likes/dislikes opened the same sign-in CTA as replies but without the comment id, so readers signing in from a like lost their place — they now get the same redirect treatment
- gated the post-auth `action` param strip on `success` being present, since Ghost only ever sets them together — a publisher's own standalone `?action=…` param is no longer dropped from permalinks
- extracted the stripping into a pure cleanPageUrl() helper so it could be unit tested
…-in-scroll-position

# Conflicts:
#	apps/comments-ui/package.json
#	apps/portal/package.json
ref https://linear.app/tryghost/issue/BER-3648/

- the pre-commit hook auto-bumped @tryghost/activitypub to 3.1.37 during a non-interactive merge of main, because the merge staged main's activitypub changes and the hook's tty prompt defaulted to yes
- this PR doesn't touch activitypub, so the version should stay at main's 3.1.36
ref https://linear.app/tryghost/issue/BER-3648/

- a comment permalink is the post's canonical URL plus an anchor; Ghost routes posts purely by path, so no query param is needed to resolve it
- guessing which params were safe to strip was both over- and under-inclusive: it dropped a publisher's own ?success=…&action=… params while letting Ghost's errorCode marker (and a potential one-time token) leak into permalinks and the post-sign-in redirect
- stripping everything also keeps ?/& out of the encoded redirect value, which avoids mis-parsing in browsers that return location.hash percent-decoded (Firefox)
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.

2 participants