@blamejs/blamejs-shop 0.4.6 → 0.4.7
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/asset-manifest.json +5 -1
- package/lib/security-middleware.js +20 -2
- package/lib/storefront.js +48 -34
- 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.4.x
|
|
10
10
|
|
|
11
|
+
- v0.4.7 (2026-06-05) — **Payment page: Trusted Types now permits Stripe's loader through a real allowlist, and the Amazon Pay button loads.** Two payment-page fixes. The site enforces Trusted Types, and Stripe's browser library assigns script URLs without registering a policy — the console logged a TrustedScriptURL violation on every payment page, the failure class that can break a 3-D Secure challenge mid-payment. Rather than relaxing enforcement, the page now registers the default Trusted Types policy before Stripe loads: it admits script URLs from Stripe's own origins and throws for anything else, so enforcement stays on with a genuine gate. Second, the express-wallet row offered an Amazon Pay button whose frame the payment page's policy refused to load; the page's frame allowance now includes the Stripe-operated CDN that serves wallet buttons, scoped to the payment page only. Every other page keeps the strict policy unchanged. **Fixed:** *Trusted Types policy for the Stripe loader* — The payment page registers the default Trusted Types policy before the Stripe script tag, synchronously, integrity-pinned like every other script. The policy admits script URLs whose origin is js.stripe.com or a subdomain of it and throws for any other host — lookalike domains, plain http, and non-Stripe CDNs are refused. Browsers without Trusted Types are unaffected. Pages outside the payment route keep require-trusted-types-for enforcement with no registered loader policy, exactly as before. · *Express wallet buttons can load their frames* — The payment page's frame allowance adds Stripe's wallet-button CDN (b.stripecdn.com) and Stripe's dynamic subdomains (*.js.stripe.com) alongside the existing js.stripe.com and hooks.stripe.com entries, restoring the Amazon Pay express button. The allowance is scoped to the payment page; the storefront-wide policy still permits no third-party frames at all.
|
|
12
|
+
|
|
11
13
|
- v0.4.6 (2026-06-05) — **Guest order pages require proof of purchase, and guests can save their order to an account in one tap.** Two changes to what happens after a guest checks out. First, the order confirmation page — which shows the buyer's name, address, and items — is no longer reachable by anyone who learns the order id. A guest order now admits exactly three readers: the browser that placed it (a sealed device cookie set at checkout, surviving the payment redirect), anyone opening the signed link in the order-receipt email (an order-scoped, expiring signed token that works on any device), and the signed-in account that owns it. Everything else sees a 404 indistinguishable from a missing order, and the reorder action is gated the same way. Second, the confirmation page now offers a guest one tap to save the order to an account: it emails a sign-in link to the address they checked out with — shown masked on screen, held only in a short-lived encrypted cookie, never written to the database — and redeeming the link creates the account if needed and attaches their guest orders by a hashed-email match. The response is identical whether or not an account already exists, the trigger is rate-limited, and a second click sends nothing. **Added:** *One-tap account setup after checkout* — A guest buyer's confirmation page offers to save the order to an account: one click emails a magic sign-in link to the checkout address (masked on screen, e.g. r***@e***.com), creating the account on redemption if none exists and linking the buyer's guest orders by hashed-email match at the moment they prove control of the inbox. Signed-in buyers never see the offer; buyers whose address already has an account get sign-in wording with the identical send path. The plaintext address lives only in an encrypted cookie for fifteen minutes — the database keeps only the one-way hash it already kept. **Changed:** *Guest order confirmation pages are access-gated* — A guest order's page requires the placing browser's sealed device cookie (set at checkout, capped to recent orders, 30-day refresh), the signed token in the order-receipt email's view-order link (order-scoped, roughly 90-day expiry, constant-time verified), or the signed-in owner. Unauthorized requests — including a signed-in customer who does not own the order — receive a 404 identical to a missing order. The reorder action carries the same gate; cancel, rating, and return actions were already owner-only. The payment-provider return flow and order analytics are unaffected: the device cookie is set before the redirect to the payment page, so the buyer lands back on their confirmation with access intact.
|
|
12
14
|
|
|
13
15
|
- v0.4.5 (2026-06-05) — **One sign-in screen: passkey first, email sign-in link as the built-in fallback.** The sign-in page previously offered the passkey ceremony with the email magic-link alternative a page away behind a link. Both now live on one screen: passkey stays the primary action, and an "Email me a sign-in link" form sits inline beneath it — a plain server-rendered form that works with JavaScript disabled. The page meets failure gracefully: a browser without WebAuthn support disables the passkey button and points to the email form, and a failed or cancelled passkey ceremony scrolls the shopper to the fallback with their typed email carried over, one tap from a sign-in link instead of a dead end. Responses are identical whether or not an account exists for the address, the trigger is rate-limited, and a visitor who is already signed in is redirected to their account instead of seeing a login form. **Changed:** *Sign-in page unifies passkey and email-link paths* — Passkey remains the primary action; the email sign-in link form renders inline on the same screen when transactional email is configured, works without JavaScript, and inherits the existing magic-link token semantics unchanged (single-use, expiring). Browsers without WebAuthn support are steered to the email form up front, and any passkey ceremony failure lands the shopper on the fallback with their email pre-filled. The email form's responses do not reveal whether an address has an account, the trigger endpoint is tightly rate-limited, and signed-in visitors are redirected away from the login form.
|
package/lib/asset-manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.4.
|
|
2
|
+
"version": "0.4.7",
|
|
3
3
|
"assets": {
|
|
4
4
|
"css/admin.css": {
|
|
5
5
|
"integrity": "sha384-6k53cvkRrxMgmeStLIoLjVXZQHqIJgTmv1Izd8TYhh1HOC4POgE6GCvx1bsalyEP",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"integrity": "sha384-BjuUhbPZ18pHFMyOwT+309BEXu+VAc54RSj8lvN93jfFGDydEgmapiAOKTQM1yma",
|
|
38
38
|
"fingerprinted": "js/passkey-register.02b0e196fb9608d8.js"
|
|
39
39
|
},
|
|
40
|
+
"js/pay-trusted-types.js": {
|
|
41
|
+
"integrity": "sha384-jD90N8l7J3Vcu+aPSFRi6AE/5XKHQl58/avwHE9PH0mHwMavX991OCsEcznEf3n5",
|
|
42
|
+
"fingerprinted": "js/pay-trusted-types.53d0df13f9480a66.js"
|
|
43
|
+
},
|
|
40
44
|
"js/pay.js": {
|
|
41
45
|
"integrity": "sha384-W11JVQhv1RZq4WhsAOglu56gTZTDz1ByLd+b1HFQBCi8xGoEhJ0PZ2yI3FqbYzt6",
|
|
42
46
|
"fingerprinted": "js/pay.683a905563e54a47.js"
|
|
@@ -265,9 +265,27 @@ function securityHeadersOpts() {
|
|
|
265
265
|
// what the provider's own CDN requires.
|
|
266
266
|
var CSP_HOSTS = {
|
|
267
267
|
stripe: {
|
|
268
|
-
|
|
268
|
+
// Stripe.js v3 starts sub-frames on per-origin `*.js.stripe.com` hosts
|
|
269
|
+
// for performance (Stripe security guide), and its dynamic loader
|
|
270
|
+
// injects those sub-resource <script>s at runtime — so BOTH the apex
|
|
271
|
+
// and the wildcard must be admitted on script-src, or the
|
|
272
|
+
// TrustedScriptURL/dynamic-load path is refused (the 3DS challenge
|
|
273
|
+
// loader rides this).
|
|
274
|
+
script: ["https://js.stripe.com", "https://*.js.stripe.com"],
|
|
269
275
|
connect: ["https://api.stripe.com"],
|
|
270
|
-
frame:
|
|
276
|
+
// frame-src: the apex + `*.js.stripe.com` carry the Payment Element /
|
|
277
|
+
// 3DS challenge frames; hooks.stripe.com carries redirect-style
|
|
278
|
+
// confirmations. `b.stripecdn.com` serves the Express Checkout wallet
|
|
279
|
+
// button assets (e.g. the Amazon Pay button iframe at
|
|
280
|
+
// b.stripecdn.com/stripethirdparty-srv/...) — without it the express
|
|
281
|
+
// wallet button iframe is blocked (net::ERR_ABORTED) while Link /
|
|
282
|
+
// Google Pay, which render from *.js.stripe.com, load fine.
|
|
283
|
+
frame: [
|
|
284
|
+
"https://js.stripe.com",
|
|
285
|
+
"https://*.js.stripe.com",
|
|
286
|
+
"https://hooks.stripe.com",
|
|
287
|
+
"https://b.stripecdn.com",
|
|
288
|
+
],
|
|
271
289
|
},
|
|
272
290
|
paypal: {
|
|
273
291
|
script: ["https://www.paypal.com", "https://www.paypalobjects.com"],
|
package/lib/storefront.js
CHANGED
|
@@ -452,8 +452,14 @@ function _islandScript(name, opts) {
|
|
|
452
452
|
// construction at the call site, so they go in without escaping.
|
|
453
453
|
var idAttr = (opts && opts.id) ? " id=\"" + opts.id + "\"" : "";
|
|
454
454
|
var policyAttr = (opts && opts.policy) ? " data-consent-policy=\"" + opts.policy + "\"" : "";
|
|
455
|
+
// Islands `defer` by default (run after parse, in document order). The
|
|
456
|
+
// Trusted Types `default` policy is the exception: it MUST register
|
|
457
|
+
// synchronously BEFORE the parser-inserted Stripe SDK <script> executes,
|
|
458
|
+
// so `opts.sync` drops `defer` (and adds no `async`) — a classic blocking
|
|
459
|
+
// <script> that runs the instant the parser reaches it.
|
|
460
|
+
var timing = (opts && opts.sync) ? "" : " defer";
|
|
455
461
|
return "<script" + idAttr + " src=\"" + _assetUrl("js/" + name) + "\"" +
|
|
456
|
-
(sri ? " integrity=\"" + sri + "\"" : "") +
|
|
462
|
+
(sri ? " integrity=\"" + sri + "\"" : "") + timing + policyAttr + "></script>";
|
|
457
463
|
}
|
|
458
464
|
|
|
459
465
|
// ---- announcement bar --------------------------------------------------
|
|
@@ -7170,6 +7176,11 @@ var PAY_PAGE =
|
|
|
7170
7176
|
" <button id=\"submit\" type=\"button\" class=\"btn-primary pay-card__submit\">Pay {{grand_total}}</button>\n" +
|
|
7171
7177
|
" <p id=\"payment-message\" class=\"pay-card__message\"></p>\n" +
|
|
7172
7178
|
" </div>\n" +
|
|
7179
|
+
// Trusted Types `default` policy — MUST be registered before the Stripe
|
|
7180
|
+
// SDK loads (emitted sync/blocking, no defer) so Stripe.js's dynamic
|
|
7181
|
+
// script-URL injections (perf sub-frames + the 3DS challenge loader) pass
|
|
7182
|
+
// the createScriptURL gate instead of being refused as un-typed.
|
|
7183
|
+
" RAW_PAY_TT_SCRIPT\n" +
|
|
7173
7184
|
" <script src=\"https://js.stripe.com/v3/\"></script>\n" +
|
|
7174
7185
|
" RAW_PAY_SCRIPT\n" +
|
|
7175
7186
|
"</section>\n";
|
|
@@ -7195,6 +7206,10 @@ function renderPayPage(opts) {
|
|
|
7195
7206
|
pk: opts.publishable_key,
|
|
7196
7207
|
client_secret: opts.client_secret,
|
|
7197
7208
|
pay_script: opts.theme.assetUrl("js/pay.js"),
|
|
7209
|
+
// Trusted Types `default` policy asset — rendered (sync, no defer)
|
|
7210
|
+
// BEFORE the Stripe SDK tag in pay.html so it registers the
|
|
7211
|
+
// createScriptURL gate ahead of Stripe.js's first dynamic load.
|
|
7212
|
+
pay_tt_script: opts.theme.assetUrl("js/pay-trusted-types.js"),
|
|
7198
7213
|
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
7199
7214
|
});
|
|
7200
7215
|
}
|
|
@@ -7206,7 +7221,9 @@ function renderPayPage(opts) {
|
|
|
7206
7221
|
grand_total: grandTotal,
|
|
7207
7222
|
pk: opts.publishable_key,
|
|
7208
7223
|
client_secret: opts.client_secret,
|
|
7209
|
-
})
|
|
7224
|
+
})
|
|
7225
|
+
.replace("RAW_PAY_TT_SCRIPT", _islandScript("pay-trusted-types.js", { sync: true }))
|
|
7226
|
+
.replace("RAW_PAY_SCRIPT", _islandScript("pay.js"));
|
|
7210
7227
|
// Operator trust badges at the checkout placement (container-only — the pay
|
|
7211
7228
|
// page isn't edge-cached). Pre-resolved + sanitized by the route; appended
|
|
7212
7229
|
// after the pay card. Empty string when no badges / no dep.
|
|
@@ -13661,43 +13678,40 @@ function mount(router, deps) {
|
|
|
13661
13678
|
res.status(503);
|
|
13662
13679
|
return res.end ? res.end("Stripe publishable key not configured") : res.send("Stripe publishable key not configured");
|
|
13663
13680
|
}
|
|
13664
|
-
// Route-scoped CSP that admits
|
|
13665
|
-
// (and Trusted Types stays
|
|
13681
|
+
// Route-scoped CSP that admits Stripe's hosts on script/connect/frame-src
|
|
13682
|
+
// (and Trusted Types stays ON) so the Stripe SDK + the same-origin
|
|
13666
13683
|
// pay.js island load — without relaxing the app-level strict CSP that
|
|
13667
13684
|
// governs every OTHER route. setHeader OVERWRITES the app-level header
|
|
13668
13685
|
// for this response only.
|
|
13669
13686
|
//
|
|
13670
|
-
//
|
|
13671
|
-
//
|
|
13672
|
-
//
|
|
13673
|
-
//
|
|
13674
|
-
//
|
|
13675
|
-
//
|
|
13676
|
-
//
|
|
13677
|
-
//
|
|
13678
|
-
//
|
|
13679
|
-
// browser
|
|
13680
|
-
//
|
|
13681
|
-
// 'TrustedScriptURL' assignment
|
|
13682
|
-
//
|
|
13683
|
-
// src
|
|
13684
|
-
//
|
|
13685
|
-
//
|
|
13686
|
-
//
|
|
13687
|
+
// TRUSTED TYPES, RESOLVED. The app-level CSP carried through verbatim by
|
|
13688
|
+
// scopedCsp keeps `require-trusted-types-for 'script'` +
|
|
13689
|
+
// `trusted-types 'allow-duplicates' default`. Stripe.js v3 registers no
|
|
13690
|
+
// Trusted Types policy of its own; it expects the APPLICATION to define
|
|
13691
|
+
// a `default` policy whose createScriptURL vets Stripe's hosts (Stripe
|
|
13692
|
+
// security guide). The pay page now ships that policy in
|
|
13693
|
+
// `pay-trusted-types.js`, emitted (sync, no defer) BEFORE the Stripe SDK
|
|
13694
|
+
// tag, so when Stripe.js dynamically injects its sub-resource <script>
|
|
13695
|
+
// (the perf sub-frames on *.js.stripe.com and the 3-D Secure
|
|
13696
|
+
// challenge-frame loader) the browser routes the URL through the policy
|
|
13697
|
+
// and admits it — instead of refusing it with "This document requires
|
|
13698
|
+
// 'TrustedScriptURL' assignment". The policy throws for any non-Stripe
|
|
13699
|
+
// origin, so Trusted Types stays a real gate, not a relaxation. The
|
|
13700
|
+
// scoped CSP's script-src/frame-src admit both `js.stripe.com` and
|
|
13701
|
+
// `*.js.stripe.com` (the sub-origins the dynamic loader uses).
|
|
13702
|
+
//
|
|
13703
|
+
// EXPRESS WALLET FRAMES. The scoped frame-src also admits
|
|
13704
|
+
// `b.stripecdn.com`, where the Express Checkout Element loads its wallet
|
|
13705
|
+
// button iframes (e.g. the Amazon Pay button at
|
|
13706
|
+
// b.stripecdn.com/stripethirdparty-srv/...); without it that iframe is
|
|
13707
|
+
// blocked (net::ERR_ABORTED) while Link / Google Pay, served from
|
|
13708
|
+
// *.js.stripe.com, render fine.
|
|
13687
13709
|
//
|
|
13688
|
-
//
|
|
13689
|
-
//
|
|
13690
|
-
//
|
|
13691
|
-
//
|
|
13692
|
-
//
|
|
13693
|
-
// *.js.stripe.com. A `default` TT policy is page-global (it vets EVERY
|
|
13694
|
-
// TrustedScriptURL/TrustedHTML/TrustedScript sink, including any future
|
|
13695
|
-
// app sink), so adopting it is a deliberate posture change, not a
|
|
13696
|
-
// drive-by — left as an operator follow-up. To reproduce + verify a fix,
|
|
13697
|
-
// pay with the Stripe 3-D Secure test card 4000002760003184 (forces an
|
|
13698
|
-
// authentication challenge, which exercises the dynamic challenge-frame
|
|
13699
|
-
// loader that trips this) and confirm the console violation is gone and
|
|
13700
|
-
// the challenge frame renders.
|
|
13710
|
+
// A live 3-D Secure path still warrants a production verification: pay
|
|
13711
|
+
// with the Stripe 3DS test card 4000002760003184 (forces an
|
|
13712
|
+
// authentication challenge that exercises the dynamic challenge-frame
|
|
13713
|
+
// loader) and confirm the challenge frame renders with no TrustedScriptURL
|
|
13714
|
+
// console violation.
|
|
13701
13715
|
res.setHeader && res.setHeader("content-security-policy", securityMiddleware.scopedCsp(["stripe"]));
|
|
13702
13716
|
// Route-scoped Permissions-Policy that re-enables the `payment` feature
|
|
13703
13717
|
// for this one response (same origin + the Stripe / Google Pay wallet
|
package/package.json
CHANGED