@blamejs/blamejs-shop 0.1.23 → 0.1.25
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 +8 -15
- package/lib/asset-manifest.json +9 -0
- package/lib/storefront.js +21 -26
- 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.1.x
|
|
10
10
|
|
|
11
|
+
- v0.1.25 (2026-05-25) — **Asset integrity and the cache-buster are correct in production now.** The renderers stamped a Subresource Integrity digest and a `?v=` cache-buster onto the theme stylesheet and island scripts by hashing the files at render time — which silently produced nothing in production, where the container image doesn't ship the theme files and the edge has no filesystem to read them. So the integrity attribute was absent live and edge-rendered pages pinned a `0.0.0` version that never changed between releases (a stylesheet update would render against a browser-cached copy). Both now come from a manifest built at release time and read directly by both runtimes. **Fixed:** *Subresource Integrity now applied on server-rendered pages* — The `integrity="sha384-…"` attribute on the default theme's stylesheet and island scripts was computed by reading the asset off disk during the render. That read fails in the deployed container (its image carries the code, not the `themes/` tree) and at the edge (no filesystem), so the attribute was silently dropped — the tamper-evidence it's meant to provide wasn't active. The digests are now computed once at release time into `lib/asset-manifest.json` (bundled for both the container and the edge worker) and read from there, so the browser actually verifies the served bytes against the pinned hash. · *Edge-rendered pages no longer pin a 0.0.0 cache-buster* — Pages rendered at the edge (home, product, search, the empty cart, blog) appended `?v=0.0.0` to the stylesheet URL because the worker had no release version to stamp — so the cache-buster never changed across deploys and a browser could keep serving an old stylesheet after a CSS update (markup against stale styling). The version now comes from the release manifest, so each release invalidates the cached asset as intended. The drift between the manifest and the on-disk assets is caught by a smoke gate (`generate-asset-manifest.js --check`).
|
|
12
|
+
|
|
11
13
|
- v0.1.23 (2026-05-25) — **The admin console renders styled again, and both surfaces now self-host their fonts.** The admin console shipped its design system as an inline <style> block, inline style="" attributes, and a cross-origin Google Fonts link — all three of which the strict `style-src 'self'` / `font-src 'self'` CSP that governs container-served routes refuses, so the console rendered as an unstyled page in production. Its styling now lives in an external stylesheet served from /assets (like the storefront's), and the typeface is self-hosted, so the console renders as designed under the same CSP that protects it. Both the storefront and the console now serve Inter from /assets rather than naming it and hoping the visitor has it installed. **Changed:** *Inter is self-hosted on both the storefront and the console* — The default theme named Inter / Inter Tight first in its font stack but shipped no font files, so the intended typography only rendered for visitors who happened to have Inter installed locally — everyone else fell back to a system sans-serif. The latin subset of Inter (400/500/600) and Inter Tight (600/700) now ships under `themes/default/assets/fonts` and is referenced via `@font-face`, so the typeface renders for every visitor and loads same-origin under `font-src 'self'` — no cross-origin font CDN on any page. The fonts are SIL OFL 1.1 (license alongside the files); `font-display:swap` paints text immediately in the system fallback and swaps when the file arrives. · *Admin console visual refresh* — With the stylesheet external it also got a pass: a proper centered sign-in card (replacing the bare form that was the first thing an operator saw), softer surfaces and shadows on cards and panels, a sticky console nav, and consistent spacing. The brand palette (ink + the storefront's accent) and every screen's structure are unchanged — this is presentation only. **Fixed:** *Admin console renders unstyled in production* — Every `/admin` screen built its design system three ways the strict CSP refuses: an inline `<style>` block, inline `style=""` attributes, and a `<link>` to fonts.googleapis.com. Container-served routes run under `style-src 'self'` / `font-src 'self'`, which drops all three — so the console arrived as unstyled HTML (a plain page) once deployed, even though it looked correct in isolation. The styling now ships as `/assets/themes/default/css/admin.css`, an external file `'self'` allows, linked with a `sha384` Subresource Integrity digest so the browser rejects a tampered or stale object; the inline attributes are gone, folded into that stylesheet. A render-shell test asserts every admin screen links the external stylesheet, carries no inline styles, and pulls no third-party font host, so the regression can't return. Operators redeploy to pick this up — `npm run deploy` uploads the new stylesheet (and the fonts below) to R2 as part of the asset sync.
|
|
12
14
|
|
|
13
15
|
- v0.1.22 (2026-05-25) — **Fix the container boot crash-loop, plus gift cards, subscription self-management, and a customers roster.** The deployable container could crash-loop on startup, taking every write route (add-to-cart, checkout, account, admin) down while edge-rendered reads kept working — this release fixes that boot path. It also adds gift cards (issue / balance / redeem-at-checkout with an operator ledger), customer subscription self-management at `/account/subscriptions`, and a read-only customers roster in the admin console. **Added:** *Gift cards — redeem at checkout, balance check, operator ledger* — A gift-card code can be applied at checkout as a credit against the order total: the amount due drops by the available balance (never below zero), the order still records the full total it owed, and the debit is recorded once per order behind an atomic `balance >= amount` guard so concurrent spends can't overdraw — a card that fully covers the order is marked paid with no card charge. Customers check a balance at `GET /gift-cards` (which is not a code-existence oracle — unknown / malformed / expired all return the same generic result). Operators work `/admin/gift-cards`: list issued cards (masked code, original + remaining balance, status, issued date) filtered by lifecycle status, issue a card (the bearer code shown once, right after creation), and open a card to read its full credit / debit / expire ledger. Codes are stored as a `namespaceHash` digest plus a 4-char hint — never in the clear. · *Customer subscription self-management* — Signed-in customers view and cancel their own subscriptions at `/account/subscriptions` — each row shows the plan summary, status, and renew / end date, with a Cancel control on cancelable subscriptions. Every action is ownership-checked: a subscription that doesn't belong to the signed-in customer is a 404, never another account's row. Cancellation runs through the payment provider, so the cancel control mounts only when the payment handle is wired; without it the list renders read-only. Customer-facing subscription creation — a subscription-checkout flow — remains a separate follow-up; this surface is for managing existing subscriptions. · *Customers admin roster* — `/admin/customers` renders a read-only account roster (newest first): display name, a short id, the join date, the sign-in method (passkey count plus any linked OAuth providers), and the order count, with cursor pagination in the browser and a JSON list (with `next_cursor`) for a bearer-token client. Account creation and changes stay with the storefront's passkey / OIDC ceremonies. Order counts and sign-in methods are resolved with bounded aggregate queries over each page's ids — no per-row database trips. Raw email is never stored, so it is never shown. **Changed:** *Console nav gains Customers and Gift cards* — The signed-in admin nav now includes Customers and Gift cards alongside the existing screens, each shown when its primitive is wired. All new endpoints content-negotiate like the rest of the console: a bearer-token client gets the JSON API, a signed-in browser gets HTML; a request without the bearer token returns the sign-in form on a GET and redirects on a write. The customers roster, the subscription cancel, and the gift-card balance page all degrade gracefully when an optional dependency (payment, or the primitive itself) isn't configured. · *Deploy now syncs theme assets to R2* — `npm run deploy` chains `wrangler deploy` with a new `scripts/sync-r2-assets.js` that uploads the repo's theme stylesheets to the R2 bucket the Worker serves `/assets/*` from. `wrangler deploy` alone ships the Worker + container but never touches R2, so a CSS change would silently drift from the deployed HTML — new markup against a stale stylesheet. Run `npm run sync-assets` to push the stylesheets without a full redeploy. · *Theme stylesheet and island scripts served with Subresource Integrity* — The default theme's stylesheet `<link>` and the externalized passkey island `<script>` tags now carry a `sha384` `integrity` attribute computed from the on-disk asset, so a browser refuses any `/assets/*` response whose bytes don't match — closing the window where a tampered or stale CDN object could execute against the page. The digests are computed once at boot and cached; a missing or unreadable asset degrades to no integrity attribute rather than blocking the render. **Fixed:** *Passkey sign-in / registration no longer blocked by CSP* — The WebAuthn passkey ceremonies on `/account/login` and `/account/register` were inline `<script>` blocks, which the storefront's strict `script-src 'self'` CSP refuses to execute — so on the deploy the sign-in and registration buttons did nothing (accounts are passkey-only, so this blocked all customer auth). The ceremonies now ship as external assets (`/assets/themes/default/js/passkey-login.js` + `passkey-register.js`) that `'self'` allows, served from R2 and uploaded by the deploy's asset sync. Same effect, CSP-clean — and a clearer split between the server-rendered form and its opt-in enhancement. · *Container no longer crash-loops on startup* — The container boot had three ways to fail before it could serve a request, each of which left edge-rendered reads working while every container-served route (add-to-cart, cart update / remove, checkout, `/account`, `/admin`) was unreachable. (1) The secure-default boot unlocks a wrapped vault **and** a wrapped audit-signing keypair, which the framework reads from `BLAMEJS_VAULT_PASSPHRASE` / `BLAMEJS_AUDIT_SIGNING_PASSPHRASE`, but the deploy contract documents a single `VAULT_PASSPHRASE` the framework never read — so the container had no passphrase source, no TTY to prompt, and exited. The entry point now bridges the documented secret onto the vault passphrase and derives a domain-separated audit-signing passphrase from it. (2) Encrypted-at-rest requires a tmpfs; the image now points it at `/dev/shm` rather than failing to find one. (3) The Docker liveness probe was `wget`, which bot-guard blocks as automation (403), so a healthy container was marked unhealthy and stopped — the probe is now a browser-shaped Node request (`scripts/healthcheck.js`) that passes bot-guard like real traffic, without exempting the health path from the security middleware. A boot gate (`test/layer-2-integration/server-boot.test.js`) spawns the real entry point and asserts it listens, answers the probe, and still blocks header-less clients. Operators must redeploy the container to pick this up.
|
package/lib/admin.js
CHANGED
|
@@ -44,22 +44,15 @@ var AUDIT_NAMESPACE = "shop_admin";
|
|
|
44
44
|
// allows this file; the deploy's R2 asset sync uploads it. The `?v=` is
|
|
45
45
|
// the per-release cache-buster; the sha384 Subresource Integrity digest
|
|
46
46
|
// (b.crypto.sri, W3C SRI 1.0) makes the browser reject a tampered or
|
|
47
|
-
// stale object. Same-origin, so no `crossorigin` is needed.
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var
|
|
47
|
+
// stale object. Same-origin, so no `crossorigin` is needed. The digest +
|
|
48
|
+
// version come from the build-time manifest (lib/asset-manifest.json)
|
|
49
|
+
// rather than hashing the file at render time, so they're present in the
|
|
50
|
+
// container image too — which doesn't ship the theme asset files. A key
|
|
51
|
+
// absent from the manifest yields no attribute.
|
|
52
|
+
var _assetManifest = require("./asset-manifest.json");
|
|
53
|
+
var _ASSET_VERSION = _assetManifest.version;
|
|
53
54
|
function _assetSri(relUnderThemeAssets) {
|
|
54
|
-
|
|
55
|
-
return _SRI_CACHE[relUnderThemeAssets];
|
|
56
|
-
}
|
|
57
|
-
var sri = null;
|
|
58
|
-
try {
|
|
59
|
-
sri = b.crypto.sri(_assetFs.readFileSync(__dirname + "/../themes/default/assets/" + relUnderThemeAssets), { algorithm: "sha384" });
|
|
60
|
-
} catch (_e) { sri = null; }
|
|
61
|
-
_SRI_CACHE[relUnderThemeAssets] = sri;
|
|
62
|
-
return sri;
|
|
55
|
+
return _assetManifest.assets[relUnderThemeAssets] || null;
|
|
63
56
|
}
|
|
64
57
|
function _adminStylesheetLink() {
|
|
65
58
|
var sri = _assetSri("css/admin.css");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.25",
|
|
3
|
+
"assets": {
|
|
4
|
+
"css/admin.css": "sha384-rvjL1QySBgNJGEUDusbbMCkL3fsjmzcwUvTp9pljo/0Nn/lErxq2AbY8EYvDF9Ch",
|
|
5
|
+
"css/main.css": "sha384-qBY97FWZDGOploZRppwrDi0A/+R4R3iCFtyS1dfrAlqHNEPluPmY0R3HB9cJntyV",
|
|
6
|
+
"js/passkey-login.js": "sha384-65i1wVHIkiS2e0YiOV6oUOF9+tNR7s6QRvpnWaEne43P+B8UvKLTpDgp4MSKNGqX",
|
|
7
|
+
"js/passkey-register.js": "sha384-RaSkJbTpc6ClfPy5QyaY96doIqBqa9CLeIB1CzWnL05pSSJEA9IrIK0S7V7igRxY"
|
|
8
|
+
}
|
|
9
|
+
}
|
package/lib/storefront.js
CHANGED
|
@@ -203,13 +203,21 @@ var LAYOUT =
|
|
|
203
203
|
// each render call to override; absent that, every storefront page
|
|
204
204
|
// references the shipped default theme — so a fresh install renders
|
|
205
205
|
// styled out of the box without any wiring.
|
|
206
|
+
// Asset integrity + version manifest — sha384 digests + the release
|
|
207
|
+
// version, baked at build time (scripts/generate-asset-manifest.js) and
|
|
208
|
+
// committed. Read here instead of hashing the files at render time so the
|
|
209
|
+
// integrity attribute and the cache-buster are present in every runtime,
|
|
210
|
+
// including the container image and the edge Worker, neither of which
|
|
211
|
+
// ships the theme asset files to hash live. See lib/asset-manifest.json.
|
|
212
|
+
var _assetManifest = require("./asset-manifest.json");
|
|
213
|
+
|
|
206
214
|
// The default theme stylesheet ships from R2 at this path. The
|
|
207
215
|
// `?v=` query is a build-time cache-buster — operator uploads to R2
|
|
208
216
|
// rewrite the bytes at the same path, and without the version
|
|
209
217
|
// param browsers happily serve the previous cached CSS for the
|
|
210
|
-
// five-minute default TTL.
|
|
211
|
-
//
|
|
212
|
-
var DEFAULT_THEME_CSS_URL = "/assets/themes/default/css/main.css?v=" +
|
|
218
|
+
// five-minute default TTL. The manifest version bumps each release,
|
|
219
|
+
// forcing every active session to re-fetch.
|
|
220
|
+
var DEFAULT_THEME_CSS_URL = "/assets/themes/default/css/main.css?v=" + _assetManifest.version;
|
|
213
221
|
|
|
214
222
|
// Footer copyright year — resolved once at module load. It's a near-static
|
|
215
223
|
// value (changes once a year); a `new Date()` allocation on every page
|
|
@@ -222,30 +230,17 @@ var _COPYRIGHT_YEAR = String(new Date().getUTCFullYear());
|
|
|
222
230
|
// 'self'` CSP blocks inline <script>, so an inline island silently fails
|
|
223
231
|
// in production. `'self'` allows these /assets/ files; the R2 asset sync
|
|
224
232
|
// (npm run sync-assets) uploads them alongside the stylesheets.
|
|
225
|
-
var _ASSET_JS_VERSION =
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// Same-origin, so no `crossorigin` is required for the check to run.
|
|
235
|
-
var _SRI_CACHE = {};
|
|
233
|
+
var _ASSET_JS_VERSION = _assetManifest.version;
|
|
234
|
+
|
|
235
|
+
// Subresource Integrity for the static assets — the browser refuses to
|
|
236
|
+
// run/apply a resource whose served bytes don't match (defense against an
|
|
237
|
+
// R2/edge compromise or on-path injection). The sha384 digest (W3C SRI
|
|
238
|
+
// 1.0) is read from the build-time manifest keyed by the path under the
|
|
239
|
+
// default theme's asset root; an absent key (a custom operator theme,
|
|
240
|
+
// whose bytes aren't ours to hash) yields no attribute. Same-origin, so
|
|
241
|
+
// no `crossorigin` is required for the check to run.
|
|
236
242
|
function _assetSri(relUnderThemeAssets) {
|
|
237
|
-
|
|
238
|
-
return _SRI_CACHE[relUnderThemeAssets];
|
|
239
|
-
}
|
|
240
|
-
var sri = null;
|
|
241
|
-
try {
|
|
242
|
-
// Forward slashes resolve cross-platform for fs reads; avoids a
|
|
243
|
-
// node:path require for one join.
|
|
244
|
-
var p = __dirname + "/../themes/default/assets/" + relUnderThemeAssets;
|
|
245
|
-
sri = b.crypto.sri(_assetFs.readFileSync(p), { algorithm: "sha384" });
|
|
246
|
-
} catch (_e) { sri = null; } // asset absent (e.g. unit-test cwd) → omit integrity, don't crash render
|
|
247
|
-
_SRI_CACHE[relUnderThemeAssets] = sri;
|
|
248
|
-
return sri;
|
|
243
|
+
return _assetManifest.assets[relUnderThemeAssets] || null;
|
|
249
244
|
}
|
|
250
245
|
function _islandScript(name) {
|
|
251
246
|
var sri = _assetSri("js/" + name);
|
package/package.json
CHANGED