@evumi/berth 0.0.0-stage → 0.0.3
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/README.md +140 -2
- package/dist/cjs/body.d.ts +9 -0
- package/dist/cjs/body.js +35 -0
- package/dist/cjs/client.d.ts +111 -0
- package/dist/cjs/client.js +165 -0
- package/dist/cjs/errors.d.ts +19 -0
- package/dist/cjs/errors.js +56 -0
- package/dist/cjs/handler.d.ts +83 -0
- package/dist/cjs/handler.js +168 -0
- package/dist/cjs/index.d.ts +4 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/sha256.d.ts +5 -0
- package/dist/cjs/sha256.js +98 -0
- package/dist/cjs/signature.d.ts +20 -0
- package/dist/cjs/signature.js +74 -0
- package/dist/cjs/util.d.ts +3 -0
- package/dist/cjs/util.js +27 -0
- package/dist/esm/body.d.ts +9 -0
- package/dist/esm/body.js +31 -0
- package/dist/esm/client.d.ts +111 -0
- package/dist/esm/client.js +159 -0
- package/dist/esm/errors.d.ts +19 -0
- package/dist/esm/errors.js +50 -0
- package/dist/esm/handler.d.ts +83 -0
- package/dist/esm/handler.js +164 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/sha256.d.ts +5 -0
- package/dist/esm/sha256.js +93 -0
- package/dist/esm/signature.d.ts +20 -0
- package/dist/esm/signature.js +70 -0
- package/dist/esm/util.d.ts +3 -0
- package/dist/esm/util.js +22 -0
- package/package.json +46 -4
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { hmacSha256, toHex } from './sha256.js';
|
|
2
|
+
const utf8 = new TextEncoder();
|
|
3
|
+
const bytes = (v) => (typeof v === 'string' ? utf8.encode(v) : v instanceof Uint8Array ? v : new Uint8Array(v));
|
|
4
|
+
const INT64_MAX = (1n << 63n) - 1n;
|
|
5
|
+
const INT64_MIN = -(1n << 63n);
|
|
6
|
+
/**
|
|
7
|
+
* Checks a Berth-Signature header (`t=<unix>,v1=<hex>`) against the raw request body and says
|
|
8
|
+
* why it fails, or null when it is valid. Synchronous; never throws. Several v1 values are
|
|
9
|
+
* accepted (one must match), so the secret can be rotated; unknown keys are ignored.
|
|
10
|
+
*/
|
|
11
|
+
export function checkSignature(secret, header, rawBody, opts = {}) {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof header !== 'string' || header.trim() === '')
|
|
14
|
+
return 'missing';
|
|
15
|
+
let t;
|
|
16
|
+
const sigs = [];
|
|
17
|
+
for (const part of header.split(',')) {
|
|
18
|
+
const p = part.trim();
|
|
19
|
+
const i = p.indexOf('=');
|
|
20
|
+
if (i < 0)
|
|
21
|
+
return 'malformed';
|
|
22
|
+
const k = p.slice(0, i);
|
|
23
|
+
const v = p.slice(i + 1);
|
|
24
|
+
if (k === 't') {
|
|
25
|
+
if (t !== undefined || !/^[+-]?[0-9]+$/.test(v))
|
|
26
|
+
return 'malformed';
|
|
27
|
+
t = BigInt(v);
|
|
28
|
+
if (t > INT64_MAX || t < INT64_MIN)
|
|
29
|
+
return 'malformed';
|
|
30
|
+
}
|
|
31
|
+
else if (k === 'v1') {
|
|
32
|
+
sigs.push(v);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (t === undefined || sigs.length === 0)
|
|
36
|
+
return 'malformed';
|
|
37
|
+
const nowOpt = opts.now ?? Date.now() / 1000;
|
|
38
|
+
const now = BigInt(Math.floor(nowOpt instanceof Date ? nowOpt.getTime() / 1000 : nowOpt));
|
|
39
|
+
const tolerance = BigInt(Math.floor(opts.toleranceSeconds ?? 300));
|
|
40
|
+
const d = now - t;
|
|
41
|
+
if ((d < 0n ? -d : d) > tolerance)
|
|
42
|
+
return 'expired';
|
|
43
|
+
const key = typeof secret === 'string' ? utf8.encode(secret) : secret;
|
|
44
|
+
const want = toHex(hmacSha256(key, utf8.encode(t.toString() + '.'), bytes(rawBody)));
|
|
45
|
+
let ok = false;
|
|
46
|
+
for (const s of sigs)
|
|
47
|
+
if (equal(s.toLowerCase(), want))
|
|
48
|
+
ok = true;
|
|
49
|
+
return ok ? null : 'mismatch';
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return 'malformed';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Constant time for equal lengths; the length of a hex HMAC is public. */
|
|
56
|
+
function equal(a, b) {
|
|
57
|
+
if (a.length !== b.length)
|
|
58
|
+
return false;
|
|
59
|
+
let diff = 0;
|
|
60
|
+
for (let i = 0; i < a.length; i++)
|
|
61
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
62
|
+
return diff === 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether a Berth-Signature header is valid for the raw request body. Verify the bytes as they
|
|
66
|
+
* arrived, before any JSON parsing. Synchronous; never throws.
|
|
67
|
+
*/
|
|
68
|
+
export function verifySignature(secret, header, rawBody, opts) {
|
|
69
|
+
return checkSignature(secret, header, rawBody, opts) === null;
|
|
70
|
+
}
|
package/dist/esm/util.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function env(name) {
|
|
2
|
+
const g = globalThis;
|
|
3
|
+
try {
|
|
4
|
+
return g.process?.env?.[name] ?? g.Deno?.env.get(name) ?? undefined;
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return undefined; // Deno without --allow-env
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function toBase64(b) {
|
|
11
|
+
let s = '';
|
|
12
|
+
for (let i = 0; i < b.length; i += 0x8000)
|
|
13
|
+
s += String.fromCharCode(...b.subarray(i, i + 0x8000));
|
|
14
|
+
return btoa(s);
|
|
15
|
+
}
|
|
16
|
+
export function fromBase64(s) {
|
|
17
|
+
const bin = atob(s);
|
|
18
|
+
const out = new Uint8Array(bin.length);
|
|
19
|
+
for (let i = 0; i < bin.length; i++)
|
|
20
|
+
out[i] = bin.charCodeAt(i);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evumi/berth",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Client for Berth project Queues and Alarms, and the verifier for signed push deliveries.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/evumi/berth.git",
|
|
9
|
+
"directory": "sdk/js-berth"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/cjs/index.js",
|
|
17
|
+
"module": "./dist/esm/index.js",
|
|
18
|
+
"types": "./dist/esm/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/esm/index.d.ts",
|
|
23
|
+
"default": "./dist/esm/index.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/cjs/index.d.ts",
|
|
27
|
+
"default": "./dist/cjs/index.js"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "rm -rf dist && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node scripts/cjs-package.mjs",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"test": "pnpm run build && vitest run"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^26.6.5",
|
|
42
|
+
"typescript": "6.0.3",
|
|
43
|
+
"vitest": "^5.0.5"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|