@shunt-network/verify 0.0.0-stage → 1.0.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/dist/core.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ export type VerifyResult = {
2
+ ok: true;
3
+ requestId: string;
4
+ } | {
5
+ ok: false;
6
+ reason: "missing_headers" | "stale_timestamp" | "bad_signature" | "replay";
7
+ };
8
+ export interface VerifyInput {
9
+ secret: string | string[];
10
+ method: string;
11
+ pathAndQuery: string;
12
+ rawBody: Buffer | Uint8Array | string;
13
+ getHeader: (name: string) => string | undefined | null;
14
+ maxSkewSec?: number;
15
+ replayCache?: ReplayCache;
16
+ }
17
+ export declare class ReplayCache {
18
+ private ttlMs;
19
+ private seen;
20
+ constructor(ttlMs?: number);
21
+ /** returns true if the id was already seen */
22
+ check(id: string): boolean;
23
+ }
24
+ export declare function verifyShunt(i: VerifyInput): VerifyResult;
25
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,eAAe,GAAG,QAAQ,CAAA;CAAE,CAAC;AAE9F,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,WAAW;IAEV,OAAO,CAAC,KAAK;IADzB,OAAO,CAAC,IAAI,CAA6B;IACzC,YAAoB,KAAK,SAAc,EAAI;IAC3C,8CAA8C;IAC9C,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAMzB;CACF;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,WAAW,GAAG,YAAY,CA4BxD"}
package/dist/core.js ADDED
@@ -0,0 +1,44 @@
1
+ import crypto from "node:crypto";
2
+ export class ReplayCache {
3
+ ttlMs;
4
+ seen = new Map();
5
+ constructor(ttlMs = 10 * 60_000) {
6
+ this.ttlMs = ttlMs;
7
+ }
8
+ /** returns true if the id was already seen */
9
+ check(id) {
10
+ const now = Date.now();
11
+ for (const [k, exp] of this.seen)
12
+ if (exp < now)
13
+ this.seen.delete(k);
14
+ if (this.seen.has(id))
15
+ return true;
16
+ this.seen.set(id, now + this.ttlMs);
17
+ return false;
18
+ }
19
+ }
20
+ export function verifyShunt(i) {
21
+ const ts = i.getHeader("x-shunt-timestamp");
22
+ const rid = i.getHeader("x-shunt-request-id");
23
+ const sig = (i.getHeader("x-shunt-signature") ?? "").replace(/^v1=/, "");
24
+ if (!ts || !rid || !sig)
25
+ return { ok: false, reason: "missing_headers" };
26
+ const skew = Math.abs(Date.now() / 1000 - Number(ts));
27
+ if (!Number.isFinite(skew) || skew > (i.maxSkewSec ?? 300))
28
+ return { ok: false, reason: "stale_timestamp" };
29
+ const bodyHash = crypto.createHash("sha256").update(i.rawBody).digest("hex");
30
+ const payload = `v1\n${ts}\n${rid}\n${i.method.toUpperCase()}\n${i.pathAndQuery}\n${bodyHash}`;
31
+ const secrets = Array.isArray(i.secret) ? i.secret : [i.secret];
32
+ const sigBuf = Buffer.from(sig, "utf8");
33
+ const valid = secrets.some((s) => {
34
+ const expected = Buffer.from(crypto.createHmac("sha256", s).update(payload).digest("hex"), "utf8");
35
+ return expected.length === sigBuf.length && crypto.timingSafeEqual(expected, sigBuf);
36
+ });
37
+ if (!valid)
38
+ return { ok: false, reason: "bad_signature" };
39
+ // only record the id AFTER the signature is valid, so attackers can't fill the cache
40
+ if (i.replayCache?.check(rid))
41
+ return { ok: false, reason: "replay" };
42
+ return { ok: true, requestId: rid };
43
+ }
44
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /** Pass to express.json({ verify: shuntRawBody }) so the raw bytes are kept. */
2
+ export declare const shuntRawBody: (req: any, _res: any, buf: Buffer) => void;
3
+ export declare function shuntExpress(secret: string | string[], opts?: {
4
+ maxSkewSec?: number;
5
+ replayProtection?: boolean;
6
+ }): (req: any, res: any, next: any) => any;
7
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,eAAO,MAAM,YAAY,QAAS,GAAG,QAAQ,GAAG,OAAO,MAAM,SAE5D,CAAC;AAEF,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EACzB,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,SAGjD,GAAG,OAAO,GAAG,QAAQ,GAAG,SAgBtC"}
@@ -0,0 +1,25 @@
1
+ import { verifyShunt, ReplayCache } from "./core.js";
2
+ /** Pass to express.json({ verify: shuntRawBody }) so the raw bytes are kept. */
3
+ export const shuntRawBody = (req, _res, buf) => {
4
+ req.rawBody = buf;
5
+ };
6
+ export function shuntExpress(secret, opts = {}) {
7
+ const replayCache = opts.replayProtection ? new ReplayCache() : undefined;
8
+ return (req, res, next) => {
9
+ const r = verifyShunt({
10
+ secret,
11
+ method: req.method,
12
+ pathAndQuery: req.originalUrl,
13
+ rawBody: req.rawBody ?? Buffer.alloc(0),
14
+ getHeader: (n) => req.get(n),
15
+ maxSkewSec: opts.maxSkewSec,
16
+ replayCache,
17
+ });
18
+ if (!r.ok) {
19
+ return res.status(401).json({ error: "Unauthorized: requests must come via Shunt" });
20
+ }
21
+ req.shunt = { requestId: r.requestId, agent: req.get("x-shunt-agent") };
22
+ next();
23
+ };
24
+ }
25
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAErD,gFAAgF;AAChF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAQ,EAAE,IAAS,EAAE,GAAW,EAAE,EAAE;IAC/D,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,UAAU,YAAY,CAC1B,MAAyB,EACzB,IAAI,GAAwD,EAAE;IAE9D,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1E,OAAO,CAAC,GAAQ,EAAE,GAAQ,EAAE,IAAS,EAAE,EAAE;QACvC,MAAM,CAAC,GAAG,WAAW,CAAC;YACpB,MAAM;YACN,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,YAAY,EAAE,GAAG,CAAC,WAAW;YAC7B,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACvC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5B,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,WAAW;SACZ,CAAC,CAAC;QACH,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACV,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,4CAA4C,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,GAAG,CAAC,KAAK,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;QACxE,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./core.js";
2
+ export * from "./express.js";
3
+ export * from "./web.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from "./core.js";
2
+ export * from "./express.js";
3
+ export * from "./web.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
package/dist/web.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { ReplayCache } from "./core.js";
2
+ export declare function verifyShuntRequest(req: Request, secret: string | string[], opts?: {
3
+ maxSkewSec?: number;
4
+ replayCache?: ReplayCache;
5
+ }): Promise<import("./core.js").VerifyResult>;
6
+ /** Next.js App Router: export const POST = withShunt(secret, async (req) => ...) */
7
+ export declare function withShunt<T extends (req: Request, ...a: any[]) => Promise<Response> | Response>(secret: string | string[], handler: T): T;
8
+ /** Hono: app.use("/paid/*", shuntHono(secret)) */
9
+ export declare const shuntHono: (secret: string | string[]) => (c: any, next: any) => Promise<any>;
10
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,WAAW,EAAE,MAAM,WAAW,CAAC;AAErD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EACzB,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,WAAW,CAAA;CAAO,6CAY9D;AAED,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,EAC7F,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,EACzB,OAAO,EAAE,CAAC,GACT,CAAC,CAQH;AAED,kDAAkD;AAClD,eAAO,MAAM,SAAS,WAAY,MAAM,GAAG,MAAM,EAAE,SAAe,GAAG,QAAQ,GAAG,iBAI/E,CAAC"}
package/dist/web.js ADDED
@@ -0,0 +1,31 @@
1
+ import { verifyShunt } from "./core.js";
2
+ export async function verifyShuntRequest(req, secret, opts = {}) {
3
+ const url = new URL(req.url);
4
+ const rawBody = new Uint8Array(await req.clone().arrayBuffer());
5
+ return verifyShunt({
6
+ secret,
7
+ method: req.method,
8
+ pathAndQuery: url.pathname + url.search,
9
+ rawBody,
10
+ getHeader: (n) => req.headers.get(n),
11
+ ...opts,
12
+ });
13
+ }
14
+ /** Next.js App Router: export const POST = withShunt(secret, async (req) => ...) */
15
+ export function withShunt(secret, handler) {
16
+ return (async (req, ...args) => {
17
+ const r = await verifyShuntRequest(req, secret);
18
+ if (!r.ok) {
19
+ return Response.json({ error: "Unauthorized: requests must come via Shunt" }, { status: 401 });
20
+ }
21
+ return handler(req, ...args);
22
+ });
23
+ }
24
+ /** Hono: app.use("/paid/*", shuntHono(secret)) */
25
+ export const shuntHono = (secret) => async (c, next) => {
26
+ const r = await verifyShuntRequest(c.req.raw, secret);
27
+ if (!r.ok)
28
+ return c.json({ error: "Unauthorized: requests must come via Shunt" }, 401);
29
+ await next();
30
+ };
31
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,WAAW,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,GAAY,EACZ,MAAyB,EACzB,IAAI,GAAuD,EAAE;IAE7D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,OAAO,WAAW,CAAC;QACjB,MAAM;QACN,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,YAAY,EAAE,GAAG,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM;QACvC,OAAO;QACP,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QACpC,GAAG,IAAI;KACR,CAAC,CAAC;AACL,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,SAAS,CACvB,MAAyB,EACzB,OAAU;IAEV,OAAO,CAAC,KAAK,EAAE,GAAY,EAAE,GAAG,IAAW,EAAE,EAAE;QAC7C,MAAM,CAAC,GAAG,MAAM,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACV,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,4CAA4C,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QACjG,CAAC;QACD,OAAO,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAM,CAAC;AACV,CAAC;AAED,kDAAkD;AAClD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAyB,EAAE,EAAE,CAAC,KAAK,EAAE,CAAM,EAAE,IAAS,EAAE,EAAE;IAClF,MAAM,CAAC,GAAG,MAAM,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACtD,IAAI,CAAC,CAAC,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,4CAA4C,EAAE,EAAE,GAAG,CAAC,CAAC;IACvF,MAAM,IAAI,EAAE,CAAC;AACf,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,21 @@
1
1
  {
2
2
  "name": "@shunt-network/verify",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "Aditya Patwa",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^26.6.5",
19
+ "typescript": "^7.0.2"
20
+ }
21
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.