@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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/esm/assertion.d.ts +22 -1
  3. package/esm/assertion.d.ts.map +1 -1
  4. package/esm/assertion.js +22 -2
  5. package/esm/attestation.d.ts +22 -0
  6. package/esm/attestation.d.ts.map +1 -1
  7. package/esm/attestation.js +22 -1
  8. package/esm/mod.d.ts +93 -1
  9. package/esm/mod.d.ts.map +1 -1
  10. package/esm/mod.js +93 -2
  11. package/esm/src/assertion.d.ts +14 -0
  12. package/esm/src/assertion.d.ts.map +1 -1
  13. package/esm/src/assertion.js +9 -0
  14. package/esm/src/attestation.d.ts +20 -1
  15. package/esm/src/attestation.d.ts.map +1 -1
  16. package/esm/src/attestation.js +15 -4
  17. package/esm/src/errors.d.ts +52 -4
  18. package/esm/src/errors.d.ts.map +1 -1
  19. package/esm/src/errors.js +47 -3
  20. package/esm/src/with-assertion.d.ts +63 -1
  21. package/esm/src/with-assertion.d.ts.map +1 -1
  22. package/esm/src/with-assertion.js +36 -3
  23. package/esm/src/with-attestation.d.ts +79 -0
  24. package/esm/src/with-attestation.d.ts.map +1 -0
  25. package/esm/src/with-attestation.js +135 -0
  26. package/package.json +4 -7
  27. package/esm/_dnt.test_polyfills.d.ts.map +0 -1
  28. package/esm/_dnt.test_shims.d.ts.map +0 -1
  29. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts.map +0 -1
  30. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts.map +0 -1
  31. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts.map +0 -1
  32. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts.map +0 -1
  33. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts.map +0 -1
  34. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts.map +0 -1
  35. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts.map +0 -1
  36. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts.map +0 -1
  37. package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts.map +0 -1
  38. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts.map +0 -1
  39. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts.map +0 -1
  40. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts.map +0 -1
  41. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts.map +0 -1
  42. package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts.map +0 -1
  43. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts.map +0 -1
  44. package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts.map +0 -1
  45. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts.map +0 -1
  46. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts.map +0 -1
  47. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts.map +0 -1
  48. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts.map +0 -1
  49. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts.map +0 -1
  50. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts.map +0 -1
  51. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts.map +0 -1
  52. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts.map +0 -1
  53. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts.map +0 -1
  54. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts.map +0 -1
  55. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts.map +0 -1
  56. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts.map +0 -1
  57. package/esm/deps/jsr.io/@std/internal/1.0.12/build_message.d.ts.map +0 -1
  58. package/esm/deps/jsr.io/@std/internal/1.0.12/diff.d.ts.map +0 -1
  59. package/esm/deps/jsr.io/@std/internal/1.0.12/diff_str.d.ts.map +0 -1
  60. package/esm/deps/jsr.io/@std/internal/1.0.12/format.d.ts.map +0 -1
  61. package/esm/deps/jsr.io/@std/internal/1.0.12/styles.d.ts.map +0 -1
  62. package/esm/deps/jsr.io/@std/internal/1.0.12/types.d.ts.map +0 -1
  63. package/esm/src/cose.d.ts.map +0 -1
  64. package/esm/tests/assertion-entry.test.d.ts.map +0 -1
  65. package/esm/tests/assertion.test.d.ts.map +0 -1
  66. package/esm/tests/attestation-entry.test.d.ts.map +0 -1
  67. package/esm/tests/attestation.test.d.ts.map +0 -1
  68. package/esm/tests/authdata.test.d.ts.map +0 -1
  69. package/esm/tests/certificate.test.d.ts.map +0 -1
  70. package/esm/tests/cose.test.d.ts.map +0 -1
  71. package/esm/tests/der.test.d.ts.map +0 -1
  72. package/esm/tests/errors.test.d.ts.map +0 -1
  73. package/esm/tests/fixtures/apple-attestation.d.ts.map +0 -1
  74. package/esm/tests/fixtures/generate-assertion.d.ts.map +0 -1
  75. package/esm/tests/utils.test.d.ts.map +0 -1
  76. package/esm/tests/with-assertion.test.d.ts.map +0 -1
package/esm/src/errors.js CHANGED
@@ -1,23 +1,48 @@
1
1
  // src/errors.ts
2
+ /** Error codes returned by {@linkcode AttestationError}. */
2
3
  export var AttestationErrorCode;
