@blamejs/blamejs-shop 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.3.x
10
10
 
11
+ - v0.3.1 (2026-05-30) — **Fix CSRF origin check refusing authenticated forms behind a TLS-terminating proxy.** The token-based CSRF protection added in 0.3.0 runs an Origin/Referer pre-check on every state-changing request. When the app is served behind a proxy that terminates TLS and forwards plain HTTP to the application — a CDN or load balancer, the standard production topology — the application saw the connection as HTTP and rebuilt its own origin as `http://<host>`, while the browser's request carried `Origin: https://<host>`. That bare scheme mismatch made the check refuse every legitimate same-origin POST as cross-origin, blocking sign-in and every account and admin form. The CSRF gate now trusts the forwarded protocol header — the same proxy stance the session cookies already use — so it derives the real public origin, and it matches against an explicit allowlist of the public origin(s), configurable via SHOP_PUBLIC_ORIGINS. The token cookie also regains its `__Host-` prefix over the forwarded HTTPS connection. No operator action is required for the default origin. **Fixed:** *Authenticated forms no longer refused behind a TLS-terminating proxy* — The CSRF origin pre-check derived the application origin from the raw proxied connection scheme, so a same-origin browser POST carrying `Origin: https://<host>` was refused as cross-origin under a CDN that forwards plain HTTP. The gate now trusts the forwarded `x-forwarded-proto` and matches against an explicit public-origin allowlist — `SHOP_PUBLIC_ORIGINS` (comma-separated), defaulting to the canonical origin — so sign-in and the account and admin forms submit correctly. The double-submit token cookie also regains its `__Host-` prefix over the forwarded HTTPS connection.
12
+
11
13
  - v0.3.0 (2026-05-30) — **Token-based CSRF protection on the account and admin forms.** Every state-changing form on the customer account surface — subscriptions, addresses, wishlist, returns, reviews, loyalty, referrals, passkey management, profile, checkout — and the entire admin console now carries a per-request double-submit CSRF token that is validated on submission. This is a defense-in-depth layer on top of the existing SameSite session cookies and the fetch-metadata cross-site gate. It lands with an update of the vendored blamejs runtime to v0.13.46, whose createApp now wires CSRF into the request lifecycle by default (closing the long-standing gap where it was documented but not enforced). The edge-rendered, cached storefront forms (add-to-cart, cookie consent, newsletter, currency switch) keep their SameSite + fetch-metadata defense — a per-session token cannot be embedded in HTML that is cached and identical for every visitor, and forcing one would break no-JS submission. No operator action or configuration change is required. **Security:** *Per-form CSRF tokens on account and admin* — The container-rendered account and admin forms now embed a hidden `_csrf` field carrying a per-request double-submit token; the server validates it (constant-time) on every state-changing POST. The passkey enrolment / sign-in islands send the matching token in the `X-CSRF-Token` header. This is layered on top of — not a replacement for — the SameSite session cookies and the fetch-metadata cross-site refusal already in place. · *Vendored blamejs runtime updated to v0.13.46* — createApp now wires CSRF (double-submit cookie), plus the cookie / CSP-nonce / fetch-metadata / body-parser layers, into the request lifecycle by default. The shop adopts the CSRF default scoped so the edge-cached, dual-rendered storefront endpoints (cart, consent, newsletter, currency) remain on their SameSite + fetch-metadata defense, while the authenticated account and admin surfaces gain the token check. A lint rule now flags any new edge-rendered POST form that would need that exemption.
12
14
 
13
15
  ## v0.2.x
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.3.0",
2
+ "version": "0.3.1",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-1SIn6oAf1DjECbRfKENZasdKHJiywGdXR58wn0hsFGcdVHzUmvfgMkEz5ANIAZJ3",
@@ -108,6 +108,20 @@ var EDGE_POST_PATHS = [
108
108
  "/announcements/",
109
109
  ];
110
110
 
111
+ // The TLS-terminated public origin(s) the storefront is served on. Behind
112
+ // the Cloudflare Worker the container socket is plain http, so the CSRF
113
+ // origin pre-check would otherwise build `http://<host>` and refuse every
114
+ // same-origin browser POST (which carries `Origin: https://<host>`) on a
115
+ // bare scheme mismatch — breaking sign-in and every authenticated form.
116
+ // The csrf gate below trusts the Worker-forwarded `x-forwarded-proto` (so
117
+ // it derives the real https origin) AND carries this explicit allowlist as
118
+ // belt-and-suspenders for the public host. Override with the comma-
119
+ // separated SHOP_PUBLIC_ORIGINS for an alternate deploy; the default is
120
+ // the canonical origin the Worker + storefront already use as their
121
+ // fallback.
122
+ var PUBLIC_ORIGINS = (process.env.SHOP_PUBLIC_ORIGINS || "https://blamejs.shop")
123
+ .split(",").map(function (s) { return s.trim(); }).filter(Boolean);
124
+
111
125
  /**
112
126
  * Resolve the real client IP for rate-limit keying. Reads the
113
127
  * Cloudflare-injected `cf-connecting-ip` first (the canonical single
@@ -184,7 +198,21 @@ function mountRouteGuards(r) {
184
198
  // shells render it into a hidden `_csrf` field. `skipStateless` passes
185
199
  // cookie-less and bearer-token (Authorization-header) requests through —
186
200
  // they cannot be CSRF-ed (no ambient cookie credential to abuse).
187
- var csrfGate = b.middleware.csrfProtect({ cookie: true, skipStateless: true });
201
+ var csrfGate = b.middleware.csrfProtect({
202
+ cookie: true,
203
+ skipStateless: true,
204
+ // Behind the Cloudflare Worker the container connection is plain http
205
+ // while the visitor is on https; trustProxy opts in the Worker-set
206
+ // `x-forwarded-proto` so the origin pre-check derives the real public
207
+ // origin (and the token cookie carries the `__Host-csrf` prefix),
208
+ // matching the session-cookie stance (storefront `_secureForReq`).
209
+ // allowedOrigins is the explicit public-host allowlist so a legitimate
210
+ // same-origin POST is never refused as cross-origin on a proxy
211
+ // scheme/host mismatch — the regression that breaks sign-in + every
212
+ // authenticated form behind the CDN.
213
+ trustProxy: true,
214
+ allowedOrigins: PUBLIC_ORIGINS,
215
+ });
188
216
  r.use(function csrfGuard(req, res, next) {
189
217
  var pathname = req.pathname || req.url || "/";
190
218
  if (_hasPrefix(pathname, WEBHOOK_PATHS) || pathname === HEALTH_PATH) return next();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {