@opensyber/tokenforge 0.1.2 → 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/LICENSE.md +32 -0
- package/README.md +205 -47
- package/dist/adapters/astro.d.ts +55 -0
- package/dist/adapters/astro.d.ts.map +1 -0
- package/dist/adapters/astro.js +91 -0
- package/dist/adapters/astro.js.map +1 -0
- package/dist/adapters/express.d.ts +15 -0
- package/dist/adapters/express.d.ts.map +1 -1
- package/dist/adapters/express.js +27 -0
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/fastify.d.ts +17 -0
- package/dist/adapters/fastify.d.ts.map +1 -1
- package/dist/adapters/fastify.js +27 -0
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/hono.d.ts +1 -1
- package/dist/adapters/hono.d.ts.map +1 -1
- package/dist/adapters/hono.js +1 -1
- package/dist/adapters/hono.js.map +1 -1
- package/dist/adapters/index.d.ts +5 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +5 -3
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/nextjs.d.ts +16 -0
- package/dist/adapters/nextjs.d.ts.map +1 -1
- package/dist/adapters/nextjs.js +26 -0
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/sveltekit.d.ts +57 -0
- package/dist/adapters/sveltekit.d.ts.map +1 -0
- package/dist/adapters/sveltekit.js +88 -0
- package/dist/adapters/sveltekit.js.map +1 -0
- package/dist/client/action-signer.d.ts +53 -0
- package/dist/client/action-signer.d.ts.map +1 -0
- package/dist/client/action-signer.js +48 -0
- package/dist/client/action-signer.js.map +1 -0
- package/dist/client/binding.d.ts +24 -7
- package/dist/client/binding.d.ts.map +1 -1
- package/dist/client/binding.js +17 -6
- package/dist/client/binding.js.map +1 -1
- package/dist/client/index.d.ts +13 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +27 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/signer.d.ts +17 -0
- package/dist/client/signer.d.ts.map +1 -1
- package/dist/client/signer.js +23 -0
- package/dist/client/signer.js.map +1 -1
- package/dist/client/webauthn.d.ts +45 -0
- package/dist/client/webauthn.d.ts.map +1 -0
- package/dist/client/webauthn.js +118 -0
- package/dist/client/webauthn.js.map +1 -0
- package/dist/server/action-verify.d.ts +56 -0
- package/dist/server/action-verify.d.ts.map +1 -0
- package/dist/server/action-verify.js +47 -0
- package/dist/server/action-verify.js.map +1 -0
- package/dist/server/aitm-heuristics.d.ts +57 -0
- package/dist/server/aitm-heuristics.d.ts.map +1 -0
- package/dist/server/aitm-heuristics.js +120 -0
- package/dist/server/aitm-heuristics.js.map +1 -0
- package/dist/server/bound-cookie.d.ts +39 -0
- package/dist/server/bound-cookie.d.ts.map +1 -0
- package/dist/server/bound-cookie.js +59 -0
- package/dist/server/bound-cookie.js.map +1 -0
- package/dist/server/cbor.d.ts +20 -0
- package/dist/server/cbor.d.ts.map +1 -0
- package/dist/server/cbor.js +110 -0
- package/dist/server/cbor.js.map +1 -0
- package/dist/server/dbsc-challenge.d.ts +51 -0
- package/dist/server/dbsc-challenge.d.ts.map +1 -0
- package/dist/server/dbsc-challenge.js +78 -0
- package/dist/server/dbsc-challenge.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/jws-verify.d.ts +46 -0
- package/dist/server/jws-verify.d.ts.map +1 -0
- package/dist/server/jws-verify.js +64 -0
- package/dist/server/jws-verify.js.map +1 -0
- package/dist/server/middleware.d.ts +15 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +27 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/oidc-verify.d.ts +56 -0
- package/dist/server/oidc-verify.d.ts.map +1 -0
- package/dist/server/oidc-verify.js +107 -0
- package/dist/server/oidc-verify.js.map +1 -0
- package/dist/server/policy.d.ts +47 -0
- package/dist/server/policy.d.ts.map +1 -0
- package/dist/server/policy.js +84 -0
- package/dist/server/policy.js.map +1 -0
- package/dist/server/step-up-policy.d.ts +36 -0
- package/dist/server/step-up-policy.d.ts.map +1 -0
- package/dist/server/step-up-policy.js +101 -0
- package/dist/server/step-up-policy.js.map +1 -0
- package/dist/server/trust-score-fixtures.d.ts +9 -0
- package/dist/server/trust-score-fixtures.d.ts.map +1 -0
- package/dist/server/trust-score-fixtures.js +23 -0
- package/dist/server/trust-score-fixtures.js.map +1 -0
- package/dist/server/webauthn-verify.d.ts +41 -0
- package/dist/server/webauthn-verify.d.ts.map +1 -0
- package/dist/server/webauthn-verify.js +175 -0
- package/dist/server/webauthn-verify.js.map +1 -0
- package/dist/server/webauthn-verify.test-helpers.d.ts +8 -0
- package/dist/server/webauthn-verify.test-helpers.d.ts.map +1 -0
- package/dist/server/webauthn-verify.test-helpers.js +39 -0
- package/dist/server/webauthn-verify.test-helpers.js.map +1 -0
- package/dist/server/webhooks.d.ts +42 -0
- package/dist/server/webhooks.d.ts.map +1 -0
- package/dist/server/webhooks.js +77 -0
- package/dist/server/webhooks.js.map +1 -0
- package/dist/shared/action-hash.d.ts +23 -0
- package/dist/shared/action-hash.d.ts.map +1 -0
- package/dist/shared/action-hash.js +35 -0
- package/dist/shared/action-hash.js.map +1 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/package.json +41 -5
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short-lived bound-cookie issuer for DBSC.
|
|
3
|
+
*
|
|
4
|
+
* Cookie value = base64url(random 32 bytes). We never store the value
|
|
5
|
+
* itself — only its SHA-256 hash. On every refresh the cookie rotates,
|
|
6
|
+
* so a leaked cookie expires within `maxAgeSeconds` and cannot be
|
|
7
|
+
* extended without a fresh JWS-signed refresh.
|
|
8
|
+
*
|
|
9
|
+
* The TLS-exporter binding hook is exposed but optional: workerd does
|
|
10
|
+
* not yet expose RFC 9266 channel-binding material, so we fall through
|
|
11
|
+
* gracefully and emit a `Sec-TF-Channel-Bound: 0` warning header at the
|
|
12
|
+
* route layer when the runtime cannot bind.
|
|
13
|
+
*/
|
|
14
|
+
export interface BoundCookie {
|
|
15
|
+
/** Opaque value sent to the client. */
|
|
16
|
+
value: string;
|
|
17
|
+
/** SHA-256 hash of value — store this, never the value. */
|
|
18
|
+
hash: string;
|
|
19
|
+
/** Max-Age in seconds. */
|
|
20
|
+
maxAgeSeconds: number;
|
|
21
|
+
/** Issued-at ISO timestamp. */
|
|
22
|
+
issuedAt: string;
|
|
23
|
+
/** Expires-at ISO timestamp. */
|
|
24
|
+
expiresAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface IssueBoundCookieOptions {
|
|
27
|
+
maxAgeSeconds?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Optional RFC 9266 TLS exporter material. If present, the cookie
|
|
30
|
+
* hash mixes the exporter so a session can't be replayed across
|
|
31
|
+
* a different TLS connection.
|
|
32
|
+
*/
|
|
33
|
+
tlsExporterHex?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function issueBoundCookie(opts?: IssueBoundCookieOptions): Promise<BoundCookie>;
|
|
36
|
+
export declare function hashBoundCookie(value: string, tlsExporterHex?: string): Promise<string>;
|
|
37
|
+
export declare function setBoundCookieHeader(cookie: BoundCookie): string;
|
|
38
|
+
export declare const BOUND_COOKIE_NAME = "__Secure-tf-bound";
|
|
39
|
+
//# sourceMappingURL=bound-cookie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-cookie.d.ts","sourceRoot":"","sources":["../../src/server/bound-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,GAAE,uBAA4B,GACjC,OAAO,CAAC,WAAW,CAAC,CAetB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE7F;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAShE;AAED,eAAO,MAAM,iBAAiB,sBAAc,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short-lived bound-cookie issuer for DBSC.
|
|
3
|
+
*
|
|
4
|
+
* Cookie value = base64url(random 32 bytes). We never store the value
|
|
5
|
+
* itself — only its SHA-256 hash. On every refresh the cookie rotates,
|
|
6
|
+
* so a leaked cookie expires within `maxAgeSeconds` and cannot be
|
|
7
|
+
* extended without a fresh JWS-signed refresh.
|
|
8
|
+
*
|
|
9
|
+
* The TLS-exporter binding hook is exposed but optional: workerd does
|
|
10
|
+
* not yet expose RFC 9266 channel-binding material, so we fall through
|
|
11
|
+
* gracefully and emit a `Sec-TF-Channel-Bound: 0` warning header at the
|
|
12
|
+
* route layer when the runtime cannot bind.
|
|
13
|
+
*/
|
|
14
|
+
const COOKIE_BYTES = 32;
|
|
15
|
+
const DEFAULT_MAX_AGE_SECONDS = 300;
|
|
16
|
+
const COOKIE_NAME = '__Secure-tf-bound';
|
|
17
|
+
export async function issueBoundCookie(opts = {}) {
|
|
18
|
+
const bytes = new Uint8Array(COOKIE_BYTES);
|
|
19
|
+
crypto.getRandomValues(bytes);
|
|
20
|
+
const value = bytesToBase64Url(bytes);
|
|
21
|
+
const hash = await hashWithExporter(value, opts.tlsExporterHex);
|
|
22
|
+
const maxAge = opts.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
|
|
23
|
+
const issued = new Date();
|
|
24
|
+
const expires = new Date(issued.getTime() + maxAge * 1000);
|
|
25
|
+
return {
|
|
26
|
+
value,
|
|
27
|
+
hash,
|
|
28
|
+
maxAgeSeconds: maxAge,
|
|
29
|
+
issuedAt: issued.toISOString(),
|
|
30
|
+
expiresAt: expires.toISOString(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export async function hashBoundCookie(value, tlsExporterHex) {
|
|
34
|
+
return hashWithExporter(value, tlsExporterHex);
|
|
35
|
+
}
|
|
36
|
+
export function setBoundCookieHeader(cookie) {
|
|
37
|
+
return [
|
|
38
|
+
`${COOKIE_NAME}=${cookie.value}`,
|
|
39
|
+
`Max-Age=${cookie.maxAgeSeconds}`,
|
|
40
|
+
'Path=/',
|
|
41
|
+
'HttpOnly',
|
|
42
|
+
'Secure',
|
|
43
|
+
'SameSite=Strict',
|
|
44
|
+
].join('; ');
|
|
45
|
+
}
|
|
46
|
+
export const BOUND_COOKIE_NAME = COOKIE_NAME;
|
|
47
|
+
async function hashWithExporter(value, exporterHex) {
|
|
48
|
+
const enc = new TextEncoder();
|
|
49
|
+
const input = exporterHex ? `${value}|${exporterHex}` : value;
|
|
50
|
+
const buf = await crypto.subtle.digest('SHA-256', enc.encode(input));
|
|
51
|
+
return bytesToBase64Url(new Uint8Array(buf));
|
|
52
|
+
}
|
|
53
|
+
function bytesToBase64Url(bytes) {
|
|
54
|
+
let bin = '';
|
|
55
|
+
for (let i = 0; i < bytes.length; i++)
|
|
56
|
+
bin += String.fromCharCode(bytes[i]);
|
|
57
|
+
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=bound-cookie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-cookie.js","sourceRoot":"","sources":["../../src/server/bound-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,uBAAuB,GAAG,GAAG,CAAC;AACpC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAyBxC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgC,EAAE;IAElC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,IAAI,uBAAuB,CAAC;IAC7D,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,OAAO;QACL,KAAK;QACL,IAAI;QACJ,aAAa,EAAE,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE;QAC9B,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE;KACjC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAa,EAAE,cAAuB;IAC1E,OAAO,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAmB;IACtD,OAAO;QACL,GAAG,WAAW,IAAI,MAAM,CAAC,KAAK,EAAE;QAChC,WAAW,MAAM,CAAC,aAAa,EAAE;QACjC,QAAQ;QACR,UAAU;QACV,QAAQ;QACR,iBAAiB;KAClB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAE7C,KAAK,UAAU,gBAAgB,CAAC,KAAa,EAAE,WAA+B;IAC5E,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,OAAO,gBAAgB,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAiB;IACzC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CBOR decoder — supports just enough of RFC 8949 to parse
|
|
3
|
+
* WebAuthn `attestationObject` and embedded COSE_Key structures.
|
|
4
|
+
*
|
|
5
|
+
* Supported major types:
|
|
6
|
+
* 0 (uint), 1 (negative int), 2 (byte string), 3 (text string),
|
|
7
|
+
* 4 (array), 5 (map).
|
|
8
|
+
* Unsupported: tags, floats, indefinite-length, simple values other than
|
|
9
|
+
* true/false/null.
|
|
10
|
+
*
|
|
11
|
+
* This is intentionally small (no npm dep) because the only inputs we ever
|
|
12
|
+
* decode are WebAuthn attestation blobs with a known structure. If you feed
|
|
13
|
+
* arbitrary CBOR you may hit `unsupported` errors — that is by design.
|
|
14
|
+
*/
|
|
15
|
+
export type CborValue = number | bigint | string | Uint8Array | boolean | null | CborValue[] | {
|
|
16
|
+
[key: string]: CborValue;
|
|
17
|
+
} | Map<CborValue, CborValue>;
|
|
18
|
+
/** Decode a CBOR-encoded buffer. Throws on unsupported encodings. */
|
|
19
|
+
export declare function decodeCbor(input: ArrayBuffer | Uint8Array): CborValue;
|
|
20
|
+
//# sourceMappingURL=cbor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cbor.d.ts","sourceRoot":"","sources":["../../src/server/cbor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,MAAM,GACN,UAAU,GACV,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC5B,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AAI9B,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS,CAKrE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal CBOR decoder — supports just enough of RFC 8949 to parse
|
|
3
|
+
* WebAuthn `attestationObject` and embedded COSE_Key structures.
|
|
4
|
+
*
|
|
5
|
+
* Supported major types:
|
|
6
|
+
* 0 (uint), 1 (negative int), 2 (byte string), 3 (text string),
|
|
7
|
+
* 4 (array), 5 (map).
|
|
8
|
+
* Unsupported: tags, floats, indefinite-length, simple values other than
|
|
9
|
+
* true/false/null.
|
|
10
|
+
*
|
|
11
|
+
* This is intentionally small (no npm dep) because the only inputs we ever
|
|
12
|
+
* decode are WebAuthn attestation blobs with a known structure. If you feed
|
|
13
|
+
* arbitrary CBOR you may hit `unsupported` errors — that is by design.
|
|
14
|
+
*/
|
|
15
|
+
/** Decode a CBOR-encoded buffer. Throws on unsupported encodings. */
|
|
16
|
+
export function decodeCbor(input) {
|
|
17
|
+
const buf = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
18
|
+
const state = { buf, pos: 0 };
|
|
19
|
+
const value = decodeItem(state);
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function byteAt(s, offset) {
|
|
23
|
+
const v = s.buf[offset];
|
|
24
|
+
if (v === undefined)
|
|
25
|
+
throw new Error('cbor: unexpected end of input');
|
|
26
|
+
return v;
|
|
27
|
+
}
|
|
28
|
+
function decodeItem(s) {
|
|
29
|
+
if (s.pos >= s.buf.length)
|
|
30
|
+
throw new Error('cbor: unexpected end of input');
|
|
31
|
+
const initial = byteAt(s, s.pos);
|
|
32
|
+
s.pos++;
|
|
33
|
+
const major = initial >> 5;
|
|
34
|
+
const info = initial & 0x1f;
|
|
35
|
+
const len = readLength(s, info);
|
|
36
|
+
switch (major) {
|
|
37
|
+
case 0: return numberify(len); // unsigned int
|
|
38
|
+
case 1: return numberify(-1n - BigInt(len)); // negative int
|
|
39
|
+
case 2: return readBytes(s, Number(len)); // byte string
|
|
40
|
+
case 3: return new TextDecoder().decode(readBytes(s, Number(len)));
|
|
41
|
+
case 4: return readArray(s, Number(len));
|
|
42
|
+
case 5: return readMap(s, Number(len));
|
|
43
|
+
case 7: return readSimple(initial);
|
|
44
|
+
default: throw new Error(`cbor: major type ${major} unsupported`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function readLength(s, info) {
|
|
48
|
+
if (info < 24)
|
|
49
|
+
return BigInt(info);
|
|
50
|
+
if (info === 24) {
|
|
51
|
+
const v = byteAt(s, s.pos);
|
|
52
|
+
s.pos++;
|
|
53
|
+
return BigInt(v);
|
|
54
|
+
}
|
|
55
|
+
if (info === 25) {
|
|
56
|
+
const v = (byteAt(s, s.pos) << 8) | byteAt(s, s.pos + 1);
|
|
57
|
+
s.pos += 2;
|
|
58
|
+
return BigInt(v);
|
|
59
|
+
}
|
|
60
|
+
if (info === 26) {
|
|
61
|
+
let v = 0;
|
|
62
|
+
for (let i = 0; i < 4; i++)
|
|
63
|
+
v = (v * 256) + byteAt(s, s.pos + i);
|
|
64
|
+
s.pos += 4;
|
|
65
|
+
return BigInt(v);
|
|
66
|
+
}
|
|
67
|
+
if (info === 27) {
|
|
68
|
+
let v = 0n;
|
|
69
|
+
for (let i = 0; i < 8; i++)
|
|
70
|
+
v = (v << 8n) | BigInt(byteAt(s, s.pos + i));
|
|
71
|
+
s.pos += 8;
|
|
72
|
+
return v;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`cbor: length info ${info} unsupported (indefinite-length not implemented)`);
|
|
75
|
+
}
|
|
76
|
+
function numberify(n) {
|
|
77
|
+
return n >= -9007199254740991n && n <= 9007199254740991n ? Number(n) : n;
|
|
78
|
+
}
|
|
79
|
+
function readBytes(s, n) {
|
|
80
|
+
const out = s.buf.slice(s.pos, s.pos + n);
|
|
81
|
+
s.pos += n;
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function readArray(s, n) {
|
|
85
|
+
const arr = [];
|
|
86
|
+
for (let i = 0; i < n; i++)
|
|
87
|
+
arr.push(decodeItem(s));
|
|
88
|
+
return arr;
|
|
89
|
+
}
|
|
90
|
+
function readMap(s, n) {
|
|
91
|
+
const m = new Map();
|
|
92
|
+
for (let i = 0; i < n; i++) {
|
|
93
|
+
const k = decodeItem(s);
|
|
94
|
+
const v = decodeItem(s);
|
|
95
|
+
m.set(k, v);
|
|
96
|
+
}
|
|
97
|
+
return m;
|
|
98
|
+
}
|
|
99
|
+
function readSimple(initial) {
|
|
100
|
+
if (initial === 0xf4)
|
|
101
|
+
return false;
|
|
102
|
+
if (initial === 0xf5)
|
|
103
|
+
return true;
|
|
104
|
+
if (initial === 0xf6)
|
|
105
|
+
return null;
|
|
106
|
+
if (initial === 0xf7)
|
|
107
|
+
return null; // undefined → null
|
|
108
|
+
throw new Error(`cbor: simple value 0x${initial.toString(16)} unsupported`);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=cbor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cbor.js","sourceRoot":"","sources":["../../src/server/cbor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DBSC challenge issuer — emits the random bytes the client must sign,
|
|
3
|
+
* stores their SHA-256 hash + a 60-second expiry, and consumes them
|
|
4
|
+
* exactly once.
|
|
5
|
+
*
|
|
6
|
+
* The challenge bytes never live at rest — we store only the hash so a
|
|
7
|
+
* DB read does not let an attacker pre-compute valid signatures.
|
|
8
|
+
*
|
|
9
|
+
* Pure logic; persistence is injected so this works on any storage.
|
|
10
|
+
*/
|
|
11
|
+
export interface ChallengeStore {
|
|
12
|
+
put(record: ChallengeRecord): Promise<void>;
|
|
13
|
+
takeIfFresh(hash: string, now: Date): Promise<ChallengeRecord | null>;
|
|
14
|
+
}
|
|
15
|
+
export interface ChallengeRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
tenantId: string;
|
|
18
|
+
challengeHash: string;
|
|
19
|
+
purpose: 'register' | 'refresh' | 'step_up';
|
|
20
|
+
sessionId?: string | null;
|
|
21
|
+
actionHash?: string | null;
|
|
22
|
+
issuedAt: string;
|
|
23
|
+
expiresAt: string;
|
|
24
|
+
consumed: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface IssueChallengeInput {
|
|
27
|
+
tenantId: string;
|
|
28
|
+
purpose: ChallengeRecord['purpose'];
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
actionHash?: string;
|
|
31
|
+
ttlSeconds?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface IssueChallengeOutput {
|
|
34
|
+
/** base64url-encoded random bytes — send to the client. */
|
|
35
|
+
challenge: string;
|
|
36
|
+
/** Stored record, returned for callers who need the id/expiry. */
|
|
37
|
+
record: ChallengeRecord;
|
|
38
|
+
}
|
|
39
|
+
export declare function issueChallenge(store: ChallengeStore, input: IssueChallengeInput): Promise<IssueChallengeOutput>;
|
|
40
|
+
export declare function consumeChallenge(store: ChallengeStore, challenge: string, expected: {
|
|
41
|
+
tenantId: string;
|
|
42
|
+
purpose: ChallengeRecord['purpose'];
|
|
43
|
+
sessionId?: string;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
ok: true;
|
|
46
|
+
record: ChallengeRecord;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
reason: string;
|
|
50
|
+
}>;
|
|
51
|
+
//# sourceMappingURL=dbsc-challenge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dbsc-challenge.d.ts","sourceRoot":"","sources":["../../src/server/dbsc-challenge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,wBAAsB,cAAc,CAClC,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAqB/B;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACtF,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAahF"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DBSC challenge issuer — emits the random bytes the client must sign,
|
|
3
|
+
* stores their SHA-256 hash + a 60-second expiry, and consumes them
|
|
4
|
+
* exactly once.
|
|
5
|
+
*
|
|
6
|
+
* The challenge bytes never live at rest — we store only the hash so a
|
|
7
|
+
* DB read does not let an attacker pre-compute valid signatures.
|
|
8
|
+
*
|
|
9
|
+
* Pure logic; persistence is injected so this works on any storage.
|
|
10
|
+
*/
|
|
11
|
+
const CHALLENGE_BYTES = 32;
|
|
12
|
+
const DEFAULT_TTL_SECONDS = 60;
|
|
13
|
+
export async function issueChallenge(store, input) {
|
|
14
|
+
const bytes = new Uint8Array(CHALLENGE_BYTES);
|
|
15
|
+
crypto.getRandomValues(bytes);
|
|
16
|
+
const challenge = bytesToBase64Url(bytes);
|
|
17
|
+
const challengeHash = await sha256Base64Url(bytes);
|
|
18
|
+
const ttl = input.ttlSeconds ?? DEFAULT_TTL_SECONDS;
|
|
19
|
+
const issued = new Date();
|
|
20
|
+
const expires = new Date(issued.getTime() + ttl * 1000);
|
|
21
|
+
const record = {
|
|
22
|
+
id: crypto.randomUUID(),
|
|
23
|
+
tenantId: input.tenantId,
|
|
24
|
+
challengeHash,
|
|
25
|
+
purpose: input.purpose,
|
|
26
|
+
sessionId: input.sessionId ?? null,
|
|
27
|
+
actionHash: input.actionHash ?? null,
|
|
28
|
+
issuedAt: issued.toISOString(),
|
|
29
|
+
expiresAt: expires.toISOString(),
|
|
30
|
+
consumed: false,
|
|
31
|
+
};
|
|
32
|
+
await store.put(record);
|
|
33
|
+
return { challenge, record };
|
|
34
|
+
}
|
|
35
|
+
export async function consumeChallenge(store, challenge, expected) {
|
|
36
|
+
const bytes = base64UrlToBytes(challenge);
|
|
37
|
+
if (!bytes)
|
|
38
|
+
return { ok: false, reason: 'malformed_challenge' };
|
|
39
|
+
const hash = await sha256Base64Url(bytes);
|
|
40
|
+
const found = await store.takeIfFresh(hash, new Date());
|
|
41
|
+
if (!found)
|
|
42
|
+
return { ok: false, reason: 'challenge_unknown_or_expired' };
|
|
43
|
+
if (found.consumed)
|
|
44
|
+
return { ok: false, reason: 'challenge_replay' };
|
|
45
|
+
if (found.tenantId !== expected.tenantId)
|
|
46
|
+
return { ok: false, reason: 'challenge_tenant_mismatch' };
|
|
47
|
+
if (found.purpose !== expected.purpose)
|
|
48
|
+
return { ok: false, reason: 'challenge_purpose_mismatch' };
|
|
49
|
+
if (expected.sessionId && found.sessionId !== expected.sessionId) {
|
|
50
|
+
return { ok: false, reason: 'challenge_session_mismatch' };
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, record: found };
|
|
53
|
+
}
|
|
54
|
+
async function sha256Base64Url(bytes) {
|
|
55
|
+
const buf = await crypto.subtle.digest('SHA-256', bytes);
|
|
56
|
+
return bytesToBase64Url(new Uint8Array(buf));
|
|
57
|
+
}
|
|
58
|
+
function bytesToBase64Url(bytes) {
|
|
59
|
+
let bin = '';
|
|
60
|
+
for (let i = 0; i < bytes.length; i++)
|
|
61
|
+
bin += String.fromCharCode(bytes[i]);
|
|
62
|
+
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
63
|
+
}
|
|
64
|
+
function base64UrlToBytes(b64) {
|
|
65
|
+
const s = b64.replace(/-/g, '+').replace(/_/g, '/');
|
|
66
|
+
const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
|
|
67
|
+
try {
|
|
68
|
+
const bin = atob(padded);
|
|
69
|
+
const out = new Uint8Array(bin.length);
|
|
70
|
+
for (let i = 0; i < bin.length; i++)
|
|
71
|
+
out[i] = bin.charCodeAt(i);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=dbsc-challenge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dbsc-challenge.js","sourceRoot":"","sources":["../../src/server/dbsc-challenge.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { tokenForgeMiddleware } from './middleware.js';
|
|
2
2
|
export type { TokenForgeOptions } from './middleware.js';
|
|
3
|
+
export { verifyWebAuthnAttestation, verifyWebAuthnAssertion, verifyClientDataJSON, } from './webauthn-verify.js';
|
|
4
|
+
export type { AttestationVerifyResult } from './webauthn-verify.js';
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
// Self-hosted routes (binding, step-up) and internal crypto utilities (trust-score,
|
|
4
4
|
// verify, crypto) are kept internal and excluded via package.json `files`.
|
|
5
5
|
export { tokenForgeMiddleware } from './middleware.js';
|
|
6
|
+
// FIDO2/WebAuthn server-side verification (Sprint E1).
|
|
7
|
+
export { verifyWebAuthnAttestation, verifyWebAuthnAssertion, verifyClientDataJSON, } from './webauthn-verify.js';
|
|
6
8
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,+EAA+E;AAC/E,oFAAoF;AACpF,2EAA2E;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,+EAA+E;AAC/E,oFAAoF;AACpF,2EAA2E;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,uDAAuD;AACvD,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact JWS verifier for DBSC refresh + action signing.
|
|
3
|
+
*
|
|
4
|
+
* Accepts compact-form JWS strings: `<header>.<payload>.<signature>` —
|
|
5
|
+
* all three parts base64url-encoded. Validates the protected header
|
|
6
|
+
* (alg, typ), the payload claims (iat, exp, nonce, sub), and the
|
|
7
|
+
* signature against the bound public key.
|
|
8
|
+
*
|
|
9
|
+
* Public key may be JWK JSON or PEM-SPKI; the existing
|
|
10
|
+
* `importPublicKey` in ./crypto.ts handles both, so this file stays
|
|
11
|
+
* focused on JWS framing.
|
|
12
|
+
*/
|
|
13
|
+
export interface JwsClaims {
|
|
14
|
+
/** Subject — DBSC sessionId or device id. */
|
|
15
|
+
sub: string;
|
|
16
|
+
/** Issued-at, epoch seconds. */
|
|
17
|
+
iat: number;
|
|
18
|
+
/** Expiry, epoch seconds. */
|
|
19
|
+
exp: number;
|
|
20
|
+
/** Per-message nonce — checked against challenge store. */
|
|
21
|
+
nonce: string;
|
|
22
|
+
/** Optional action descriptor (per-action step-up). */
|
|
23
|
+
action?: string;
|
|
24
|
+
/** Optional SHA-256 over the action body, base64url. */
|
|
25
|
+
actionHash?: string;
|
|
26
|
+
/** Optional TLS exporter binding, hex. */
|
|
27
|
+
tlsExporter?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface VerifyJwsOptions {
|
|
30
|
+
/** Bound public key string (JWK or PEM SPKI). */
|
|
31
|
+
publicKey: string;
|
|
32
|
+
/** Maximum age (seconds) of the JWS — defaults to 60. */
|
|
33
|
+
maxAgeSeconds?: number;
|
|
34
|
+
/** Override clock for tests. */
|
|
35
|
+
now?: Date;
|
|
36
|
+
}
|
|
37
|
+
export type VerifyJwsResult = {
|
|
38
|
+
ok: true;
|
|
39
|
+
claims: JwsClaims;
|
|
40
|
+
protectedHeader: Record<string, unknown>;
|
|
41
|
+
} | {
|
|
42
|
+
ok: false;
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
45
|
+
export declare function verifyCompactJws(jws: string, opts: VerifyJwsOptions): Promise<VerifyJwsResult>;
|
|
46
|
+
//# sourceMappingURL=jws-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jws-verify.d.ts","sourceRoot":"","sources":["../../src/server/jws-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,SAAS;IACxB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gCAAgC;IAChC,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACzE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAIlC,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,eAAe,CAAC,CA0C1B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact JWS verifier for DBSC refresh + action signing.
|
|
3
|
+
*
|
|
4
|
+
* Accepts compact-form JWS strings: `<header>.<payload>.<signature>` —
|
|
5
|
+
* all three parts base64url-encoded. Validates the protected header
|
|
6
|
+
* (alg, typ), the payload claims (iat, exp, nonce, sub), and the
|
|
7
|
+
* signature against the bound public key.
|
|
8
|
+
*
|
|
9
|
+
* Public key may be JWK JSON or PEM-SPKI; the existing
|
|
10
|
+
* `importPublicKey` in ./crypto.ts handles both, so this file stays
|
|
11
|
+
* focused on JWS framing.
|
|
12
|
+
*/
|
|
13
|
+
import { importPublicKey, verifySignature } from './crypto.js';
|
|
14
|
+
const SUPPORTED_ALGS = new Set(['ES256']);
|
|
15
|
+
export async function verifyCompactJws(jws, opts) {
|
|
16
|
+
const parts = jws.split('.');
|
|
17
|
+
if (parts.length !== 3)
|
|
18
|
+
return { ok: false, reason: 'jws_malformed' };
|
|
19
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
20
|
+
let header;
|
|
21
|
+
let payload;
|
|
22
|
+
try {
|
|
23
|
+
header = JSON.parse(b64UrlDecodeString(headerB64));
|
|
24
|
+
payload = JSON.parse(b64UrlDecodeString(payloadB64));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { ok: false, reason: 'jws_malformed' };
|
|
28
|
+
}
|
|
29
|
+
const alg = header.alg;
|
|
30
|
+
if (typeof alg !== 'string' || !SUPPORTED_ALGS.has(alg)) {
|
|
31
|
+
return { ok: false, reason: 'jws_unsupported_alg' };
|
|
32
|
+
}
|
|
33
|
+
const claims = payload;
|
|
34
|
+
if (!claims.sub || !claims.nonce || typeof claims.iat !== 'number' || typeof claims.exp !== 'number') {
|
|
35
|
+
return { ok: false, reason: 'jws_missing_claims' };
|
|
36
|
+
}
|
|
37
|
+
const now = opts.now ?? new Date();
|
|
38
|
+
const nowSec = Math.floor(now.getTime() / 1000);
|
|
39
|
+
const maxAge = opts.maxAgeSeconds ?? 60;
|
|
40
|
+
if (claims.exp < nowSec)
|
|
41
|
+
return { ok: false, reason: 'jws_expired' };
|
|
42
|
+
if (claims.iat > nowSec + 5)
|
|
43
|
+
return { ok: false, reason: 'jws_future_iat' };
|
|
44
|
+
if (nowSec - claims.iat > maxAge)
|
|
45
|
+
return { ok: false, reason: 'jws_too_old' };
|
|
46
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
47
|
+
let key;
|
|
48
|
+
try {
|
|
49
|
+
key = await importPublicKey(opts.publicKey);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { ok: false, reason: 'jws_bad_public_key' };
|
|
53
|
+
}
|
|
54
|
+
const ok = await verifySignature(key, sigB64, signingInput);
|
|
55
|
+
if (!ok)
|
|
56
|
+
return { ok: false, reason: 'jws_bad_signature' };
|
|
57
|
+
return { ok: true, claims: claims, protectedHeader: header };
|
|
58
|
+
}
|
|
59
|
+
function b64UrlDecodeString(b64) {
|
|
60
|
+
const s = b64.replace(/-/g, '+').replace(/_/g, '/');
|
|
61
|
+
const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
|
|
62
|
+
return atob(padded);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=jws-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jws-verify.js","sourceRoot":"","sources":["../../src/server/jws-verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,6 +23,21 @@ export interface TokenForgeOptions {
|
|
|
23
23
|
* @returns Hono middleware handler.
|
|
24
24
|
*/
|
|
25
25
|
export declare function tokenForgeMiddleware(options: TokenForgeOptions): MiddlewareHandler;
|
|
26
|
+
/**
|
|
27
|
+
* Per-route step-up gate. Use after `tokenForgeMiddleware` to require a
|
|
28
|
+
* higher trust score on sensitive routes than the global allow threshold.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* app.use(tokenForgeMiddleware({ apiKey }));
|
|
32
|
+
* app.use('/admin/*', requireFreshSig({ minTrustScore: 90 }));
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Responds with 403 `elevated_trust_required` when the cached `tf` context
|
|
36
|
+
* is missing or below the threshold.
|
|
37
|
+
*/
|
|
38
|
+
export declare function requireFreshSig(opts?: {
|
|
39
|
+
minTrustScore?: number;
|
|
40
|
+
}): MiddlewareHandler;
|
|
26
41
|
/** @internal */
|
|
27
42
|
export declare function shouldSkip(path: string, skipPaths?: string[]): boolean;
|
|
28
43
|
/** @internal */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAI7D,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAcD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,GACzB,iBAAiB,CAgFnB;AAED,gBAAgB;AAChB,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAMtE;AAED,gBAAgB;AAChB,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,OAAO,CAaT"}
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAI7D,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAcD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,GACzB,iBAAiB,CAgFnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,iBAAiB,CAcxF;AAED,gBAAgB;AAChB,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAMtE;AAED,gBAAgB;AAChB,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,OAAO,CAaT"}
|
|
@@ -77,6 +77,33 @@ export function tokenForgeMiddleware(options) {
|
|
|
77
77
|
await next();
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Per-route step-up gate. Use after `tokenForgeMiddleware` to require a
|
|
82
|
+
* higher trust score on sensitive routes than the global allow threshold.
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* app.use(tokenForgeMiddleware({ apiKey }));
|
|
86
|
+
* app.use('/admin/*', requireFreshSig({ minTrustScore: 90 }));
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* Responds with 403 `elevated_trust_required` when the cached `tf` context
|
|
90
|
+
* is missing or below the threshold.
|
|
91
|
+
*/
|
|
92
|
+
export function requireFreshSig(opts = {}) {
|
|
93
|
+
const min = opts.minTrustScore ?? 90;
|
|
94
|
+
return async (c, next) => {
|
|
95
|
+
const tf = c.get('tf');
|
|
96
|
+
const score = tf?.trustScore ?? 0;
|
|
97
|
+
if (score < min) {
|
|
98
|
+
return c.json({
|
|
99
|
+
error: 'elevated_trust_required',
|
|
100
|
+
action: 'step_up_required',
|
|
101
|
+
trustScore: score,
|
|
102
|
+
}, 403);
|
|
103
|
+
}
|
|
104
|
+
await next();
|
|
105
|
+
};
|
|
106
|
+
}
|
|
80
107
|
/** @internal */
|
|
81
108
|
export function shouldSkip(path, skipPaths) {
|
|
82
109
|
if (!skipPaths)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"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"}
|