3
4
  (function (AttestationErrorCode) {
5
+ /** CBOR decoding or structural validation failed. */
4
6
  AttestationErrorCode["INVALID_FORMAT"] = "INVALID_FORMAT";
7
+ /** X.509 certificate chain verification failed. */
5
8
  AttestationErrorCode["INVALID_CERTIFICATE_CHAIN"] = "INVALID_CERTIFICATE_CHAIN";
9
+ /** Computed nonce does not match the nonce in the leaf certificate. */
6
10
  AttestationErrorCode["NONCE_MISMATCH"] = "NONCE_MISMATCH";
11
+ /** RP ID hash does not match SHA-256 of the app ID. */
7
12
  AttestationErrorCode["RP_ID_MISMATCH"] = "RP_ID_MISMATCH";
13
+ /** Public key hash does not match the provided key ID. */
8
14
  AttestationErrorCode["KEY_ID_MISMATCH"] = "KEY_ID_MISMATCH";
15
+ /** Sign count is not zero (required for attestation). */
9
16
  AttestationErrorCode["INVALID_COUNTER"] = "INVALID_COUNTER";
17
+ /** AAGUID does not match the expected environment (production/development). */
10
18
  AttestationErrorCode["INVALID_AAGUID"] = "INVALID_AAGUID";
19
+ /**
20
+ * The `consumeChallenge` callback returned `false`, meaning the challenge
21
+ * was missing, expired, or already consumed. Used by {@linkcode withAttestation}.
22
+ */
23
+ AttestationErrorCode["CHALLENGE_INVALID"] = "CHALLENGE_INVALID";
24
+ /**
25
+ * An internal or storage callback error occurred (used by
26
+ * {@linkcode withAttestation}). The original error is attached via
27
+ * `Error.cause` and is deliberately NOT reflected in the HTTP response
28
+ * body to avoid leaking database schema or driver diagnostics.
29
+ */
30
+ AttestationErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
11
31
  })(AttestationErrorCode || (AttestationErrorCode = {}));
