@bradford-tech/supabase-integrity-attest 0.3.1 → 0.4.1
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 +1 -1
- package/esm/assertion.d.ts +22 -1
- package/esm/assertion.d.ts.map +1 -1
- package/esm/assertion.js +22 -2
- package/esm/attestation.d.ts +22 -0
- package/esm/attestation.d.ts.map +1 -1
- package/esm/attestation.js +22 -1
- package/esm/mod.d.ts +93 -1
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +93 -2
- package/esm/src/assertion.d.ts +14 -0
- package/esm/src/assertion.d.ts.map +1 -1
- package/esm/src/assertion.js +9 -0
- package/esm/src/attestation.d.ts +20 -1
- package/esm/src/attestation.d.ts.map +1 -1
- package/esm/src/attestation.js +15 -4
- package/esm/src/errors.d.ts +52 -4
- package/esm/src/errors.d.ts.map +1 -1
- package/esm/src/errors.js +47 -3
- package/esm/src/with-assertion.d.ts +63 -1
- package/esm/src/with-assertion.d.ts.map +1 -1
- package/esm/src/with-assertion.js +36 -3
- package/esm/src/with-attestation.d.ts +79 -0
- package/esm/src/with-attestation.d.ts.map +1 -0
- package/esm/src/with-attestation.js +135 -0
- package/package.json +4 -7
- package/esm/_dnt.test_polyfills.d.ts.map +0 -1
- package/esm/_dnt.test_shims.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/build_message.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/diff.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/diff_str.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/format.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/styles.d.ts.map +0 -1
- package/esm/deps/jsr.io/@std/internal/1.0.12/types.d.ts.map +0 -1
- package/esm/src/cose.d.ts.map +0 -1
- package/esm/tests/assertion-entry.test.d.ts.map +0 -1
- package/esm/tests/assertion.test.d.ts.map +0 -1
- package/esm/tests/attestation-entry.test.d.ts.map +0 -1
- package/esm/tests/attestation.test.d.ts.map +0 -1
- package/esm/tests/authdata.test.d.ts.map +0 -1
- package/esm/tests/certificate.test.d.ts.map +0 -1
- package/esm/tests/cose.test.d.ts.map +0 -1
- package/esm/tests/der.test.d.ts.map +0 -1
- package/esm/tests/errors.test.d.ts.map +0 -1
- package/esm/tests/fixtures/apple-attestation.d.ts.map +0 -1
- package/esm/tests/fixtures/generate-assertion.d.ts.map +0 -1
- package/esm/tests/utils.test.d.ts.map +0 -1
- package/esm/tests/with-assertion.test.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ assertions using the WebCrypto API. Built for Deno and Supabase Edge Functions.
|
|
|
10
10
|
deno add jsr:@bradford-tech/supabase-integrity-attest
|
|
11
11
|
|
|
12
12
|
# npm
|
|
13
|
-
|
|
13
|
+
npm install @bradford-tech/supabase-integrity-attest
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Subpath imports
|
package/esm/assertion.d.ts
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight assertion-only entry point. Avoids pulling in `asn1js` and
|
|
3
|
+
* `@noble/curves`, keeping the bundle minimal for assertion-only use cases.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAssertion,
|
|
8
|
+
* withAssertion,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/assertion";
|
|
10
|
+
*
|
|
11
|
+
* const { signCount } = await verifyAssertion(
|
|
12
|
+
* { appId: "TEAMID.com.example.app" },
|
|
13
|
+
* assertion,
|
|
14
|
+
* clientData,
|
|
15
|
+
* publicKeyPem,
|
|
16
|
+
* previousSignCount,
|
|
17
|
+
* );
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
1
22
|
export { verifyAssertion } from "./src/assertion.js";
|
|
2
23
|
export type { AppInfo, AssertionResult } from "./src/assertion.js";
|
|
3
24
|
export { AssertionError, AssertionErrorCode } from "./src/errors.js";
|
|
4
25
|
export { withAssertion } from "./src/with-assertion.js";
|
|
5
26
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
|
6
|
-
export type { AssertionContext, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
27
|
+
export type { AssertionContext, AssertionTimings, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
7
28
|
//# sourceMappingURL=assertion.d.ts.map
|
package/esm/assertion.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../src/assertion.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../src/assertion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGrE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC"}
|
package/esm/assertion.js
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight assertion-only entry point. Avoids pulling in `asn1js` and
|
|
3
|
+
* `@noble/curves`, keeping the bundle minimal for assertion-only use cases.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAssertion,
|
|
8
|
+
* withAssertion,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/assertion";
|
|
10
|
+
*
|
|
11
|
+
* const { signCount } = await verifyAssertion(
|
|
12
|
+
* { appId: "TEAMID.com.example.app" },
|
|
13
|
+
* assertion,
|
|
14
|
+
* clientData,
|
|
15
|
+
* publicKeyPem,
|
|
16
|
+
* previousSignCount,
|
|
17
|
+
* );
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
2
22
|
export { verifyAssertion } from "./src/assertion.js";
|
|
3
23
|
export { AssertionError, AssertionErrorCode } from "./src/errors.js";
|
|
4
|
-
// withAssertion
|
|
24
|
+
// withAssertion middleware
|
|
5
25
|
export { withAssertion } from "./src/with-assertion.js";
|
|
6
26
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
package/esm/attestation.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full attestation entry point including certificate chain verification
|
|
3
|
+
* dependencies (`asn1js`, `@noble/curves`).
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAttestation,
|
|
8
|
+
* withAttestation,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/attestation";
|
|
10
|
+
*
|
|
11
|
+
* const { publicKeyPem } = await verifyAttestation(
|
|
12
|
+
* { appId: "TEAMID.com.example.app" },
|
|
13
|
+
* keyId,
|
|
14
|
+
* challenge,
|
|
15
|
+
* attestation,
|
|
16
|
+
* );
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
1
21
|
export { verifyAttestation } from "./src/attestation.js";
|
|
2
22
|
export type { AppInfo, AttestationResult, VerifyAttestationOptions, } from "./src/attestation.js";
|
|
3
23
|
export { AttestationError, AttestationErrorCode } from "./src/errors.js";
|
|
24
|
+
export { withAttestation } from "./src/with-attestation.js";
|
|
25
|
+
export type { AttestationContext, AttestationTimings, ExtractAttestationFn, WithAttestationOptions, } from "./src/with-attestation.js";
|
|
4
26
|
//# sourceMappingURL=attestation.d.ts.map
|
package/esm/attestation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,YAAY,EACV,OAAO,EACP,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGzE,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC"}
|
package/esm/attestation.js
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Full attestation entry point including certificate chain verification
|
|
3
|
+
* dependencies (`asn1js`, `@noble/curves`).
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* import {
|
|
7
|
+
* verifyAttestation,
|
|
8
|
+
* withAttestation,
|
|
9
|
+
* } from "@bradford-tech/supabase-integrity-attest/attestation";
|
|
10
|
+
*
|
|
11
|
+
* const { publicKeyPem } = await verifyAttestation(
|
|
12
|
+
* { appId: "TEAMID.com.example.app" },
|
|
13
|
+
* keyId,
|
|
14
|
+
* challenge,
|
|
15
|
+
* attestation,
|
|
16
|
+
* );
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
2
21
|
export { verifyAttestation } from "./src/attestation.js";
|
|
3
22
|
export { AttestationError, AttestationErrorCode } from "./src/errors.js";
|
|
23
|
+
// withAttestation middleware
|
|
24
|
+
export { withAttestation } from "./src/with-attestation.js";
|
package/esm/mod.d.ts
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Apple App Attest attestations and assertions using WebCrypto.
|
|
3
|
+
*
|
|
4
|
+
* This library implements Apple's full
|
|
5
|
+
* [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity)
|
|
6
|
+
* server-side verification — CBOR decoding, X.509 certificate chain
|
|
7
|
+
* validation, nonce verification, ECDSA signature checks — using only
|
|
8
|
+
* WebCrypto APIs so it runs in Supabase Edge Functions, Deno Deploy,
|
|
9
|
+
* and other edge runtimes with no native dependencies.
|
|
10
|
+
*
|
|
11
|
+
* ## Attestation
|
|
12
|
+
*
|
|
13
|
+
* Verify a new device and extract its public key:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { verifyAttestation } from "@bradford-tech/supabase-integrity-attest";
|
|
17
|
+
*
|
|
18
|
+
* const { publicKeyPem, receipt, signCount } = await verifyAttestation(
|
|
19
|
+
* { appId: "TEAMID.com.example.app" },
|
|
20
|
+
* keyId,
|
|
21
|
+
* challenge,
|
|
22
|
+
* attestation,
|
|
23
|
+
* );
|
|
24
|
+
* // Store publicKeyPem and signCount for future assertion verification
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* ## Assertion
|
|
28
|
+
*
|
|
29
|
+
* Verify ongoing requests from an already-attested device:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { verifyAssertion } from "@bradford-tech/supabase-integrity-attest";
|
|
33
|
+
*
|
|
34
|
+
* const { signCount } = await verifyAssertion(
|
|
35
|
+
* { appId: "TEAMID.com.example.app" },
|
|
36
|
+
* assertion,
|
|
37
|
+
* clientData,
|
|
38
|
+
* publicKeyPem,
|
|
39
|
+
* previousSignCount,
|
|
40
|
+
* );
|
|
41
|
+
* // Persist the updated signCount
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ## Middleware
|
|
45
|
+
*
|
|
46
|
+
* Use the {@linkcode withAttestation} and {@linkcode withAssertion}
|
|
47
|
+
* wrappers for automatic verification, callback-driven storage, and
|
|
48
|
+
* typed error handling:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import {
|
|
52
|
+
* withAttestation,
|
|
53
|
+
* withAssertion,
|
|
54
|
+
* } from "@bradford-tech/supabase-integrity-attest";
|
|
55
|
+
*
|
|
56
|
+
* const attestHandler = withAttestation(
|
|
57
|
+
* {
|
|
58
|
+
* appId: "TEAMID.com.example.app",
|
|
59
|
+
* consumeChallenge,
|
|
60
|
+
* storeDeviceKey,
|
|
61
|
+
* },
|
|
62
|
+
* (_req, ctx) =>
|
|
63
|
+
* Response.json({ ok: true, deviceId: ctx.deviceId }),
|
|
64
|
+
* );
|
|
65
|
+
*
|
|
66
|
+
* const protectedHandler = withAssertion(
|
|
67
|
+
* {
|
|
68
|
+
* appId: "TEAMID.com.example.app",
|
|
69
|
+
* getDeviceKey,
|
|
70
|
+
* commitSignCount,
|
|
71
|
+
* },
|
|
72
|
+
* (_req, ctx) =>
|
|
73
|
+
* Response.json({ hello: ctx.deviceId, counter: ctx.signCount }),
|
|
74
|
+
* );
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* ## Subpath imports
|
|
78
|
+
*
|
|
79
|
+
* For smaller bundles, import only what you need:
|
|
80
|
+
*
|
|
81
|
+
* - `@bradford-tech/supabase-integrity-attest/attestation` — attestation
|
|
82
|
+
* (`verifyAttestation`, `withAttestation`). Full crypto deps.
|
|
83
|
+
* - `@bradford-tech/supabase-integrity-attest/assertion` — assertion
|
|
84
|
+
* (`verifyAssertion`, `withAssertion`). Excludes `asn1js` and
|
|
85
|
+
* `@noble/curves` to keep the bundle minimal.
|
|
86
|
+
*
|
|
87
|
+
* Full documentation: {@link https://integrity-attest.bradford.tech}
|
|
88
|
+
*
|
|
89
|
+
* @module
|
|
90
|
+
*/
|
|
1
91
|
export type { AppInfo, AttestationResult, VerifyAttestationOptions, } from "./src/attestation.js";
|
|
2
92
|
export type { AssertionResult } from "./src/assertion.js";
|
|
3
93
|
export { verifyAttestation } from "./src/attestation.js";
|
|
@@ -5,5 +95,7 @@ export { verifyAssertion } from "./src/assertion.js";
|
|
|
5
95
|
export { AssertionError, AssertionErrorCode, AttestationError, AttestationErrorCode, } from "./src/errors.js";
|
|
6
96
|
export { withAssertion } from "./src/with-assertion.js";
|
|
7
97
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
|
8
|
-
export type { AssertionContext, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
98
|
+
export type { AssertionContext, AssertionTimings, DeviceKey, ExtractAssertionFn, WithAssertionOptions, } from "./src/with-assertion.js";
|
|
99
|
+
export { withAttestation } from "./src/with-attestation.js";
|
|
100
|
+
export type { AttestationContext, AttestationTimings, ExtractAttestationFn, WithAttestationOptions, } from "./src/with-attestation.js";
|
|
9
101
|
//# sourceMappingURL=mod.d.ts.map
|
package/esm/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AAEH,YAAY,EACV,OAAO,EACP,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC"}
|
package/esm/mod.js
CHANGED
|
@@ -1,7 +1,98 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Verify Apple App Attest attestations and assertions using WebCrypto.
|
|
3
|
+
*
|
|
4
|
+
* This library implements Apple's full
|
|
5
|
+
* [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity)
|
|
6
|
+
* server-side verification — CBOR decoding, X.509 certificate chain
|
|
7
|
+
* validation, nonce verification, ECDSA signature checks — using only
|
|
8
|
+
* WebCrypto APIs so it runs in Supabase Edge Functions, Deno Deploy,
|
|
9
|
+
* and other edge runtimes with no native dependencies.
|
|
10
|
+
*
|
|
11
|
+
* ## Attestation
|
|
12
|
+
*
|
|
13
|
+
* Verify a new device and extract its public key:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { verifyAttestation } from "@bradford-tech/supabase-integrity-attest";
|
|
17
|
+
*
|
|
18
|
+
* const { publicKeyPem, receipt, signCount } = await verifyAttestation(
|
|
19
|
+
* { appId: "TEAMID.com.example.app" },
|
|
20
|
+
* keyId,
|
|
21
|
+
* challenge,
|
|
22
|
+
* attestation,
|
|
23
|
+
* );
|
|
24
|
+
* // Store publicKeyPem and signCount for future assertion verification
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* ## Assertion
|
|
28
|
+
*
|
|
29
|
+
* Verify ongoing requests from an already-attested device:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { verifyAssertion } from "@bradford-tech/supabase-integrity-attest";
|
|
33
|
+
*
|
|
34
|
+
* const { signCount } = await verifyAssertion(
|
|
35
|
+
* { appId: "TEAMID.com.example.app" },
|
|
36
|
+
* assertion,
|
|
37
|
+
* clientData,
|
|
38
|
+
* publicKeyPem,
|
|
39
|
+
* previousSignCount,
|
|
40
|
+
* );
|
|
41
|
+
* // Persist the updated signCount
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ## Middleware
|
|
45
|
+
*
|
|
46
|
+
* Use the {@linkcode withAttestation} and {@linkcode withAssertion}
|
|
47
|
+
* wrappers for automatic verification, callback-driven storage, and
|
|
48
|
+
* typed error handling:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* import {
|
|
52
|
+
* withAttestation,
|
|
53
|
+
* withAssertion,
|
|
54
|
+
* } from "@bradford-tech/supabase-integrity-attest";
|
|
55
|
+
*
|
|
56
|
+
* const attestHandler = withAttestation(
|
|
57
|
+
* {
|
|
58
|
+
* appId: "TEAMID.com.example.app",
|
|
59
|
+
* consumeChallenge,
|
|
60
|
+
* storeDeviceKey,
|
|
61
|
+
* },
|
|
62
|
+
* (_req, ctx) =>
|
|
63
|
+
* Response.json({ ok: true, deviceId: ctx.deviceId }),
|
|
64
|
+
* );
|
|
65
|
+
*
|
|
66
|
+
* const protectedHandler = withAssertion(
|
|
67
|
+
* {
|
|
68
|
+
* appId: "TEAMID.com.example.app",
|
|
69
|
+
* getDeviceKey,
|
|
70
|
+
* commitSignCount,
|
|
71
|
+
* },
|
|
72
|
+
* (_req, ctx) =>
|
|
73
|
+
* Response.json({ hello: ctx.deviceId, counter: ctx.signCount }),
|
|
74
|
+
* );
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* ## Subpath imports
|
|
78
|
+
*
|
|
79
|
+
* For smaller bundles, import only what you need:
|
|
80
|
+
*
|
|
81
|
+
* - `@bradford-tech/supabase-integrity-attest/attestation` — attestation
|
|
82
|
+
* (`verifyAttestation`, `withAttestation`). Full crypto deps.
|
|
83
|
+
* - `@bradford-tech/supabase-integrity-attest/assertion` — assertion
|
|
84
|
+
* (`verifyAssertion`, `withAssertion`). Excludes `asn1js` and
|
|
85
|
+
* `@noble/curves` to keep the bundle minimal.
|
|
86
|
+
*
|
|
87
|
+
* Full documentation: {@link https://integrity-attest.bradford.tech}
|
|
88
|
+
*
|
|
89
|
+
* @module
|
|
90
|
+
*/
|
|
2
91
|
export { verifyAttestation } from "./src/attestation.js";
|
|
3
92
|
export { verifyAssertion } from "./src/assertion.js";
|
|
4
93
|
export { AssertionError, AssertionErrorCode, AttestationError, AttestationErrorCode, } from "./src/errors.js";
|
|
5
|
-
// withAssertion
|
|
94
|
+
// withAssertion middleware
|
|
6
95
|
export { withAssertion } from "./src/with-assertion.js";
|
|
7
96
|
export { DEFAULT_ASSERTION_HEADER, DEFAULT_DEVICE_ID_HEADER, } from "./src/with-assertion.js";
|
|
97
|
+
// withAttestation middleware
|
|
98
|
+
export { withAttestation } from "./src/with-attestation.js";
|
package/esm/src/assertion.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
|
+
/** Identifies the app whose assertions are being verified. */
|
|
1
2
|
export interface AppInfo {
|
|
3
|
+
/** Apple App ID in the format `TEAMID.bundleId`. */
|
|
2
4
|
appId: string;
|
|
5
|
+
/** Set to `true` when verifying assertions from the development environment. */
|
|
3
6
|
developmentEnv?: boolean;
|
|
4
7
|
}
|
|
8
|
+
/** Successful assertion verification result. */
|
|
5
9
|
export interface AssertionResult {
|
|
10
|
+
/** Updated sign count to persist for the next assertion. */
|
|
6
11
|
signCount: number;
|
|
7
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Verify an Apple App Attest assertion.
|
|
15
|
+
*
|
|
16
|
+
* Validates the CBOR-encoded assertion against the expected app ID,
|
|
17
|
+
* checks the monotonic sign counter, and verifies the ECDSA signature
|
|
18
|
+
* using the device's public key.
|
|
19
|
+
*
|
|
20
|
+
* @throws {AssertionError} If any verification step fails.
|
|
21
|
+
*/
|
|
8
22
|
export declare function verifyAssertion(appInfo: AppInfo, assertion: Uint8Array | string, clientData: Uint8Array | string, publicKeyPem: string, previousSignCount: number): Promise<AssertionResult>;
|
|
9
23
|
//# sourceMappingURL=assertion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../../src/src/assertion.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,UAAU,GAAG,MAAM,EAC9B,UAAU,EAAE,UAAU,GAAG,MAAM,EAC/B,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,eAAe,CAAC,CA2G1B"}
|
|
1
|
+
{"version":3,"file":"assertion.d.ts","sourceRoot":"","sources":["../../src/src/assertion.ts"],"names":[],"mappings":"AAaA,8DAA8D;AAC9D,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,gDAAgD;AAChD,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,UAAU,GAAG,MAAM,EAC9B,UAAU,EAAE,UAAU,GAAG,MAAM,EAC/B,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,eAAe,CAAC,CA2G1B"}
|
package/esm/src/assertion.js
CHANGED
|
@@ -4,6 +4,15 @@ import { parseAssertionAuthData } from "./authdata.js";
|
|
|
4
4
|
import { derToRaw } from "./der.js";
|
|
5
5
|
import { AssertionError, AssertionErrorCode } from "./errors.js";
|
|
6
6
|
import { concat, constantTimeEqual, decodeBase64Bytes, importPemPublicKey, toBytes, } from "./utils.js";
|
|
7
|
+
/**
|
|
8
|
+
* Verify an Apple App Attest assertion.
|
|
9
|
+
*
|
|
10
|
+
* Validates the CBOR-encoded assertion against the expected app ID,
|
|
11
|
+
* checks the monotonic sign counter, and verifies the ECDSA signature
|
|
12
|
+
* using the device's public key.
|
|
13
|
+
*
|
|
14
|
+
* @throws {AssertionError} If any verification step fails.
|
|
15
|
+
*/
|
|
7
16
|
export async function verifyAssertion(appInfo, assertion, clientData, publicKeyPem, previousSignCount) {
|
|
8
17
|
const assertionBytes = decodeBase64Bytes(assertion);
|
|
9
18
|
const clientDataBytes = toBytes(clientData);
|
package/esm/src/attestation.d.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
+
/** Identifies the app being attested. */
|
|
1
2
|
export interface AppInfo {
|
|
3
|
+
/** Apple App ID in the format `TEAMID.bundleId`. */
|
|
2
4
|
appId: string;
|
|
5
|
+
/** Set to `true` when verifying attestations from the development environment. */
|
|
3
6
|
developmentEnv?: boolean;
|
|
4
7
|
}
|
|
8
|
+
/** Successful attestation verification result. */
|
|
5
9
|
export interface AttestationResult {
|
|
10
|
+
/** PEM-encoded ECDSA P-256 public key extracted from the attestation. */
|
|
6
11
|
publicKeyPem: string;
|
|
12
|
+
/** Raw App Attest receipt bytes for server-side refresh. */
|
|
7
13
|
receipt: Uint8Array;
|
|
14
|
+
/** Initial sign count (always `0` for attestation). */
|
|
8
15
|
signCount: number;
|
|
9
16
|
}
|
|
17
|
+
/** Options for {@linkcode verifyAttestation}. */
|
|
10
18
|
export interface VerifyAttestationOptions {
|
|
11
|
-
/** Override date for certificate chain validation (for testing with expired certs) */
|
|
19
|
+
/** Override date for certificate chain validation (for testing with expired certs). */
|
|
12
20
|
checkDate?: Date;
|
|
13
21
|
}
|
|
14
22
|
/**
|
|
@@ -32,6 +40,17 @@ export interface AttestationCbor {
|
|
|
32
40
|
};
|
|
33
41
|
authData: Uint8Array;
|
|
34
42
|
}
|
|
43
|
+
/** Decode an Apple App Attest attestation object from raw CBOR bytes. */
|
|
35
44
|
export declare function decodeAttestationCbor(data: Uint8Array): AttestationCbor;
|
|
45
|
+
/**
|
|
46
|
+
* Verify an Apple App Attest attestation.
|
|
47
|
+
*
|
|
48
|
+
* Implements the full server-side verification described in
|
|
49
|
+
* [Apple's documentation](https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server):
|
|
50
|
+
* CBOR decode, certificate chain validation, nonce check, key extraction,
|
|
51
|
+
* AAGUID check, and credential ID verification.
|
|
52
|
+
*
|
|
53
|
+
* @throws {AttestationError} If any verification step fails.
|
|
54
|
+
*/
|
|
36
55
|
export declare function verifyAttestation(appInfo: AppInfo, keyId: string, challenge: Uint8Array | string, attestation: Uint8Array | string, options?: VerifyAttestationOptions): Promise<AttestationResult>;
|
|
37
56
|
//# sourceMappingURL=attestation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../src/src/attestation.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,
|
|
1
|
+
{"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../../src/src/attestation.ts"],"names":[],"mappings":"AAaA,yCAAyC;AACzC,MAAM,WAAW,OAAO;IACtB,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,OAAO,EAAE,UAAU,CAAC;IACpB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iDAAiD;AACjD,MAAM,WAAW,wBAAwB;IACvC,uFAAuF;IACvF,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QACP,GAAG,EAAE,UAAU,EAAE,CAAC;QAClB,OAAO,EAAE,UAAU,CAAC;KACrB,CAAC;IACF,QAAQ,EAAE,UAAU,CAAC;CACtB;AAsGD,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,GAAG,eAAe,CAyEvE;AAED;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,UAAU,GAAG,MAAM,EAC9B,WAAW,EAAE,UAAU,GAAG,MAAM,EAChC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,iBAAiB,CAAC,CAkJ5B"}
|
package/esm/src/attestation.js
CHANGED
|
@@ -20,10 +20,10 @@ function readCborUint(data, offset) {
|
|
|
20
20
|
}
|
|
21
21
|
if (additional === 26) {
|
|
22
22
|
return {
|
|
23
|
-
value: ((data[offset + 1] << 24)
|
|
24
|
-
(data[offset + 2] << 16)
|
|
25
|
-
(data[offset + 3] << 8)
|
|
26
|
-
data[offset + 4],
|
|
23
|
+
value: ((data[offset + 1] << 24) |
|
|
24
|
+
(data[offset + 2] << 16) |
|
|
25
|
+
(data[offset + 3] << 8) |
|
|
26
|
+
data[offset + 4]) >>> 0,
|
|
27
27
|
end: offset + 5,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -88,6 +88,7 @@ function findCborTextKey(data, key, startOffset) {
|
|
|
88
88
|
}
|
|
89
89
|
return -1;
|
|
90
90
|
}
|
|
91
|
+
/** Decode an Apple App Attest attestation object from raw CBOR bytes. */
|
|
91
92
|
export function decodeAttestationCbor(data) {
|
|
92
93
|
// Verify top-level is a CBOR map
|
|
93
94
|
const majorType = (data[0] >> 5) & 0x07;
|
|
@@ -154,6 +155,16 @@ export function decodeAttestationCbor(data) {
|
|
|
154
155
|
const { value: authData } = readCborBytes(data, authDataValuePos);
|
|
155
156
|
return { fmt, attStmt: { x5c, receipt }, authData };
|
|
156
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Verify an Apple App Attest attestation.
|
|
160
|
+
*
|
|
161
|
+
* Implements the full server-side verification described in
|
|
162
|
+
* [Apple's documentation](https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server):
|
|
163
|
+
* CBOR decode, certificate chain validation, nonce check, key extraction,
|
|
164
|
+
* AAGUID check, and credential ID verification.
|
|
165
|
+
*
|
|
166
|
+
* @throws {AttestationError} If any verification step fails.
|
|
167
|
+
*/
|
|
157
168
|
export async function verifyAttestation(appInfo, keyId, challenge, attestation, options) {
|
|
158
169
|
// Decode attestation bytes from base64 if string
|
|
159
170
|
let attestationBytes;
|
package/esm/src/errors.d.ts
CHANGED
|
@@ -1,29 +1,77 @@
|
|
|
1
|
+
/** Error codes returned by {@linkcode AttestationError}. */
|
|
1
2
|
export declare enum AttestationErrorCode {
|
|
3
|
+
/** CBOR decoding or structural validation failed. */
|
|
2
4
|
INVALID_FORMAT = "INVALID_FORMAT",
|
|
5
|
+
/** X.509 certificate chain verification failed. */
|
|
3
6
|
INVALID_CERTIFICATE_CHAIN = "INVALID_CERTIFICATE_CHAIN",
|
|
7
|
+
/** Computed nonce does not match the nonce in the leaf certificate. */
|
|
4
8
|
NONCE_MISMATCH = "NONCE_MISMATCH",
|
|
9
|
+
/** RP ID hash does not match SHA-256 of the app ID. */
|
|
5
10
|
RP_ID_MISMATCH = "RP_ID_MISMATCH",
|
|
11
|
+
/** Public key hash does not match the provided key ID. */
|
|
6
12
|
KEY_ID_MISMATCH = "KEY_ID_MISMATCH",
|
|
13
|
+
/** Sign count is not zero (required for attestation). */
|
|
7
14
|
INVALID_COUNTER = "INVALID_COUNTER",
|
|
8
|
-
|
|
15
|
+
/** AAGUID does not match the expected environment (production/development). */
|
|
16
|
+
INVALID_AAGUID = "INVALID_AAGUID",
|
|
17
|
+
/**
|
|
18
|
+
* The `consumeChallenge` callback returned `false`, meaning the challenge
|
|
19
|
+
* was missing, expired, or already consumed. Used by {@linkcode withAttestation}.
|
|
20
|
+
*/
|
|
21
|
+
CHALLENGE_INVALID = "CHALLENGE_INVALID",
|
|
22
|
+
/**
|
|
23
|
+
* An internal or storage callback error occurred (used by
|
|
24
|
+
* {@linkcode withAttestation}). The original error is attached via
|
|
25
|
+
* `Error.cause` and is deliberately NOT reflected in the HTTP response
|
|
26
|
+
* body to avoid leaking database schema or driver diagnostics.
|
|
27
|
+
*/
|
|
28
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
9
29
|
}
|
|
30
|
+
/** Thrown when attestation verification fails. */
|
|
10
31
|
export declare class AttestationError extends Error {
|
|
32
|
+
/** Machine-readable error code. */
|
|
11
33
|
readonly code: AttestationErrorCode;
|
|
34
|
+
/** Discriminant for `instanceof` checks in catch blocks. Always `"AttestationError"`. */
|
|
12
35
|
readonly name = "AttestationError";
|
|
13
|
-
|
|
36
|
+
/** Create an AttestationError with a machine-readable code and human-readable message. */
|
|
37
|
+
constructor(
|
|
38
|
+
/** Machine-readable error code. */
|
|
39
|
+
code: AttestationErrorCode, message: string, options?: {
|
|
40
|
+
cause?: unknown;
|
|
41
|
+
});
|
|
14
42
|
}
|
|
43
|
+
/** Error codes returned by {@linkcode AssertionError}. */
|
|
15
44
|
export declare enum AssertionErrorCode {
|
|
45
|
+
/** CBOR decoding or structural validation failed. */
|
|
16
46
|
INVALID_FORMAT = "INVALID_FORMAT",
|
|
47
|
+
/** RP ID hash does not match SHA-256 of the app ID. */
|
|
17
48
|
RP_ID_MISMATCH = "RP_ID_MISMATCH",
|
|
49
|
+
/** Sign count was not greater than the previously stored value. */
|
|
18
50
|
COUNTER_NOT_INCREMENTED = "COUNTER_NOT_INCREMENTED",
|
|
51
|
+
/** ECDSA signature verification failed. */
|
|
19
52
|
SIGNATURE_INVALID = "SIGNATURE_INVALID",
|
|
53
|
+
/** No device key found for the given device ID (used by {@linkcode withAssertion}). */
|
|
20
54
|
DEVICE_NOT_FOUND = "DEVICE_NOT_FOUND",
|
|
21
|
-
|
|
55
|
+
/** An internal or storage callback error occurred (used by {@linkcode withAssertion}). */
|
|
56
|
+
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
57
|
+
/**
|
|
58
|
+
* The `commitSignCount` callback returned `false`, meaning another
|
|
59
|
+
* concurrent request already advanced the stored counter past this value.
|
|
60
|
+
* Indicates an expected race under concurrent load, not a client bug.
|
|
61
|
+
* Used by {@linkcode withAssertion}.
|
|
62
|
+
*/
|
|
63
|
+
SIGN_COUNT_STALE = "SIGN_COUNT_STALE"
|
|
22
64
|
}
|
|
65
|
+
/** Thrown when assertion verification fails. */
|
|
23
66
|
export declare class AssertionError extends Error {
|
|
67
|
+
/** Machine-readable error code. */
|
|
24
68
|
readonly code: AssertionErrorCode;
|
|
69
|
+
/** Discriminant for `instanceof` checks in catch blocks. Always `"AssertionError"`. */
|
|
25
70
|
readonly name = "AssertionError";
|
|
26
|
-
|
|
71
|
+
/** Create an AssertionError with a machine-readable code and human-readable message. */
|
|
72
|
+
constructor(
|
|
73
|
+
/** Machine-readable error code. */
|
|
74
|
+
code: AssertionErrorCode, message: string, options?: {
|
|
27
75
|
cause?: unknown;
|
|
28
76
|
});
|
|
29
77
|
}
|
package/esm/src/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/src/errors.ts"],"names":[],"mappings":"AAEA,oBAAY,oBAAoB;IAC9B,cAAc,mBAAmB;IACjC,yBAAyB,8BAA8B;IACvD,cAAc,mBAAmB;IACjC,cAAc,mBAAmB;IACjC,eAAe,oBAAoB;IACnC,eAAe,oBAAoB;IACnC,cAAc,mBAAmB;CAClC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/src/errors.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,oBAAY,oBAAoB;IAC9B,qDAAqD;IACrD,cAAc,mBAAmB;IACjC,mDAAmD;IACnD,yBAAyB,8BAA8B;IACvD,uEAAuE;IACvE,cAAc,mBAAmB;IACjC,uDAAuD;IACvD,cAAc,mBAAmB;IACjC,0DAA0D;IAC1D,eAAe,oBAAoB;IACnC,yDAAyD;IACzD,eAAe,oBAAoB;IACnC,+EAA+E;IAC/E,cAAc,mBAAmB;IACjC;;;OAGG;IACH,iBAAiB,sBAAsB;IACvC;;;;;OAKG;IACH,cAAc,mBAAmB;CAClC;AAED,kDAAkD;AAClD,qBAAa,gBAAiB,SAAQ,KAAK;IAKvC,mCAAmC;aACnB,IAAI,EAAE,oBAAoB;IAL5C,yFAAyF;IACzF,SAAkB,IAAI,sBAAsB;IAC5C,0FAA0F;;IAExF,mCAAmC;IACnB,IAAI,EAAE,oBAAoB,EAC1C,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAIhC;AAED,0DAA0D;AAC1D,oBAAY,kBAAkB;IAC5B,qDAAqD;IACrD,cAAc,mBAAmB;IACjC,uDAAuD;IACvD,cAAc,mBAAmB;IACjC,mEAAmE;IACnE,uBAAuB,4BAA4B;IACnD,2CAA2C;IAC3C,iBAAiB,sBAAsB;IACvC,uFAAuF;IACvF,gBAAgB,qBAAqB;IACrC,0FAA0F;IAC1F,cAAc,mBAAmB;IACjC;;;;;OAKG;IACH,gBAAgB,qBAAqB;CACtC;AAED,gDAAgD;AAChD,qBAAa,cAAe,SAAQ,KAAK;IAKrC,mCAAmC;aACnB,IAAI,EAAE,kBAAkB;IAL1C,uFAAuF;IACvF,SAAkB,IAAI,oBAAoB;IAC1C,wFAAwF;;IAEtF,mCAAmC;IACnB,IAAI,EAAE,kBAAkB,EACxC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAIhC"}
|