Skip to content

fix: accept Shopify preview/myshopify origins on personaliser submit CORS#395

Merged
joelgarthwaite merged 1 commit into
mainfrom
fix/personaliser-submit-cors-shopify-origins
May 29, 2026
Merged

fix: accept Shopify preview/myshopify origins on personaliser submit CORS#395
joelgarthwaite merged 1 commit into
mainfrom
fix/personaliser-submit-cors-shopify-origins

Conversation

@joelgarthwaite

@joelgarthwaite joelgarthwaite commented May 29, 2026

Copy link
Copy Markdown
Owner

Summary

The personaliser storefront widget's photo upload hits /api/personaliser/submit cross-origin. The endpoint only echoed Access-Control-Allow-Origin for a static list (brightivy.com, www.brightivy.com, the dev store). Testing the widget through an unpublished theme's *.shopifypreview.com preview host was blocked by the browser — Safari surfaced it as a generic "Load failed" on the Add-to-Cart step — because the CORS preflight returned no Access-Control-Allow-Origin for that dynamic origin.

Fix

Pattern-match Shopify-owned storefront domains (*.myshopify.com, *.shopifypreview.com) in addition to the explicit production hosts, while still rejecting look-alike / suffix-spoof origins (e.g. shopifypreview.com.evil.com, plain http://).

Verification

  • Origin-matching unit-checked against 9 cases (prod, www, dev-store, dynamic preview, dynamic myshopify, null, evil.com, suffix-spoof, http) — all pass.
  • tsc --noEmit clean.
  • Production brightivy.com behaviour unchanged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Updated cross-origin request validation to support a wider range of Shopify development and preview store URLs, improving compatibility with various deployment environments.

Review Change Stack

…CORS

The /api/personaliser/submit endpoint only echoed Access-Control-Allow-Origin
for a static list (brightivy.com + the dev-store). Testing the storefront
widget through an unpublished theme's *.shopifypreview.com preview host was
blocked by the browser (Safari 'Load failed') because the preflight returned
no ACAO for that origin. Pattern-match Shopify-owned storefront domains
(*.myshopify.com, *.shopifypreview.com) in addition to the explicit prod
hosts, while still rejecting look-alike/suffix-spoof origins.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
valhalla-daily-pnl Ready Ready Preview, Comment May 29, 2026 7:34am

Request Review

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

CORS origin validation in the personaliser submit endpoint is refactored to replace a hardcoded allowlist containing a specific dev-store hostname with a smaller explicit list of production domains plus a case-insensitive regex pattern that dynamically permits any https://<subdomain>.myshopify.com or https://<subdomain>.shopifypreview.com origin.

Changes

CORS Origin Pattern Validation

Layer / File(s) Summary
CORS origin pattern and validation refactor
src/app/api/personaliser/submit/route.ts
SHOPIFY_ORIGIN_PATTERN regex matcher and isAllowedOrigin() helper function are introduced to validate Shopify domain origins. ALLOWED_ORIGINS reduced to two production storefront domains. getCorsHeaders() refactored to conditionally set Access-Control-Allow-Origin header only when isAllowedOrigin(origin) returns true.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A regex guards the gates with care,
Shopify subdomains everywhere—
Dev stores dance, production stands,
Flexible CORS in rabbit hands. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: updating CORS handling to accept Shopify preview and myshopify origins in the personaliser submit endpoint.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/personaliser-submit-cors-shopify-origins

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.

ESLint install timed out. The project may have too many dependencies for the sandbox.


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

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 `@src/app/api/personaliser/submit/route.ts`:
- Around line 25-35: getCorsHeaders currently mirrors the request Origin into
Access-Control-Allow-Origin when isAllowedOrigin(origin) is true but doesn't add
a Vary header; update getCorsHeaders to add "Vary": "Origin" whenever you set
headers['Access-Control-Allow-Origin'] (i.e., inside the isAllowedOrigin(origin)
branch) so caches/CDNs won't reuse responses for different origins—modify the
getCorsHeaders function accordingly and ensure the header key matches "Vary"
with value "Origin".
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 571546c0-be08-4ccc-99b9-09f2649271fd

📥 Commits

Reviewing files that changed from the base of the PR and between f45da09 and 3316bc7.

📒 Files selected for processing (1)
  • src/app/api/personaliser/submit/route.ts

Comment on lines 25 to 35
function getCorsHeaders(origin: string | null): Record<string, string> {
const headers: Record<string, string> = {
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
};
if (origin && ALLOWED_ORIGINS.includes(origin)) {
headers['Access-Control-Allow-Origin'] = origin;
if (isAllowedOrigin(origin)) {
headers['Access-Control-Allow-Origin'] = origin as string;
}
return headers;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Should responses with dynamic Access-Control-Allow-Origin include Vary: Origin to avoid cache issues?

💡 Result:

Yes, responses that dynamically generate the Access-Control-Allow-Origin (ACAO) header based on the request's Origin header should include Vary: Origin to prevent significant caching issues [1][2][3][4]. When a server uses a dynamic ACAO policy (e.g., reflecting the request origin or checking it against a whitelist), it must inform shared caches—such as CDNs, proxy servers, and even the browser's own cache—that the response is dependent on the Origin request header [1][2][3]. Without the Vary: Origin header, these caches may incorrectly serve a cached response generated for one origin to a subsequent request from a different origin [2][4]. This can lead to CORS errors where the browser blocks the response because the cached ACAO header does not match the current request's origin, or even potential cache poisoning scenarios [5][2][4]. Best practices include: - Always include Vary: Origin when the ACAO header is dynamic (i.e., not a static string or the * wildcard) [1][2][3]. - Apply this header to all responses that are susceptible to CORS-related caching, including non-CORS requests (requests missing an Origin header), to ensure that a non-CORS response is not mistakenly served to a later CORS request [6][7]. - Do not use Vary: Origin if your ACAO response is always identical, such as when using the * wildcard or a single hardcoded origin string [8]. In these cases, including the header is unnecessary and can reduce cache efficiency [9][8].

Citations:


Add Vary: Origin to CORS headers when Access-Control-Allow-Origin is computed dynamically

Since getCorsHeaders reflects the request’s Origin into Access-Control-Allow-Origin, include Vary: Origin so shared caches/CDNs don’t reuse a cached response with one origin’s ACAO for a different origin.

♻️ Emit Vary: Origin
 function getCorsHeaders(origin: string | null): Record<string, string> {
   const headers: Record<string, string> = {
     'Access-Control-Allow-Methods': 'POST, OPTIONS',
     'Access-Control-Allow-Headers': 'Content-Type',
     'Access-Control-Max-Age': '86400',
+    Vary: 'Origin',
   };
   if (isAllowedOrigin(origin)) {
     headers['Access-Control-Allow-Origin'] = origin as string;
   }
   return headers;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getCorsHeaders(origin: string | null): Record<string, string> {
const headers: Record<string, string> = {
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
};
if (origin && ALLOWED_ORIGINS.includes(origin)) {
headers['Access-Control-Allow-Origin'] = origin;
if (isAllowedOrigin(origin)) {
headers['Access-Control-Allow-Origin'] = origin as string;
}
return headers;
}
function getCorsHeaders(origin: string | null): Record<string, string> {
const headers: Record<string, string> = {
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
'Vary': 'Origin',
};
if (isAllowedOrigin(origin)) {
headers['Access-Control-Allow-Origin'] = origin as string;
}
return headers;
}
🤖 Prompt for 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.

In `@src/app/api/personaliser/submit/route.ts` around lines 25 - 35,
getCorsHeaders currently mirrors the request Origin into
Access-Control-Allow-Origin when isAllowedOrigin(origin) is true but doesn't add
a Vary header; update getCorsHeaders to add "Vary": "Origin" whenever you set
headers['Access-Control-Allow-Origin'] (i.e., inside the isAllowedOrigin(origin)
branch) so caches/CDNs won't reuse responses for different origins—modify the
getCorsHeaders function accordingly and ensure the header key matches "Vary"
with value "Origin".

@joelgarthwaite joelgarthwaite merged commit 9400a32 into main May 29, 2026
3 checks passed
@joelgarthwaite joelgarthwaite deleted the fix/personaliser-submit-cors-shopify-origins branch May 29, 2026 09:41
joelgarthwaite pushed a commit that referenced this pull request May 31, 2026
#395)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
joelgarthwaite added a commit that referenced this pull request May 31, 2026
… on submit (#398)

* feat(personaliser): store block photo_crop + high-res reference image on submit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(personaliser): add Vary: Origin to dynamic CORS headers (CodeRabbit #395)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(personaliser): stable reference path + upsert (no orphans); clarify crop sanitiser + EXIF (CodeRabbit)

- reference image now block-references/{id}.{ext} + upsert:true (service-role key),
  mirroring the photo path — overwrites in place, no orphaned 300dpi composites.
  Drops immutable cacheControl so the stable URL never serves a stale composite.
- soften sanitiseBlockPhotoCrop comment (it's stricter than the case path, not a mirror).
- note the reference is a canvas composite (no EXIF) so no sharp() strip is needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(personaliser): log when block reference image is skipped (oversize/wrong-type) (CodeRabbit)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Joel Garthwaite <info@joelgarthwaite.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant