Skip to content

Commit 4b2e027

Browse files
author
vmarta_sfemu
committed
Sync from monorepo
Template version: 1.0.0-alpha.1 Uses NPM packages @salesforce/storefront-next-* v1.0.0-alpha.1 Synced by: vmarta_sfemu Monorepo SHA: 11b3b49590ea390deae421aef2c7ec2c5dcf09b9 Latest change: 11b3b4959 - Bump version to 1.0.0-alpha.1 (#1920)
1 parent 823c016 commit 4b2e027

77 files changed

Lines changed: 4131 additions & 3303 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ The docs below are where architectural detail lives — consult them for tasks i
243243
- [docs/README-AUTH.md](./docs/README-AUTH.md) — Authentication patterns
244244
- [docs/README-EMAIL-VERIFICATION.md](./docs/README-EMAIL-VERIFICATION.md) — Email verification: OTP flows, passwordless registration/login, account details badge, Change Email
245245
- [docs/README-TURNSTILE.md](./docs/README-TURNSTILE.md) — Cloudflare Turnstile bot protection (BFF verification, three-tier health, fail-open)
246+
- [docs/README-SECURITY-HEADERS.md](./docs/README-SECURITY-HEADERS.md) — Default security response headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
246247
- [docs/README-I18N.md](./docs/README-I18N.md) — Internationalization
247248
- [docs/README-MULTI-SITE.md](./docs/README-MULTI-SITE.md) — Site context and locale URL routing
248249
- [docs/README-PAGE-DESIGNER.md](./docs/README-PAGE-DESIGNER.md) — Page Designer component development (decorators, metadata)

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
<!-- Auto-generated by `pnpm changeset version` at release time. Do not edit directly — see CONTRIBUTING.md § Changesets to add an entry via `pnpm changeset`. -->
22

3+
## 1.0.0-alpha.1
4+
5+
### Minor Changes
6+
7+
- #1907 `3c9c3e5` Thanks @j-sheth_sfemu! - Wire SDK security-headers middleware into the React Router middleware chain. Apply per-request CSP nonce to the inline `__APP_CONFIG__` script. New customer-facing doc: `docs/README-SECURITY-HEADERS.md`.
8+
9+
### Patch Changes
10+
11+
- #1858 `6ee0b80` Thanks @daniel-diaz_sfemu! - Lazy-load below-the-fold UITarget extension components to fix TBT regression (#1858)
12+
13+
- #1885 `0e568cf` Thanks @jie-dai_sfemu! - Fix: store `dwsourcecode_*` cookie value as the bare source-code string (e.g. `email`) instead of a JSON-encoded object so SFRA storefronts running side-by-side on the same cookie name can read it. The shopper-context cookie (`storefront-next-context_*`) is unchanged. An empty `?src=` parameter now writes an empty cookie (effectively clearing it) instead of `{"sourceCode":""}`.
14+
15+
- #1898 `dee3c0a` Thanks @mjuraschik_sfemu! - Fall back to category-level page assignment during manifest resolution when a product-keyed page lookup misses. `resolveDynamicPageId` and `resolvePage` now accept an optional `categoryId` (string or Promise) consulted only after the product lookup misses, and the Page Designer resolution middleware threads the request's categoryId through whenever a productId is also present.
16+
17+
- #1917 `51466d3` Thanks @vmarta_sfemu! - Make `en-GB` the authoritative locale for type-safe `t()` keys. Previously `en-GB` was the runtime fallback (`config.server.ts`) and the source the type augmentation read (`i18next.server.ts`), but every locale's `index.ts` — including `en-GB` itself — used `satisfies DeepPartial<typeof enUS>`, making `en-US` the compile-time root. The two only happened to agree because `en-GB` currently holds every key. This flips the `satisfies` chain to `DeepPartial<typeof enGB>` so the compile-time and runtime sources of truth match. No runtime behavior change.
18+
19+
- #1918 `bbb0926` Thanks @vmarta_sfemu! - Merge `product.json` back into `translations.json` for all 17 locales. The split was originally introduced to dodge a TypeScript compiler crash; with the locale tree settled, the merge now typechecks cleanly. Keeps a single canonical translations file per locale. The runtime backend still registers `product` as a top-level i18next namespace, so all `t('product:…')` and `useTranslation('product')` call sites are unaffected.
20+
21+
- #1909 `d7c73e1` Thanks @vmarta_sfemu! - Cut `pnpm lint` runtime by disabling `@typescript-eslint/no-misused-promises` `checksVoidReturn.attributes`. The sub-check scans every JSX event-handler attribute (`onClick`, `onChange`, …) against the value's return type, which fans out across thousands of TSX files and pushed `pnpm lint` past 30 minutes on customer 1x CI runners. The rule still fires on argument, property, return, and variable positions, so it continues to catch real misuse — only the JSX-attribute traversal is dropped.
22+
23+
- #1893 `e0139ad` Thanks @alex-vuong_sfemu! - Fix: `getOrCreateWishlist` no longer blocks the loader for ~1.5s on every fresh `customerId`. The POST response from `createCustomerProductList` is now used directly when it carries an `id`, eliminating the hardcoded `setTimeout(1500)` + post-create GET that was previously waiting for index propagation. The sleep + re-fetch remains as a fallback for the rare case where the POST returns a body without `id`, gated by a `warn` log so production telemetry can confirm. Affects every wishlist-mounting route (homepage, cart, PDP, PLP, search, account overview) on first load after SLAS issues a new `gcid`.
24+
25+
- Updated dependencies [`d355c50`, `dee3c0a`, `af03e94`, `3c9c3e5`]:
26+
- @salesforce/storefront-next-runtime@1.0.0-alpha.1
27+
- @salesforce/storefront-next-dev@1.0.0-alpha.1
28+
329
## 1.0.0-alpha.0
430

531
### Minor Changes

config.server.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
// Relative paths required here — this file is evaluated by vite-node during
1717
// react-router typegen (via routes.ts), before Vite aliases are resolved.
18-
import { defineConfig } from '@salesforce/storefront-next-runtime/config';
18+
import { defineConfig, defaultSecurityHeaders } from '@salesforce/storefront-next-runtime/config';
1919
import type { Config } from './src/types/config';
2020
import { TrackingConsent } from './src/types/tracking-consent';
2121

@@ -547,6 +547,27 @@ export default defineConfig<Config>(
547547
enabled: process.env.TURNSTILE_VERIFICATION_ENABLED === 'true',
548548
},
549549
},
550+
// Security response headers. Default shape is imported from the SDK
551+
// so customers can override individual fields via PUBLIC__ env vars
552+
// (e.g. PUBLIC__app__security__headers__csp__reportOnly=true). The
553+
// SDK middleware merges customer overrides over these defaults.
554+
//
555+
// To extend CSP, spread `defaultCspDirectives`:
556+
//
557+
// import { defaultCspDirectives } from '@salesforce/storefront-next-runtime/security';
558+
// headers: {
559+
// ...defaultSecurityHeaders,
560+
// csp: {
561+
// directives: {
562+
// ...defaultCspDirectives,
563+
// 'script-src': [...defaultCspDirectives['script-src']!, 'https://cdn.foo.com'],
564+
// },
565+
// reportOnly: false,
566+
// },
567+
// },
568+
//
569+
// See docs/README-SECURITY-HEADERS.md for the defaults table and recipes.
570+
headers: defaultSecurityHeaders,
550571
},
551572
},
552573
},

