Skip to content

Conversation

@9larsons
Copy link
Contributor

@9larsons 9larsons commented Dec 3, 2025

ref https://linear.app/ghost/issue/NY-693/

  • updated middleware to pass through query/search params with path

Note

Private login redirects now keep original query strings (e.g., UTM params) while still preventing cross-domain redirects.

  • Private blogging middleware (core/frontend/apps/private-blogging/lib/middleware.js)
    • Update getRedirectUrl to parse with URL, validate host, and return pathname + search to preserve query strings.
  • Tests (test/unit/frontend/apps/private-blogging/middleware.test.js)
    • Add assertions ensuring redirects retain query params on root and nested paths.
    • Maintain checks that full/external URLs redirect to /.

Written by Cursor Bugbot for commit b510f4c. This will update automatically on new commits. Configure here.

ref https://linear.app/ghost/issue/NY-693/
- updated middleware to pass through query/search params with path
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

This change modifies the redirect URL handling in the private blogging middleware to properly preserve query strings during login redirects. The getRedirectUrl function was updated to parse URL components separately (pathname and search), explicitly return '/' when the target host differs from the base, and concatenate pathname with search parameters when hosts match instead of returning only the pathname. Two new test cases were added to verify that UTM parameters and path-based query strings are preserved during the login redirect flow.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • The middleware change is localized to a single function with straightforward URL parsing logic
  • Test cases are additive with no modifications to existing tests
  • Changes follow a consistent pattern centered on query string preservation
  • Key areas to verify during review:
    • The host-checking logic correctly differentiates between matching and non-matching hosts
    • The pathname + search concatenation properly reconstructs the redirect URL with query parameters intact
    • Both test cases accurately reflect the expected behavior of the modified function

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main fix: preserving query parameters during private site login, which directly matches the core changes in the middleware and test files.
Description check ✅ Passed The description is directly related to the changeset, explaining the middleware update to preserve query strings and the corresponding test additions with clear examples (UTM params).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-private-site-query-params

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
ghost/core/core/frontend/apps/private-blogging/lib/middleware.js (1)

28-46: Redirect URL parsing and query preservation look solid; host check could be simplified

The updated getRedirectUrl correctly:

  • Decodes the r param safely under a try/catch.
  • Uses URL to normalise the path.
  • Preserves the query string via pathname + search.
  • Still prevents redirects off-instance by collapsing suspicious values (e.g. full URLs or //host-style paths) back to /.

As a small optional tidy-up, you could drop the extra target URL construction and use parsedUrl directly for the host check, which keeps all logic in one place and avoids a second parse:

 function getRedirectUrl(query) {
     try {
-        const redirect = decodeURIComponent(query.r || '/');
-        const parsedUrl = new URL(redirect, config.get('url'));
-        const pathname = parsedUrl.pathname;
-        const search = parsedUrl.search;
-
-        const base = new URL(config.get('url'));
-        const target = new URL(pathname, config.get('url'));
-        // Make sure we don't redirect outside of the instance
-        if (target.host !== base.host) {
-            return '/';
-        }
-        // Preserve query string (e.g., UTM parameters)
-        return pathname + search;
+        const redirect = decodeURIComponent(query.r || '/');
+        const baseUrl = config.get('url');
+        const base = new URL(baseUrl);
+        const parsedUrl = new URL(redirect, baseUrl);
+
+        // Make sure we don't redirect outside of the instance
+        if (parsedUrl.host !== base.host) {
+            return '/';
+        }
+
+        // Preserve query string (e.g., UTM parameters)
+        return parsedUrl.pathname + parsedUrl.search;
     } catch (e) {
         return '/';
     }
 }

Behavior should remain the same (including the existing tests around full URLs and http://britney.com//example.com), but the intent becomes a bit clearer.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b2b1f2c and b510f4c.

📒 Files selected for processing (2)
  • ghost/core/core/frontend/apps/private-blogging/lib/middleware.js (1 hunks)
  • ghost/core/test/unit/frontend/apps/private-blogging/middleware.test.js (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.
📚 Learning: 2025-03-13T09:00:20.205Z
Learnt from: mike182uk
Repo: TryGhost/Ghost PR: 22471
File: apps/admin-x-activitypub/src/utils/pending-activity.ts:13-71
Timestamp: 2025-03-13T09:00:20.205Z
Learning: The pending activity utilities in the Ghost ActivityPub module are covered by tests in the file `apps/admin-x-activitypub/test/unit/utils/pending-activity.ts`.

Applied to files:

  • ghost/core/test/unit/frontend/apps/private-blogging/middleware.test.js
📚 Learning: 2025-09-03T12:28:11.174Z
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24779
File: ghost/core/core/server/services/members/members-api/controllers/RouterController.js:34-51
Timestamp: 2025-09-03T12:28:11.174Z
Learning: The extractRefererOrRedirect function in Ghost's RouterController (ghost/core/core/server/services/members/members-api/controllers/RouterController.js) is working as intended according to maintainer kevinansfield. The current handling of autoRedirect, redirect parameter parsing, and referrer logic does not need the security-related changes suggested around autoRedirect coercion, relative URL handling, or same-origin checks.

Applied to files:

  • ghost/core/core/frontend/apps/private-blogging/lib/middleware.js
📚 Learning: 2025-04-23T15:44:52.549Z
Learnt from: 9larsons
Repo: TryGhost/Ghost PR: 21866
File: ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js:10-19
Timestamp: 2025-04-23T15:44:52.549Z
Learning: The existing implementation in `ghost/core/core/server/web/admin/middleware/serve-auth-frame-file.js` using `path.parse(req.url).base` is secure against path traversal attacks as it properly extracts only the filename component without any directory parts.

Applied to files:

  • ghost/core/core/frontend/apps/private-blogging/lib/middleware.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Ghost-CLI tests
  • GitHub Check: Acceptance tests (Node 22.18.0, sqlite3)
  • GitHub Check: Acceptance tests (Node 22.18.0, mysql8)
  • GitHub Check: Legacy tests (Node 22.18.0, sqlite3)
  • GitHub Check: Legacy tests (Node 22.18.0, mysql8)
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Lint
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build & Push
🔇 Additional comments (1)
ghost/core/test/unit/frontend/apps/private-blogging/middleware.test.js (1)

283-307: New tests accurately cover query-string preservation for private login redirects

Both added specs set up req.body, req.session, and req.query.r consistently with the existing suite and assert the exact res.redirect target, ensuring:

  • Root-level URLs preserve UTM-style query params.
  • Path URLs preserve their query strings as well.

This aligns with the new getRedirectUrl behavior and keeps the existing security-focused tests (full URLs / other domains) intact.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 3, 2025

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 19897988807 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@9larsons 9larsons merged commit 269e00d into main Dec 3, 2025
37 of 39 checks passed
@9larsons 9larsons deleted the fix-private-site-query-params branch December 3, 2025 15:09
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