@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,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC ID-token verifier (Sprint 40 — workforce mode).
|
|
3
|
+
*
|
|
4
|
+
* Verifies an OIDC ID token against an IdP's JWKS by `kid`. Supports
|
|
5
|
+
* RS256 + ES256 — the algorithms that Okta, Entra, Google Workspace,
|
|
6
|
+
* and Auth0 ship by default. Stateless: the JWKS is passed in, so
|
|
7
|
+
* caching/HTTP lives one layer up (see jwks-cache.ts).
|
|
8
|
+
*
|
|
9
|
+
* Validates signature, `iss`, `aud`, `exp`, `nbf`, `iat`. Returns the
|
|
10
|
+
* verified claims so the caller can extract `sub` and link it to a
|
|
11
|
+
* subject row before issuing a DBSC challenge.
|
|
12
|
+
*/
|
|
13
|
+
export interface OidcClaims {
|
|
14
|
+
iss: string;
|
|
15
|
+
sub: string;
|
|
16
|
+
aud: string | string[];
|
|
17
|
+
exp: number;
|
|
18
|
+
iat: number;
|
|
19
|
+
nbf?: number;
|
|
20
|
+
email?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
groups?: string[];
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
export interface JwksKey {
|
|
26
|
+
kid: string;
|
|
27
|
+
kty: 'RSA' | 'EC';
|
|
28
|
+
alg?: string;
|
|
29
|
+
use?: string;
|
|
30
|
+
n?: string;
|
|
31
|
+
e?: string;
|
|
32
|
+
crv?: string;
|
|
33
|
+
x?: string;
|
|
34
|
+
y?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface VerifyOidcOptions {
|
|
37
|
+
expectedIssuer: string;
|
|
38
|
+
expectedAudience: string;
|
|
39
|
+
jwks: {
|
|
40
|
+
keys: JwksKey[];
|
|
41
|
+
};
|
|
42
|
+
/** Override for tests. */
|
|
43
|
+
now?: Date;
|
|
44
|
+
/** Clock skew tolerance in seconds (default 60). */
|
|
45
|
+
clockSkewSeconds?: number;
|
|
46
|
+
}
|
|
47
|
+
export type VerifyOidcResult = {
|
|
48
|
+
ok: true;
|
|
49
|
+
claims: OidcClaims;
|
|
50
|
+
protectedHeader: Record<string, unknown>;
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: string;
|
|
54
|
+
};
|
|
55
|
+
export declare function verifyOidcIdToken(jwt: string, opts: VerifyOidcOptions): Promise<VerifyOidcResult>;
|
|
56
|
+
//# sourceMappingURL=oidc-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-verify.d.ts","sourceRoot":"","sources":["../../src/server/oidc-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC;IAC1B,0BAA0B;IAC1B,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC1E;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAIlC,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAyD3B"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC ID-token verifier (Sprint 40 — workforce mode).
|
|
3
|
+
*
|
|
4
|
+
* Verifies an OIDC ID token against an IdP's JWKS by `kid`. Supports
|
|
5
|
+
* RS256 + ES256 — the algorithms that Okta, Entra, Google Workspace,
|
|
6
|
+
* and Auth0 ship by default. Stateless: the JWKS is passed in, so
|
|
7
|
+
* caching/HTTP lives one layer up (see jwks-cache.ts).
|
|
8
|
+
*
|
|
9
|
+
* Validates signature, `iss`, `aud`, `exp`, `nbf`, `iat`. Returns the
|
|
10
|
+
* verified claims so the caller can extract `sub` and link it to a
|
|
11
|
+
* subject row before issuing a DBSC challenge.
|
|
12
|
+
*/
|
|
13
|
+
const SUPPORTED_ALGS = new Set(['RS256', 'ES256']);
|
|
14
|
+
export async function verifyOidcIdToken(jwt, opts) {
|
|
15
|
+
const parts = jwt.split('.');
|
|
16
|
+
if (parts.length !== 3)
|
|
17
|
+
return { ok: false, reason: 'jwt_malformed' };
|
|
18
|
+
const [headerB64, payloadB64, sigB64] = parts;
|
|
19
|
+
let header;
|
|
20
|
+
let claims;
|
|
21
|
+
try {
|
|
22
|
+
header = JSON.parse(b64UrlDecodeString(headerB64));
|
|
23
|
+
claims = JSON.parse(b64UrlDecodeString(payloadB64));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { ok: false, reason: 'jwt_malformed' };
|
|
27
|
+
}
|
|
28
|
+
const alg = header.alg;
|
|
29
|
+
const kid = header.kid;
|
|
30
|
+
if (typeof alg !== 'string' || !SUPPORTED_ALGS.has(alg)) {
|
|
31
|
+
return { ok: false, reason: 'jwt_unsupported_alg' };
|
|
32
|
+
}
|
|
33
|
+
if (typeof kid !== 'string')
|
|
34
|
+
return { ok: false, reason: 'jwt_missing_kid' };
|
|
35
|
+
const key = opts.jwks.keys.find((k) => k.kid === kid);
|
|
36
|
+
if (!key)
|
|
37
|
+
return { ok: false, reason: 'jwks_kid_not_found' };
|
|
38
|
+
if (claims.iss !== opts.expectedIssuer)
|
|
39
|
+
return { ok: false, reason: 'iss_mismatch' };
|
|
40
|
+
const audMatch = Array.isArray(claims.aud)
|
|
41
|
+
? claims.aud.includes(opts.expectedAudience)
|
|
42
|
+
: claims.aud === opts.expectedAudience;
|
|
43
|
+
if (!audMatch)
|
|
44
|
+
return { ok: false, reason: 'aud_mismatch' };
|
|
45
|
+
const now = opts.now ?? new Date();
|
|
46
|
+
const nowSec = Math.floor(now.getTime() / 1000);
|
|
47
|
+
const skew = opts.clockSkewSeconds ?? 60;
|
|
48
|
+
if (typeof claims.exp !== 'number' || claims.exp + skew < nowSec) {
|
|
49
|
+
return { ok: false, reason: 'jwt_expired' };
|
|
50
|
+
}
|
|
51
|
+
if (typeof claims.nbf === 'number' && claims.nbf - skew > nowSec) {
|
|
52
|
+
return { ok: false, reason: 'jwt_not_yet_valid' };
|
|
53
|
+
}
|
|
54
|
+
if (typeof claims.iat !== 'number' || claims.iat - skew > nowSec) {
|
|
55
|
+
return { ok: false, reason: 'jwt_future_iat' };
|
|
56
|
+
}
|
|
57
|
+
const cryptoKey = await importJwk(key, alg);
|
|
58
|
+
if (!cryptoKey)
|
|
59
|
+
return { ok: false, reason: 'jwks_bad_key' };
|
|
60
|
+
const signingInput = toAb(new TextEncoder().encode(`${headerB64}.${payloadB64}`));
|
|
61
|
+
const signatureBytes = base64UrlToBytes(sigB64);
|
|
62
|
+
if (!signatureBytes)
|
|
63
|
+
return { ok: false, reason: 'jwt_bad_signature' };
|
|
64
|
+
const signature = toAb(signatureBytes);
|
|
65
|
+
const verified = alg === 'RS256'
|
|
66
|
+
? await crypto.subtle.verify('RSASSA-PKCS1-v1_5', cryptoKey, signature, signingInput)
|
|
67
|
+
: await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, cryptoKey, signature, signingInput);
|
|
68
|
+
if (!verified)
|
|
69
|
+
return { ok: false, reason: 'jwt_bad_signature' };
|
|
70
|
+
return { ok: true, claims, protectedHeader: header };
|
|
71
|
+
}
|
|
72
|
+
async function importJwk(key, alg) {
|
|
73
|
+
try {
|
|
74
|
+
if (alg === 'RS256') {
|
|
75
|
+
return await crypto.subtle.importKey('jwk', { kty: key.kty, n: key.n, e: key.e, alg, ext: true }, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['verify']);
|
|
76
|
+
}
|
|
77
|
+
return await crypto.subtle.importKey('jwk', { kty: key.kty, crv: key.crv, x: key.x, y: key.y, ext: true }, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function b64UrlDecodeString(b64) {
|
|
84
|
+
const s = b64.replace(/-/g, '+').replace(/_/g, '/');
|
|
85
|
+
const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
|
|
86
|
+
return atob(padded);
|
|
87
|
+
}
|
|
88
|
+
function toAb(u8) {
|
|
89
|
+
const buf = new ArrayBuffer(u8.byteLength);
|
|
90
|
+
new Uint8Array(buf).set(u8);
|
|
91
|
+
return buf;
|
|
92
|
+
}
|
|
93
|
+
function base64UrlToBytes(b64) {
|
|
94
|
+
const s = b64.replace(/-/g, '+').replace(/_/g, '/');
|
|
95
|
+
const padded = s + '='.repeat((4 - (s.length % 4)) % 4);
|
|
96
|
+
try {
|
|
97
|
+
const bin = atob(padded);
|
|
98
|
+
const out = new Uint8Array(bin.length);
|
|
99
|
+
for (let i = 0; i < bin.length; i++)
|
|
100
|
+
out[i] = bin.charCodeAt(i);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=oidc-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-verify.js","sourceRoot":"","sources":["../../src/server/oidc-verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy DSL evaluator (Sprint 38).
|
|
3
|
+
*
|
|
4
|
+
* Each policy is a JSON object: { if_any?: Rule[]; if_all?: Rule[]; then: Action }.
|
|
5
|
+
* Rules are predicate atoms over a flat `PolicyContext` describing the
|
|
6
|
+
* current refresh attempt. The evaluator is pure and synchronous — no
|
|
7
|
+
* IO, no time, no surprises — so it stays unit-testable and safe to
|
|
8
|
+
* call inside the request critical path.
|
|
9
|
+
*
|
|
10
|
+
* Action precedence: block > revoke_session > step_up > allow.
|
|
11
|
+
* Multiple policies for the same tenant are folded with `combineActions`
|
|
12
|
+
* so the most restrictive verdict wins.
|
|
13
|
+
*/
|
|
14
|
+
export type PolicyAction = 'allow' | 'step_up' | 'block' | 'revoke_session';
|
|
15
|
+
export interface PolicyContext {
|
|
16
|
+
geoCountry: string | null;
|
|
17
|
+
asn: string | null;
|
|
18
|
+
bindingClass: 'native_dbsc' | 'webauthn' | 'webcrypto' | null;
|
|
19
|
+
signals: string[];
|
|
20
|
+
sensitivePath: boolean;
|
|
21
|
+
isoHour: number;
|
|
22
|
+
}
|
|
23
|
+
export type Rule = {
|
|
24
|
+
geo_country_in: string[];
|
|
25
|
+
} | {
|
|
26
|
+
geo_country_not_in: string[];
|
|
27
|
+
} | {
|
|
28
|
+
asn_in: string[];
|
|
29
|
+
} | {
|
|
30
|
+
binding_class: PolicyContext['bindingClass'];
|
|
31
|
+
} | {
|
|
32
|
+
has_signal: string;
|
|
33
|
+
} | {
|
|
34
|
+
sensitive_path: boolean;
|
|
35
|
+
} | {
|
|
36
|
+
hour_between: [number, number];
|
|
37
|
+
};
|
|
38
|
+
export interface Policy {
|
|
39
|
+
if_any?: Rule[];
|
|
40
|
+
if_all?: Rule[];
|
|
41
|
+
then: PolicyAction;
|
|
42
|
+
}
|
|
43
|
+
export declare function evaluatePolicy(policy: Policy, ctx: PolicyContext): PolicyAction;
|
|
44
|
+
export declare function evaluatePolicies(policies: Policy[], ctx: PolicyContext): PolicyAction;
|
|
45
|
+
export declare function combineActions(a: PolicyAction, b: PolicyAction): PolicyAction;
|
|
46
|
+
export declare function parsePolicyRules(raw: string): Policy | null;
|
|
47
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../src/server/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,EAAE,aAAa,GAAG,UAAU,GAAG,WAAW,GAAG,IAAI,CAAC;IAC9D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,IAAI,GACZ;IAAE,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,GAC5B;IAAE,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAAE,GAChC;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GACpB;IAAE,aAAa,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;CAAE,GAChD;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GACtB;IAAE,cAAc,EAAE,OAAO,CAAA;CAAE,GAC3B;IAAE,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,MAAM;IACrB,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC;IAChB,IAAI,EAAE,YAAY,CAAC;CACpB;AASD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAU/E;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAIrF;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,GAAG,YAAY,CAE7E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc3D"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy DSL evaluator (Sprint 38).
|
|
3
|
+
*
|
|
4
|
+
* Each policy is a JSON object: { if_any?: Rule[]; if_all?: Rule[]; then: Action }.
|
|
5
|
+
* Rules are predicate atoms over a flat `PolicyContext` describing the
|
|
6
|
+
* current refresh attempt. The evaluator is pure and synchronous — no
|
|
7
|
+
* IO, no time, no surprises — so it stays unit-testable and safe to
|
|
8
|
+
* call inside the request critical path.
|
|
9
|
+
*
|
|
10
|
+
* Action precedence: block > revoke_session > step_up > allow.
|
|
11
|
+
* Multiple policies for the same tenant are folded with `combineActions`
|
|
12
|
+
* so the most restrictive verdict wins.
|
|
13
|
+
*/
|
|
14
|
+
const PRECEDENCE = {
|
|
15
|
+
allow: 0,
|
|
16
|
+
step_up: 1,
|
|
17
|
+
revoke_session: 2,
|
|
18
|
+
block: 3,
|
|
19
|
+
};
|
|
20
|
+
export function evaluatePolicy(policy, ctx) {
|
|
21
|
+
const anyRules = policy.if_any ?? [];
|
|
22
|
+
const allRules = policy.if_all ?? [];
|
|
23
|
+
const anyMatch = anyRules.length > 0 && anyRules.some((r) => evalRule(r, ctx));
|
|
24
|
+
const allMatch = allRules.length > 0 && allRules.every((r) => evalRule(r, ctx));
|
|
25
|
+
const conditionTriggered = (anyRules.length > 0 && anyMatch) ||
|
|
26
|
+
(allRules.length > 0 && allMatch) ||
|
|
27
|
+
(anyRules.length === 0 && allRules.length === 0);
|
|
28
|
+
return conditionTriggered ? policy.then : 'allow';
|
|
29
|
+
}
|
|
30
|
+
export function evaluatePolicies(policies, ctx) {
|
|
31
|
+
return policies
|
|
32
|
+
.map((p) => evaluatePolicy(p, ctx))
|
|
33
|
+
.reduce(combineActions, 'allow');
|
|
34
|
+
}
|
|
35
|
+
export function combineActions(a, b) {
|
|
36
|
+
return PRECEDENCE[a] >= PRECEDENCE[b] ? a : b;
|
|
37
|
+
}
|
|
38
|
+
export function parsePolicyRules(raw) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(raw);
|
|
41
|
+
if (!parsed || typeof parsed !== 'object')
|
|
42
|
+
return null;
|
|
43
|
+
const p = parsed;
|
|
44
|
+
if (p.then !== 'allow' && p.then !== 'step_up' && p.then !== 'block' && p.then !== 'revoke_session') {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (p.if_any !== undefined && !Array.isArray(p.if_any))
|
|
48
|
+
return null;
|
|
49
|
+
if (p.if_all !== undefined && !Array.isArray(p.if_all))
|
|
50
|
+
return null;
|
|
51
|
+
return p;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function evalRule(rule, ctx) {
|
|
58
|
+
if ('geo_country_in' in rule) {
|
|
59
|
+
return ctx.geoCountry !== null && rule.geo_country_in.includes(ctx.geoCountry);
|
|
60
|
+
}
|
|
61
|
+
if ('geo_country_not_in' in rule) {
|
|
62
|
+
return ctx.geoCountry !== null && !rule.geo_country_not_in.includes(ctx.geoCountry);
|
|
63
|
+
}
|
|
64
|
+
if ('asn_in' in rule) {
|
|
65
|
+
return ctx.asn !== null && rule.asn_in.includes(ctx.asn);
|
|
66
|
+
}
|
|
67
|
+
if ('binding_class' in rule) {
|
|
68
|
+
return ctx.bindingClass === rule.binding_class;
|
|
69
|
+
}
|
|
70
|
+
if ('has_signal' in rule) {
|
|
71
|
+
return ctx.signals.includes(rule.has_signal);
|
|
72
|
+
}
|
|
73
|
+
if ('sensitive_path' in rule) {
|
|
74
|
+
return ctx.sensitivePath === rule.sensitive_path;
|
|
75
|
+
}
|
|
76
|
+
if ('hour_between' in rule) {
|
|
77
|
+
const [start, end] = rule.hour_between;
|
|
78
|
+
if (start <= end)
|
|
79
|
+
return ctx.isoHour >= start && ctx.isoHour < end;
|
|
80
|
+
return ctx.isoHour >= start || ctx.isoHour < end;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/server/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-action step-up policy (Sprint 39).
|
|
3
|
+
*
|
|
4
|
+
* Tenant-scoped JSON config describing which routes require fresh PoP
|
|
5
|
+
* (a recent action-signing JWS) vs WebAuthn re-prompt vs nothing
|
|
6
|
+
* special. Will eventually live on `tf_tenants.stepUpActions` (per
|
|
7
|
+
* CISCO-dua spec) — schema migration pending. The pure logic here
|
|
8
|
+
* is independent of where the actions blob is stored.
|
|
9
|
+
*
|
|
10
|
+
* Shape (validated by `parseStepUpActions`):
|
|
11
|
+
* [
|
|
12
|
+
* { path: '/checkout', requireFreshSig: true, freshSigMaxAgeSec: 60 },
|
|
13
|
+
* { path: '/admin/*', requireFreshSig: true, freshSigMaxAgeSec: 30, requireWebAuthn: true },
|
|
14
|
+
* { path: '/health', requireFreshSig: false, requireWebAuthn: false }
|
|
15
|
+
* ]
|
|
16
|
+
*
|
|
17
|
+
* Matching rules:
|
|
18
|
+
* - exact path match wins over glob-prefix
|
|
19
|
+
* - longest glob-prefix wins among prefix-matches
|
|
20
|
+
* - no match → default verdict (no step-up, no WebAuthn, default 60s)
|
|
21
|
+
*/
|
|
22
|
+
export interface StepUpAction {
|
|
23
|
+
path: string;
|
|
24
|
+
requireFreshSig?: boolean;
|
|
25
|
+
freshSigMaxAgeSec?: number;
|
|
26
|
+
requireWebAuthn?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface StepUpVerdict {
|
|
29
|
+
matched: boolean;
|
|
30
|
+
requireFreshSig: boolean;
|
|
31
|
+
freshSigMaxAgeSec: number;
|
|
32
|
+
requireWebAuthn: boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function parseStepUpActions(raw: string): StepUpAction[] | null;
|
|
35
|
+
export declare function evaluateStepUpPolicy(actions: readonly StepUpAction[], requestPath: string): StepUpVerdict;
|
|
36
|
+
//# sourceMappingURL=step-up-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-policy.d.ts","sourceRoot":"","sources":["../../src/server/step-up-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAcD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI,CA4BrE;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,YAAY,EAAE,EAChC,WAAW,EAAE,MAAM,GAClB,aAAa,CAiBf"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-action step-up policy (Sprint 39).
|
|
3
|
+
*
|
|
4
|
+
* Tenant-scoped JSON config describing which routes require fresh PoP
|
|
5
|
+
* (a recent action-signing JWS) vs WebAuthn re-prompt vs nothing
|
|
6
|
+
* special. Will eventually live on `tf_tenants.stepUpActions` (per
|
|
7
|
+
* CISCO-dua spec) — schema migration pending. The pure logic here
|
|
8
|
+
* is independent of where the actions blob is stored.
|
|
9
|
+
*
|
|
10
|
+
* Shape (validated by `parseStepUpActions`):
|
|
11
|
+
* [
|
|
12
|
+
* { path: '/checkout', requireFreshSig: true, freshSigMaxAgeSec: 60 },
|
|
13
|
+
* { path: '/admin/*', requireFreshSig: true, freshSigMaxAgeSec: 30, requireWebAuthn: true },
|
|
14
|
+
* { path: '/health', requireFreshSig: false, requireWebAuthn: false }
|
|
15
|
+
* ]
|
|
16
|
+
*
|
|
17
|
+
* Matching rules:
|
|
18
|
+
* - exact path match wins over glob-prefix
|
|
19
|
+
* - longest glob-prefix wins among prefix-matches
|
|
20
|
+
* - no match → default verdict (no step-up, no WebAuthn, default 60s)
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_FRESH_SIG_MAX_AGE_SEC = 60;
|
|
23
|
+
const MAX_ACTIONS = 50;
|
|
24
|
+
const MIN_FRESH_SIG_SEC = 5;
|
|
25
|
+
const MAX_FRESH_SIG_SEC = 600;
|
|
26
|
+
const DEFAULT_VERDICT = {
|
|
27
|
+
matched: false,
|
|
28
|
+
requireFreshSig: false,
|
|
29
|
+
freshSigMaxAgeSec: DEFAULT_FRESH_SIG_MAX_AGE_SEC,
|
|
30
|
+
requireWebAuthn: false,
|
|
31
|
+
};
|
|
32
|
+
export function parseStepUpActions(raw) {
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (!Array.isArray(parsed))
|
|
41
|
+
return null;
|
|
42
|
+
if (parsed.length > MAX_ACTIONS)
|
|
43
|
+
return null;
|
|
44
|
+
const actions = [];
|
|
45
|
+
for (const candidate of parsed) {
|
|
46
|
+
if (!candidate || typeof candidate !== 'object')
|
|
47
|
+
return null;
|
|
48
|
+
const c = candidate;
|
|
49
|
+
if (typeof c.path !== 'string' || c.path.length === 0 || c.path.length > 256)
|
|
50
|
+
return null;
|
|
51
|
+
const action = { path: c.path };
|
|
52
|
+
if (c.requireFreshSig !== undefined) {
|
|
53
|
+
if (typeof c.requireFreshSig !== 'boolean')
|
|
54
|
+
return null;
|
|
55
|
+
action.requireFreshSig = c.requireFreshSig;
|
|
56
|
+
}
|
|
57
|
+
if (c.freshSigMaxAgeSec !== undefined) {
|
|
58
|
+
if (typeof c.freshSigMaxAgeSec !== 'number' || !Number.isInteger(c.freshSigMaxAgeSec))
|
|
59
|
+
return null;
|
|
60
|
+
if (c.freshSigMaxAgeSec < MIN_FRESH_SIG_SEC || c.freshSigMaxAgeSec > MAX_FRESH_SIG_SEC)
|
|
61
|
+
return null;
|
|
62
|
+
action.freshSigMaxAgeSec = c.freshSigMaxAgeSec;
|
|
63
|
+
}
|
|
64
|
+
if (c.requireWebAuthn !== undefined) {
|
|
65
|
+
if (typeof c.requireWebAuthn !== 'boolean')
|
|
66
|
+
return null;
|
|
67
|
+
action.requireWebAuthn = c.requireWebAuthn;
|
|
68
|
+
}
|
|
69
|
+
actions.push(action);
|
|
70
|
+
}
|
|
71
|
+
return actions;
|
|
72
|
+
}
|
|
73
|
+
export function evaluateStepUpPolicy(actions, requestPath) {
|
|
74
|
+
// Exact match wins outright.
|
|
75
|
+
for (const a of actions) {
|
|
76
|
+
if (!a.path.endsWith('*') && a.path === requestPath)
|
|
77
|
+
return verdictFrom(a, true);
|
|
78
|
+
}
|
|
79
|
+
// Otherwise, longest matching glob-prefix wins.
|
|
80
|
+
let best = null;
|
|
81
|
+
let bestLen = -1;
|
|
82
|
+
for (const a of actions) {
|
|
83
|
+
if (!a.path.endsWith('*'))
|
|
84
|
+
continue;
|
|
85
|
+
const prefix = a.path.slice(0, -1);
|
|
86
|
+
if (requestPath.startsWith(prefix) && prefix.length > bestLen) {
|
|
87
|
+
best = a;
|
|
88
|
+
bestLen = prefix.length;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return best ? verdictFrom(best, true) : DEFAULT_VERDICT;
|
|
92
|
+
}
|
|
93
|
+
function verdictFrom(a, matched) {
|
|
94
|
+
return {
|
|
95
|
+
matched,
|
|
96
|
+
requireFreshSig: a.requireFreshSig ?? false,
|
|
97
|
+
freshSigMaxAgeSec: a.freshSigMaxAgeSec ?? DEFAULT_FRESH_SIG_MAX_AGE_SEC,
|
|
98
|
+
requireWebAuthn: a.requireWebAuthn ?? false,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=step-up-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-up-policy.js","sourceRoot":"","sources":["../../src/server/step-up-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test signals for `trust-score.test.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Pulled out so the test file fits the 200L cap. Pure factory — no
|
|
5
|
+
* side effects, returns a TrustSignals shape with sensible defaults.
|
|
6
|
+
*/
|
|
7
|
+
import type { TrustSignals } from '../shared/types.js';
|
|
8
|
+
export declare function makeSignals(overrides?: Partial<TrustSignals>): TrustSignals;
|
|
9
|
+
//# sourceMappingURL=trust-score-fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-score-fixtures.d.ts","sourceRoot":"","sources":["../../src/server/trust-score-fixtures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,wBAAgB,WAAW,CAAC,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GAAG,YAAY,CAc/E"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test signals for `trust-score.test.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Pulled out so the test file fits the 200L cap. Pure factory — no
|
|
5
|
+
* side effects, returns a TrustSignals shape with sensible defaults.
|
|
6
|
+
*/
|
|
7
|
+
import { hashFingerprint } from './trust-score.js';
|
|
8
|
+
export function makeSignals(overrides = {}) {
|
|
9
|
+
const ua = 'Mozilla/5.0 TestAgent';
|
|
10
|
+
return {
|
|
11
|
+
signatureValid: true,
|
|
12
|
+
ipAddress: '192.168.1.10',
|
|
13
|
+
originalIp: '192.168.1.10',
|
|
14
|
+
countryCode: 'US',
|
|
15
|
+
originalCountry: 'US',
|
|
16
|
+
userAgent: ua,
|
|
17
|
+
originalFingerprint: hashFingerprint(ua),
|
|
18
|
+
requestTimestamp: 1000,
|
|
19
|
+
sessionCreatedAt: 900,
|
|
20
|
+
...overrides,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=trust-score-fixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-score-fixtures.js","sourceRoot":"","sources":["../../src/server/trust-score-fixtures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,UAAU,WAAW,CAAC,YAAmC,EAAE;IAC/D,MAAM,EAAE,GAAG,uBAAuB,CAAC;IACnC,OAAO;QACL,cAAc,EAAE,IAAI;QACpB,SAAS,EAAE,cAAc;QACzB,UAAU,EAAE,cAAc;QAC1B,WAAW,EAAE,IAAI;QACjB,eAAe,EAAE,IAAI;QACrB,SAAS,EAAE,EAAE;QACb,mBAAmB,EAAE,eAAe,CAAC,EAAE,CAAC;QACxC,gBAAgB,EAAE,IAAI;QACtB,gBAAgB,EAAE,GAAG;QACrB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side WebAuthn verifier — runs in Cloudflare Workers (Web Crypto only).
|
|
3
|
+
*
|
|
4
|
+
* Limitations (intentional, documented):
|
|
5
|
+
* - We use attestation: 'none' on the client, so attestation statement
|
|
6
|
+
* verification is skipped (only the authData → COSE_Key extraction matters).
|
|
7
|
+
* - Only ES256 (alg -7, ECDSA P-256) keys are accepted on the server. EdDSA
|
|
8
|
+
* is offered to the client for forward-compat but Web Crypto support is
|
|
9
|
+
* spotty in Workers; reject EdDSA at verify time with a clear error.
|
|
10
|
+
* - Counter check is not enforced here. Callers can read the 4-byte counter
|
|
11
|
+
* out of authenticatorData[33..37] if they want to persist + compare.
|
|
12
|
+
*/
|
|
13
|
+
export interface AttestationVerifyResult {
|
|
14
|
+
publicKeyJwk: JsonWebKey;
|
|
15
|
+
credentialId: Uint8Array;
|
|
16
|
+
rpIdHash: Uint8Array;
|
|
17
|
+
}
|
|
18
|
+
/** base64url → ArrayBuffer */
|
|
19
|
+
export declare function b64uToBuf(b64url: string): ArrayBuffer;
|
|
20
|
+
/** Uint8Array → base64url (no padding). */
|
|
21
|
+
export declare function bufToB64u(buf: ArrayBuffer | Uint8Array): string;
|
|
22
|
+
/** Parse clientDataJSON and verify challenge + origin. */
|
|
23
|
+
export declare function verifyClientDataJSON(clientDataJSON: string, expectedChallenge: string, expectedOrigin: string, expectedType: 'webauthn.create' | 'webauthn.get'): {
|
|
24
|
+
ok: true;
|
|
25
|
+
} | {
|
|
26
|
+
ok: false;
|
|
27
|
+
reason: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Verify a WebAuthn attestation (registration). Returns the extracted
|
|
31
|
+
* public key as JWK plus the credentialId for persistence.
|
|
32
|
+
*/
|
|
33
|
+
export declare function verifyWebAuthnAttestation(attestationObjectB64u: string, clientDataJSONB64u: string, expectedChallenge: string, expectedOrigin: string): Promise<AttestationVerifyResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Verify a WebAuthn assertion. The signature is over
|
|
36
|
+
* (authenticatorData || sha256(clientDataJSON)).
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyWebAuthnAssertion(signatureB64u: string, authenticatorDataB64u: string, clientDataJSONB64u: string, publicKeyJwk: JsonWebKey, expectedChallenge: string, expectedOrigin: string): Promise<boolean>;
|
|
39
|
+
/** WebAuthn ECDSA sigs are DER-encoded; Web Crypto wants raw r||s. */
|
|
40
|
+
export declare function derToRawEcdsa(der: Uint8Array): Uint8Array;
|
|
41
|
+
//# sourceMappingURL=webauthn-verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webauthn-verify.d.ts","sourceRoot":"","sources":["../../src/server/webauthn-verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,UAAU,CAAC;IACzB,YAAY,EAAE,UAAU,CAAC;IACzB,QAAQ,EAAE,UAAU,CAAC;CACtB;AAED,8BAA8B;AAC9B,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAOrD;AAED,2CAA2C;AAC3C,wBAAgB,SAAS,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAK/D;AAED,0DAA0D;AAC1D,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,iBAAiB,GAAG,cAAc,GAC/C;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAY9C;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,qBAAqB,EAAE,MAAM,EAC7B,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,uBAAuB,CAAC,CAWlC;AAiCD;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,MAAM,EACrB,qBAAqB,EAAE,MAAM,EAC7B,kBAAkB,EAAE,MAAM,EAC1B,YAAY,EAAE,UAAU,EACxB,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,CA4BlB;AASD,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAuBzD"}
|