@blamejs/blamejs-shop 0.0.129 → 0.1.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 +6 -0
- package/README.md +1 -1
- package/lib/admin.js +275 -9
- package/lib/affiliates.js +4 -3
- package/lib/analytics.js +3 -2
- package/lib/api-keys.js +1 -1
- package/lib/assembly-instructions.js +2 -1
- package/lib/auto-replenish.js +4 -3
- package/lib/backorder.js +2 -1
- package/lib/business-hours.js +8 -1
- package/lib/carrier-accounts.js +1 -1
- package/lib/carrier-rates.js +1 -1
- package/lib/cart-abandonment.js +3 -2
- package/lib/cart-bulk-ops.js +2 -1
- package/lib/cart-recovery.js +5 -4
- package/lib/cart.js +6 -2
- package/lib/catalog-drafts.js +1 -1
- package/lib/click-and-collect.js +3 -2
- package/lib/clickstream.js +4 -3
- package/lib/config.js +2 -1
- package/lib/cookie-consent.js +2 -1
- package/lib/credit-limits.js +2 -1
- package/lib/currency-display.js +2 -1
- package/lib/customer-activity.js +3 -2
- package/lib/customer-impersonation.js +3 -3
- package/lib/customer-merge.js +4 -3
- package/lib/customer-portal.js +4 -4
- package/lib/customer-risk-profile.js +2 -1
- package/lib/customer-segments.js +2 -1
- package/lib/customer-surveys.js +6 -3
- package/lib/delivery-estimate.js +2 -2
- package/lib/demand-forecast.js +2 -1
- package/lib/discount-analytics.js +2 -2
- package/lib/dunning.js +4 -1
- package/lib/email-warmup.js +6 -1
- package/lib/email.js +1 -8
- package/lib/error-log.js +3 -2
- package/lib/event-log.js +3 -2
- package/lib/fraud-screen.js +3 -1
- package/lib/fulfillment-sla.js +3 -1
- package/lib/index.js +11 -3
- package/lib/inventory-allocations.js +3 -0
- package/lib/inventory-snapshots.js +2 -1
- package/lib/invoice-renderer.js +2 -1
- package/lib/line-gift-wrap.js +6 -1
- package/lib/live-chat.js +2 -1
- package/lib/loyalty-redemption.js +2 -1
- package/lib/newsletter.js +6 -1
- package/lib/operator-activity-feed.js +4 -3
- package/lib/operator-sessions.js +7 -7
- package/lib/order-exchanges.js +1 -0
- package/lib/order-timeline.js +2 -1
- package/lib/payment-retries.js +2 -1
- package/lib/payment.js +5 -4
- package/lib/pixel-events.js +6 -5
- package/lib/preorder.js +2 -1
- package/lib/print-queue.js +2 -1
- package/lib/product-compare.js +2 -1
- package/lib/product-qa.js +2 -1
- package/lib/push-notifications.js +6 -5
- package/lib/recently-viewed.js +7 -2
- package/lib/recommendations.js +7 -2
- package/lib/referral-leaderboard.js +2 -1
- package/lib/refund-automation.js +1 -1
- package/lib/refund-policy.js +1 -1
- package/lib/reorder-reminders.js +2 -1
- package/lib/reorder-thresholds.js +2 -1
- package/lib/robots-config.js +1 -0
- package/lib/sales-reports.js +17 -14
- package/lib/sales-tax-filings.js +2 -1
- package/lib/save-for-later.js +2 -1
- package/lib/search-suggestions.js +1 -1
- package/lib/shipping-insurance.js +2 -1
- package/lib/shipping-labels.js +3 -2
- package/lib/shipping-zones.js +1 -0
- package/lib/shrinkage-report.js +9 -8
- package/lib/sms-dispatcher.js +6 -5
- package/lib/stock-alerts.js +1 -1
- package/lib/stock-receipts.js +2 -1
- package/lib/store-credit.js +2 -1
- package/lib/storefront-forms.js +1 -1
- package/lib/storefront.js +93 -112
- package/lib/subscription-analytics.js +7 -2
- package/lib/subscription-controls.js +9 -8
- package/lib/subscription-gifts.js +2 -1
- package/lib/subscriptions.js +2 -0
- package/lib/support-tickets.js +4 -4
- package/lib/tax-cert-renewals.js +2 -1
- package/lib/tax-remittance.js +2 -1
- package/lib/theme-assets.js +1 -1
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +16 -0
- package/lib/vendor/blamejs/README.md +6 -4
- package/lib/vendor/blamejs/SECURITY.md +2 -0
- package/lib/vendor/blamejs/api-snapshot.json +255 -2
- package/lib/vendor/blamejs/index.js +1 -0
- package/lib/vendor/blamejs/lib/cose.js +284 -10
- package/lib/vendor/blamejs/lib/crypto.js +119 -0
- package/lib/vendor/blamejs/lib/did.js +416 -0
- package/lib/vendor/blamejs/lib/mdoc.js +122 -0
- package/lib/vendor/blamejs/lib/network-dnssec.js +328 -0
- package/lib/vendor/blamejs/lib/network.js +1 -0
- package/lib/vendor/blamejs/lib/vc.js +231 -33
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.41.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.42.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.43.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.44.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.45.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.46.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.47.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.48.json +22 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +47 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +101 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-self-test.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/did.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mdoc.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +63 -0
- package/lib/vendor-invoices.js +1 -1
- package/lib/webhook-receiver.js +8 -2
- package/lib/webhook-subscriptions.js +1 -1
- package/lib/webhooks.js +6 -5
- package/lib/winback-campaigns.js +2 -1
- package/lib/wishlist-alerts.js +2 -1
- package/lib/wishlist-digest.js +2 -1
- package/package.json +1 -1
package/lib/storefront-forms.js
CHANGED
|
@@ -90,7 +90,7 @@ var MAX_LIST_LIMIT = 200;
|
|
|
90
90
|
var DEFAULT_LIST_LIMIT = 50;
|
|
91
91
|
var DEFAULT_THROTTLE_LIMIT = 5;
|
|
92
92
|
var MAX_THROTTLE_LIMIT = 1000;
|
|
93
|
-
var THROTTLE_WINDOW_MS =
|
|
93
|
+
var THROTTLE_WINDOW_MS = _b().constants.TIME.minutes(1);
|
|
94
94
|
|
|
95
95
|
var SESSION_NAMESPACE = "storefront-form-session";
|
|
96
96
|
|
package/lib/storefront.js
CHANGED
|
@@ -1510,9 +1510,9 @@ var CART_LINE =
|
|
|
1510
1510
|
" </span>\n" +
|
|
1511
1511
|
" </a>\n" +
|
|
1512
1512
|
" </td>\n" +
|
|
1513
|
-
" <td>{{qty}}</td>\n" +
|
|
1514
|
-
" <td class=\"price\">{{unit}}</td>\n" +
|
|
1515
|
-
" <td class=\"price\">{{total}}</td>\n" +
|
|
1513
|
+
" <td data-label=\"Qty\">{{qty}}</td>\n" +
|
|
1514
|
+
" <td class=\"price\" data-label=\"Unit\">{{unit}}</td>\n" +
|
|
1515
|
+
" <td class=\"price\" data-label=\"Total\">{{total}}</td>\n" +
|
|
1516
1516
|
"</tr>\n";
|
|
1517
1517
|
|
|
1518
1518
|
// Editable cart line — shown on the /cart page. Includes an inline
|
|
@@ -1536,14 +1536,14 @@ var CART_LINE_EDITABLE =
|
|
|
1536
1536
|
" </span>\n" +
|
|
1537
1537
|
" </a>\n" +
|
|
1538
1538
|
" </td>\n" +
|
|
1539
|
-
" <td class=\"cart-line__qty\">\n" +
|
|
1539
|
+
" <td class=\"cart-line__qty\" data-label=\"Qty\">\n" +
|
|
1540
1540
|
" <form method=\"post\" action=\"/cart/lines/{{line_id}}/update\" class=\"cart-line__update\">\n" +
|
|
1541
|
-
" <input type=\"number\" name=\"qty\" value=\"{{qty}}\" min=\"1\" max=\"
|
|
1542
|
-
" <button type=\"submit\" class=\"cart-line__btn\">Update</button>\n" +
|
|
1541
|
+
" <input type=\"number\" name=\"qty\" value=\"{{qty}}\" min=\"1\" max=\"99999\" class=\"cart-line__qty-input\" aria-label=\"Quantity\">\n" +
|
|
1542
|
+
" <button type=\"submit\" class=\"cart-line__btn cart-line__btn--update\">Update</button>\n" +
|
|
1543
1543
|
" </form>\n" +
|
|
1544
1544
|
" </td>\n" +
|
|
1545
|
-
" <td class=\"price\">{{unit}}</td>\n" +
|
|
1546
|
-
" <td class=\"price\">{{total}}</td>\n" +
|
|
1545
|
+
" <td class=\"price\" data-label=\"Price\">{{unit}}</td>\n" +
|
|
1546
|
+
" <td class=\"price\" data-label=\"Total\">{{total}}</td>\n" +
|
|
1547
1547
|
" <td class=\"cart-line__remove-cell\">\n" +
|
|
1548
1548
|
" RAW_CART_LINE_SAVE" +
|
|
1549
1549
|
" <form method=\"post\" action=\"/cart/lines/{{line_id}}/remove\">\n" +
|
|
@@ -1691,7 +1691,7 @@ var ORDER_PAGE =
|
|
|
1691
1691
|
" <div class=\"order-page__items\">\n" +
|
|
1692
1692
|
" <h2 class=\"pdp__variants-title\">Items</h2>\n" +
|
|
1693
1693
|
" <div class=\"table-scroll\">\n" +
|
|
1694
|
-
" <table>\n" +
|
|
1694
|
+
" <table class=\"cart-table\">\n" +
|
|
1695
1695
|
" <thead><tr><th>Product</th><th>Qty</th><th>Unit</th><th>Total</th></tr></thead>\n" +
|
|
1696
1696
|
" <tbody>{{line_rows}}</tbody>\n" +
|
|
1697
1697
|
" </table>\n" +
|
|
@@ -2082,7 +2082,6 @@ function renderNotFound(opts) {
|
|
|
2082
2082
|
// — it's a routing key, not an authentication token. The cart itself
|
|
2083
2083
|
// transitions to `customer_id` on login via cart.setCustomer.
|
|
2084
2084
|
var SESSION_COOKIE_NAME = "shop_sid";
|
|
2085
|
-
var SESSION_COOKIE_MAX = 60 * 60 * 24 * 30; // 30 days
|
|
2086
2085
|
|
|
2087
2086
|
// Authenticated-customer cookie — carries an opaque sealed envelope
|
|
2088
2087
|
// `{ customer_id, exp }`, AEAD-encrypted via b.vault.seal so the
|
|
@@ -2092,79 +2091,85 @@ var SESSION_COOKIE_MAX = 60 * 60 * 24 * 30; // 30 days
|
|
|
2092
2091
|
// rotated vault invalidates every outstanding auth cookie (operator-
|
|
2093
2092
|
// initiated logout-everywhere).
|
|
2094
2093
|
var AUTH_COOKIE_NAME = "shop_auth";
|
|
2095
|
-
var AUTH_COOKIE_MAX = 60 * 60 * 24 * 14; // 14 days
|
|
2096
|
-
var AUTH_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
2097
2094
|
|
|
2098
2095
|
// WebAuthn ceremony state cookie — short-lived envelope holding the
|
|
2099
2096
|
// random challenge + the ceremony-scoped metadata so register-finish /
|
|
2100
2097
|
// login-finish can verify the same challenge the browser was sent.
|
|
2101
2098
|
// Path-scoped to /account so it never leaks to other routes.
|
|
2102
2099
|
var CHALLENGE_COOKIE_NAME = "shop_auth_chal";
|
|
2103
|
-
var CHALLENGE_COOKIE_MAX = 5 * 60; // 5 minutes
|
|
2104
2100
|
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2101
|
+
// Short-lived cookie carrying the Stripe PaymentIntent client_secret
|
|
2102
|
+
// from POST /checkout to GET /pay/:order_id. Path-scoped to /pay/ +
|
|
2103
|
+
// SameSite=Strict so it's only ever sent to the pay route.
|
|
2104
|
+
var PAY_COOKIE_NAME = "shop_pay";
|
|
2105
|
+
|
|
2106
|
+
// Shape of a valid session id — mirrors cart.js's SESSION_ID_RE.
|
|
2107
|
+
var SID_SHAPE_RE = /^[A-Za-z0-9_-]{16,64}$/;
|
|
2108
|
+
|
|
2109
|
+
// All cookie transport composes the framework's cookie primitive
|
|
2110
|
+
// (`b.cookies`) — RFC 6265 parse/serialize, prefix invariants, and the
|
|
2111
|
+
// vault-sealed read/write helpers — rather than hand-built Set-Cookie
|
|
2112
|
+
// strings and manual header splitting. The jar is memoized; it only
|
|
2113
|
+
// captures the vault reference (seal/unseal run lazily per call), so
|
|
2114
|
+
// building it before vault.init() has completed is safe.
|
|
2115
|
+
var _jar = null;
|
|
2116
|
+
function _cookieJar() {
|
|
2117
|
+
if (!_jar) {
|
|
2118
|
+
_jar = _b().cookies.create({
|
|
2119
|
+
vault: _b().vault,
|
|
2120
|
+
defaults: { httpOnly: true, secure: true, sameSite: "Lax", path: "/" },
|
|
2121
|
+
});
|
|
2118
2122
|
}
|
|
2119
|
-
return
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
function _setSidCookie(res, sid) {
|
|
2123
|
-
var attrs = "Max-Age=" + SESSION_COOKIE_MAX + "; Path=/; HttpOnly; Secure; SameSite=Lax";
|
|
2124
|
-
var header = SESSION_COOKIE_NAME + "=" + encodeURIComponent(sid) + "; " + attrs;
|
|
2125
|
-
if (typeof res.appendHeader === "function") res.appendHeader("Set-Cookie", header);
|
|
2126
|
-
else if (typeof res.setHeader === "function") res.setHeader("Set-Cookie", header);
|
|
2123
|
+
return _jar;
|
|
2127
2124
|
}
|
|
2128
2125
|
|
|
2129
2126
|
function _readCookie(req, name) {
|
|
2130
|
-
|
|
2131
|
-
if (!raw) return null;
|
|
2132
|
-
var parts = raw.split(";");
|
|
2133
|
-
for (var i = 0; i < parts.length; i += 1) {
|
|
2134
|
-
var p = parts[i].trim();
|
|
2135
|
-
var eq = p.indexOf("=");
|
|
2136
|
-
if (eq <= 0) continue;
|
|
2137
|
-
if (p.slice(0, eq) === name) {
|
|
2138
|
-
try { return decodeURIComponent(p.slice(eq + 1)); }
|
|
2139
|
-
catch (_e) { return null; }
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
return null;
|
|
2127
|
+
return _cookieJar().read(req, name);
|
|
2143
2128
|
}
|
|
2144
2129
|
|
|
2145
|
-
function
|
|
2146
|
-
|
|
2147
|
-
|
|
2130
|
+
function _readSidCookie(req) {
|
|
2131
|
+
// A cookie carrying anything but a well-shaped session id (a stale
|
|
2132
|
+
// value from an old deploy, a tampered cookie, garbage) reads as "no
|
|
2133
|
+
// session" rather than reaching cart.bySession — which throws on a
|
|
2134
|
+
// malformed id and would turn every page that renders the cart count
|
|
2135
|
+
// into a 500. The cookie grants zero authority, so dropping a
|
|
2136
|
+
// malformed one silently is safe.
|
|
2137
|
+
var v = _cookieJar().read(req, SESSION_COOKIE_NAME);
|
|
2138
|
+
return v && SID_SHAPE_RE.test(v) ? v : null;
|
|
2148
2139
|
}
|
|
2149
2140
|
|
|
2150
|
-
function
|
|
2151
|
-
var
|
|
2152
|
-
|
|
2141
|
+
function _setSidCookie(res, sid) {
|
|
2142
|
+
var T = _b().constants.TIME;
|
|
2143
|
+
_cookieJar().write(res, SESSION_COOKIE_NAME, sid, { expires: new Date(Date.now() + T.days(30)) });
|
|
2153
2144
|
}
|
|
2154
2145
|
|
|
2146
|
+
// Auth + WebAuthn-challenge cookies carry a vault-sealed JSON envelope.
|
|
2147
|
+
// writeSealed/readSealed handle the seal + the on-wire prefix; the
|
|
2148
|
+
// caller works in plain objects.
|
|
2149
|
+
function _setAuthCookie(res, env) {
|
|
2150
|
+
var T = _b().constants.TIME;
|
|
2151
|
+
_cookieJar().writeSealed(res, AUTH_COOKIE_NAME, JSON.stringify(env), { expires: new Date(Date.now() + T.days(14)) });
|
|
2152
|
+
}
|
|
2155
2153
|
function _clearAuthCookie(res) {
|
|
2156
|
-
|
|
2157
|
-
AUTH_COOKIE_NAME + "=; Max-Age=0; Path=/; HttpOnly; Secure; SameSite=Lax");
|
|
2154
|
+
_cookieJar().clear(res, AUTH_COOKIE_NAME);
|
|
2158
2155
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2156
|
+
function _readAuthEnv(req) {
|
|
2157
|
+
var raw = _cookieJar().readSealed(req, AUTH_COOKIE_NAME);
|
|
2158
|
+
if (raw === null) return null;
|
|
2159
|
+
try { return JSON.parse(raw); } catch (_e) { return null; }
|
|
2163
2160
|
}
|
|
2164
2161
|
|
|
2162
|
+
function _setChallengeCookie(res, env) {
|
|
2163
|
+
var T = _b().constants.TIME;
|
|
2164
|
+
_cookieJar().writeSealed(res, CHALLENGE_COOKIE_NAME, JSON.stringify(env), { expires: new Date(Date.now() + T.minutes(5)), path: "/account" });
|
|
2165
|
+
}
|
|
2165
2166
|
function _clearChallengeCookie(res) {
|
|
2166
|
-
|
|
2167
|
-
|
|
2167
|
+
_cookieJar().clear(res, CHALLENGE_COOKIE_NAME, { path: "/account" });
|
|
2168
|
+
}
|
|
2169
|
+
function _readChallengeEnv(req) {
|
|
2170
|
+
var raw = _cookieJar().readSealed(req, CHALLENGE_COOKIE_NAME);
|
|
2171
|
+
if (raw === null) return null;
|
|
2172
|
+
try { return JSON.parse(raw); } catch (_e) { return null; }
|
|
2168
2173
|
}
|
|
2169
2174
|
|
|
2170
2175
|
// ---- account-page renderers --------------------------------------------
|
|
@@ -2303,10 +2308,10 @@ var ACCOUNT_DASH_PAGE =
|
|
|
2303
2308
|
|
|
2304
2309
|
var ACCOUNT_DASH_ORDER_ROW =
|
|
2305
2310
|
"<tr>\n" +
|
|
2306
|
-
" <td><a href=\"/orders/{{order_id}}\" class=\"account-order__id\"><code>{{order_id_short}}</code></a></td>\n" +
|
|
2307
|
-
" <td class=\"account-order__items\">RAW_ACCOUNT_ORDER_THUMBS</td>\n" +
|
|
2308
|
-
" <td><span class=\"pdp__badge {{status_class}}\">{{status}}</span></td>\n" +
|
|
2309
|
-
" <td class=\"price\">{{total}}</td>\n" +
|
|
2311
|
+
" <td data-label=\"Order\"><a href=\"/orders/{{order_id}}\" class=\"account-order__id\"><code>{{order_id_short}}</code></a></td>\n" +
|
|
2312
|
+
" <td class=\"account-order__items\" data-label=\"Items\">RAW_ACCOUNT_ORDER_THUMBS</td>\n" +
|
|
2313
|
+
" <td data-label=\"Status\"><span class=\"pdp__badge {{status_class}}\">{{status}}</span></td>\n" +
|
|
2314
|
+
" <td class=\"price\" data-label=\"Total\">{{total}}</td>\n" +
|
|
2310
2315
|
"</tr>\n";
|
|
2311
2316
|
|
|
2312
2317
|
function renderAccount(opts) {
|
|
@@ -2407,7 +2412,7 @@ function mount(router, deps) {
|
|
|
2407
2412
|
// so the /admin landing + the onNotFound 404 handler (mounted
|
|
2408
2413
|
// outside the `if (deps.customers)` block below) can reach it.
|
|
2409
2414
|
async function _cartCountForReq(req) {
|
|
2410
|
-
var sid =
|
|
2415
|
+
var sid = _readSidCookie(req);
|
|
2411
2416
|
if (!sid) return 0;
|
|
2412
2417
|
var c = await deps.cart.bySession(sid);
|
|
2413
2418
|
if (!c) return 0;
|
|
@@ -2421,10 +2426,7 @@ function mount(router, deps) {
|
|
|
2421
2426
|
// reader rather than a copy per call site. A missing / malformed /
|
|
2422
2427
|
// expired cookie returns null — never throws.
|
|
2423
2428
|
function _currentCustomerEnv(req) {
|
|
2424
|
-
var
|
|
2425
|
-
if (!raw) return null;
|
|
2426
|
-
var env;
|
|
2427
|
-
try { env = JSON.parse(_b().vault.unseal(raw)); } catch (_e) { return null; }
|
|
2429
|
+
var env = _readAuthEnv(req);
|
|
2428
2430
|
if (!env || !env.customer_id || !env.exp || env.exp < Date.now()) return null;
|
|
2429
2431
|
return env;
|
|
2430
2432
|
}
|
|
@@ -2775,11 +2777,11 @@ function mount(router, deps) {
|
|
|
2775
2777
|
idempotency_key: "checkout:" + c.id + ":" + _b().uuid.v7(),
|
|
2776
2778
|
});
|
|
2777
2779
|
// Set a short-lived pay cookie so /pay/:order_id can serve the
|
|
2778
|
-
// client_secret without re-running confirm.
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2780
|
+
// client_secret without re-running confirm. Scoped to /pay/ +
|
|
2781
|
+
// SameSite=Strict so it's only ever sent to the pay route.
|
|
2782
|
+
_cookieJar().write(res, PAY_COOKIE_NAME, result.payment_intent.client_secret, {
|
|
2783
|
+
expires: new Date(Date.now() + _b().constants.TIME.minutes(15)), path: "/pay/", sameSite: "Strict",
|
|
2784
|
+
});
|
|
2783
2785
|
res.status(303);
|
|
2784
2786
|
res.setHeader && res.setHeader("location", "/pay/" + result.order.id);
|
|
2785
2787
|
return res.end ? res.end() : res.send("");
|
|
@@ -2798,14 +2800,7 @@ function mount(router, deps) {
|
|
|
2798
2800
|
// Read the client_secret from the shop_pay cookie set on POST
|
|
2799
2801
|
// /checkout. The cookie is scoped Path=/pay/ + SameSite=Strict
|
|
2800
2802
|
// so it's only sent to the pay route and never cross-origin.
|
|
2801
|
-
var
|
|
2802
|
-
var clientSecret = null;
|
|
2803
|
-
rawCookies.split(";").forEach(function (p) {
|
|
2804
|
-
var t = p.trim();
|
|
2805
|
-
if (t.indexOf("shop_pay=") === 0) {
|
|
2806
|
-
try { clientSecret = decodeURIComponent(t.slice("shop_pay=".length)); } catch (_e) { /* drop */ }
|
|
2807
|
-
}
|
|
2808
|
-
});
|
|
2803
|
+
var clientSecret = _readCookie(req, PAY_COOKIE_NAME);
|
|
2809
2804
|
if (!clientSecret) {
|
|
2810
2805
|
res.status(303); res.setHeader && res.setHeader("location", "/cart");
|
|
2811
2806
|
return res.end ? res.end() : res.send("");
|
|
@@ -2871,16 +2866,6 @@ function mount(router, deps) {
|
|
|
2871
2866
|
return _b().crypto.toBase64Url(buf);
|
|
2872
2867
|
}
|
|
2873
2868
|
|
|
2874
|
-
function _sealEnvelope(obj) {
|
|
2875
|
-
return _b().vault.seal(JSON.stringify(obj));
|
|
2876
|
-
}
|
|
2877
|
-
function _unsealEnvelope(s) {
|
|
2878
|
-
try {
|
|
2879
|
-
var raw = _b().vault.unseal(s);
|
|
2880
|
-
return JSON.parse(raw);
|
|
2881
|
-
} catch (_e) { return null; }
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
2869
|
function _currentCustomer(req) {
|
|
2885
2870
|
return _currentCustomerEnv(req);
|
|
2886
2871
|
}
|
|
@@ -2940,13 +2925,12 @@ function mount(router, deps) {
|
|
|
2940
2925
|
// Seal the ceremony state (challenge + customer_id) into the
|
|
2941
2926
|
// shop_auth_chal cookie so register-finish verifies against
|
|
2942
2927
|
// the same challenge without server-side state.
|
|
2943
|
-
|
|
2928
|
+
_setChallengeCookie(res, {
|
|
2944
2929
|
kind: "register",
|
|
2945
2930
|
customer_id: customer.id,
|
|
2946
2931
|
challenge: startOpts.challenge,
|
|
2947
2932
|
created_at: Date.now(),
|
|
2948
2933
|
});
|
|
2949
|
-
_setChallengeCookie(res, sealed);
|
|
2950
2934
|
res.status(200);
|
|
2951
2935
|
res.setHeader && res.setHeader("content-type", "application/json");
|
|
2952
2936
|
return res.end ? res.end(JSON.stringify(startOpts)) : res.send(JSON.stringify(startOpts));
|
|
@@ -2959,10 +2943,9 @@ function mount(router, deps) {
|
|
|
2959
2943
|
|
|
2960
2944
|
router.post("/account/passkey/register-finish", async function (req, res) {
|
|
2961
2945
|
try {
|
|
2962
|
-
var
|
|
2963
|
-
if (!
|
|
2964
|
-
|
|
2965
|
-
if (!env || env.kind !== "register") {
|
|
2946
|
+
var env = _readChallengeEnv(req);
|
|
2947
|
+
if (!env) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
|
|
2948
|
+
if (env.kind !== "register") {
|
|
2966
2949
|
res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge");
|
|
2967
2950
|
}
|
|
2968
2951
|
var att = _readJsonBody(req);
|
|
@@ -2991,10 +2974,10 @@ function mount(router, deps) {
|
|
|
2991
2974
|
transports: transports,
|
|
2992
2975
|
});
|
|
2993
2976
|
_clearChallengeCookie(res);
|
|
2994
|
-
_setAuthCookie(res,
|
|
2977
|
+
_setAuthCookie(res, {
|
|
2995
2978
|
customer_id: env.customer_id,
|
|
2996
|
-
exp: Date.now() +
|
|
2997
|
-
})
|
|
2979
|
+
exp: Date.now() + _b().constants.TIME.days(14),
|
|
2980
|
+
});
|
|
2998
2981
|
res.status(200);
|
|
2999
2982
|
return res.end ? res.end("ok") : res.send("ok");
|
|
3000
2983
|
} catch (e) {
|
|
@@ -3029,13 +3012,12 @@ function mount(router, deps) {
|
|
|
3029
3012
|
allowCredentials: allow,
|
|
3030
3013
|
userVerification: "preferred",
|
|
3031
3014
|
});
|
|
3032
|
-
|
|
3015
|
+
_setChallengeCookie(res, {
|
|
3033
3016
|
kind: "login",
|
|
3034
3017
|
email_hash: hash,
|
|
3035
3018
|
challenge: startOpts.challenge,
|
|
3036
3019
|
created_at: Date.now(),
|
|
3037
3020
|
});
|
|
3038
|
-
_setChallengeCookie(res, sealed);
|
|
3039
3021
|
res.status(200);
|
|
3040
3022
|
res.setHeader && res.setHeader("content-type", "application/json");
|
|
3041
3023
|
return res.end ? res.end(JSON.stringify(startOpts)) : res.send(JSON.stringify(startOpts));
|
|
@@ -3048,10 +3030,9 @@ function mount(router, deps) {
|
|
|
3048
3030
|
|
|
3049
3031
|
router.post("/account/passkey/login-finish", async function (req, res) {
|
|
3050
3032
|
try {
|
|
3051
|
-
var
|
|
3052
|
-
if (!
|
|
3053
|
-
|
|
3054
|
-
if (!env || env.kind !== "login") { res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge"); }
|
|
3033
|
+
var env = _readChallengeEnv(req);
|
|
3034
|
+
if (!env) { res.status(400); return res.end ? res.end("missing challenge") : res.send("missing challenge"); }
|
|
3035
|
+
if (env.kind !== "login") { res.status(400); return res.end ? res.end("bad challenge") : res.send("bad challenge"); }
|
|
3055
3036
|
var assertion = _readJsonBody(req);
|
|
3056
3037
|
var credentialId = assertion.id || assertion.rawId;
|
|
3057
3038
|
if (!credentialId) { res.status(400); return res.end ? res.end("missing credential id") : res.send("missing credential id"); }
|
|
@@ -3090,7 +3071,7 @@ function mount(router, deps) {
|
|
|
3090
3071
|
}
|
|
3091
3072
|
// Merge the anonymous cart into a customer-owned cart so
|
|
3092
3073
|
// the shopper doesn't lose items on sign-in.
|
|
3093
|
-
var sid =
|
|
3074
|
+
var sid = _readSidCookie(req);
|
|
3094
3075
|
if (sid) {
|
|
3095
3076
|
try {
|
|
3096
3077
|
var anonCart = await deps.cart.bySession(sid);
|
|
@@ -3098,10 +3079,10 @@ function mount(router, deps) {
|
|
|
3098
3079
|
} catch (_e) { /* best-effort merge; sign-in itself succeeds */ }
|
|
3099
3080
|
}
|
|
3100
3081
|
_clearChallengeCookie(res);
|
|
3101
|
-
_setAuthCookie(res,
|
|
3082
|
+
_setAuthCookie(res, {
|
|
3102
3083
|
customer_id: customer.id,
|
|
3103
|
-
exp: Date.now() +
|
|
3104
|
-
})
|
|
3084
|
+
exp: Date.now() + _b().constants.TIME.days(14),
|
|
3085
|
+
});
|
|
3105
3086
|
res.status(200);
|
|
3106
3087
|
return res.end ? res.end("ok") : res.send("ok");
|
|
3107
3088
|
} catch (e) {
|
|
@@ -162,10 +162,15 @@
|
|
|
162
162
|
|
|
163
163
|
// ---- constants ----------------------------------------------------------
|
|
164
164
|
|
|
165
|
+
// `_b()` is a hoisted function declaration (defined below); resolving the
|
|
166
|
+
// framework constants here at module-eval is safe — the index entry point
|
|
167
|
+
// exposes `framework` before the require cascade.
|
|
168
|
+
var C = _b().constants;
|
|
169
|
+
|
|
165
170
|
var CACHE_NAMESPACE = "subscription-analytics-cache";
|
|
166
171
|
|
|
167
|
-
var DEFAULT_TTL_MS = 5
|
|
168
|
-
var ONE_DAY_MS =
|
|
172
|
+
var DEFAULT_TTL_MS = C.TIME.minutes(5); // 5 minutes
|
|
173
|
+
var ONE_DAY_MS = C.TIME.days(1);
|
|
169
174
|
var ONE_YEAR_MS = 365 * ONE_DAY_MS;
|
|
170
175
|
var DEFAULT_LTV_WINDOW_MS = 90 * ONE_DAY_MS;
|
|
171
176
|
|
|
@@ -55,6 +55,7 @@ function _b() {
|
|
|
55
55
|
if (!bShop) bShop = require("./index");
|
|
56
56
|
return bShop.framework;
|
|
57
57
|
}
|
|
58
|
+
var C = _b().constants;
|
|
58
59
|
|
|
59
60
|
// ---- constants ----------------------------------------------------------
|
|
60
61
|
|
|
@@ -89,12 +90,12 @@ var FREQUENCIES = [
|
|
|
89
90
|
// step skipNext / changeFrequency surface; a calendar-accurate
|
|
90
91
|
// scheduler is out of scope for v1.
|
|
91
92
|
var PERIOD_MS = Object.freeze({
|
|
92
|
-
weekly: 7
|
|
93
|
-
biweekly: 14
|
|
94
|
-
monthly: 30
|
|
95
|
-
quarterly: 90
|
|
96
|
-
semiannual: 182
|
|
97
|
-
annual: 365
|
|
93
|
+
weekly: C.TIME.days(7),
|
|
94
|
+
biweekly: C.TIME.days(14),
|
|
95
|
+
monthly: C.TIME.days(30),
|
|
96
|
+
quarterly: C.TIME.days(90),
|
|
97
|
+
semiannual: C.TIME.days(182),
|
|
98
|
+
annual: C.TIME.days(365),
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
// Plan-interval → frequency fallback. When the row has no `frequency`
|
|
@@ -115,7 +116,7 @@ function _frequencyFromPlan(plan) {
|
|
|
115
116
|
return "monthly";
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
var REACTIVATE_GRACE_MS = 90
|
|
119
|
+
var REACTIVATE_GRACE_MS = C.TIME.days(90);
|
|
119
120
|
var MAX_REASON_LEN = 280;
|
|
120
121
|
var MAX_SKIP_COUNT = 12;
|
|
121
122
|
var MAX_QUANTITY = 1000000;
|
|
@@ -599,7 +600,7 @@ function create(opts) {
|
|
|
599
600
|
if (now - row.cancelled_at > REACTIVATE_GRACE_MS) {
|
|
600
601
|
var gErr = new Error(
|
|
601
602
|
"subscriptionControls.reactivate: refused — cancellation is older than the " +
|
|
602
|
-
(REACTIVATE_GRACE_MS / (
|
|
603
|
+
(REACTIVATE_GRACE_MS / C.TIME.days(1)) + "-day grace window"
|
|
603
604
|
);
|
|
604
605
|
gErr.code = "SUBSCRIPTION_REACTIVATE_GRACE_EXPIRED";
|
|
605
606
|
throw gErr;
|
|
@@ -72,6 +72,7 @@ function _b() {
|
|
|
72
72
|
if (!bShop) bShop = require("./index");
|
|
73
73
|
return bShop.framework;
|
|
74
74
|
}
|
|
75
|
+
var C = _b().constants;
|
|
75
76
|
|
|
76
77
|
// ---- constants ----------------------------------------------------------
|
|
77
78
|
|
|
@@ -94,7 +95,7 @@ var MAX_LIST_LIMIT = 200;
|
|
|
94
95
|
var DEFAULT_LIST_LIMIT = 50;
|
|
95
96
|
// Default redemption window: 365 days. Operators wanting a different
|
|
96
97
|
// horizon pass `expires_at_ms` (absolute) at purchase time.
|
|
97
|
-
var DEFAULT_EXPIRY_MS = 365
|
|
98
|
+
var DEFAULT_EXPIRY_MS = C.TIME.days(365);
|
|
98
99
|
|
|
99
100
|
// ---- validators ---------------------------------------------------------
|
|
100
101
|
|
package/lib/subscriptions.js
CHANGED
|
@@ -214,7 +214,9 @@ function _extractFromStripeObject(obj) {
|
|
|
214
214
|
// Stripe nests current_period_start/_end at the top level when the
|
|
215
215
|
// event is `customer.subscription.*`. Both are unix seconds; we
|
|
216
216
|
// store epoch ms.
|
|
217
|
+
// allow:raw-time-literal — Stripe sends unix SECONDS; the * 1000 converts a runtime field to epoch ms, not a fixed duration
|
|
217
218
|
var periodStart = obj && obj.current_period_start != null ? obj.current_period_start * 1000 : null;
|
|
219
|
+
// allow:raw-time-literal — Stripe sends unix SECONDS; the * 1000 converts a runtime field to epoch ms, not a fixed duration
|
|
218
220
|
var periodEnd = obj && obj.current_period_end != null ? obj.current_period_end * 1000 : null;
|
|
219
221
|
return {
|
|
220
222
|
stripe_subscription_id: obj && obj.id,
|
package/lib/support-tickets.js
CHANGED
|
@@ -75,10 +75,10 @@ var ALLOWED_STATUSES = [
|
|
|
75
75
|
// ticket is considered breached. Closed / resolved tickets never
|
|
76
76
|
// breach. Drives `slaCheck()` for the scheduler.
|
|
77
77
|
var SLA_MS = {
|
|
78
|
-
urgent: 1
|
|
79
|
-
high: 4
|
|
80
|
-
normal: 24
|
|
81
|
-
low: 72
|
|
78
|
+
urgent: _b().constants.TIME.hours(1),
|
|
79
|
+
high: _b().constants.TIME.hours(4),
|
|
80
|
+
normal: _b().constants.TIME.hours(24),
|
|
81
|
+
low: _b().constants.TIME.hours(72),
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
// FSM. Encoded as an explicit allow-list keyed by from-state. Every
|
package/lib/tax-cert-renewals.js
CHANGED
|
@@ -71,6 +71,7 @@ function _b() {
|
|
|
71
71
|
if (!bShop) bShop = require("./index");
|
|
72
72
|
return bShop.framework;
|
|
73
73
|
}
|
|
74
|
+
var C = _b().constants;
|
|
74
75
|
|
|
75
76
|
var JURISDICTION_RE = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/;
|
|
76
77
|
var SLUG_RE = /^[a-z0-9](?:[a-z0-9._-]{0,126}[a-z0-9])?$/;
|
|
@@ -81,7 +82,7 @@ var MAX_ESCALATE_DAYS = 365;
|
|
|
81
82
|
var MAX_ESCALATED_TO_LEN = 256;
|
|
82
83
|
var DEFAULT_CHANNELS = Object.freeze(["email"]);
|
|
83
84
|
|
|
84
|
-
var DAY_MS =
|
|
85
|
+
var DAY_MS = C.TIME.days(1);
|
|
85
86
|
|
|
86
87
|
var STATUSES = Object.freeze(["queued", "sent", "escalated", "renewed", "expired"]);
|
|
87
88
|
|
package/lib/tax-remittance.js
CHANGED
|
@@ -90,6 +90,7 @@ function _b() {
|
|
|
90
90
|
if (!bShop) bShop = require("./index");
|
|
91
91
|
return bShop.framework;
|
|
92
92
|
}
|
|
93
|
+
var C = _b().constants;
|
|
93
94
|
|
|
94
95
|
// ---- constants ----------------------------------------------------------
|
|
95
96
|
|
|
@@ -107,7 +108,7 @@ var MAX_PENALTY_REASON_LEN = 1000;
|
|
|
107
108
|
|
|
108
109
|
var DEFAULT_DAYS_LATE_MIN = 1;
|
|
109
110
|
var MAX_DAYS_LATE_MIN = 3650; // ~10 years
|
|
110
|
-
var MS_PER_DAY =
|
|
111
|
+
var MS_PER_DAY = C.TIME.days(1);
|
|
111
112
|
|
|
112
113
|
var CONTROL_BYTE_RE = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/;
|
|
113
114
|
|
package/lib/theme-assets.js
CHANGED
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
|
|
4
4
|
"packages": {
|
|
5
5
|
"blamejs": {
|
|
6
|
-
"version": "0.12.
|
|
7
|
-
"tag": "v0.12.
|
|
6
|
+
"version": "0.12.48",
|
|
7
|
+
"tag": "v0.12.48",
|
|
8
8
|
"license": "Apache-2.0",
|
|
9
9
|
"author": "blamejs contributors",
|
|
10
10
|
"source": "https://github.com/blamejs/blamejs",
|
|
@@ -8,6 +8,22 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.48 (2026-05-25) — **`b.network.dns.dnssec` — local DNSSEC signature verification (RFC 4035).** Verify a DNS answer's RRSIG signature yourself instead of trusting the upstream resolver's AD bit. b.network.dns.dnssec.verifyRrset reconstructs the RFC 4034 §3.1.8.1 signed data — the RRSIG RDATA without the signature, followed by the RRset in canonical form (owner names lowercased, RRs ordered by canonical RDATA, the RRSIG's Original TTL) — and checks the signature against the DNSKEY, enforcing the inception / expiration window. Supports RSA/SHA-256 (alg 8), ECDSA P-256/SHA-256 (13), ECDSA P-384/SHA-384 (14), and Ed25519 (15) — the modern deployed set. verifyDs checks a delegation-signer digest against a DNSKEY (SHA-256 / SHA-384) and keyTag computes the RFC 4034 Appendix B key tag. The verification core is what a chain-walker composes; it defends against a compromised or on-path resolver that lies about authentication. **Added:** *`b.network.dns.dnssec.verifyRrset(opts)`* — Verifies an RRSIG over a canonicalised RRset against a DNSKEY. `opts` carries the owner `name`, the RR `type`, the wire-format `rdatas`, the parsed `rrsig` (algorithm / labels / originalTtl / inception / expiration / keyTag / signerName / signature), and the `dnskey` (algorithm + raw public key). The signed data is rebuilt per RFC 4034 §3.1.8.1: the RRSIG prefix (type covered | algorithm | labels | original TTL | expiration | inception | key tag | canonical signer name) followed by each RR in canonical form (lowercased owner | type | class | original TTL | rdlen | rdata), sorted by `Buffer.compare` on the RDATA. The validity window is enforced against `opts.at` (defaults to now; an invalid Date is refused, not treated as now). An RRSIG whose algorithm disagrees with the DNSKEY is refused before any key is built. RR types that embed domain names in their RDATA (NS, CNAME, SOA, MX, SRV, …) need RDATA-internal name-lowercasing this version does not perform, so they are refused with `dnssec/uncanonicalizable-type` rather than mis-validated; the security-critical DNSKEY / DS and the name-free address / text types (A, AAAA, TXT, CAA, TLSA, …) are fully supported. · *`b.network.dns.dnssec.verifyDs(opts)` / `b.network.dns.dnssec.keyTag(dnskeyRdata)`* — `verifyDs` confirms a delegation-signer record matches a DNSKEY: it checks the key tag, then compares the DS digest (SHA-256 type 2 / SHA-384 type 4) against the digest computed over the canonical owner name and the DNSKEY RDATA, constant-time. `keyTag` computes the RFC 4034 Appendix B 16-bit key tag from a DNSKEY's full RDATA — the identifier an RRSIG or DS uses to select the signing key. Together with `verifyRrset` these are the per-RRset building blocks a recursive chain-walk (root → TLD → zone) composes; the chain-walk itself, NSEC / NSEC3 denial-of-existence, and the bundled IANA root trust anchor are not part of this core.
|
|
12
|
+
|
|
13
|
+
- v0.12.47 (2026-05-25) — **`b.cose.mac0` / `b.cose.macVerify0` — COSE_Mac0 (RFC 9052 §6.2).** Completes the COSE message-type set (COSE_Sign1 / COSE_Encrypt0 / COSE_Mac0) with single shared-key MACs. b.cose.mac0 produces a tagged COSE_Mac0 over a payload using HMAC-SHA-256/384/512 (the COSE-standard MAC algorithms; HMAC is symmetric, so its post-quantum strength is preserved). b.cose.macVerify0 recomputes the tag over the MAC_structure and compares it in constant time, with a mandatory algorithm allowlist. Use when both parties hold a shared key — e.g. an ECDH-derived key — and a non-repudiable signature is not wanted; detached payloads are supported (the proximity mdoc device-MAC variant and MACed CWTs are the consumers). Composes b.cbor + the framework's constant-time compare; no new runtime dependency. **Added:** *`b.cose.mac0(payload, opts)` / `b.cose.macVerify0(coseMac0, opts)`* — `mac0` emits a tagged COSE_Mac0 (tag 17) with `alg` (`HMAC-256/256` | `HMAC-384/384` | `HMAC-512/512`) in the protected header and the HMAC tag computed over the MAC_structure `["MAC0", protected, external_aad, payload]`; `detached: true` emits a nil payload. `macVerify0` reads the algorithm from the protected header (must be in the required `opts.algorithms` allowlist), recomputes the tag, and compares it constant-time — a wrong key, tampered tag, or `external_aad` mismatch is refused with `cose/bad-tag`; a detached payload is supplied via `opts.externalPayload`. `external_aad` binds context into the tag.
|
|
14
|
+
|
|
15
|
+
- v0.12.46 (2026-05-25) — **`b.mdoc.verifyDeviceAuth` — ISO 18013-5 mdoc device authentication.** Completes mdoc verification with the holder-binding half (ISO 18013-5 §9.1.3, signature variant). verifyIssuerSigned proves the data is issuer-signed; verifyDeviceAuth proves the presenter controls the device key the issuer bound into the MSO, so a captured issuer-signed document cannot be replayed by anyone else. The device's COSE_Sign1 (deviceSigned.deviceAuth.deviceSignature) is verified over the detached DeviceAuthentication structure ["DeviceAuthentication", SessionTranscript, DocType, DeviceNameSpacesBytes] using the device key from verifyIssuerSigned().deviceKey (now surfaced) and the operator-supplied SessionTranscript that binds the proof to this exact exchange (the presentation protocol — e.g. OpenID4VP — defines the transcript). Composes the v0.12.45 b.cose detached-payload verify + importKey. The MAC variant (deviceMac / COSE_Mac0, used in proximity flows with a reader ephemeral key) is deferred and refused with mdoc/device-mac-unsupported. No new runtime dependency. **Added:** *`b.mdoc.verifyDeviceAuth(opts)` + `deviceKey` on the verifyIssuerSigned result* — `verifyDeviceAuth({ deviceKey, deviceSigned, docType, sessionTranscript, algorithms })` imports the device key (a COSE_Key via `b.cose.importKey`, or a KeyObject), reconstructs the detached `DeviceAuthentication` payload, and verifies the `deviceSignature` COSE_Sign1 against the mandatory algorithm allowlist — a mismatched `sessionTranscript` or `docType` fails the signature. `verifyIssuerSigned` now returns `deviceKey` (the MSO `deviceKeyInfo.deviceKey`) so the two checks chain. The MAC variant (`deviceMac`) is refused with `mdoc/device-mac-unsupported` pending COSE_Mac0 + reader-key support.
|
|
16
|
+
|
|
17
|
+
- v0.12.45 (2026-05-25) — **`b.cose` adds detached-payload sign/verify + `b.cose.importKey` (COSE_Key).** Two RFC 9052 / 9053 completions to the COSE substrate, both useable today and the prerequisites for mdoc device authentication and C2PA claim verification. Detached payloads (RFC 9052 §4.1): b.cose.sign with detached:true emits a COSE_Sign1 whose payload slot is nil — the signature still covers the payload, and the caller transmits it out of band; b.cose.verify takes the payload back as opts.externalPayload and binds it into the Sig_structure. A detached token verified without externalPayload is refused, and supplying externalPayload for an attached token is refused as ambiguous. COSE_Key import (RFC 9052 §7): b.cose.importKey turns a COSE_Key CBOR map into a node:crypto public KeyObject for b.cose.verify, accepting EC2 (P-256 / P-384 / P-521) and OKP (Ed25519) with the curve allowlisted so an unexpected key type is refused. No new runtime dependency. **Added:** *Detached COSE_Sign1 payloads + `b.cose.importKey(coseKey)`* — `b.cose.sign(payload, { detached: true })` emits a nil-payload COSE_Sign1 (the signature covers the payload regardless); `b.cose.verify(coseSign1, { externalPayload })` reconstructs the Sig_structure from the supplied payload, refusing a detached token with no `externalPayload` (`cose/detached-no-payload`) and refusing `externalPayload` on an attached token (`cose/payload-ambiguous`). `b.cose.importKey(coseKey)` maps a COSE_Key map (`kty` 2/EC2 with `crv` P-256/384/521, or `kty` 1/OKP with Ed25519) to a public KeyObject, allowlisting `kty`/`crv` and refusing anything else with `cose/unsupported-key` — the verification key embedded in an mdoc MSO or COSE_Key header is consumed this way.
|
|
18
|
+
|
|
19
|
+
- v0.12.44 (2026-05-25) — **`b.did` adds the did:jwk method.** Completes b.did's method set with did:jwk alongside did:key and did:web. did:jwk encodes a public key as a base64url-encoded JWK directly in the identifier, so resolution is deterministic and offline — the same self-contained shape as did:key but in JWK form, which is what OpenID4VCI and the EU Digital Identity Wallet ecosystem commonly use. b.did.resolve("did:jwk:…") returns the verification key as a node:crypto KeyObject (kty/crv allowlisted — Ed25519 / P-256 / P-384 / secp256k1 — so an unexpected key type is refused, not blindly imported), and b.did.keyToDid(publicKey, { method: "jwk" }) produces a did:jwk from a key (the private member is stripped). No new runtime dependency. **Added:** *did:jwk in `b.did.resolve` / `b.did.keyToDid`* — `resolve` decodes the base64url JWK (bounded via `b.safeJson`), allowlists its `kty`/`crv`, and returns `{ didDocument, verificationMethods: [{ publicKey, … }] }` with the key as a KeyObject ready for `b.vc` / `b.mdoc` / `b.scitt`; `keyToDid(publicKey, { method: "jwk" })` encodes a public key as `did:jwk:<base64url-JWK>` (default remains `did:key`). Malformed base64url-JSON is refused with `did/bad-jwk` and an unsupported key type with `did/unsupported-key`.
|
|
20
|
+
|
|
21
|
+
- v0.12.43 (2026-05-25) — **`b.crypto.selfTest` — FIPS 140-3-style power-on self-test for the crypto stack.** A power-on self-test over the framework's cryptographic primitives — the integrity check a FIPS 140-3-validated module runs at start-up. The hash / XOF checks are known-answer tests against NIST FIPS 202 published vectors (SHA3-256 / SHA3-512 / SHAKE256), so they confirm the framework's hashing matches the standard rather than merely itself; the AEAD check round-trips XChaCha20-Poly1305 and confirms a tampered ciphertext is rejected; and the post-quantum checks run a pairwise-consistency + negative test for ML-KEM-1024, ML-DSA-87, and SLH-DSA-SHAKE-256f (a fresh keypair must encaps/decaps and sign/verify consistently and reject a tampered signature — FIPS 140-3 §10.3 pairwise consistency, since the runtime exposes no seed-injection API for a fixed-seed KAT). selfTest returns a structured report and, by default, throws on any failure so a broken crypto stack fails closed at boot rather than silently producing bad output. Operators in regulated deployments can run it at start-up as a self-integrity gate. **Added:** *`b.crypto.selfTest(opts?)`* — Runs eight checks — SHA3-512 / SHA3-256 / SHAKE256 known-answer tests (NIST FIPS 202), HMAC-SHA3-512 determinism, XChaCha20-Poly1305 round-trip + tamper-detect, and ML-KEM-1024 / ML-DSA-87 / SLH-DSA-SHAKE-256f pairwise-consistency + negative tests — and returns `{ ok, results: [{ name, ok, detail? }], failures, ranAt }`. Throws `crypto/self-test-failed` (with the report attached) on any failure unless `opts.throwOnFailure` is `false`. Exercises the framework's real primitive paths so a self-test failure means the shipped crypto is broken.
|
|
22
|
+
|
|
23
|
+
- v0.12.42 (2026-05-24) — **`b.vc.present` / `b.vc.verifyPresentation` — W3C Verifiable Presentations.** Completes b.vc with the holder side: a Verifiable Presentation is a holder-signed envelope wrapping one or more credentials, proving the presenter controls the key the credentials were issued to. b.vc.present builds and signs a VerifiablePresentation (each credential enveloped per VC-JOSE-COSE) as a compact JWS (vp+jwt) or COSE_Sign1 (application/vp+cose), matching b.vc.issue's algorithms; an optional nonce / audience is embedded in the signed presentation for holder-binding and replay protection. b.vc.verifyPresentation verifies the holder signature (auto-detected jose/cose, mandatory algorithm allowlist, JOSE none refused), the VCDM structure, and the embedded nonce / audience / expectedHolder when given, and — with verifyCredentials: true — verifies each enveloped credential through b.vc.verify and returns them. The holder is typically a DID, resolved to a key via b.did. Composes b.cose; no new runtime dependency. **Added:** *`b.vc.present(opts)` / `b.vc.verifyPresentation(secured, opts)`* — `present` wraps `opts.credentials` (secured VCs — compact-JWS strings or COSE_Sign1 bytes, each enveloped as an `EnvelopedVerifiableCredential` data: URI) in a `VerifiablePresentation` signed by the holder, with optional `nonce` / `audience` embedded for binding. `verifyPresentation` verifies the holder signature against the mandatory `opts.algorithms` allowlist (JOSE `none` always refused), re-checks the VCDM structure, enforces `expectedHolder` / `nonce` / `audience` when supplied, and with `verifyCredentials: true` verifies each enveloped credential through `b.vc.verify` (using `opts.credentialOpts`), returning `{ presentation, holder, credentials, securing, alg }`. The enveloped-credential count is bounded. A `vp+jwt` presentation is refused by `b.vc.verify` and a `vc+jwt` credential is refused by `verifyPresentation` — the media-type binding keeps the two surfaces distinct.
|
|
24
|
+
|
|
25
|
+
- v0.12.41 (2026-05-24) — **`b.did` — W3C DID resolution (did:key + did:web) feeding the credential verifiers.** Resolve W3C Decentralized Identifiers (DID Core 1.0) to verification keys — the link that lets a credential's issuer be named by a DID rather than a raw key. Resolve the issuer DID of a b.vc / b.mdoc / b.scitt credential to a node:crypto KeyObject and hand it to the verifier. did:key encodes the public key in the identifier (multicodec + base58btc), so resolution is deterministic and offline — Ed25519, P-256, P-384, and secp256k1 round-trip; did:web places the DID document at an HTTPS URL derived from the identifier, with the network fetch left to the operator (the framework parses the operator-fetched document and extracts its verification methods, as publicKeyMultibase or publicKeyJwk). b.did.keyToDid encodes a KeyObject as a did:key (an issuer naming itself), b.did.parse splits the identifier (and returns the did:web URL to fetch), and b.did.resolve returns the document and verification keys. DID Core 1.0 is a W3C Recommendation; the method specs (did:key W3C CCG report, did:web DID method registry — EUDI-mandated) are deployed-stable. Composes node:crypto; no new runtime dependency. **Added:** *`b.did.resolve(did, opts?)` / `b.did.keyToDid(publicKey)` / `b.did.parse(did)`* — `resolve` returns `{ didDocument, verificationMethods: [{ id, controller, type, publicKey }] }` with each `publicKey` a `node:crypto` KeyObject ready for `b.vc.verify` / `b.mdoc.verifyIssuerSigned` / `b.scitt.verifyStatement`. did:key resolves deterministically and offline (base58btc + multicodec → Ed25519 raw key or EC compressed point, rebuilt via SPKI); did:web requires the operator to pass the fetched DID document as `opts.document` (the URL to GET is on `b.did.parse(did).url`) and the document `id` must match the requested DID. A publicKeyJwk in a DID document is imported only after its `kty`/`crv` is allowlisted (Ed25519 / P-256 / P-384 / secp256k1) — an unexpected key type from an untrusted document is refused, not blindly imported. `keyToDid` encodes an Ed25519 / P-256 / P-384 / secp256k1 KeyObject as a did:key; `parse` derives the did:web HTTPS URL (`host[:port][:path]` → `https://host/path/did.json`, or `/.well-known/did.json`). Unknown methods, malformed base58, unsupported multicodec codes, and unsupported key types are each refused.
|
|
26
|
+
|
|
11
27
|
- v0.12.40 (2026-05-24) — **`b.mdoc` — ISO 18013-5 mdoc / mDL issuer-data verification.** Verify the issuer-signed data of an ISO/IEC 18013-5 mdoc — the credential format behind mobile driving licences (mDL) and the ISO track of the EU Digital Identity Wallet. This is the relying-party side: confirm that the data elements a holder presents were signed by the issuer and have not been altered. An mdoc's IssuerSigned carries the disclosed data elements and an issuerAuth that is a COSE_Sign1 (b.cose) over a Mobile Security Object (MSO) holding a per-element digest. b.mdoc.verifyIssuerSigned verifies the COSE signature with the issuer certificate from the COSE x5chain header, parses the MSO, enforces its validityInfo window, and recomputes each disclosed element's digest (the full Tag-24 IssuerSignedItemBytes) to match it against the MSO constant-time — the integrity check that makes selective disclosure trustworthy. An absent or mismatched digest is refused. Signing algorithms follow b.cose verification (the classical ES256/384/512 + EdDSA that real mDL issuers use; the caller names the allowlist); opts.trustAnchorsPem additionally verifies the issuer certificate chain. This completes the credential trio alongside W3C VCDM (b.vc) and IETF SD-JWT VC (b.auth.sdJwtVc). Composes b.cose + b.cbor; no new runtime dependency. **Added:** *`b.mdoc.verifyIssuerSigned(issuerSigned, opts)`* — Takes the CBOR `IssuerSigned` map (the operator extracts it from the device response / QR) and returns `{ docType, version, digestAlgorithm, validityInfo, namespaces, signerCert, alg }`. Verifies the COSE_Sign1 `issuerAuth` against the mandatory `opts.algorithms` allowlist using the issuer certificate from its `x5chain` (label 33) header; parses the Tag-24 Mobile Security Object; enforces the MSO `validityInfo` window against `opts.at` (default now; must be a valid Date; malformed dates fail closed); and recomputes the digest of every disclosed `IssuerSignedItem` (over the full Tag-24 bytes, with the MSO `digestAlgorithm` — SHA-256/384/512) to match the MSO `valueDigests` constant-time — an absent or mismatched digest is refused with `mdoc/digest-mismatch`. `opts.expectedDocType` pins the document type; `opts.trustAnchorsPem` (a PEM string or array) additionally verifies the issuer certificate chain and validity at the asserted time. A malformed `x5chain` certificate is refused with a clean `mdoc/bad-cert`. The mdoc device-authentication half (the SessionTranscript-bound holder-binding proof) is a presentation-protocol concern and is not part of issuer-data verification.
|
|
12
28
|
|
|
13
29
|
- v0.12.39 (2026-05-24) — **`b.vc` — W3C Verifiable Credentials 2.0 (issue / verify, JOSE + COSE securing).** Issue and verify W3C Verifiable Credentials (VC Data Model 2.0, a W3C Recommendation) secured per Securing Verifiable Credentials using JOSE and COSE (VC-JOSE-COSE, also a W3C Recommendation, May 2025). A verifiable credential is a tamper-evident, signed set of claims an issuer makes about a subject — a diploma, a membership, a license, an age assertion. Two securing mechanisms are supported, both signing the credential itself (no JWT/CWT claims wrapper): JOSE produces a compact JWS with the vc+jwt media type, signed with ES256/384/512 or EdDSA; COSE produces a COSE_Sign1 (application/vc+cose) over b.cose, which also accepts ML-DSA-87 for PQC-forward deployments. b.vc.verify auto-detects the form from the input, requires an algorithm allowlist, always refuses the JOSE none algorithm, re-checks the VCDM 2.0 structural rules, and enforces the validFrom / validUntil window. This is the W3C credential model, distinct from the IETF SD-JWT VC already at b.auth.sdJwtVc. Composes b.cose; no new runtime dependency. **Added:** *`b.vc.issue(credential, opts)` / `b.vc.verify(secured, opts)`* — `issue` validates the credential against the VCDM 2.0 structural rules (the `credentials/v2` context first, a `VerifiableCredential` type, an issuer, a credential subject) and signs it: `securing: "jose"` returns a compact JWS string (`typ` header `vc+jwt`), `securing: "cose"` returns COSE_Sign1 bytes (`typ` header `application/vc+cose`, content type `application/vc`) via `b.cose`. The credential is the exact signed payload — no JWT/CWT claims are injected. `verify` auto-detects the securing form from the input (compact-JWS string vs. COSE_Sign1 bytes), verifies the signature against the mandatory `opts.algorithms` allowlist (the JOSE `none` algorithm is always refused), re-checks the structural rules, enforces the `validFrom` / `validUntil` window against `opts.at` (default now; must be a valid Date), and optionally matches `opts.expectedIssuer` against the credential issuer id. Returns `{ credential, securing, alg, issuer }`.
|