Skip to content

Change authentication for self-service migrations to send API key instead of JWT#24462

Merged
PaulAdamDavis merged 3 commits intomainfrom
migrate-auth
Jul 22, 2025
Merged

Change authentication for self-service migrations to send API key instead of JWT#24462
PaulAdamDavis merged 3 commits intomainfrom
migrate-auth

Conversation

@PaulAdamDavis
Copy link
Member

@PaulAdamDavis PaulAdamDavis commented Jul 21, 2025

ref https://linear.app/ghost/issue/CON-372/change-ghost-self-service-migrations-app-auth

  • The self-service migrations app is currently authenticated with a JWT, but the expire time for these is capped at 5 minutes in the Admin API key auth service
  • I wasn't aware of this in Update self-service migration token expiration time to 24 hours #24119, making that change ineffective
  • The API key used to make the JWT is already available to privileged users of Ghost Admin
  • This commit changes the migrate service to send the API key rather than a JWT (and handling that in the self-service app) yields the same result, allowing migrations more time to process, improving the end result
  • It also removes the now-unused jose dependency

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 21, 2025

Walkthrough

The changes involve renaming and simplifying the method responsible for retrieving an API credential in the MigrateService class. The method apiToken was renamed to apiKey, and its implementation was changed to return the raw API key string directly, removing the previous logic for JWT token generation and parsing. The dependent postMessagePayload method was updated to use the new apiKey method and to include the raw API key in the payload under the property apiKey instead of apiToken. The sendRouteUpdate method was modified to post messages to the specific migrateUrl origin instead of using a wildcard origin. Corresponding unit tests were updated to reflect these changes, replacing all references to apiToken with apiKey. Additionally, the jose dependency was removed from the package.json as it was no longer used.

Estimated code review effort

2 (~20 minutes)

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ghost/admin/app/services/migrate.js

Oops! Something went wrong! :(

ESLint: 8.57.1

Error: Failed to load parser '@babel/eslint-parser' declared in 'ghost/admin/.eslintrc.js': Cannot find module '@babel/eslint-parser'
Require stack:

  • /ghost/admin/.eslintrc.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1369:15)
    at require.resolve (node:internal/modules/helpers:145:19)
    at Object.resolve (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2346:46)
    at ConfigArrayFactory._loadParser (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3325:39)
    at ConfigArrayFactory._normalizeObjectConfigDataBody (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3099:43)
    at _normalizeObjectConfigDataBody.next ()
    at ConfigArrayFactory._normalizeObjectConfigData (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3040:20)
    at _normalizeObjectConfigData.next ()
    at ConfigArrayFactory.loadInDirectory (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2886:28)
    at CascadingConfigArrayFactory._loadConfigInAncestors (/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3871:46)

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f1b2ac and 648c5cb.

📒 Files selected for processing (1)
  • ghost/admin/app/services/migrate.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/admin/app/services/migrate.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). (1)
  • GitHub Check: Setup
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the migration [pull request] Includes migration for review label Jul 21, 2025
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/admin/app/services/migrate.js (1)

3-3: Remove unused import.

The SignJWT import from 'jose' is no longer used since JWT generation was removed from the apiKey method.

-import {SignJWT} from 'jose';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between caa1802620761c22fb61812052dc162ca02c3ccb and f99b47f3f21296e7e706af66081b661073c2ffeb.