32
+ /** Thrown when attestation verification fails. */
12
33
  export class AttestationError extends Error {
13
- constructor(code, message) {
14
- super(message);
34
+ /** Create an AttestationError with a machine-readable code and human-readable message. */
35
+ constructor(
36
+ /** Machine-readable error code. */
37
+ code, message, options) {
38
+ super(message, options);
15
39
  Object.defineProperty(this, "code", {
16
40
  enumerable: true,
17
41
  configurable: true,
18
42
  writable: true,
19
43
  value: code
20
44
  });
45
+ /** Discriminant for `instanceof` checks in catch blocks. Always `"AttestationError"`. */
21
46
  Object.defineProperty(this, "name", {
22
47
  enumerable: true,
23
48
  configurable: true,
@@ -26,17 +51,35 @@ export class AttestationError extends Error {
26
51
  });
27
52
  }
28
53
  }
54
+ /** Error codes returned by {@linkcode AssertionError}. */
29
55
  export var AssertionErrorCode;
30
56
  (function (AssertionErrorCode) {
57
+ /** CBOR decoding or structural validation failed. */
31
58
  AssertionErrorCode["INVALID_FORMAT"] = "INVALID_FORMAT";
59
+ /** RP ID hash does not match SHA-256 of the app ID. */
32
60
  AssertionErrorCode["RP_ID_MISMATCH"] = "RP_ID_MISMATCH";
61
+ /** Sign count was not greater than the previously stored value. */
33
62
  AssertionErrorCode["COUNTER_NOT_INCREMENTED"] = "COUNTER_NOT_INCREMENTED";
63
+ /** ECDSA signature verification failed. */
34
64
  AssertionErrorCode["SIGNATURE_INVALID"] = "SIGNATURE_INVALID";
65
+ /** No device key found for the given device ID (used by {@linkcode withAssertion}). */
35
66
  AssertionErrorCode["DEVICE_NOT_FOUND"] = "DEVICE_NOT_FOUND";
67
+ /** An internal or storage callback error occurred (used by {@linkcode withAssertion}). */
36
68
  AssertionErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
69
+ /**
70
+ * The `commitSignCount` callback returned `false`, meaning another
71
+ * concurrent request already advanced the stored counter past this value.
72
+ * Indicates an expected race under concurrent load, not a client bug.
73
+ * Used by {@linkcode withAssertion}.
74
+ */
75
+ AssertionErrorCode["SIGN_COUNT_STALE"] = "SIGN_COUNT_STALE";
37
76
  })(AssertionErrorCode || (AssertionErrorCode = {}));
77
+ /** Thrown when assertion verification fails. */
38
78
  export class AssertionError extends Error {
39
- constructor(code, message, options) {
79
+ /** Create an AssertionError with a machine-readable code and human-readable message. */
80
+ constructor(
81
+ /** Machine-readable error code. */
82
+ code, message, options) {
40
83
  super(message, options);
41
84
  Object.defineProperty(this, "code", {
42
85
  enumerable: true,
@@ -44,6 +87,7 @@ export class AssertionError extends Error {
44
87
  writable: true,
45
88
  value: code
46
89
  });
90
+ /** Discriminant for `instanceof` checks in catch blocks. Always `"AssertionError"`. */
47
91
  Object.defineProperty(this, "name", {
48
92
  enumerable: true,
49
93
  configurable: true,
@@ -1,27 +1,89 @@
1
1
  import { AssertionError } from "./errors.js";
2
+ /** Default HTTP header name for the base64-encoded assertion. */
2
3
  export declare const DEFAULT_ASSERTION_HEADER = "X-App-Attest-Assertion";
4
+ /** Default HTTP header name for the device identifier. */
3
5
  export declare const DEFAULT_DEVICE_ID_HEADER = "X-App-Attest-Device-Id";
6
+ /** Stored device key material returned by the `getDeviceKey` callback. */
4
7
  export type DeviceKey = {
8
+ /** PEM-encoded ECDSA P-256 public key from attestation. */
5
9
  publicKeyPem: string;
10
+ /** Last verified sign count for this device. */
6
11
  signCount: number;
7
12
  };
13
+ /**
14
+ * Library-internal timing spans for an assertion verification, in
15
+ * milliseconds. Exposed on {@linkcode AssertionContext.timings} so the
16
+ * wrapped handler can emit them as part of its own Server-Timing response.
17
+ */
18
+ export type AssertionTimings = {
19
+ /** Parse request headers and read raw body bytes. */
20
+ extractMs: number;
21
+ /** `getDeviceKey` callback wall-clock duration. */
22
+ getDeviceKeyMs: number;
23
+ /** Cryptographic verification (CBOR decode, ECDSA verify, counter check). */
24
+ verifyMs: number;
25
+ /** `commitSignCount` callback wall-clock duration. */
26
+ commitMs: number;
27
+ };
28
+ /** Context passed to the inner handler after successful assertion verification. */
8
29
  export type AssertionContext = {
30
+ /** Device identifier from the request. */
9
31
  deviceId: string;
32
+ /** Updated sign count after verification. */
10
33
  signCount: number;
34
+ /** Raw request body bytes (the client data that was signed). */
11
35
  rawBody: Uint8Array;
36
+ /** Library-internal timings, ready to merge into Server-Timing. */
37
+ timings: AssertionTimings;
12
38
  };
39
+ /** Custom function to extract assertion data from an incoming request. */
13
40
  export type ExtractAssertionFn = (req: Request) => Promise<{
14
41
  assertion: string;
15
42
  deviceId: string;
16
43
  clientData: Uint8Array;
17
44
  }>;
45
+ /** Configuration for the {@linkcode withAssertion} middleware. */
18
46
  export type WithAssertionOptions = {
47
+ /** Apple App ID in the format `TEAMID.bundleId`. */
19
48
  appId: string;
49
+ /** Set to `true` for development environment attestations. */
20
50
  developmentEnv?: boolean;
51
+ /** Retrieve the stored device key for a given device ID. Return `null` if not found. */
21
52
  getDeviceKey: (deviceId: string) => Promise<DeviceKey | null>;
22
- updateSignCount: (deviceId: string, newSignCount: number) => Promise<void>;
53
+ /**
54
+ * Atomically advance the stored sign count. MUST be implemented as a
55
+ * compare-and-swap: only update if the currently stored value is strictly
56
+ * less than `newSignCount`. Returns `true` if the row was advanced,
57
+ * `false` if another concurrent request already advanced past this value.
58
+ *
59
+ * Recommended SQL pattern against Postgres:
60
+ *
61
+ * ```sql
62
+ * UPDATE app_attest_devices
63
+ * SET sign_count = $1, last_seen_at = now()
64
+ * WHERE device_id = $2 AND sign_count < $1
65
+ * ```
66
+ *
67
+ * Return `rowCount > 0`. The library converts `false` into
68
+ * `AssertionError(SIGN_COUNT_STALE)`.
69
+ */
70
+ commitSignCount: (deviceId: string, newSignCount: number) => Promise<boolean>;
71
+ /** Override the default header-based assertion extraction. */
23
72
  extractAssertion?: ExtractAssertionFn;
73
+ /** Custom error response handler. Defaults to JSON error responses. */
24
74
  onError?: (error: AssertionError, req: Request) => Response | Promise<Response>;
25
75
  };
76
+ /**
77
+ * Request handler middleware that verifies App Attest assertions.
78
+ *
79
+ * Wraps a handler function with automatic assertion verification,
80
+ * device key lookup, and atomic sign-count commit. Returns a new handler
81
+ * that rejects unauthenticated requests with appropriate HTTP error responses.
82
+ *
83
+ * The `commitSignCount` callback MUST implement compare-and-swap semantics
84
+ * (see {@linkcode WithAssertionOptions.commitSignCount}) — a non-atomic
85
+ * unconditional write will silently corrupt replay protection under
86
+ * concurrent load.
87
+ */
26
88
  export declare function withAssertion(options: WithAssertionOptions, handler: (req: Request, context: AssertionContext) => Response | Promise<Response>): (req: Request) => Promise<Response>;
27
89
  //# sourceMappingURL=with-assertion.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"with-assertion.d.ts","sourceRoot":"","sources":["../../src/src/with-assertion.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAsB,MAAM,aAAa,CAAC;AAEjE,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AACjE,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AAEjE,MAAM,MAAM,SAAS,GAAG;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC9D,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,gBAAgB,CAAC,EAAE,kBAAkB,CAAC;IACtC,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,OAAO,KACT,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC,CAAC;AAmCF,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,EAC7B,OAAO,EAAE,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,gBAAgB,KACtB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAuErC"}
1
+ {"version":3,"file":"with-assertion.d.ts","sourceRoot":"","sources":["../../src/src/with-assertion.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAsB,MAAM,aAAa,CAAC;AAEjE,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AACjE,0DAA0D;AAC1D,eAAO,MAAM,wBAAwB,2BAA2B,CAAC;AAEjE,0EAA0E;AAC1E,MAAM,MAAM,SAAS,GAAG;IACtB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,mFAAmF;AACnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,OAAO,EAAE,UAAU,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,gBAAgB,CAAC;CAC3B,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;CACxB,CAAC,CAAC;AAEH,kEAAkE;AAClE,MAAM,MAAM,oBAAoB,GAAG;IACjC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wFAAwF;IACxF,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAC9D;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,kBAAkB,CAAC;IACtC,uEAAuE;IACvE,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,cAAc,EACrB,GAAG,EAAE,OAAO,KACT,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC,CAAC;AAmCF;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,EAC7B,OAAO,EAAE,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,gBAAgB,KACtB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CA+FrC"}
@@ -1,7 +1,9 @@
1
1
  // src/with-assertion.ts
2
2
  import { verifyAssertion } from "./assertion.js";
3
3
  import { AssertionError, AssertionErrorCode } from "./errors.js";
4
+ /** Default HTTP header name for the base64-encoded assertion. */
4
5
  export const DEFAULT_ASSERTION_HEADER = "X-App-Attest-Assertion";
6
+ /** Default HTTP header name for the device identifier. */
5
7
  export const DEFAULT_DEVICE_ID_HEADER = "X-App-Attest-Device-Id";
6
8
  async function defaultExtractAssertion(req) {
7
9
  const assertion = req.headers.get(DEFAULT_ASSERTION_HEADER);
@@ -20,6 +22,18 @@ function defaultErrorResponse(error) {
20
22
  : 401;
21
23
  return new Response(JSON.stringify({ error: error.message, code: error.code }), { status, headers: { "Content-Type": "application/json" } });
22
24
  }
25
+ /**
26
+ * Request handler middleware that verifies App Attest assertions.
27
+ *
28
+ * Wraps a handler function with automatic assertion verification,
29
+ * device key lookup, and atomic sign-count commit. Returns a new handler
30
+ * that rejects unauthenticated requests with appropriate HTTP error responses.
31
+ *
32
+ * The `commitSignCount` callback MUST implement compare-and-swap semantics
33
+ * (see {@linkcode WithAssertionOptions.commitSignCount}) — a non-atomic
34
+ * unconditional write will silently corrupt replay protection under
35
+ * concurrent load.
36
+ */
23
37
  export function withAssertion(options, handler) {
24
38
  const appInfo = {
25
39
  appId: options.appId,
@@ -30,11 +44,20 @@ export function withAssertion(options, handler) {
30
44
  let deviceId;
31
45
  let clientData;
32
46
  let newSignCount;
33
- // Steps 1-4: extract, verify, update sign count
47
+ const timings = {
48
+ extractMs: 0,
49
+ getDeviceKeyMs: 0,
50
+ verifyMs: 0,
51
+ commitMs: 0,
52
+ };
53
+ // Steps 1-4: extract, verify, commit sign count
34
54
  try {
55
+ const extractStart = performance.now();
35
56
  const extracted = await extract(req);
57
+ timings.extractMs = performance.now() - extractStart;
36
58
  deviceId = extracted.deviceId;
37
59
  clientData = extracted.clientData;
60
+ const getKeyStart = performance.now();
38
61
  let deviceKey;
39
62
  try {
40
63
  deviceKey = await options.getDeviceKey(deviceId);
@@ -42,15 +65,24 @@ export function withAssertion(options, handler) {
42
65
  catch (err) {
43
66
  throw new AssertionError(AssertionErrorCode.INTERNAL_ERROR, "Storage callback failed", { cause: err });
44
67
  }
68
+ timings.getDeviceKeyMs = performance.now() - getKeyStart;
45
69
  if (!deviceKey) {
46
70
  throw new AssertionError(AssertionErrorCode.DEVICE_NOT_FOUND, "Device not found");
47
71
  }
72
+ const verifyStart = performance.now();
48
73
  const result = await verifyAssertion(appInfo, extracted.assertion, clientData, deviceKey.publicKeyPem, deviceKey.signCount);
74
+ timings.verifyMs = performance.now() - verifyStart;
75
+ const commitStart = performance.now();
76
+ let committed;
49
77
  try {
50
- await options.updateSignCount(deviceId, result.signCount);
78
+ committed = await options.commitSignCount(deviceId, result.signCount);
51
79
  }
52
80
  catch (err) {
53
- throw new AssertionError(AssertionErrorCode.INTERNAL_ERROR, "Failed to update sign count", { cause: err });
81
+ throw new AssertionError(AssertionErrorCode.INTERNAL_ERROR, "Failed to commit sign count", { cause: err });
82
+ }
83
+ timings.commitMs = performance.now() - commitStart;
84
+ if (!committed) {
85
+ throw new AssertionError(AssertionErrorCode.SIGN_COUNT_STALE, `Sign count ${result.signCount} is stale — another concurrent request already advanced past it`);
54
86
  }
55
87
  newSignCount = result.signCount;
56
88
  }
@@ -67,6 +99,7 @@ export function withAssertion(options, handler) {
67
99
  deviceId,
68
100
  signCount: newSignCount,
69
101
  rawBody: clientData,
102
+ timings,
70
103
  });
71
104
  };
72
105
  }
@@ -0,0 +1,79 @@
1
+ import { AttestationError } from "./errors.js";
2
+ /**
3
+ * Library-internal timing spans for an attestation verification, in
4
+ * milliseconds. Exposed on {@linkcode AttestationContext.timings}.
5
+ */
6
+ export type AttestationTimings = {
7
+ /** Parse request body + decode base64 fields. */
8
+ extractMs: number;
9
+ /** `consumeChallenge` callback wall-clock duration. */
10
+ consumeChallengeMs: number;
11
+ /** Cryptographic verification (CBOR decode, cert chain, nonce, key extract). */
12
+ verifyMs: number;
13
+ /** `storeDeviceKey` callback wall-clock duration. */
14
+ storeDeviceKeyMs: number;
15
+ };
16
+ /** Context passed to the inner handler after successful attestation verification. */
17
+ export type AttestationContext = {
18
+ /** Device identifier (Apple-issued `keyId`) from the request. */
19
+ deviceId: string;
20
+ /** PEM-encoded ECDSA P-256 public key extracted from the attestation. */
21
+ publicKeyPem: string;
22
+ /** Initial sign count from the attestation (always `0`). */
23
+ signCount: number;
24
+ /** Raw App Attest receipt bytes. */
25
+ receipt: Uint8Array;
26
+ /** Library-internal timings, ready to merge into Server-Timing. */
27
+ timings: AttestationTimings;
28
+ };
29
+ /** Custom function to extract attestation data from an incoming request. */
30
+ export type ExtractAttestationFn = (req: Request) => Promise<{
31
+ deviceId: string;
32
+ challenge: Uint8Array;
33
+ attestation: Uint8Array;
34
+ }>;
35
+ /** Configuration for the {@linkcode withAttestation} middleware. */
36
+ export type WithAttestationOptions = {
37
+ /** Apple App ID in the format `TEAMID.bundleId`. */
38
+ appId: string;
39
+ /** Set to `true` for development environment attestations. */
40
+ developmentEnv?: boolean;
41
+ /**
42
+ * Atomically consume a previously-issued challenge. Return `true` if the
43
+ * challenge was valid, unused, and unexpired (and is now consumed);
44
+ * `false` otherwise. Implementations should use `DELETE ... RETURNING`
45
+ * to guarantee single-use semantics.
46
+ *
47
+ * The library converts `false` into `AttestationError(CHALLENGE_INVALID)`.
48
+ */
49
+ consumeChallenge: (challenge: Uint8Array) => Promise<boolean>;
50
+ /**
51
+ * Persist the verified device key row. Caller chooses INSERT vs UPSERT —
52
+ * re-attesting an existing deviceId is cryptographically safe (Apple has
53
+ * re-signed) so UPSERT is usually correct.
54
+ */
55
+ storeDeviceKey: (row: {
56
+ deviceId: string;
57
+ publicKeyPem: string;
58
+ signCount: number;
59
+ receipt: Uint8Array;
60
+ }) => Promise<void>;
61
+ /** Override the default body-based attestation extraction. */
62
+ extractAttestation?: ExtractAttestationFn;
63
+ /** Custom error response handler. Defaults to JSON error responses. */
64
+ onError?: (error: AttestationError, req: Request) => Response | Promise<Response>;
65
+ };
66
+ /**
67
+ * Request handler middleware that verifies App Attest attestations.
68
+ *
69
+ * Wraps a handler with automatic challenge consumption, cryptographic
70
+ * attestation verification, and device key persistence. Returns a new
71
+ * handler that rejects invalid attestations with appropriate HTTP
72
+ * error responses.
73
+ *
74
+ * The symmetric pair of {@linkcode withAssertion} — use this on your
75
+ * one-time device registration endpoint, then use `withAssertion` on
76
+ * every protected business endpoint.
77
+ */
78
+ export declare function withAttestation(options: WithAttestationOptions, handler: (req: Request, context: AttestationContext) => Response | Promise<Response>): (req: Request) => Promise<Response>;
79
+ //# sourceMappingURL=with-attestation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-attestation.d.ts","sourceRoot":"","sources":["../../src/src/with-attestation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAwB,MAAM,aAAa,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gFAAgF;IAChF,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,EAAE,UAAU,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,UAAU,CAAC;IACtB,WAAW,EAAE,UAAU,CAAC;CACzB,CAAC,CAAC;AAEH,oEAAoE;AACpE,MAAM,MAAM,sBAAsB,GAAG;IACnC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;OAOG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D;;;;OAIG;IACH,cAAc,EAAE,CAAC,GAAG,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,UAAU,CAAC;KACrB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,oBAAoB,CAAC;IAC1C,uEAAuE;IACvE,OAAO,CAAC,EAAE,CACR,KAAK,EAAE,gBAAgB,EACvB,GAAG,EAAE,OAAO,KACT,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACnC,CAAC;AAwEF;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,CACP,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,kBAAkB,KACxB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAChC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAkGrC"}
@@ -0,0 +1,135 @@
1
+ // src/with-attestation.ts
2
+ import { decodeBase64 } from "../deps/jsr.io/@std/encoding/1.0.10/base64.js";
3
+ import { verifyAttestation } from "./attestation.js";
4
+ import { AttestationError, AttestationErrorCode } from "./errors.js";
5
+ /**
6
+ * Default extractor: reads a JSON body of the shape
7
+ * `{ keyId: string, challenge: string, attestation: string }` where all
8
+ * three fields are base64-encoded per Apple's standard wire format.
9
+ */
10
+ async function defaultExtractAttestation(req) {
11
+ let body;
12
+ try {
13
+ body = await req.json();
14
+ }
15
+ catch (err) {
16
+ throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, `Failed to parse attestation request body as JSON: ${err instanceof Error ? err.message : String(err)}`);
17
+ }
18
+ if (typeof body !== "object" || body === null ||
19
+ typeof body.keyId !== "string" ||
20
+ typeof body.challenge !== "string" ||
21
+ typeof body.attestation !== "string") {
22
+ throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, "Attestation request body must include { keyId, challenge, attestation } as base64 strings");
23
+ }
24
+ const typed = body;
25
+ let challenge;
26
+ let attestation;
27
+ try {
28
+ challenge = decodeBase64(typed.challenge);
29
+ }
30
+ catch {
31
+ throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, "challenge is not valid base64");
32
+ }
33
+ try {
34
+ attestation = decodeBase64(typed.attestation);
35
+ }
36
+ catch {
37
+ throw new AttestationError(AttestationErrorCode.INVALID_FORMAT, "attestation is not valid base64");
38
+ }
39
+ return { deviceId: typed.keyId, challenge, attestation };
40
+ }
41
+ function defaultErrorResponse(error) {
42
+ const status = error.code === AttestationErrorCode.INTERNAL_ERROR
43
+ ? 500
44
+ : error.code === AttestationErrorCode.INVALID_FORMAT
45
+ ? 400
46
+ : 401;
47
+ return new Response(JSON.stringify({ error: error.message, code: error.code }), { status, headers: { "Content-Type": "application/json" } });
48
+ }
49
+ /**
50
+ * Request handler middleware that verifies App Attest attestations.
51
+ *
52
+ * Wraps a handler with automatic challenge consumption, cryptographic
53
+ * attestation verification, and device key persistence. Returns a new
54
+ * handler that rejects invalid attestations with appropriate HTTP
55
+ * error responses.
56
+ *
57
+ * The symmetric pair of {@linkcode withAssertion} — use this on your
58
+ * one-time device registration endpoint, then use `withAssertion` on
59
+ * every protected business endpoint.
60
+ */
61
+ export function withAttestation(options, handler) {
62
+ const appInfo = {
63
+ appId: options.appId,
64
+ developmentEnv: options.developmentEnv ?? false,
65
+ };
66
+ const extract = options.extractAttestation ?? defaultExtractAttestation;
67
+ return async (req) => {
68
+ let deviceId;
69
+ let publicKeyPem;
70
+ let receipt;
71
+ const timings = {
72
+ extractMs: 0,
73
+ consumeChallengeMs: 0,
74
+ verifyMs: 0,
75
+ storeDeviceKeyMs: 0,
76
+ };
77
+ try {
78
+ const extractStart = performance.now();
79
+ const extracted = await extract(req);
80
+ timings.extractMs = performance.now() - extractStart;
81
+ deviceId = extracted.deviceId;
82
+ const consumeStart = performance.now();
83
+ let challengeOk;
84
+ try {
85
+ challengeOk = await options.consumeChallenge(extracted.challenge);
86
+ }
87
+ catch (err) {
88
+ // Static message — the original error is attached via `cause` and
89
+ // never reaches the wire. Callback errors from Postgres drivers
90
+ // routinely contain schema details, constraint names, and other
91
+ // info that must not leak to unauthenticated clients.
92
+ throw new AttestationError(AttestationErrorCode.INTERNAL_ERROR, "consumeChallenge callback failed", { cause: err });
93
+ }
94
+ timings.consumeChallengeMs = performance.now() - consumeStart;
95
+ if (!challengeOk) {
96
+ throw new AttestationError(AttestationErrorCode.CHALLENGE_INVALID, "Challenge is missing, expired, or already consumed");
97
+ }
98
+ const verifyStart = performance.now();
99
+ const result = await verifyAttestation(appInfo, deviceId, extracted.challenge, extracted.attestation);
100
+ timings.verifyMs = performance.now() - verifyStart;
101
+ publicKeyPem = result.publicKeyPem;
102
+ receipt = result.receipt;
103
+ const storeStart = performance.now();
104
+ try {
105
+ await options.storeDeviceKey({
106
+ deviceId,
107
+ publicKeyPem,
108
+ signCount: result.signCount,
109
+ receipt,
110
+ });
111
+ }
112
+ catch (err) {
113
+ // Static message — see consumeChallenge catch above.
114
+ throw new AttestationError(AttestationErrorCode.INTERNAL_ERROR, "storeDeviceKey callback failed", { cause: err });
115
+ }
116
+ timings.storeDeviceKeyMs = performance.now() - storeStart;
117
+ }
118
+ catch (err) {
119
+ // Non-AttestationError escapes (unexpected runtime errors, programmer
120
+ // bugs, etc.) are wrapped as INTERNAL_ERROR with a static message.
121
+ // The original is attached via `cause` and never reaches the wire.
122
+ const error = err instanceof AttestationError
123
+ ? err
124
+ : new AttestationError(AttestationErrorCode.INTERNAL_ERROR, "Internal error", { cause: err });
125
+ return options.onError?.(error, req) ?? defaultErrorResponse(error);
126
+ }
127
+ return await handler(req, {
128
+ deviceId,
129
+ publicKeyPem,
130
+ signCount: 0,
131
+ receipt,
132
+ timings,
133
+ });
134
+ };
135
+ }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@bradford-tech/supabase-integrity-attest",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Verify Apple App Attest attestations and assertions using WebCrypto.",
5
+ "homepage": "https://integrity-attest.bradford.tech",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/bradford-tech/supabase-integrity-attest.git"
@@ -22,18 +23,14 @@
22
23
  "import": "./esm/attestation.js"
23
24
  }
24
25
  },
25
- "scripts": {
26
- "test": "node test_runner.js"
27
- },
26
+ "scripts": {},
28
27
  "dependencies": {
29
28
  "@noble/curves": "^2.0.1",
30
29
  "asn1js": "^3.0.7",
31
30
  "cborg": "^4.5.8"
32
31
  },
33
32
  "devDependencies": {
34
- "@types/node": "^20.9.0",
35
- "picocolors": "^1.0.0",
36
- "@deno/shim-deno-test": "~0.5.0"
33
+ "@types/node": "^20.9.0"
37
34
  },
38
35
  "_generatedBy": "dnt@dev"
39
36
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dnt.test_polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.test_polyfills.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,KAAK;QACb,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB;CACF;AAED,OAAO,EAAE,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"_dnt.test_shims.d.ts","sourceRoot":"","sources":["../src/_dnt.test_shims.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAK5C,eAAO,MAAM,aAAa;;CAA2C,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"almost_equals.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/almost_equals.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,MAAM,QAmBb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"array_includes.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/array_includes.ts"],"names":[],"mappings":"AAMA,0FAA0F;AAC1F,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;AAOpD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EACvB,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EACzB,GAAG,CAAC,EAAE,MAAM,GACX,IAAI,CAgCN"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/assert.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,IAAI,CAI5D"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"assertion_error.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/assertion_error.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,cAAe,SAAQ,KAAK;IACvC;;;;OAIG;gBACS,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"equal.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/equal.ts"],"names":[],"mappings":"AA0FA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAgHrD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"equals.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/equals.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,CAAC,EACX,GAAG,CAAC,EAAE,MAAM,QAmBb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"exists.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/exists.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,MAAM,EAAE,CAAC,EACT,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC,CAOlC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"fail.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/fail.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,CAGxC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"false.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/false.ts"],"names":[],"mappings":"AAIA,uDAAuD;AACvD,MAAM,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAE3D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,IAAI,IAAI,KAAK,CAI1E"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"greater.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/greater.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,QAMpE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"greater_or_equal.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,CAAC,EACX,GAAG,CAAC,EAAE,MAAM,QASb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"instance_of.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/instance_of.ts"],"names":[],"mappings":"AAIA,sBAAsB;AAEtB,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;AACzD,4BAA4B;AAC5B,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,cAAc,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;AAE3E;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAE9B,CAAC,SAAS,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EAE9C,MAAM,EAAE,OAAO,EACf,YAAY,EAAE,CAAC,EACf,GAAG,SAAK,GACP,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC,CAAC,CAAC,CA6BnC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"is_error.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/is_error.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EACnD,KAAK,EAAE,OAAO,EAEd,UAAU,CAAC,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAC/C,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAC5B,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,KAAK,IAAI,CAAC,CA8BpB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"less.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/less.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,QAMjE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"less_or_equal.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/less_or_equal.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,CAAC,EACX,GAAG,CAAC,EAAE,MAAM,QASb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/match.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,QAMb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/mod.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;GAeG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"not_equals.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/not_equals.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,QAUtE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"not_instance_of.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/not_instance_of.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,CAAC,EACtC,MAAM,EAAE,CAAC,EAET,cAAc,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAClD,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAKjC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"not_match.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/not_match.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,QAMb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"not_strict_equals.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,MAAM,EAAE,CAAC,EACT,QAAQ,EAAE,CAAC,EACX,GAAG,CAAC,EAAE,MAAM,QAYb"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"object_match.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/object_match.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,iBAAiB,CAE/B,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,GAAG,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,EACtC,GAAG,CAAC,EAAE,MAAM,GACX,IAAI,CAUN"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"rejects.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/rejects.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,WAAW,CAAC,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAAC;AACpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EACnD,EAAE,EAAE,MAAM,WAAW,CAAC,OAAO,CAAC,EAE9B,UAAU,EAAE,QAAQ,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAC9C,WAAW,CAAC,EAAE,MAAM,EACpB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,CAAC,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"strict_equals.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/jsr.io/@std/assert/1.0.19/strict_equals.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,CAAC,EACX,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,IAAI,CAAC,CAgCrB"}