@intenque/network-sdk 2.2.0-beta.1 → 2.2.0
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 +67 -1
- package/README.md +6 -5
- package/dist/capture/index.cjs +1 -1
- package/dist/capture/index.js +1 -1
- package/dist/cli/index.js +20 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/{constants-LVhc0PNb.d.cts → constants-BJeva5wC.d.cts} +1 -1
- package/dist/{constants-LVhc0PNb.d.ts → constants-BJeva5wC.d.ts} +1 -1
- package/dist/react/index.cjs +2 -2
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +9 -9
- package/dist/react/index.d.ts +9 -9
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +14 -9
- package/dist/server/index.d.ts +14 -9
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,73 @@
|
|
|
3
3
|
`@intenque/network-sdk`. The SDK major is the manifest schema major (spec §6.1). Design decisions are in the hub
|
|
4
4
|
repository: `docs/Network_CrossPromo_Plan.md` and `docs/spec-addendum-phase0-auth.md` ("# Phase 3: conversions").
|
|
5
5
|
|
|
6
|
-
## 2.2.0
|
|
6
|
+
## 2.2.0
|
|
7
|
+
|
|
8
|
+
The first non-beta release of the Phase 3 (conversions) line; the code is 2.2.0-beta.2 plus the production root.
|
|
9
|
+
|
|
10
|
+
- **Production root `root-2026`** (ECDSA P-256, generated offline by the owner; spec §4.5, docs/install-guide.md
|
|
11
|
+
"Adding a production root") is compiled into `rootPublicKeys`. A site that does not set `manifestRootKeys` now
|
|
12
|
+
trusts manifests whose signer is certified under `root-2026`; `init` / `sync-fallback` / `doctor` no longer need
|
|
13
|
+
`--root-keys` against the production hub. `DEV_ROOT_PUBLIC_KEYS` (`root-dev-a01e0d`) stays a separate export and
|
|
14
|
+
is never merged in. Adding a root is a minor; removing one is a major (§4.5 "Root rotation").
|
|
15
|
+
- Sizes (gzip): react 11 987 / 12 000 B, server 8 063 / 8 100 B, capture 1 494 / 1 500 B. The root adds ~90 B to
|
|
16
|
+
`react`; the same bundle gives them back without a behaviour change: the click-id module reuses the verifier's
|
|
17
|
+
base64url encoder and hex helper, `mintClickId` packs its header with a `DataView`, `withClickId` and `buildHref`
|
|
18
|
+
share one fragment splitter, and the signal environment reads `globalThis.document` & co. instead of `typeof`
|
|
19
|
+
guards.
|
|
20
|
+
- `SDK_VERSION` / wire `sdk` is `2.2.0`; the `sdk-publish` workflow publishes a non-prerelease to the `latest`
|
|
21
|
+
dist-tag, so a bare `npm install @intenque/network-sdk` resolves to this version.
|
|
22
|
+
|
|
23
|
+
## 2.2.0-beta.2
|
|
24
|
+
|
|
25
|
+
Security fix round (hub Phase 3 WP19). No API change; one new drop reason.
|
|
26
|
+
|
|
27
|
+
### Server entry (`@intenque/network-sdk/server`)
|
|
28
|
+
|
|
29
|
+
- **Misrouted answers are retried, not dropped.** `403`, `404` and any 3xx the classifier sees (`300`, `304`, or a
|
|
30
|
+
`fetch` that hands redirects back) were terminal after one attempt, so a missing route prefix, a WAF or a
|
|
31
|
+
maintenance page in front of the hub lost every report in that window. They are now `misrouted`
|
|
32
|
+
(`classifyResponse` returns the new kind): retried in process after 2, 8 and 30 s, then every 10 min from the
|
|
33
|
+
outbox, and dropped only 24 h after the first 401 / missing-config / misrouted answer with reason `misrouted_24h`
|
|
34
|
+
(the clock is the existing `unauthorizedSince` field, shared with the 401 rule; no outbox migration). A real
|
|
35
|
+
redirect (`301`/`302`/`303`/`307`/`308`, with or without `Location`) is still a transport error under
|
|
36
|
+
`redirect: 'error'` (status 0, retryable), as before. `400`, other `409`s, `402`, `413`, `422` and the rest of 4xx
|
|
37
|
+
stay terminal.
|
|
38
|
+
- **The responder's error code is bounded.** A code is kept (logged in `inr_outbox_dropped_total` /
|
|
39
|
+
`INR_UNAUTHORIZED`, stored in `lastError.code`) only when it matches `^[A-Z][A-Z0-9_]{0,63}$`; anything else
|
|
40
|
+
(a captive portal, a misrouted proxy, a 5000-character string) is `null`.
|
|
41
|
+
- **`referralMiddleware()` strips `inrRef` from any object body** (arrays and class instances too, not only plain
|
|
42
|
+
objects) with `Reflect.deleteProperty`, and never throws: a frozen body or a proxy that refuses keeps the field,
|
|
43
|
+
a `null` or primitive body is left alone. `req.inrRef` is set as before.
|
|
44
|
+
- **`forget()` stops a running send chain.** Before each in-process retry the chain checks that its outbox row (or
|
|
45
|
+
spill entry) still exists; once `forget(clickId)` purged it, no further attempt is sent. An outbox that errors or
|
|
46
|
+
times out on that check keeps the chain sending (as before).
|
|
47
|
+
- **`forget()` during a drain tick's spill move.** The drainer moved a snapshot of the in-memory spill, so a spilled
|
|
48
|
+
report that `forget(clickId)` removed while an earlier entry was being written was inserted into the outbox after
|
|
49
|
+
the purge (and `forget()` had returned `true`); the next tick sent the erased click id again. The move now skips an
|
|
50
|
+
entry no longer spilled, and removes the row again when the entry left the spill while its own write was in flight
|
|
51
|
+
(forget, or a send that finished). A failed write still leaves the entry spilled for the next tick.
|
|
52
|
+
|
|
53
|
+
### Verifier (`@intenque/network-sdk/react`, the manifest verifier)
|
|
54
|
+
|
|
55
|
+
- **Destination URLs: RFC 3986 characters only after the authority** (unreserved, sub-delims, `:`, `@`, `/`, `?`,
|
|
56
|
+
`%XX`, one `#`). The hub now stores business URLs with `^`, `|`, a backtick, `{`, `}`, `[`, `]` and non-ASCII
|
|
57
|
+
percent-encoded and refuses
|
|
58
|
+
to sign anything else, so the SDK and the hub agree on every URL in every engine (a raw `^` or `|` used to pass
|
|
59
|
+
the hub and fail the SDK in browsers, taking the whole manifest down).
|
|
60
|
+
- **Signer dates must be their own `toISOString()`** (`2027-02-30`, `T24:00`, second 60 are `malformed`), the same
|
|
61
|
+
rule as the hub's verifier: engines disagree on those dates.
|
|
62
|
+
- **A root key that is not exactly `crv: 'P-256'` never verifies** (Firefox's WebCrypto accepts `p-256`).
|
|
63
|
+
- **A root key must have the shape the hub requires of its own roots** (`PublicJwkSchema`: `kty: 'EC'`,
|
|
64
|
+
`crv: 'P-256'`, `x` / `y` canonical base64url of 32 bytes), else `root_sig`; other members are ignored, as
|
|
65
|
+
before. The SDK handed WebCrypto `kty: 'EC'` whatever the site's `manifestRootKeys` said, so `kty: 'ec'`, `'RSA'`
|
|
66
|
+
or none verified in every engine while the hub refuses such a key, and a padded `x` verified in WebKit only.
|
|
67
|
+
- **Hosts with right-to-left text** (hub side, no SDK change): the hub now refuses a business URL whose host breaks
|
|
68
|
+
the RFC 5893 bidi rule (e.g. `24.בדיקה.co.il`, `1shop.عرب.com`, `aא.com`, `٣.com`), which Firefox's URL parser
|
|
69
|
+
refuses, so a signed manifest can no longer be `malformed` in Firefox alone. The SDK schema does not repeat that
|
|
70
|
+
check (bundle size; the hub never signs such a host).
|
|
71
|
+
|
|
72
|
+
## 2.2.0-beta.1
|
|
7
73
|
|
|
8
74
|
### Server entry (`@intenque/network-sdk/server`)
|
|
9
75
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Adds the **"Discover Other Businesses"** section, referral capture and conversio
|
|
|
4
4
|
network site. Spec: `docs/Network_CrossPromo_Plan.md` §6 in the hub repository; the timed per-site checklist is
|
|
5
5
|
`docs/install-guide.md`.
|
|
6
6
|
|
|
7
|
-
Version `2.2.0
|
|
7
|
+
Version `2.2.0` reads manifest **schema 2** (SDK major = manifest schema major). Zero runtime dependencies;
|
|
8
8
|
`react` / `react-dom` ^18 || ^19 are optional peers (only the `react` entry needs them).
|
|
9
9
|
|
|
10
10
|
| Entry | Exports |
|
|
@@ -25,9 +25,10 @@ npx network-sdk init --site-key <site-key> # writes src/network.config.js
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
`init` fails with `site <key> has no published manifest; Publish in the admin first` when the hub has not published
|
|
28
|
-
the site yet (step 1 of the checklist).
|
|
29
|
-
explicitly: `--url <cdn-base> --root-keys '<MANIFEST_ROOT_PUBLIC_KEYS json>'`
|
|
30
|
-
inlined into the generated scripts and removed by re-running `init`
|
|
28
|
+
the site yet (step 1 of the checklist). The production root `root-2026` is compiled in since 2.2.0; against the
|
|
29
|
+
development hub pass its root and CDN explicitly: `--url <cdn-base> --root-keys '<MANIFEST_ROOT_PUBLIC_KEYS json>'`
|
|
30
|
+
(development only; the flags are inlined into the generated scripts and removed by re-running `init --force`
|
|
31
|
+
without them against production).
|
|
31
32
|
|
|
32
33
|
Then paste what `init` prints:
|
|
33
34
|
|
|
@@ -65,7 +66,7 @@ The timed steps 7–8 of `docs/install-guide.md`, as made on JCM. Node ≥ 18, E
|
|
|
65
66
|
|
|
66
67
|
```sh
|
|
67
68
|
cd backend
|
|
68
|
-
npm i @intenque/network-sdk@2.2.0
|
|
69
|
+
npm i @intenque/network-sdk@2.2.0
|
|
69
70
|
```
|
|
70
71
|
|
|
71
72
|
Env, as `SECRET` values on the backend component (names only in `.env.example`): `INR_ENABLED=false` (only the
|
package/dist/capture/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},M=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of y(e))!k.call(n,r)&&r!==t&&u(n,r,{get:()=>e[r],enumerable:!(o=m(e,r))||o.enumerable});return n};var L=n=>M(u({},"__esModule",{value:!0}),n);var w={};C(w,{attachReferral:()=>A,attachReferralHeader:()=>O,captureReferral:()=>
|
|
1
|
+
"use strict";var u=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var C=(n,e)=>{for(var t in e)u(n,t,{get:e[t],enumerable:!0})},M=(n,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of y(e))!k.call(n,r)&&r!==t&&u(n,r,{get:()=>e[r],enumerable:!(o=m(e,r))||o.enumerable});return n};var L=n=>M(u({},"__esModule",{value:!0}),n);var w={};C(w,{attachReferral:()=>A,attachReferralHeader:()=>O,captureReferral:()=>T,clearReferral:()=>N,getReferral:()=>I,onConsentChange:()=>b,setConsent:()=>E});module.exports=L(w);var _="2.2.0";function S(n,e,t){let o=s=>{try{t?.(s)}catch{}return s};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let r=s=>fetch(n,{method:"POST",mode:"no-cors",keepalive:s,credentials:"omit",body:e});try{return r(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void r(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var f="inr_ref",h=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(f)||"null");e?t.setItem(f,JSON.stringify(e)):t.removeItem(f)}catch{}return null},R=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!R(),g="",x=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||g==t)return;g=t;let o=r=>{let s=Date.now();S(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:r&&[...new Uint8Array(r,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:_,t:s,d:Math.min(h,Math.max(0,s-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function T(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},r=c(0),s={last:o,first:r?.first||o};if(n.consentMode=="require-consent"&&!p()){l=s;return}c(0,s),p()&&c(1,s),x(s)}catch{}}function E(n){try{if(n=="granted"){let e=l;if(l=null,e&&R())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&x(e))}else l=null,c(1,null)}catch{}}var b=E;function N(){l=null,c(0,null),c(1,null)}function I(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>h?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function A(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),r=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),s=r.pathname;if(r.origin!=o.origin&&!e.origins?.includes(r.origin)||e.routes&&!e.routes.some(d=>d.test?~s.search(d):d==s))return t;let i=I();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var O=(n,e={})=>A(n,{...e,mode:"header"});
|
package/dist/capture/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var f="2.2.0
|
|
1
|
+
var f="2.2.0";function _(n,e,t){let o=r=>{try{t?.(r)}catch{}return r};try{if(location.protocol=="https:"&&/^http:/i.test(n))return o(!1)}catch{}try{if(navigator.sendBeacon(n,e))return o(!0)}catch{}let s=r=>fetch(n,{method:"POST",mode:"no-cors",keepalive:r,credentials:"omit",body:e});try{return s(!0).then(()=>o(!0),()=>{try{if(document.visibilityState=="visible")return void s(!1).then(()=>o(!0),()=>o(!1))}catch{}o(!1)}),!0}catch{return o(!1)}}var u="inr_ref",g=2592e6,a={},l=null,c=(n,e)=>{try{let t=n?localStorage:sessionStorage;if(e===void 0)return JSON.parse(t.getItem(u)||"null");e?t.setItem(u,JSON.stringify(e)):t.removeItem(u)}catch{}return null},h=()=>!!navigator.globalPrivacyControl,p=()=>a.consent?.get()=="granted"&&!h(),S="",R=n=>{let e=a.endpoint,t=n.last.c;if(!e||!a.siteKey||S==t)return;S=t;let o=s=>{let r=Date.now();_(e.replace(/\/+$/,"")+"/landing",JSON.stringify({v:2,k:a.siteKey,c:t,scope:"session",ph:s&&[...new Uint8Array(s,0,8)].map(i=>(i+256).toString(16).slice(1)).join(""),sdk:f,t:r,d:Math.min(g,Math.max(0,r-n.last.t))}))};try{crypto.subtle.digest("SHA-256",new TextEncoder().encode(location.pathname)).then(o,()=>o())}catch{o()}};function C(n={}){a=n;try{let e=new URL(location.href),t=e.searchParams.get("inr");if(!t||(e.searchParams.delete("inr"),history.replaceState(history.state,"",e.href),!/^[A-Za-z0-9_-]{32}$/.test(t)))return;let o={c:t,t:Date.now(),from:e.searchParams.get("utm_source")},s=c(0),r={last:o,first:s?.first||o};if(n.consentMode=="require-consent"&&!p()){l=r;return}c(0,r),p()&&c(1,r),R(r)}catch{}}function x(n){try{if(n=="granted"){let e=l;if(l=null,e&&h())return;let t=e||c(0);t&&(c(0,t),p()&&c(1,t),e&&R(e))}else l=null,c(1,null)}catch{}}var M=x;function L(){l=null,c(0,null),c(1,null)}function E(){try{let n=c(0),e=c(1),t=n&&(!e||n.last.t>=e.last.t)?n:e;return!t||Date.now()-t.last.t>g?null:{clickId:t.last.c,firstClickId:t.first.c==t.last.c?null:t.first.c,capturedAt:t.last.t,tokenScope:n&&n.last.c==t.last.c?"session":"persistent",consented:!!p()}}catch{return null}}function I(n,e={}){n.interceptors.request.use(t=>{try{if(/^(get|head|options)?$/i.test(t.method||""))return t;let o=new URL((t.baseURL||n.defaults?.baseURL||"").replace(/\/*$/,"/"),location.origin),s=new URL((t.url||"").replace(/^\/(?!\/)/,""),o),r=s.pathname;if(s.origin!=o.origin&&!e.origins?.includes(s.origin)||e.routes&&!e.routes.some(d=>d.test?~r.search(d):d==r))return t;let i=E();if(!i)return t;e.mode=="header"?(t.headers=t.headers||{},t.headers["X-INR-Ref"]=i.clickId,t.headers["X-INR-Ref-Scope"]=i.tokenScope,i.firstClickId&&(t.headers["X-INR-Ref-First"]=i.firstClickId)):(t.data==null||Object.getPrototypeOf(t.data)==Object.prototype)&&(t.data={...t.data,inrRef:{clickId:i.clickId,firstClickId:i.firstClickId,tokenScope:i.tokenScope}})}catch{}return t})}var T=(n,e={})=>I(n,{...e,mode:"header"});export{I as attachReferral,T as attachReferralHeader,C as captureReferral,L as clearReferral,E as getReferral,M as onConsentChange,x as setConsent};
|
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { webcrypto } from "crypto";
|
|
5
5
|
|
|
6
6
|
// src/constants.ts
|
|
7
|
-
var SDK_VERSION = "2.2.0
|
|
7
|
+
var SDK_VERSION = "2.2.0";
|
|
8
8
|
var MANIFEST_SCHEMA_MAJOR = 2;
|
|
9
9
|
var DEFAULT_CDN_BASE = "https://inr-network-public.nyc3.cdn.digitaloceanspaces.com/network/v1";
|
|
10
10
|
var MANIFEST_MAX_BYTES = 24 * 1024;
|
|
@@ -129,7 +129,17 @@ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "
|
|
|
129
129
|
import { dirname, join, resolve } from "path";
|
|
130
130
|
|
|
131
131
|
// src/rootPublicKeys.ts
|
|
132
|
-
var rootPublicKeys = [
|
|
132
|
+
var rootPublicKeys = [
|
|
133
|
+
{
|
|
134
|
+
kid: "root-2026",
|
|
135
|
+
jwk: {
|
|
136
|
+
kty: "EC",
|
|
137
|
+
crv: "P-256",
|
|
138
|
+
x: "9Xly8AJB8fMVSBjA1TK7HnAxxYRXwwlPU54zl_AkUcs",
|
|
139
|
+
y: "Tehja7BG1JLpIHfIMJjSRWPpZKXCvRIXS-EtAv1Mc68"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
];
|
|
133
143
|
|
|
134
144
|
// src/verify/schema.ts
|
|
135
145
|
var SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
@@ -141,6 +151,7 @@ var COORD_RE = /^[A-Za-z0-9_-]{42}[AEIMQUYcgkosw048]$/;
|
|
|
141
151
|
var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
|
|
142
152
|
var TOKEN_RE = /^[a-z0-9-]+\.\d+\.\d{8}\.[0-9a-f]{12}$/;
|
|
143
153
|
var HOST_RE = /^[a-z0-9.-]+$/;
|
|
154
|
+
var URL_TAIL_RE = /^([\w.~!$&'()*+,;=:@/?-]|%[\da-f]{2})*(#([\w.~!$&'()*+,;=:@/?-]|%[\da-f]{2})*)?$/i;
|
|
144
155
|
var Invalid = class extends Error {
|
|
145
156
|
};
|
|
146
157
|
var fail = () => {
|
|
@@ -220,7 +231,7 @@ function destinationUrlProblem(v) {
|
|
|
220
231
|
if (host.startsWith("[") || /^\d+(\.\d+){3}$/.test(host)) return "IP addresses are not allowed";
|
|
221
232
|
if (!HOST_RE.test(host) || !host.includes(".") || host.startsWith(".") || host.endsWith("."))
|
|
222
233
|
return "invalid hostname";
|
|
223
|
-
if (u.toString() !== v) return "must be normalised";
|
|
234
|
+
if (u.toString() !== v || !URL_TAIL_RE.test(v.slice(u.origin.length))) return "must be normalised";
|
|
224
235
|
return null;
|
|
225
236
|
}
|
|
226
237
|
function destinationUrl(v) {
|
|
@@ -470,9 +481,10 @@ var keyCache = /* @__PURE__ */ new WeakMap();
|
|
|
470
481
|
async function importKey(jwk, api) {
|
|
471
482
|
const hit = keyCache.get(jwk);
|
|
472
483
|
if (hit) return hit;
|
|
484
|
+
const { kty, crv, x, y } = jwk;
|
|
473
485
|
const p = api.importKey(
|
|
474
486
|
"jwk",
|
|
475
|
-
{ kty
|
|
487
|
+
{ ...parsePublicJwk({ kty, crv, x, y }), ext: true },
|
|
476
488
|
{ name: "ECDSA", namedCurve: "P-256" },
|
|
477
489
|
false,
|
|
478
490
|
["verify"]
|
|
@@ -514,7 +526,9 @@ function signedPayload(m) {
|
|
|
514
526
|
}
|
|
515
527
|
var isKidList = (v) => Array.isArray(v) && v.every((k) => typeof k === "string");
|
|
516
528
|
function parseIso(s) {
|
|
517
|
-
|
|
529
|
+
const t = Date.parse(s);
|
|
530
|
+
if (!new Date(t).toISOString().startsWith(s.slice(0, 19))) fail();
|
|
531
|
+
return t;
|
|
518
532
|
}
|
|
519
533
|
async function verifyManifest(input, opts) {
|
|
520
534
|
try {
|
|
@@ -541,8 +555,7 @@ async function verifyManifest(input, opts) {
|
|
|
541
555
|
return { ok: false, reason: "root_sig" };
|
|
542
556
|
const notBefore = parseIso(s.notBefore);
|
|
543
557
|
const notAfter = parseIso(s.notAfter);
|
|
544
|
-
if (
|
|
545
|
-
return { ok: false, reason: "malformed" };
|
|
558
|
+
if (notAfter < notBefore) return { ok: false, reason: "malformed" };
|
|
546
559
|
if (opts.source === "live" && (t < notBefore || t > notAfter + SIGNER_GRACE_MS))
|
|
547
560
|
return { ok: false, reason: "window" };
|
|
548
561
|
if (m.sig.kid !== s.kid) return { ok: false, reason: "kid_mismatch" };
|