📒 Files selected for processing (2)
  • ghost/admin/app/services/migrate.js (1 hunks)
  • ghost/admin/tests/unit/services/migrate-test.js (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
ghost/admin/tests/unit/services/migrate-test.js (3)

Learnt from: ErisDS
PR: #23588
File: ghost/core/test/e2e-api/admin/backup.test.js:136-148
Timestamp: 2025-05-29T10:37:26.369Z
Learning: In the Ghost test framework, when testing CSV exports via the admin API, the response body field is empty while the actual CSV data is provided through the text field. Tests should use expectEmptyBody() and then validate the CSV content via .expect(({text}) => ...) - this is not contradictory behavior.

Learnt from: mike182uk
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 Ghost's ActivityPub module are thoroughly tested in apps/admin-x-activitypub/test/unit/utils/pending-activity.ts, which covers generatePendingActivityId, isPendingActivity, and generatePendingActivity functions.

Learnt from: mike182uk
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.

🔇 Additional comments (5)
ghost/admin/app/services/migrate.js (2)

31-42: LGTM! Simplified API key retrieval aligns with PR objectives.

The method correctly retrieves and returns the raw API key instead of generating a JWT. This addresses the 5-minute JWT expiration limitation mentioned in the PR objectives and allows migrations more time to process.


45-50: LGTM! Payload correctly updated to use raw API key.

The changes properly update the method call from apiToken() to apiKey() and the payload property from apiToken to apiKey, maintaining consistency with the authentication method change.

ghost/admin/tests/unit/services/migrate-test.js (3)

29-29: LGTM! Test stub correctly updated to match service method rename.

The stub properly reflects the change from apiToken to apiKey method in the service.


41-41: LGTM! Payload property assertion updated correctly.

The test assertion properly checks for the new apiKey property instead of apiToken, maintaining alignment with the service payload structure.


44-44: LGTM! Value assertion updated to match new property name.

The assertion correctly validates payload.apiKey instead of payload.apiToken, ensuring test coverage remains intact after the authentication method change.

@ErisDS ErisDS force-pushed the 6.x branch 2 times, most recently from 7b135d2 to 336f1e7 Compare July 21, 2025 16:32
@PaulAdamDavis PaulAdamDavis changed the base branch from 6.x to main July 21, 2025 18:54
…tead of JWT

ref https://linear.app/ghost/issue/CON-372/change-ghost-self-service-migrations-app-auth

- The self-service migrations app is currently authenticated with a JWT, but the expire time for these is capped at 5 minutes in the (Admin API key auth service)[https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/services/auth/api-key/admin.js]
- I wasn't aware of this in #24119, making that change ineffective
- The API key used to make the JWT is already available to privileged users of Ghost Admin
- Changing the migrate service to send the API key rather than a JWT (and handling that in the self-service app) yields the same result; allowing migraitons more time to process, improving the end result
@codecov
Copy link

codecov bot commented Jul 21, 2025

Codecov Report

Attention: Patch coverage is 33.33333% with 2 lines in your changes missing coverage. Please review.

Project coverage is 71.55%. Comparing base (aeb3299) to head (648c5cb).
Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
ghost/admin/app/services/migrate.js 33.33% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #24462   +/-   ##
=======================================
  Coverage   71.55%   71.55%           
=======================================
  Files        1529     1529           
  Lines      114778   114775    -3     
  Branches    13833    13833           
=======================================
+ Hits        82127    82130    +3     
- Misses      31631    31643   +12     
+ Partials     1020     1002   -18     
Flag Coverage Δ
admin-tests 48.71% <33.33%> (+0.02%) ⬆️
e2e-tests 71.55% <33.33%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Collaborator

@allouis allouis left a comment

Choose a reason for hiding this comment

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

Calling postMessage with '*' as the origin makes me feel a little uncomfortable, especially when sending an API key

I see we're passing '*' when updating the route info, but I can't see where this is happening when sending the key

We should restrict the postMessage call to the origin of the migrate service, this ensures that data can only ever be sent there, and not to a different site

There's a small note about this in the MDN docs here https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#targetorigin

@PaulAdamDavis
Copy link
Member Author

PaulAdamDavis commented Jul 22, 2025

@allouis Good call. I have updated the sendRouteUpdate method.

The payload that contains the API key is sent to the iframe here https://github.com/TryGhost/Ghost/blob/main/ghost/admin/app/components/gh-migrate-iframe.js#L65 and is untouched in this PR. The origin is migrateUrl in the service.

These are the only 2 places where data is sent. On the receiving end, we check the origin here https://github.com/TryGhost/Ghost/blob/main/ghost/admin/app/components/gh-migrate-iframe.js#L30-L31

@allouis allouis removed the migration [pull request] Includes migration for review label Jul 22, 2025
@PaulAdamDavis PaulAdamDavis merged commit c689049 into main Jul 22, 2025
51 checks passed
@PaulAdamDavis PaulAdamDavis deleted the migrate-auth branch July 22, 2025 10:00
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.

3 participants