docs/README-AUTH.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ export async function callbackAction({ request, context }: ActionFunctionArgs) {
290290
}
291291
```
292292

293+
> **CSP note:** When you add a social-login provider beyond the defaults, extend `connect-src` (and any redirect/popup origins) in your `app.security.headers.csp.directives` config. See [README-SECURITY-HEADERS.md](./README-SECURITY-HEADERS.md).
294+
293295
### Custom Auth Operations
294296

295297
For custom auth workflows, use the provided server-side helpers:

docs/README-CONFIG-OPTIONS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,14 @@ PUBLIC__app__auth__otpLength=6
606606

607607
---
608608

609+
## security
610+
611+
### `security.headers`
612+
613+
Default security response headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy). Empty `{}` uses SDK defaults. See [README-SECURITY-HEADERS.md](./README-SECURITY-HEADERS.md) for the defaults table and extension recipes.
614+
615+
---
616+
609617
## features
610618

611619
Site feature flags that enable or disable specific functionality.

docs/README-CONFIG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,3 +646,7 @@ Complex values can be encoded as JSON strings — the merge mechanism parses any
646646
# Override multiple cart configuration values at once
647647
# PUBLIC__app__pages__cart='{"quantityUpdateDebounce":1000,"maxQuantityPerItem":500,"enableSaveForLater":true}'
648648
```
649+
650+
## Related
651+
652+
- [README-SECURITY-HEADERS.md](./README-SECURITY-HEADERS.md) — Security response headers config (`app.security.headers.*`)

