@blamejs/blamejs-shop 0.3.1 → 0.3.2
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 +2 -0
- package/lib/admin.js +12 -7
- package/lib/asset-manifest.json +1 -1
- package/package.json +1 -1
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.2 (2026-05-30) — **Creating a product lands you on its detail screen to finish setup.** Creating a product from the admin console now takes you straight to that product's detail screen — where you add a variant, set its price, and add stock — instead of returning you to the product list. A banner on the new product names the remaining steps to make it sellable. This removes the hunt-for-the-product-you-just-made step from the catalog setup flow. **Changed:** *Product create flows straight into editing* — Submitting the new-product form now redirects to the product's detail screen with a banner naming the next steps — add a variant with a SKU, set its price, and add stock — rather than bouncing back to the product list. Configuring a just-created product no longer requires finding it again first.
|
|
12
|
+
|
|
11
13
|
- 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
14
|
|
|
13
15
|
- 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.
|
package/lib/admin.js
CHANGED
|
@@ -507,10 +507,13 @@ function mount(router, deps) {
|
|
|
507
507
|
return p;
|
|
508
508
|
}),
|
|
509
509
|
async function (req, res) {
|
|
510
|
-
// Browser form submit — create, then redirect (PRG)
|
|
510
|
+
// Browser form submit — create, then redirect (PRG) straight to the
|
|
511
|
+
// new product's detail screen so the operator continues setup (variant,
|
|
512
|
+
// price, stock) there instead of hunting for it in the list. Bad input
|
|
511
513
|
// re-renders the products page with a notice, never a 500.
|
|
514
|
+
var made;
|
|
512
515
|
try {
|
|
513
|
-
await catalog.products.create(req.body || {});
|
|
516
|
+
made = await catalog.products.create(req.body || {});
|
|
514
517
|
} catch (e) {
|
|
515
518
|
if (e instanceof TypeError || e.code === "CATALOG_DUPLICATE" || /slug|exists|duplicate/i.test(e.message || "")) {
|
|
516
519
|
var page = await catalog.products.list({ limit: 100 });
|
|
@@ -521,8 +524,8 @@ function mount(router, deps) {
|
|
|
521
524
|
}
|
|
522
525
|
throw e;
|
|
523
526
|
}
|
|
524
|
-
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".product.create", outcome: "success", metadata: {} });
|
|
525
|
-
_redirect(res, "/admin/products?created=1");
|
|
527
|
+
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".product.create", outcome: "success", metadata: { id: made.id } });
|
|
528
|
+
_redirect(res, "/admin/products/" + encodeURIComponent(made.id) + "?created=1");
|
|
526
529
|
},
|
|
527
530
|
));
|
|
528
531
|
|
|
@@ -629,8 +632,9 @@ function mount(router, deps) {
|
|
|
629
632
|
prices_by_variant: model.prices_by_variant, media: model.media,
|
|
630
633
|
asset_prefix: assetPrefix, upload_available: !!r2,
|
|
631
634
|
default_currency: await _defaultCurrency(),
|
|
632
|
-
saved:
|
|
633
|
-
|
|
635
|
+
saved: url && url.searchParams.get("saved"),
|
|
636
|
+
created: url && url.searchParams.get("created"),
|
|
637
|
+
notice: (url && url.searchParams.get("err")) ? "That action couldn't be completed." : null,
|
|
634
638
|
}));
|
|
635
639
|
},
|
|
636
640
|
));
|
|
@@ -7943,6 +7947,7 @@ function renderAdminProduct(opts) {
|
|
|
7943
7947
|
var pid = _htmlEscape(p.id);
|
|
7944
7948
|
|
|
7945
7949
|
var saved = opts.saved ? "<div class=\"banner banner--ok\">Saved.</div>" : "";
|
|
7950
|
+
var created = opts.created ? "<div class=\"banner banner--ok\">Product created. Add a variant with a SKU, set its price, and add stock to make it sellable.</div>" : "";
|
|
7946
7951
|
var notice = opts.notice ? "<div class=\"banner banner--err\">" + _htmlEscape(opts.notice) + "</div>" : "";
|
|
7947
7952
|
|
|
7948
7953
|
// ---- product fields edit ---------------------------------------------
|
|
@@ -8128,7 +8133,7 @@ function renderAdminProduct(opts) {
|
|
|
8128
8133
|
"<span class=\"status-pill " + statusCls + "\">" + _htmlEscape(p.status) + "</span>" +
|
|
8129
8134
|
" · <a href=\"/products/" + _htmlEscape(encodeURIComponent(p.slug)) + "\" target=\"_blank\" rel=\"noreferrer\">View on storefront →</a></p>";
|
|
8130
8135
|
|
|
8131
|
-
var body = "<section><h2>" + _htmlEscape(p.title) + "</h2>" + saved + notice + head +
|
|
8136
|
+
var body = "<section><h2>" + _htmlEscape(p.title) + "</h2>" + created + saved + notice + head +
|
|
8132
8137
|
"<div class=\"actions-row mt-0\">" + archiveAction + "</div>" +
|
|
8133
8138
|
editForm + "</section>" + variantsSection + mediaSection;
|
|
8134
8139
|
return _renderAdminShell(opts.shop_name, "Product " + p.slug, body, "products", opts.nav_available);
|
package/lib/asset-manifest.json
CHANGED
package/package.json
CHANGED