docs/README-SECURITY-HEADERS.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# Security Response Headers
2+
3+
Storefront Next ships default security response headers from the SDK. Every storefront generated from this template inherits them automatically — no opt-in needed.
4+
5+
## What ships by default
6+
7+
| Header | Default value |
8+
|---|---|
9+
| `Content-Security-Policy` | See [CSP directives](#csp-directives) below |
10+
| `Strict-Transport-Security` | `max-age=15552000; includeSubDomains` (only on Managed Runtime; suppressed locally) |
11+
| `X-Frame-Options` | `SAMEORIGIN` |
12+
| `X-Content-Type-Options` | `nosniff` |
13+
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
14+
| `Permissions-Policy` | `camera=(), microphone=(), geolocation=()` |
15+
16+
### CSP directives
17+
18+
| Directive | Default value | Why |
19+
|---|---|---|
20+
| `default-src` | `'self'` | Restricts every fetch type not otherwise listed. |
21+
| `script-src` | `'self' https://challenges.cloudflare.com 'nonce-<per-request>'` | Strict — no `'unsafe-inline'` or `'unsafe-eval'`. The per-request nonce permits the `__APP_CONFIG__` inline script. Cloudflare origin permits the Turnstile widget. |
22+
| `style-src` | `'self' 'unsafe-inline'` | Tailwind v4 + shadcn rely on inline styles. |
23+
| `img-src` | `'self' data: https://*.commercecloud.salesforce.com https://*.demandware.net` | DIS image URLs. |
24+
| `font-src` | `'self' data:` | Self-hosted web fonts. |
25+
| `connect-src` | `'self' https://*.commercecloud.salesforce.com https://*.demandware.net https://challenges.cloudflare.com` | SCAPI calls + browser-initiated XHR from the Turnstile widget. |
26+
| `frame-src` | `https://challenges.cloudflare.com` | Turnstile widget iframe. |
27+
| `frame-ancestors` | `'self'` | Modern equivalent of `X-Frame-Options`. |
28+
| `form-action` | `'self'` | Restricts form POST targets. CSP3 does NOT fall back to `default-src` for this directive — without it, forms can POST anywhere. |
29+
| `base-uri` | `'self'` | Prevents `<base href>` injection. |
30+
| `object-src` | `'none'` | Blocks Flash and other plugin content. |
31+
| `upgrade-insecure-requests` | enabled | Browser auto-upgrades HTTP subresources to HTTPS. |
32+
33+
## Where the config lives
34+
35+
All security-headers config is under `app.security.headers.*` in `config.server.ts`. (The sibling `app.security.turnstile.*` is unrelated — that's bot protection. Both live under `security` because they're defense-in-depth concerns.)
36+
37+
## Extending CSP for a new origin
38+
39+
Spread `defaultCspDirectives` and override per directive. **Each directive you set fully replaces the SDK default** — copy from the defaults to extend.
40+
41+
```ts
42+
// config.server.ts
43+
import { defaultCspDirectives } from '@salesforce/storefront-next-runtime/security';
44+
45+
export default defineConfig<Config>({
46+
app: {
47+
//
48+
security: {
49+
turnstile: { /* …existing turnstile config… */ },
50+
headers: {
51+
csp: {
52+
directives: {
53+
...defaultCspDirectives,
54+
'script-src': [
55+
...defaultCspDirectives['script-src']!,
56+
'https://cdn.example.com',
57+
],
58+
'connect-src': [
59+
...defaultCspDirectives['connect-src']!,
60+
'https://api.segment.io',
61+
],
62+
},
63+
},
64+
},
65+
},
66+
},
67+
});
68+
```
69+
70+
## Disabling a header
71+
72+
Set the field to `false` to disable a single header. Other headers remain.
73+
74+
```ts
75+
security: {
76+
headers: {
77+
hsts: false, // disable HSTS only
78+
permissionsPolicy: false, // disable Permissions-Policy only
79+
},
80+
}
81+
```
82+
83+
To disable everything (for debugging only):
84+
85+
```ts
86+
security: { headers: { enabled: false } }
87+
```
88+
89+
A startup warning is logged whenever any header is disabled.
90+
91+
## Migrating from PWA Kit
92+
93+
PWA Kit shipped its own permissive defaults; Storefront Next defaults are stricter. For a safe rollout:
94+
95+
1. Deploy with CSP in **report-only** mode:
96+
```ts
97+
security: { headers: { csp: { reportOnly: true } } }
98+
```
99+
The browser won't block anything but will log all violations to DevTools (and to any configured `report-uri`). A startup warning is logged on every server boot in this mode.
100+
2. Watch DevTools / browser violation reports for a week or two. Identify legitimate origins your storefront uses (analytics CDNs, third-party widgets, etc.).
101+
3. Add those origins to your CSP via the spread pattern above.
102+
4. Flip `reportOnly: false` (or remove the field) and redeploy. Enforcement is now on.
103+
104+
## Troubleshooting CSP violations
105+
106+
Open the browser DevTools console. Violations look like:
107+
108+
> Refused to load the script `https://cdn.example.com/foo.js` because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-…' https://challenges.cloudflare.com".
109+
110+
The directive name in the message tells you what to extend. Common culprits:
111+
112+
| Symptom | Likely fix |
113+
|---|---|
114+
| "Refused to load the script" | Add the origin to `script-src` |
115+
| "Refused to connect to" | Add the origin to `connect-src` |
116+
| "Refused to load the image" | Add the origin to `img-src` |
117+
| "Refused to apply inline style" | Custom inline styles need `'unsafe-inline'` (already on by default) — check the directive in the violation message |
118+
119+
## Environment variable overrides
120+
121+
Standard `PUBLIC__` env-var override applies. The path uses double-underscore separators following the existing `security.turnstile.*` pattern:
122+
123+
```bash
124+
# Toggle CSP report-only mode (recommended use case for env vars):
125+
PUBLIC__app__security__headers__csp__reportOnly=true
126+
127+
# Disable HSTS entirely:
128+
PUBLIC__app__security__headers__hsts=false
129+
```
130+
131+
Replacing individual CSP directive names is **not supported via env vars** because the env-var path uses `__` as a segment separator and CSP directive names contain hyphens (`script-src`, `connect-src`, …). Override the whole `directives` map as a JSON blob instead:
132+
133+
```bash
134+
# Override the entire CSP directives map (note: this REPLACES all defaults):
135+
PUBLIC__app__security__headers__csp__directives='{"default-src":["'"'"'self'"'"'"],"script-src":["'"'"'self'"'"'","https://cdn.example.com"]}'
136+
```
137+
138+
Because this fully replaces the SDK defaults, env-var CSP overrides are best reserved for unusual cases. For most directive changes, edit `config.server.ts` and spread `defaultCspDirectives` — the shell escaping is unforgiving and the default-replacement semantics are easy to get wrong.
139+
140+
See [README-CONFIG.md](./README-CONFIG.md) for the full env-var override system.
141+
142+
## Related docs
143+
144+
- [README-CONFIG.md](./README-CONFIG.md) — Configuration system
145+
- [README-CONFIG-OPTIONS.md](./README-CONFIG-OPTIONS.md) — Configuration reference
146+
- [README-AUTH.md](./README-AUTH.md) — Auth and social-login (extends `connect-src`)
147+
- [README-TURNSTILE.md](./README-TURNSTILE.md) — Cloudflare Turnstile (already permitted in defaults)

e2e/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@salesforce/storefront-next-e2e",
3-
"version": "1.0.0-alpha.0",
3+
"version": "1.0.0-alpha.1",
44
"private": true,
55
"scripts": {
66
"typecheck": "pnpm def && tsc --noEmit",

e2e/src/pages/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ export const pageObjects = {
4141
accountWishlistPage: './src/pages/account-wishlist.page.ts',
4242
wishlistPage: './src/pages/wishlist.page.ts',
4343
passwordlessLoginPage: './src/pages/passwordless-login.page.ts',
44+
securityHeadersPage: './src/pages/security-headers.page.ts',
4445
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright 2026 Salesforce, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
const { I } = inject();
18+
import { buildSitePath } from '../utils/url-utils';
19+
20+
class SecurityHeadersPage {
21+
/**
22+
* Navigate to the homepage and capture the response headers.
23+
*
24+
* `page.goto` requires an absolute URL (CodeceptJS's `I.amOnPage` resolves
25+
* a relative path against the configured `BASE_URL`, but we don't go
26+
* through `amOnPage` here because we need the Response object to read
27+
* headers off it). Match the canonical resolution pattern from
28+
* `storefront.page.ts`: `new URL(buildSitePath('/'), BASE_URL).toString()`.
29+
*
30+
* `I.usePlaywrightTo` does not propagate return values; the headers are
31+
* captured via a closed-over variable.
32+
*/
33+
async grabHomepageHeaders(): Promise<Record<string, string>> {
34+
const baseUrl = process.env.BASE_URL || 'http://localhost:5173';
35+
const url = new URL(buildSitePath('/'), baseUrl).toString();
36+
let headers: Record<string, string> = {};
37+
await I.usePlaywrightTo('grab homepage response headers', async ({ page }) => {
38+
const response = await page.goto(url);
39+
headers = response?.headers() ?? {};
40+
});
41+
return headers;
42+
}
43+
}
44+
45+
export = new SecurityHeadersPage();

0 commit comments

Comments
 (0)