@spfn/auth 0.2.0-beta.9 → 0.2.0-beta.90

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 (52) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +984 -1743
  3. package/dist/authenticate-CnccboAg.d.ts +1383 -0
  4. package/dist/client-proof.d.ts +677 -0
  5. package/dist/client-proof.js +1814 -0
  6. package/dist/client-proof.js.map +1 -0
  7. package/dist/config.d.ts +487 -39
  8. package/dist/config.js +243 -29
  9. package/dist/config.js.map +1 -1
  10. package/dist/errors.d.ts +208 -3
  11. package/dist/errors.js +140 -1
  12. package/dist/errors.js.map +1 -1
  13. package/dist/index.d.ts +391 -109
  14. package/dist/index.js +186 -7
  15. package/dist/index.js.map +1 -1
  16. package/dist/nextjs/api.js +591 -61
  17. package/dist/nextjs/api.js.map +1 -1
  18. package/dist/nextjs/client.d.ts +28 -0
  19. package/dist/nextjs/client.js +80 -0
  20. package/dist/nextjs/client.js.map +1 -0
  21. package/dist/nextjs/server.d.ts +92 -3
  22. package/dist/nextjs/server.js +288 -24
  23. package/dist/nextjs/server.js.map +1 -1
  24. package/dist/server.d.ts +2495 -1089
  25. package/dist/server.js +6212 -1499
  26. package/dist/server.js.map +1 -1
  27. package/dist/session-CFK4BT25.d.ts +53 -0
  28. package/dist/types-CD95yudz.d.ts +98 -0
  29. package/migrations/20251125021229_premium_famine/snapshot.json +2641 -0
  30. package/migrations/20260225130050_smooth_the_fury/migration.sql +3 -0
  31. package/migrations/20260225130050_smooth_the_fury/snapshot.json +2686 -0
  32. package/migrations/20260308141417_deep_iceman/migration.sql +11 -0
  33. package/migrations/20260308141417_deep_iceman/snapshot.json +2686 -0
  34. package/migrations/20260308151309_perfect_deathbird/migration.sql +3 -0
  35. package/migrations/20260308151309_perfect_deathbird/snapshot.json +2731 -0
  36. package/migrations/20260308201135_concerned_rawhide_kid/migration.sql +5 -0
  37. package/migrations/20260308201135_concerned_rawhide_kid/snapshot.json +2786 -0
  38. package/migrations/20260629103209_lethal_lifeguard/migration.sql +32 -0
  39. package/migrations/20260629103209_lethal_lifeguard/snapshot.json +2786 -0
  40. package/migrations/20260709073531_easy_hardball/migration.sql +24 -0
  41. package/migrations/20260709073531_easy_hardball/snapshot.json +3119 -0
  42. package/migrations/20260714081434_glossy_major_mapleleaf/migration.sql +1 -0
  43. package/migrations/20260714081434_glossy_major_mapleleaf/snapshot.json +3112 -0
  44. package/migrations/20260804105939_amazing_bushwacker/migration.sql +3 -0
  45. package/migrations/20260804105939_amazing_bushwacker/snapshot.json +3112 -0
  46. package/migrations/20260804110033_fat_piledriver/migration.sql +2 -0
  47. package/migrations/20260804110033_fat_piledriver/snapshot.json +3138 -0
  48. package/package.json +60 -46
  49. package/dist/dto-CRlgoCP5.d.ts +0 -645
  50. package/migrations/meta/0000_snapshot.json +0 -1632
  51. package/migrations/meta/_journal.json +0 -13
  52. /package/migrations/{0000_premium_famine.sql → 20251125021229_premium_famine/migration.sql} +0 -0
@@ -0,0 +1,1814 @@
1
+ // src/server/client-proof/canonical-json.ts
2
+ var CanonicalJsonError = class extends Error {
3
+ constructor(code) {
4
+ super(`canonical JSON: ${code}`);
5
+ this.code = code;
6
+ this.name = "CanonicalJsonError";
7
+ }
8
+ };
9
+ var INT64_MIN = -(2n ** 63n);
10
+ var INT64_MAX = 2n ** 63n - 1n;
11
+ function parseCanonicalJson(bytes) {
12
+ let text2;
13
+ try {
14
+ text2 = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
15
+ } catch {
16
+ throw new CanonicalJsonError("INVALID_UTF8");
17
+ }
18
+ const parser = new Parser(text2);
19
+ const value = parser.parseValue();
20
+ parser.skipWhitespace();
21
+ if (!parser.atEnd()) {
22
+ throw new CanonicalJsonError("TRAILING_CONTENT");
23
+ }
24
+ return value;
25
+ }
26
+ function isCanonicalBytes(bytes, value) {
27
+ const encoded = encodeCanonicalJson(value);
28
+ if (encoded.length !== bytes.length) {
29
+ return false;
30
+ }
31
+ for (let i = 0; i < encoded.length; i++) {
32
+ if (encoded[i] !== bytes[i]) {
33
+ return false;
34
+ }
35
+ }
36
+ return true;
37
+ }
38
+ var Parser = class {
39
+ constructor(text2) {
40
+ this.text = text2;
41
+ }
42
+ pos = 0;
43
+ atEnd() {
44
+ return this.pos >= this.text.length;
45
+ }
46
+ skipWhitespace() {
47
+ while (!this.atEnd()) {
48
+ const c = this.text[this.pos];
49
+ if (c === " " || c === " " || c === "\n" || c === "\r") {
50
+ this.pos++;
51
+ continue;
52
+ }
53
+ break;
54
+ }
55
+ }
56
+ parseValue() {
57
+ this.skipWhitespace();
58
+ if (this.atEnd()) {
59
+ throw new CanonicalJsonError("UNEXPECTED_END");
60
+ }
61
+ const c = this.text[this.pos];
62
+ if (c === "{") {
63
+ return this.parseObject();
64
+ }
65
+ if (c === "[") {
66
+ return this.parseArray();
67
+ }
68
+ if (c === '"') {
69
+ return this.parseString();
70
+ }
71
+ if (c === "-" || c >= "0" && c <= "9") {
72
+ return this.parseNumber();
73
+ }
74
+ if (this.text.startsWith("null", this.pos)) {
75
+ this.pos += 4;
76
+ return null;
77
+ }
78
+ if (this.text.startsWith("true", this.pos)) {
79
+ this.pos += 4;
80
+ return true;
81
+ }
82
+ if (this.text.startsWith("false", this.pos)) {
83
+ this.pos += 5;
84
+ return false;
85
+ }
86
+ throw new CanonicalJsonError("INVALID_TOKEN");
87
+ }
88
+ parseObject() {
89
+ this.pos++;
90
+ const members2 = /* @__PURE__ */ new Map();
91
+ this.skipWhitespace();
92
+ if (this.atEnd()) {
93
+ throw new CanonicalJsonError("UNEXPECTED_END");
94
+ }
95
+ if (this.text[this.pos] === "}") {
96
+ this.pos++;
97
+ return members2;
98
+ }
99
+ for (; ; ) {
100
+ this.skipWhitespace();
101
+ if (this.atEnd()) {
102
+ throw new CanonicalJsonError("UNEXPECTED_END");
103
+ }
104
+ if (this.text[this.pos] !== '"') {
105
+ throw new CanonicalJsonError("INVALID_TOKEN");
106
+ }
107
+ const key = this.parseString();
108
+ if (members2.has(key)) {
109
+ throw new CanonicalJsonError("DUPLICATE_KEY");
110
+ }
111
+ this.skipWhitespace();
112
+ if (this.atEnd()) {
113
+ throw new CanonicalJsonError("UNEXPECTED_END");
114
+ }
115
+ if (this.text[this.pos] !== ":") {
116
+ throw new CanonicalJsonError("INVALID_TOKEN");
117
+ }
118
+ this.pos++;
119
+ members2.set(key, this.parseValue());
120
+ this.skipWhitespace();
121
+ if (this.atEnd()) {
122
+ throw new CanonicalJsonError("UNEXPECTED_END");
123
+ }
124
+ const next = this.text[this.pos];
125
+ if (next === ",") {
126
+ this.pos++;
127
+ continue;
128
+ }
129
+ if (next === "}") {
130
+ this.pos++;
131
+ return members2;
132
+ }
133
+ throw new CanonicalJsonError("INVALID_TOKEN");
134
+ }
135
+ }
136
+ parseArray() {
137
+ this.pos++;
138
+ const items = [];
139
+ this.skipWhitespace();
140
+ if (this.atEnd()) {
141
+ throw new CanonicalJsonError("UNEXPECTED_END");
142
+ }
143
+ if (this.text[this.pos] === "]") {
144
+ this.pos++;
145
+ return items;
146
+ }
147
+ for (; ; ) {
148
+ items.push(this.parseValue());
149
+ this.skipWhitespace();
150
+ if (this.atEnd()) {
151
+ throw new CanonicalJsonError("UNEXPECTED_END");
152
+ }
153
+ const next = this.text[this.pos];
154
+ if (next === ",") {
155
+ this.pos++;
156
+ continue;
157
+ }
158
+ if (next === "]") {
159
+ this.pos++;
160
+ return items;
161
+ }
162
+ throw new CanonicalJsonError("INVALID_TOKEN");
163
+ }
164
+ }
165
+ parseString() {
166
+ this.pos++;
167
+ let out = "";
168
+ for (; ; ) {
169
+ if (this.atEnd()) {
170
+ throw new CanonicalJsonError("UNEXPECTED_END");
171
+ }
172
+ const c = this.text[this.pos];
173
+ const code = this.text.charCodeAt(this.pos);
174
+ if (c === '"') {
175
+ this.pos++;
176
+ return out;
177
+ }
178
+ if (c === "\\") {
179
+ out += this.parseEscape();
180
+ continue;
181
+ }
182
+ if (code < 32) {
183
+ throw new CanonicalJsonError("INVALID_TOKEN");
184
+ }
185
+ out += c;
186
+ this.pos++;
187
+ }
188
+ }
189
+ parseEscape() {
190
+ this.pos++;
191
+ if (this.atEnd()) {
192
+ throw new CanonicalJsonError("UNEXPECTED_END");
193
+ }
194
+ const c = this.text[this.pos];
195
+ this.pos++;
196
+ switch (c) {
197
+ case '"':
198
+ return '"';
199
+ case "\\":
200
+ return "\\";
201
+ case "/":
202
+ return "/";
203
+ case "b":
204
+ return "\b";
205
+ case "f":
206
+ return "\f";
207
+ case "n":
208
+ return "\n";
209
+ case "r":
210
+ return "\r";
211
+ case "t":
212
+ return " ";
213
+ case "u":
214
+ return this.parseUnicodeEscape();
215
+ default:
216
+ throw new CanonicalJsonError("INVALID_ESCAPE");
217
+ }
218
+ }
219
+ parseUnicodeEscape() {
220
+ const high = this.readHex4();
221
+ if (high >= 56320 && high <= 57343) {
222
+ throw new CanonicalJsonError("INVALID_ESCAPE");
223
+ }
224
+ if (high < 55296 || high > 56319) {
225
+ return String.fromCharCode(high);
226
+ }
227
+ if (this.text[this.pos] !== "\\" || this.text[this.pos + 1] !== "u") {
228
+ throw new CanonicalJsonError("INVALID_ESCAPE");
229
+ }
230
+ this.pos += 2;
231
+ const low = this.readHex4();
232
+ if (low < 56320 || low > 57343) {
233
+ throw new CanonicalJsonError("INVALID_ESCAPE");
234
+ }
235
+ return String.fromCharCode(high, low);
236
+ }
237
+ readHex4() {
238
+ if (this.pos + 4 > this.text.length) {
239
+ throw new CanonicalJsonError("UNEXPECTED_END");
240
+ }
241
+ const hex = this.text.slice(this.pos, this.pos + 4);
242
+ if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
243
+ throw new CanonicalJsonError("INVALID_ESCAPE");
244
+ }
245
+ this.pos += 4;
246
+ return parseInt(hex, 16);
247
+ }
248
+ parseNumber() {
249
+ const start = this.pos;
250
+ if (this.text[this.pos] === "-") {
251
+ this.pos++;
252
+ }
253
+ if (this.atEnd()) {
254
+ throw new CanonicalJsonError("UNEXPECTED_END");
255
+ }
256
+ const first = this.text[this.pos];
257
+ if (first < "0" || first > "9") {
258
+ throw new CanonicalJsonError("INVALID_TOKEN");
259
+ }
260
+ if (first === "0") {
261
+ this.pos++;
262
+ } else {
263
+ while (!this.atEnd() && this.text[this.pos] >= "0" && this.text[this.pos] <= "9") {
264
+ this.pos++;
265
+ }
266
+ }
267
+ if (!this.atEnd()) {
268
+ const next = this.text[this.pos];
269
+ if (next >= "0" && next <= "9") {
270
+ throw new CanonicalJsonError("INVALID_TOKEN");
271
+ }
272
+ if (next === "." || next === "e" || next === "E") {
273
+ throw new CanonicalJsonError("NON_INTEGER_NUMBER");
274
+ }
275
+ }
276
+ const value = BigInt(this.text.slice(start, this.pos));
277
+ if (value < INT64_MIN || value > INT64_MAX) {
278
+ throw new CanonicalJsonError("INTEGER_OUT_OF_RANGE");
279
+ }
280
+ return value;
281
+ }
282
+ };
283
+ function encodeCanonicalJson(value) {
284
+ return new TextEncoder().encode(encodeToString(value));
285
+ }
286
+ function encodeToString(value) {
287
+ if (value === null) {
288
+ return "null";
289
+ }
290
+ if (typeof value === "boolean") {
291
+ return value ? "true" : "false";
292
+ }
293
+ if (typeof value === "bigint") {
294
+ return value.toString();
295
+ }
296
+ if (typeof value === "string") {
297
+ return encodeString(value);
298
+ }
299
+ if (Array.isArray(value)) {
300
+ return `[${value.map(encodeToString).join(",")}]`;
301
+ }
302
+ const keys = [...value.keys()].sort(compareByCodePoints);
303
+ const members2 = keys.map((key) => `${encodeString(key)}:${encodeToString(value.get(key))}`);
304
+ return `{${members2.join(",")}}`;
305
+ }
306
+ function compareByCodePoints(a, b) {
307
+ let i = 0;
308
+ let j = 0;
309
+ while (i < a.length && j < b.length) {
310
+ const ca = a.codePointAt(i);
311
+ const cb = b.codePointAt(j);
312
+ if (ca !== cb) {
313
+ return ca - cb;
314
+ }
315
+ i += ca > 65535 ? 2 : 1;
316
+ j += cb > 65535 ? 2 : 1;
317
+ }
318
+ return a.length - i - (b.length - j);
319
+ }
320
+ function encodeString(value) {
321
+ let out = '"';
322
+ for (const ch of value) {
323
+ const code = ch.codePointAt(0);
324
+ if (ch === '"') {
325
+ out += '\\"';
326
+ } else if (ch === "\\") {
327
+ out += "\\\\";
328
+ } else if (code === 8) {
329
+ out += "\\b";
330
+ } else if (code === 12) {
331
+ out += "\\f";
332
+ } else if (code === 10) {
333
+ out += "\\n";
334
+ } else if (code === 13) {
335
+ out += "\\r";
336
+ } else if (code === 9) {
337
+ out += "\\t";
338
+ } else if (code < 32) {
339
+ out += `\\u00${code.toString(16).padStart(2, "0")}`;
340
+ } else {
341
+ out += ch;
342
+ }
343
+ }
344
+ return out + '"';
345
+ }
346
+
347
+ // src/server/client-proof/proof.ts
348
+ import { createHash, createPrivateKey, createPublicKey, sign, verify } from "crypto";
349
+ var CLIENT_PROOF_PROFILE = "clientProofV1";
350
+ var ABSENT_BODY_SHA256 = "0".repeat(64);
351
+ var DEFAULT_REPLAY_WINDOW_MILLIS = 3e5;
352
+ var PROOF_INPUT_FIELDS = [
353
+ "profile",
354
+ "method",
355
+ "path",
356
+ "clientId",
357
+ "keyId",
358
+ "nonce",
359
+ "issuedAtMillis",
360
+ "bodySha256"
361
+ ];
362
+ var PROOF_INPUT_SEPARATOR = "\n";
363
+ var PROOF_SIGNATURE_BYTES = 64;
364
+ var PROOF_SIGNATURE_HEX_LENGTH = PROOF_SIGNATURE_BYTES * 2;
365
+ var PROOF_SIGNATURE_PATTERN = /^[0-9a-f]{128}$/;
366
+ var RAW_SIGNATURE_ENCODING = "ieee-p1363";
367
+ var ProofInputError = class extends Error {
368
+ constructor() {
369
+ super("proof input field contains a C0 control character");
370
+ this.name = "ProofInputError";
371
+ }
372
+ };
373
+ function canonicalProofInput(input) {
374
+ const values = {
375
+ profile: CLIENT_PROOF_PROFILE,
376
+ method: input.method,
377
+ path: input.path,
378
+ clientId: input.clientId,
379
+ keyId: input.keyId,
380
+ nonce: input.nonce,
381
+ issuedAtMillis: input.issuedAtMillis.toString(),
382
+ bodySha256: input.bodySha256
383
+ };
384
+ const fields = PROOF_INPUT_FIELDS.map((name) => values[name]);
385
+ for (const field of fields) {
386
+ for (const ch of field) {
387
+ if (ch.codePointAt(0) < 32) {
388
+ throw new ProofInputError();
389
+ }
390
+ }
391
+ }
392
+ return fields.join(PROOF_INPUT_SEPARATOR);
393
+ }
394
+ function parseClientProofPublicKey(spkiDerBase64) {
395
+ const key = createPublicKey({
396
+ key: Buffer.from(spkiDerBase64, "base64"),
397
+ format: "der",
398
+ type: "spki"
399
+ });
400
+ if (key.asymmetricKeyType !== "ec" || key.asymmetricKeyDetails?.namedCurve !== "prime256v1") {
401
+ throw new Error("a clientProofV1 public key must be an ECDSA P-256 key");
402
+ }
403
+ return key;
404
+ }
405
+ function verifyClientProof(input, presentedProof, publicKey) {
406
+ const data = Buffer.from(canonicalProofInput(input), "utf8");
407
+ if (!PROOF_SIGNATURE_PATTERN.test(presentedProof)) {
408
+ return false;
409
+ }
410
+ return verify(
411
+ "sha256",
412
+ data,
413
+ { key: publicKey, dsaEncoding: RAW_SIGNATURE_ENCODING },
414
+ Buffer.from(presentedProof, "hex")
415
+ );
416
+ }
417
+ function signClientProof(input, privateKeyPkcs8DerBase64) {
418
+ const key = createPrivateKey({
419
+ key: Buffer.from(privateKeyPkcs8DerBase64, "base64"),
420
+ format: "der",
421
+ type: "pkcs8"
422
+ });
423
+ return sign(
424
+ "sha256",
425
+ Buffer.from(canonicalProofInput(input), "utf8"),
426
+ { key, dsaEncoding: RAW_SIGNATURE_ENCODING }
427
+ ).toString("hex");
428
+ }
429
+ function sha256Hex(bytes) {
430
+ return createHash("sha256").update(bytes).digest("hex");
431
+ }
432
+
433
+ // src/server/client-proof/refusal.ts
434
+ import { randomBytes } from "crypto";
435
+ var HTTP_STATUS = {
436
+ PROOF_INVALID: 401,
437
+ PROOF_REPLAYED: 401,
438
+ PROOF_EXPIRED: 401,
439
+ SESSION_REVOKED: 401,
440
+ PROFILE_REJECTED: 400,
441
+ CONTRACT_UNSUPPORTED: 409
442
+ };
443
+ function newHexId() {
444
+ return randomBytes(16).toString("hex");
445
+ }
446
+ var ClientProofRefusal = class _ClientProofRefusal {
447
+ constructor(code, message) {
448
+ this.code = code;
449
+ this.message = message;
450
+ }
451
+ get httpStatus() {
452
+ return HTTP_STATUS[this.code];
453
+ }
454
+ /** The canonical bytes of `{"error":{"code":…,"message":…,"requestId":…}}`. */
455
+ envelopeBytes(requestId) {
456
+ const error = /* @__PURE__ */ new Map([
457
+ ["code", this.code],
458
+ ["message", this.message],
459
+ ["requestId", requestId]
460
+ ]);
461
+ return encodeCanonicalJson(/* @__PURE__ */ new Map([["error", error]]));
462
+ }
463
+ /** Nothing request-derived reaches a log through this. */
464
+ toString() {
465
+ return `ClientProofRefusal(${this.code})`;
466
+ }
467
+ // ---- shape: what arrived is not the contract (rule 2) -------------------
468
+ static unroutable() {
469
+ return contractViolation("no operation in this contract answers that method and path");
470
+ }
471
+ static malformedHeaders() {
472
+ return contractViolation("the request does not carry the contract header fields exactly once each");
473
+ }
474
+ static missingContentType() {
475
+ return contractViolation("a request that carries a body must declare the contract content type");
476
+ }
477
+ static bodyTooLarge() {
478
+ return contractViolation("the request body exceeds the size this server accepts");
479
+ }
480
+ /**
481
+ * The body parsed but its bytes are not the canonical form of what it
482
+ * parsed to. Not PROOF_INVALID even though it is discovered next to the
483
+ * proof: the proof over these bytes verifies perfectly well, and an
484
+ * auth-family answer would tell the client to re-handshake and send the
485
+ * same non-canonical bytes again.
486
+ */
487
+ static bodyNotCanonical() {
488
+ return contractViolation("the request body is not the canonical JSON form of the value it encodes");
489
+ }
490
+ static bodyNotTheDeclaredType() {
491
+ return contractViolation("the request body is not the request type this operation declares");
492
+ }
493
+ static sessionHeaderMisplaced() {
494
+ return contractViolation("the session header is present exactly on the operations that require one");
495
+ }
496
+ static unprocessable() {
497
+ return contractViolation("the request could not be processed");
498
+ }
499
+ /**
500
+ * A client that ships separately from the server said nothing about which
501
+ * contract it was built against. Without it the server cannot tell whether
502
+ * the two ends agree, and answering as though they do is what produces the
503
+ * undecodable body this check exists to replace.
504
+ */
505
+ static contractVersionMissing() {
506
+ return contractViolation("a client of this kind must state the contract version it was generated from");
507
+ }
508
+ static contractVersionUnsupported() {
509
+ return contractViolation("the stated contract version is outside the range this server serves");
510
+ }
511
+ // ---- the profile allowlist ----------------------------------------------
512
+ static profileRejected() {
513
+ return new _ClientProofRefusal("PROFILE_REJECTED", "the named auth profile is not on this contract's allowlist");
514
+ }
515
+ // ---- auth: a new session might clear it (rule 1) -------------------------
516
+ static sessionRevoked() {
517
+ return new _ClientProofRefusal("SESSION_REVOKED", "the key or session was revoked");
518
+ }
519
+ static proofExpired() {
520
+ return new _ClientProofRefusal("PROOF_EXPIRED", "issuedAtMillis falls outside the replay window");
521
+ }
522
+ static proofReplayed() {
523
+ return new _ClientProofRefusal("PROOF_REPLAYED", "the nonce was already used inside the replay window");
524
+ }
525
+ static proofInvalid() {
526
+ return new _ClientProofRefusal("PROOF_INVALID", "the client proof did not verify");
527
+ }
528
+ };
529
+ function contractViolation(message) {
530
+ return new ClientProofRefusal("CONTRACT_UNSUPPORTED", message);
531
+ }
532
+
533
+ // src/server/client-proof/replay-store.ts
534
+ import { getCache } from "@spfn/core/cache";
535
+ function replayLedgerKey(clientId, nonce) {
536
+ return JSON.stringify([clientId, nonce]);
537
+ }
538
+ var MemoryReplayLedger = class {
539
+ /** replayLedgerKey(...) → the millis it was spent at. */
540
+ spent = /* @__PURE__ */ new Map();
541
+ isSpent(clientId, nonce) {
542
+ return this.spent.has(replayLedgerKey(clientId, nonce));
543
+ }
544
+ /** Records the pair at `atMillis`; false when it was already spent. */
545
+ spend(clientId, nonce, atMillis) {
546
+ const key = replayLedgerKey(clientId, nonce);
547
+ if (this.spent.has(key)) {
548
+ return false;
549
+ }
550
+ this.spent.set(key, atMillis);
551
+ return true;
552
+ }
553
+ /** Drops entries older than the window, judged against `nowMillis`. */
554
+ prune(nowMillis, windowMillis) {
555
+ for (const [key, spentAtMillis] of this.spent) {
556
+ if (nowMillis - spentAtMillis > windowMillis) {
557
+ this.spent.delete(key);
558
+ }
559
+ }
560
+ }
561
+ get size() {
562
+ return this.spent.size;
563
+ }
564
+ clear() {
565
+ this.spent.clear();
566
+ }
567
+ };
568
+ var MemoryReplayStore = class {
569
+ constructor(windowMillis = DEFAULT_REPLAY_WINDOW_MILLIS) {
570
+ this.windowMillis = windowMillis;
571
+ }
572
+ ledger = new MemoryReplayLedger();
573
+ async isSpent(clientId, nonce) {
574
+ this.ledger.prune(Date.now(), this.windowMillis);
575
+ return this.ledger.isSpent(clientId, nonce);
576
+ }
577
+ async spend(clientId, nonce) {
578
+ const now = Date.now();
579
+ this.ledger.prune(now, this.windowMillis);
580
+ return this.ledger.spend(clientId, nonce, now);
581
+ }
582
+ };
583
+ var RedisReplayStore = class {
584
+ constructor(windowMillis = DEFAULT_REPLAY_WINDOW_MILLIS) {
585
+ this.windowMillis = windowMillis;
586
+ }
587
+ async isSpent(clientId, nonce) {
588
+ return await this.cache().exists(this.key(clientId, nonce)) === 1;
589
+ }
590
+ async spend(clientId, nonce) {
591
+ return await this.cache().set(this.key(clientId, nonce), "1", "PX", this.windowMillis, "NX") === "OK";
592
+ }
593
+ cache() {
594
+ const cache = getCache();
595
+ if (!cache) {
596
+ throw new Error("client-proof replay ledger: cache is not available");
597
+ }
598
+ return cache;
599
+ }
600
+ key(clientId, nonce) {
601
+ return `spfn:auth:client-proof:replay:${sha256Hex(Buffer.from(replayLedgerKey(clientId, nonce), "utf8"))}`;
602
+ }
603
+ };
604
+ var configured = null;
605
+ function configureClientProofReplayStore(store) {
606
+ configured = store;
607
+ }
608
+ function getClientProofReplayStore() {
609
+ configured ??= new MemoryReplayStore();
610
+ return configured;
611
+ }
612
+
613
+ // src/server/client-proof/state.ts
614
+ function systemClock() {
615
+ return { nowMillis: () => Date.now() };
616
+ }
617
+ var TestClock = class {
618
+ constructor(millis) {
619
+ this.millis = millis;
620
+ }
621
+ nowMillis() {
622
+ return this.millis;
623
+ }
624
+ advance(byMillis) {
625
+ this.millis += byMillis;
626
+ }
627
+ };
628
+ var DEFAULT_SESSION_TTL_MILLIS = 6e5;
629
+ var ClientProofState = class {
630
+ replayWindowMillis;
631
+ clock;
632
+ initialPublicKeys;
633
+ publicKeys = /* @__PURE__ */ new Map();
634
+ sessions = /* @__PURE__ */ new Map();
635
+ /** The replay ledger — the shared memory implementation, used dev-only here. */
636
+ spentNonces = new MemoryReplayLedger();
637
+ revokedKeyIds = /* @__PURE__ */ new Set();
638
+ holds = /* @__PURE__ */ new Map();
639
+ initialSessionTtlMillis;
640
+ sessionTtlMillis;
641
+ requestCount = 0;
642
+ handshakeCount = 0;
643
+ echoCount = 0;
644
+ itemsListCount = 0;
645
+ refusalCount = 0;
646
+ constructor(options) {
647
+ this.clock = options.clock ?? systemClock();
648
+ this.initialSessionTtlMillis = options.sessionTtlMillis ?? DEFAULT_SESSION_TTL_MILLIS;
649
+ this.sessionTtlMillis = this.initialSessionTtlMillis;
650
+ this.replayWindowMillis = options.replayWindowMillis ?? DEFAULT_REPLAY_WINDOW_MILLIS;
651
+ this.initialPublicKeys = new Map(
652
+ Object.entries(options.publicKeys).map(([keyId, spki]) => [keyId, parseClientProofPublicKey(spki)])
653
+ );
654
+ for (const [keyId, key] of this.initialPublicKeys) {
655
+ this.publicKeys.set(keyId, key);
656
+ }
657
+ }
658
+ // ---- key registration --------------------------------------------------
659
+ /**
660
+ * Registers (or replaces) the public key `keyId` presents proofs under.
661
+ *
662
+ * @throws when the key is not base64 SPKI DER naming a P-256 key.
663
+ */
664
+ registerPublicKey(keyId, publicKeySpkiDerBase64) {
665
+ this.publicKeys.set(keyId, parseClientProofPublicKey(publicKeySpkiDerBase64));
666
+ }
667
+ // ---- admission ---------------------------------------------------------
668
+ /**
669
+ * Runs the contract's checks in the contract's order and returns the
670
+ * refusal, or null when the request is admitted (spending its nonce).
671
+ */
672
+ admit(args) {
673
+ const now = this.clock.nowMillis();
674
+ this.prune(now);
675
+ if (this.revokedKeyIds.has(args.keyId)) {
676
+ return ClientProofRefusal.sessionRevoked();
677
+ }
678
+ if (args.requiresSession) {
679
+ const session = args.presentedSessionId === null ? void 0 : this.sessions.get(args.presentedSessionId);
680
+ if (session === void 0 || session.expiresAtMillis <= now || session.keyId !== args.keyId || session.clientId !== args.clientId) {
681
+ return ClientProofRefusal.sessionRevoked();
682
+ }
683
+ }
684
+ const age = now - Number(args.proofInput.issuedAtMillis);
685
+ if (age < 0 || age > this.replayWindowMillis) {
686
+ return ClientProofRefusal.proofExpired();
687
+ }
688
+ if (this.spentNonces.isSpent(args.clientId, args.proofInput.nonce)) {
689
+ return ClientProofRefusal.proofReplayed();
690
+ }
691
+ const publicKey = this.publicKeys.get(args.keyId);
692
+ if (publicKey === void 0) {
693
+ return ClientProofRefusal.proofInvalid();
694
+ }
695
+ if (!verifyClientProof(args.proofInput, args.presentedProof, publicKey)) {
696
+ return ClientProofRefusal.proofInvalid();
697
+ }
698
+ this.spentNonces.spend(args.clientId, args.proofInput.nonce, Number(args.proofInput.issuedAtMillis));
699
+ return null;
700
+ }
701
+ // ---- sessions ----------------------------------------------------------
702
+ /** Opens a session and returns its id and the expiry the server advertises. */
703
+ openSession(clientId, keyId) {
704
+ const now = this.clock.nowMillis();
705
+ this.prune(now);
706
+ const sessionId = newHexId();
707
+ const expiresAtMillis = now + this.sessionTtlMillis;
708
+ this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });
709
+ return { sessionId, expiresAtMillis };
710
+ }
711
+ /** Test hook: installs a session with a chosen id (wire-fixture replays). */
712
+ seedSession(sessionId, clientId, keyId, expiresAtMillis) {
713
+ this.sessions.set(sessionId, { clientId, keyId, expiresAtMillis });
714
+ }
715
+ /** Drops every session, as a restart would. Advertised expiries stay told. */
716
+ expireSessions() {
717
+ this.sessions.clear();
718
+ }
719
+ /** Revokes a key and drops the sessions it opened. */
720
+ revokeKey(keyId) {
721
+ this.revokedKeyIds.add(keyId);
722
+ for (const [sessionId, session] of this.sessions) {
723
+ if (session.keyId === keyId) {
724
+ this.sessions.delete(sessionId);
725
+ }
726
+ }
727
+ }
728
+ setSessionTtlMillis(millis) {
729
+ this.sessionTtlMillis = millis;
730
+ }
731
+ /** Returns the state to how it started, counters and registered keys included. */
732
+ reset() {
733
+ this.publicKeys.clear();
734
+ for (const [keyId, key] of this.initialPublicKeys) {
735
+ this.publicKeys.set(keyId, key);
736
+ }
737
+ this.sessions.clear();
738
+ this.spentNonces.clear();
739
+ this.revokedKeyIds.clear();
740
+ this.holds.clear();
741
+ this.sessionTtlMillis = this.initialSessionTtlMillis;
742
+ this.requestCount = 0;
743
+ this.handshakeCount = 0;
744
+ this.echoCount = 0;
745
+ this.itemsListCount = 0;
746
+ this.refusalCount = 0;
747
+ }
748
+ // ---- delays (dev/test only) --------------------------------------------
749
+ /** Makes the next `count` requests to `path` wait `millis` before processing. */
750
+ holdPath(path, millis, count) {
751
+ this.holds.set(path, { millis, remaining: count });
752
+ }
753
+ /** Consumes one configured delay for `path`; returns how long to wait, or 0. */
754
+ takeHoldMillis(path) {
755
+ const hold2 = this.holds.get(path);
756
+ if (hold2 === void 0) {
757
+ return 0;
758
+ }
759
+ hold2.remaining -= 1;
760
+ if (hold2.remaining <= 0) {
761
+ this.holds.delete(path);
762
+ }
763
+ return hold2.millis;
764
+ }
765
+ // ---- counters ----------------------------------------------------------
766
+ recordRequest() {
767
+ this.requestCount += 1;
768
+ }
769
+ recordOperation(operationId) {
770
+ if (operationId === "auth.clientProof.handshake") {
771
+ this.handshakeCount += 1;
772
+ } else if (operationId === "echo.send") {
773
+ this.echoCount += 1;
774
+ } else if (operationId === "items.list") {
775
+ this.itemsListCount += 1;
776
+ }
777
+ }
778
+ recordRefusal() {
779
+ this.refusalCount += 1;
780
+ }
781
+ stats() {
782
+ this.prune(this.clock.nowMillis());
783
+ return {
784
+ requestCount: this.requestCount,
785
+ handshakeCount: this.handshakeCount,
786
+ echoCount: this.echoCount,
787
+ itemsListCount: this.itemsListCount,
788
+ refusalCount: this.refusalCount,
789
+ liveSessionCount: this.sessions.size,
790
+ spentNonceCount: this.spentNonces.size
791
+ };
792
+ }
793
+ nowMillis() {
794
+ return this.clock.nowMillis();
795
+ }
796
+ /** The clock, exposed for the dev control surface's advance-clock route. */
797
+ get clockRef() {
798
+ return this.clock;
799
+ }
800
+ // ---- housekeeping ------------------------------------------------------
801
+ /**
802
+ * Drops what can no longer affect an answer. The nonce predicate is the
803
+ * exact negation of the window check in `admit`: an entry is dropped only
804
+ * once a proof carrying that issuedAtMillis would be refused as expired
805
+ * anyway. Dropping one moment earlier would let a nonce inside the window
806
+ * be spent twice.
807
+ */
808
+ prune(nowMillis) {
809
+ for (const [sessionId, session] of this.sessions) {
810
+ if (session.expiresAtMillis <= nowMillis) {
811
+ this.sessions.delete(sessionId);
812
+ }
813
+ }
814
+ this.spentNonces.prune(nowMillis, this.replayWindowMillis);
815
+ }
816
+ };
817
+
818
+ // src/server/client-proof/admission.ts
819
+ var CLIENT_PROOF_HEADERS = {
820
+ profile: "x-spfn-auth-profile",
821
+ clientId: "x-spfn-client-id",
822
+ keyId: "x-spfn-key-id",
823
+ nonce: "x-spfn-nonce",
824
+ issuedAtMillis: "x-spfn-issued-at",
825
+ proof: "x-spfn-proof",
826
+ session: "x-spfn-session"
827
+ };
828
+ var CLIENT_PROOF_CONTENT_TYPE = "application/json";
829
+ var INT64_MIN2 = -(2n ** 63n);
830
+ var INT64_MAX2 = 2n ** 63n - 1n;
831
+ function admitClientProofRequest(args) {
832
+ const credentials = readCredentials(args.headers);
833
+ if (credentials === null) {
834
+ return refused(ClientProofRefusal.malformedHeaders());
835
+ }
836
+ if (credentials.profile !== CLIENT_PROOF_PROFILE) {
837
+ return refused(ClientProofRefusal.profileRejected());
838
+ }
839
+ if (!isRequestContentType(args.headers.get("content-type"))) {
840
+ return refused(ClientProofRefusal.missingContentType());
841
+ }
842
+ if (args.requiresSession !== (credentials.sessionId !== null)) {
843
+ return refused(ClientProofRefusal.sessionHeaderMisplaced());
844
+ }
845
+ let value;
846
+ try {
847
+ value = parseCanonicalJson(args.body);
848
+ } catch {
849
+ return refused(ClientProofRefusal.bodyNotCanonical());
850
+ }
851
+ if (!isCanonicalBytes(args.body, value)) {
852
+ return refused(ClientProofRefusal.bodyNotCanonical());
853
+ }
854
+ const proofInput = {
855
+ method: args.method,
856
+ path: args.path,
857
+ clientId: credentials.clientId,
858
+ keyId: credentials.keyId,
859
+ nonce: credentials.nonce,
860
+ issuedAtMillis: credentials.issuedAtMillis,
861
+ bodySha256: sha256Hex(args.body)
862
+ };
863
+ let refusal;
864
+ try {
865
+ refusal = args.state.admit({
866
+ clientId: credentials.clientId,
867
+ keyId: credentials.keyId,
868
+ presentedSessionId: credentials.sessionId,
869
+ requiresSession: args.requiresSession,
870
+ proofInput,
871
+ presentedProof: credentials.proof
872
+ });
873
+ } catch {
874
+ return refused(ClientProofRefusal.unprocessable());
875
+ }
876
+ if (refusal !== null) {
877
+ return refused(refusal);
878
+ }
879
+ return { admitted: true, value, credentials };
880
+ }
881
+ function refused(refusal) {
882
+ return { admitted: false, refusal };
883
+ }
884
+ function readCredentials(headers) {
885
+ const profile = headers.get(CLIENT_PROOF_HEADERS.profile);
886
+ const clientId = headers.get(CLIENT_PROOF_HEADERS.clientId);
887
+ const keyId = headers.get(CLIENT_PROOF_HEADERS.keyId);
888
+ const nonce = headers.get(CLIENT_PROOF_HEADERS.nonce);
889
+ const issuedAtRaw = headers.get(CLIENT_PROOF_HEADERS.issuedAtMillis);
890
+ const proof = headers.get(CLIENT_PROOF_HEADERS.proof);
891
+ if (profile === null || clientId === null || keyId === null || nonce === null || issuedAtRaw === null || proof === null) {
892
+ return null;
893
+ }
894
+ const issuedAtMillis = parseInt64(issuedAtRaw);
895
+ if (issuedAtMillis === null) {
896
+ return null;
897
+ }
898
+ return {
899
+ profile,
900
+ clientId,
901
+ keyId,
902
+ nonce,
903
+ issuedAtMillis,
904
+ proof,
905
+ sessionId: headers.get(CLIENT_PROOF_HEADERS.session)
906
+ };
907
+ }
908
+ function parseInt64(raw) {
909
+ if (!/^[+-]?\d{1,19}$/.test(raw)) {
910
+ return null;
911
+ }
912
+ const value = BigInt(raw);
913
+ if (value < INT64_MIN2 || value > INT64_MAX2) {
914
+ return null;
915
+ }
916
+ return value;
917
+ }
918
+ function isRequestContentType(value) {
919
+ if (value === null) {
920
+ return false;
921
+ }
922
+ return value.split(";")[0].trim().toLowerCase() === CLIENT_PROOF_CONTENT_TYPE;
923
+ }
924
+
925
+ // src/server/client-proof/contract-types.ts
926
+ var CONTRACT_OPERATIONS = [
927
+ {
928
+ id: "auth.clientProof.handshake",
929
+ method: "POST",
930
+ path: "/v1/auth/client-proof/handshake",
931
+ authProfile: "clientProofV1",
932
+ requiresSession: false,
933
+ requestType: "HandshakeRequest",
934
+ responseType: "HandshakeResponse",
935
+ summary: "Presents a client proof and opens a session."
936
+ },
937
+ {
938
+ id: "echo.send",
939
+ method: "POST",
940
+ path: "/v1/echo",
941
+ authProfile: "clientProofV1",
942
+ requiresSession: true,
943
+ requestType: "EchoRequest",
944
+ responseType: "EchoResponse",
945
+ summary: "Authenticated round trip used as the smallest real vertical slice."
946
+ },
947
+ {
948
+ id: "items.list",
949
+ method: "POST",
950
+ path: "/v1/items/list",
951
+ authProfile: "clientProofV1",
952
+ requiresSession: true,
953
+ requestType: "ListItemsRequest",
954
+ responseType: "ListItemsResponse",
955
+ summary: "Authenticated paged read covering optional fields and arrays."
956
+ }
957
+ ];
958
+ var AUTH_SURFACE_OPERATIONS = [
959
+ {
960
+ id: "auth.enroll.register",
961
+ method: "POST",
962
+ path: "/_auth/register",
963
+ authProfile: "none",
964
+ requiresSession: false,
965
+ requestType: "RegisterRequest",
966
+ responseType: "RegisterResponse",
967
+ summary: "Registers an account with a verification token and enrolls the client-generated public key."
968
+ },
969
+ {
970
+ id: "auth.enroll.login",
971
+ method: "POST",
972
+ path: "/_auth/login",
973
+ authProfile: "none",
974
+ requiresSession: false,
975
+ requestType: "LoginRequest",
976
+ responseType: "LoginResponse",
977
+ summary: "Authenticates with password credentials and enrolls a fresh client-generated public key."
978
+ },
979
+ {
980
+ id: "auth.enroll.oauthNative",
981
+ method: "POST",
982
+ path: "/_auth/oauth/{provider}/native",
983
+ authProfile: "none",
984
+ requiresSession: false,
985
+ requestType: "OauthNativeRequest",
986
+ responseType: "OauthNativeResponse",
987
+ summary: "Verifies a native/web social id_token server-side and enrolls the client-generated public key."
988
+ },
989
+ {
990
+ id: "auth.keys.rotate",
991
+ method: "POST",
992
+ path: "/_auth/keys/rotate",
993
+ authProfile: "clientProofV1",
994
+ requiresSession: false,
995
+ requestType: "RotateKeyRequest",
996
+ responseType: "RotateKeyResponse",
997
+ summary: "Replaces the authenticated key with a new client-generated public key before its TTL runs out."
998
+ },
999
+ {
1000
+ id: "auth.keys.list",
1001
+ method: "POST",
1002
+ path: "/_auth/keys/list",
1003
+ authProfile: "clientProofV1",
1004
+ requiresSession: false,
1005
+ requestType: "ListKeysRequest",
1006
+ responseType: "ListKeysResponse",
1007
+ summary: "Lists the keys registered to the caller, one per device that can sign for them."
1008
+ },
1009
+ {
1010
+ id: "auth.keys.revoke",
1011
+ method: "POST",
1012
+ path: "/_auth/keys/revoke",
1013
+ authProfile: "clientProofV1",
1014
+ requiresSession: false,
1015
+ requestType: "RevokeKeyRequest",
1016
+ responseType: "RevokeKeyResponse",
1017
+ summary: "Revokes one of the caller's keys, signing that device out."
1018
+ },
1019
+ {
1020
+ id: "auth.keys.revokeAll",
1021
+ method: "POST",
1022
+ path: "/_auth/keys/revoke-all",
1023
+ authProfile: "clientProofV1",
1024
+ requiresSession: false,
1025
+ requestType: "RevokeAllKeysRequest",
1026
+ responseType: "RevokeAllKeysResponse",
1027
+ summary: "Revokes every key the caller has, sparing the calling device unless asked otherwise."
1028
+ }
1029
+ ];
1030
+ var ContractTypeError = class extends Error {
1031
+ constructor() {
1032
+ super("not the declared contract type");
1033
+ this.name = "ContractTypeError";
1034
+ }
1035
+ };
1036
+ function decodeHandshakeRequest(value) {
1037
+ const members2 = objectWithKeys(value, ["clientId", "keyId", "nonce", "issuedAtMillis"], []);
1038
+ return {
1039
+ clientId: text(members2.get("clientId")),
1040
+ keyId: text(members2.get("keyId")),
1041
+ nonce: text(members2.get("nonce")),
1042
+ issuedAtMillis: integer(members2.get("issuedAtMillis"))
1043
+ };
1044
+ }
1045
+ function decodeEchoRequest(value) {
1046
+ const members2 = objectWithKeys(value, ["message", "sequence"], []);
1047
+ return {
1048
+ message: text(members2.get("message")),
1049
+ sequence: integer(members2.get("sequence"))
1050
+ };
1051
+ }
1052
+ function decodeListItemsRequest(value) {
1053
+ const members2 = objectWithKeys(value, ["limit"], ["cursor"]);
1054
+ const request = { limit: integer(members2.get("limit")) };
1055
+ if (members2.has("cursor")) {
1056
+ request.cursor = text(members2.get("cursor"));
1057
+ }
1058
+ return request;
1059
+ }
1060
+ function objectWithKeys(value, required2, optional2) {
1061
+ if (!(value instanceof Map)) {
1062
+ throw new ContractTypeError();
1063
+ }
1064
+ for (const key of required2) {
1065
+ if (!value.has(key)) {
1066
+ throw new ContractTypeError();
1067
+ }
1068
+ }
1069
+ for (const key of value.keys()) {
1070
+ if (!required2.includes(key) && !optional2.includes(key)) {
1071
+ throw new ContractTypeError();
1072
+ }
1073
+ }
1074
+ return value;
1075
+ }
1076
+ function text(value) {
1077
+ if (typeof value !== "string") {
1078
+ throw new ContractTypeError();
1079
+ }
1080
+ return value;
1081
+ }
1082
+ function integer(value) {
1083
+ if (typeof value !== "bigint") {
1084
+ throw new ContractTypeError();
1085
+ }
1086
+ return value;
1087
+ }
1088
+ function encodeHandshakeResponse(sessionId, expiresAtMillis) {
1089
+ return /* @__PURE__ */ new Map([
1090
+ ["sessionId", sessionId],
1091
+ ["expiresAtMillis", expiresAtMillis]
1092
+ ]);
1093
+ }
1094
+ function encodeEchoResponse(message, sequence, serverTimeMillis) {
1095
+ return /* @__PURE__ */ new Map([
1096
+ ["message", message],
1097
+ ["sequence", sequence],
1098
+ ["serverTimeMillis", serverTimeMillis]
1099
+ ]);
1100
+ }
1101
+ function encodeListItemsResponse(items, nextCursor) {
1102
+ const encodedItems = items.map((item) => /* @__PURE__ */ new Map([
1103
+ ["id", item.id],
1104
+ ["name", item.name],
1105
+ ["updatedAtMillis", item.updatedAtMillis]
1106
+ ]));
1107
+ const members2 = /* @__PURE__ */ new Map([["items", encodedItems]]);
1108
+ if (nextCursor !== null) {
1109
+ members2.set("nextCursor", nextCursor);
1110
+ }
1111
+ return members2;
1112
+ }
1113
+
1114
+ // src/server/client-proof/dev-control.ts
1115
+ var CONTROL_PREFIX = "/control/";
1116
+ var CONTROL_TOKEN_HEADER = "x-spfn-reference-control";
1117
+ var HTTP_OK = 200;
1118
+ var HTTP_BAD_REQUEST = 400;
1119
+ var HTTP_FORBIDDEN = 403;
1120
+ var HTTP_NOT_FOUND = 404;
1121
+ var HTTP_CONFLICT = 409;
1122
+ var MAX_CONTROL_BODY_BYTES = 4096;
1123
+ async function handleControlRequest(state, controlToken, path, request) {
1124
+ if (path === "/control/health") {
1125
+ return answer(HTTP_OK, /* @__PURE__ */ new Map([["status", "ok"]]));
1126
+ }
1127
+ if (request.headers.get(CONTROL_TOKEN_HEADER) !== controlToken) {
1128
+ return answer(HTTP_FORBIDDEN, failure("control token"));
1129
+ }
1130
+ const raw = new Uint8Array(await request.arrayBuffer());
1131
+ const body = raw.length > MAX_CONTROL_BODY_BYTES ? raw.slice(0, MAX_CONTROL_BODY_BYTES) : raw;
1132
+ switch (path) {
1133
+ case "/control/stats":
1134
+ return stats(state);
1135
+ case "/control/reset":
1136
+ state.reset();
1137
+ return ok();
1138
+ case "/control/expire-sessions":
1139
+ state.expireSessions();
1140
+ return ok();
1141
+ case "/control/register-key":
1142
+ return registerKey(state, body);
1143
+ case "/control/revoke-key":
1144
+ return revokeKey(state, body);
1145
+ case "/control/session-ttl":
1146
+ return sessionTtl(state, body);
1147
+ case "/control/hold":
1148
+ return hold(state, body);
1149
+ case "/control/advance-clock":
1150
+ return advanceClock(state, body);
1151
+ default:
1152
+ return answer(HTTP_NOT_FOUND, failure("unknown control route"));
1153
+ }
1154
+ }
1155
+ function stats(state) {
1156
+ const counters = state.stats();
1157
+ return answer(HTTP_OK, withOk(/* @__PURE__ */ new Map([
1158
+ ["echoCount", BigInt(counters.echoCount)],
1159
+ ["handshakeCount", BigInt(counters.handshakeCount)],
1160
+ ["itemsListCount", BigInt(counters.itemsListCount)],
1161
+ ["liveSessionCount", BigInt(counters.liveSessionCount)],
1162
+ ["refusalCount", BigInt(counters.refusalCount)],
1163
+ ["requestCount", BigInt(counters.requestCount)],
1164
+ ["spentNonceCount", BigInt(counters.spentNonceCount)]
1165
+ ])));
1166
+ }
1167
+ function registerKey(state, body) {
1168
+ const keyId = stringField(body, "keyId");
1169
+ const publicKey = stringField(body, "publicKey");
1170
+ if (keyId === null) {
1171
+ return badRequest("keyId");
1172
+ }
1173
+ if (publicKey === null) {
1174
+ return badRequest("publicKey");
1175
+ }
1176
+ try {
1177
+ state.registerPublicKey(keyId, publicKey);
1178
+ } catch {
1179
+ return badRequest("publicKey");
1180
+ }
1181
+ return ok();
1182
+ }
1183
+ function revokeKey(state, body) {
1184
+ const keyId = stringField(body, "keyId");
1185
+ if (keyId === null) {
1186
+ return badRequest("keyId");
1187
+ }
1188
+ state.revokeKey(keyId);
1189
+ return ok();
1190
+ }
1191
+ function sessionTtl(state, body) {
1192
+ const ttlMillis = integerField(body, "ttlMillis");
1193
+ if (ttlMillis === null) {
1194
+ return badRequest("ttlMillis");
1195
+ }
1196
+ state.setSessionTtlMillis(Number(ttlMillis));
1197
+ return ok();
1198
+ }
1199
+ function hold(state, body) {
1200
+ const path = stringField(body, "path");
1201
+ const millis = integerField(body, "millis");
1202
+ const count = integerField(body, "count");
1203
+ if (path === null) {
1204
+ return badRequest("path");
1205
+ }
1206
+ if (millis === null) {
1207
+ return badRequest("millis");
1208
+ }
1209
+ if (count === null) {
1210
+ return badRequest("count");
1211
+ }
1212
+ state.holdPath(path, Number(millis), Number(count));
1213
+ return ok();
1214
+ }
1215
+ function advanceClock(state, body) {
1216
+ const clock = state.clockRef;
1217
+ if (!(clock instanceof TestClock)) {
1218
+ return answer(HTTP_CONFLICT, failure("server is running on the system clock"));
1219
+ }
1220
+ const millis = integerField(body, "millis");
1221
+ if (millis === null) {
1222
+ return badRequest("millis");
1223
+ }
1224
+ clock.advance(Number(millis));
1225
+ return ok();
1226
+ }
1227
+ function members(body) {
1228
+ if (body.length === 0) {
1229
+ return /* @__PURE__ */ new Map();
1230
+ }
1231
+ let parsed;
1232
+ try {
1233
+ parsed = parseCanonicalJson(body);
1234
+ } catch {
1235
+ return null;
1236
+ }
1237
+ return parsed instanceof Map ? parsed : null;
1238
+ }
1239
+ function stringField(body, field) {
1240
+ const value = members(body)?.get(field);
1241
+ return typeof value === "string" ? value : null;
1242
+ }
1243
+ function integerField(body, field) {
1244
+ const value = members(body)?.get(field);
1245
+ return typeof value === "bigint" ? value : null;
1246
+ }
1247
+ function badRequest(field) {
1248
+ return answer(HTTP_BAD_REQUEST, failure(`missing or malformed field: ${field}`));
1249
+ }
1250
+ function ok() {
1251
+ return answer(HTTP_OK, withOk(/* @__PURE__ */ new Map()));
1252
+ }
1253
+ function failure(reason) {
1254
+ return /* @__PURE__ */ new Map([["ok", false], ["reason", reason]]);
1255
+ }
1256
+ function withOk(extra) {
1257
+ extra.set("ok", true);
1258
+ return extra;
1259
+ }
1260
+ function answer(status, value) {
1261
+ const bytes = encodeCanonicalJson(value);
1262
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
1263
+ return new Response(buffer, { status, headers: { "content-type": "application/json" } });
1264
+ }
1265
+
1266
+ // src/server/client-proof/contract-bundle.ts
1267
+ import { createHash as createHash2 } from "crypto";
1268
+
1269
+ // src/server/types.ts
1270
+ var KEY_ALGORITHM = ["ES256", "RS256"];
1271
+
1272
+ // src/server/client-proof/wire-headers.ts
1273
+ var CLIENT_IDENTITY_HEADERS = {
1274
+ kind: "x-spfn-client-kind",
1275
+ version: "x-spfn-client-version",
1276
+ contractVersion: "x-spfn-client-contract-version"
1277
+ };
1278
+ var SERVER_CONTRACT_HEADERS = {
1279
+ version: "x-spfn-server-contract-version",
1280
+ supportedRange: "x-spfn-supported-contract-range"
1281
+ };
1282
+ var CLIENT_KINDS = ["web", "ios", "android"];
1283
+ function isAppKind(kind) {
1284
+ return kind !== "web";
1285
+ }
1286
+
1287
+ // src/server/client-proof/contract-bundle.ts
1288
+ var CONTRACT_VERSION = "0.6.0";
1289
+ var CONTRACT_MAJOR = 0;
1290
+ var CONTRACT_SUPPORTED_RANGE = ">=0.6.0 <0.7.0";
1291
+ function required(name, type) {
1292
+ return { name, type, optional: false };
1293
+ }
1294
+ function optional(name, type) {
1295
+ return { name, type, optional: true };
1296
+ }
1297
+ var CONTRACT_TYPES = [
1298
+ {
1299
+ name: "HandshakeRequest",
1300
+ fields: [
1301
+ required("clientId", "string"),
1302
+ required("keyId", "string"),
1303
+ required("nonce", "string"),
1304
+ required("issuedAtMillis", "integer")
1305
+ ]
1306
+ },
1307
+ {
1308
+ name: "HandshakeResponse",
1309
+ fields: [
1310
+ required("sessionId", "string"),
1311
+ required("expiresAtMillis", "integer")
1312
+ ]
1313
+ },
1314
+ {
1315
+ name: "EchoRequest",
1316
+ fields: [
1317
+ required("message", "string"),
1318
+ required("sequence", "integer")
1319
+ ]
1320
+ },
1321
+ {
1322
+ name: "EchoResponse",
1323
+ fields: [
1324
+ required("message", "string"),
1325
+ required("sequence", "integer"),
1326
+ required("serverTimeMillis", "integer")
1327
+ ]
1328
+ },
1329
+ {
1330
+ name: "ListItemsRequest",
1331
+ fields: [
1332
+ required("limit", "integer"),
1333
+ optional("cursor", "string")
1334
+ ]
1335
+ },
1336
+ {
1337
+ name: "Item",
1338
+ fields: [
1339
+ required("id", "string"),
1340
+ required("name", "string"),
1341
+ required("updatedAtMillis", "integer")
1342
+ ]
1343
+ },
1344
+ {
1345
+ name: "ListItemsResponse",
1346
+ fields: [
1347
+ required("items", "array<Item>"),
1348
+ optional("nextCursor", "string")
1349
+ ]
1350
+ },
1351
+ {
1352
+ name: "RegisterRequest",
1353
+ fields: [
1354
+ optional("email", "string"),
1355
+ optional("phone", "string"),
1356
+ required("verificationToken", "string"),
1357
+ required("password", "string"),
1358
+ required("publicKey", "string"),
1359
+ required("keyId", "string"),
1360
+ required("fingerprint", "string"),
1361
+ required("algorithm", "KeyAlgorithm")
1362
+ ]
1363
+ },
1364
+ {
1365
+ name: "RegisterResponse",
1366
+ fields: [
1367
+ required("userId", "string"),
1368
+ required("publicId", "string"),
1369
+ optional("email", "string"),
1370
+ optional("phone", "string")
1371
+ ]
1372
+ },
1373
+ {
1374
+ name: "LoginRequest",
1375
+ fields: [
1376
+ optional("email", "string"),
1377
+ optional("phone", "string"),
1378
+ required("password", "string"),
1379
+ required("publicKey", "string"),
1380
+ required("keyId", "string"),
1381
+ required("fingerprint", "string"),
1382
+ required("algorithm", "KeyAlgorithm"),
1383
+ optional("oldKeyId", "string")
1384
+ ]
1385
+ },
1386
+ {
1387
+ name: "LoginResponse",
1388
+ fields: [
1389
+ required("userId", "string"),
1390
+ required("publicId", "string"),
1391
+ optional("email", "string"),
1392
+ optional("phone", "string"),
1393
+ required("passwordChangeRequired", "boolean")
1394
+ ]
1395
+ },
1396
+ {
1397
+ name: "OauthNativeRequest",
1398
+ fields: [
1399
+ required("idToken", "string"),
1400
+ required("nonce", "string"),
1401
+ optional("accessToken", "string"),
1402
+ required("publicKey", "string"),
1403
+ required("keyId", "string"),
1404
+ required("fingerprint", "string"),
1405
+ required("algorithm", "KeyAlgorithm")
1406
+ ]
1407
+ },
1408
+ {
1409
+ name: "OauthNativeResponse",
1410
+ fields: [
1411
+ required("userId", "string"),
1412
+ required("keyId", "string"),
1413
+ required("isNewUser", "boolean")
1414
+ ]
1415
+ },
1416
+ {
1417
+ name: "RotateKeyRequest",
1418
+ fields: [
1419
+ required("publicKey", "string"),
1420
+ required("keyId", "string"),
1421
+ required("fingerprint", "string"),
1422
+ required("algorithm", "KeyAlgorithm")
1423
+ ]
1424
+ },
1425
+ {
1426
+ name: "RotateKeyResponse",
1427
+ fields: [
1428
+ required("success", "boolean"),
1429
+ required("keyId", "string")
1430
+ ]
1431
+ },
1432
+ {
1433
+ name: "ListKeysRequest",
1434
+ fields: [
1435
+ optional("includeRevoked", "boolean")
1436
+ ]
1437
+ },
1438
+ {
1439
+ name: "KeySummary",
1440
+ fields: [
1441
+ required("keyId", "string"),
1442
+ optional("deviceName", "string"),
1443
+ optional("platform", "string"),
1444
+ required("algorithm", "KeyAlgorithm"),
1445
+ required("fingerprintPrefix", "string"),
1446
+ required("createdAtMillis", "integer"),
1447
+ optional("lastUsedAtMillis", "integer"),
1448
+ optional("expiresAtMillis", "integer"),
1449
+ required("isExpired", "boolean"),
1450
+ required("isActive", "boolean"),
1451
+ optional("revokedAtMillis", "integer")
1452
+ ]
1453
+ },
1454
+ {
1455
+ name: "ListKeysResponse",
1456
+ fields: [
1457
+ required("keys", "array<KeySummary>")
1458
+ ]
1459
+ },
1460
+ {
1461
+ name: "RevokeKeyRequest",
1462
+ fields: [
1463
+ required("keyId", "string")
1464
+ ]
1465
+ },
1466
+ {
1467
+ name: "RevokeKeyResponse",
1468
+ fields: [
1469
+ required("keyId", "string"),
1470
+ required("selfRevoked", "boolean")
1471
+ ]
1472
+ },
1473
+ {
1474
+ name: "RevokeAllKeysRequest",
1475
+ fields: [
1476
+ optional("includeCurrent", "boolean")
1477
+ ]
1478
+ },
1479
+ {
1480
+ name: "RevokeAllKeysResponse",
1481
+ fields: [
1482
+ required("revokedCount", "integer"),
1483
+ required("currentKeyRevoked", "boolean")
1484
+ ]
1485
+ }
1486
+ ];
1487
+ var CONTRACT_ENUMS = [
1488
+ { name: "KeyAlgorithm", values: [...KEY_ALGORITHM] }
1489
+ ];
1490
+ var BUNDLE_FILENAME = "spfn-mobile-contract.json";
1491
+ var BUNDLE_REPO_PATH = `contracts/mobile/${BUNDLE_FILENAME}`;
1492
+
1493
+ // src/server/client-proof/wire-version.ts
1494
+ function readClientIdentity(headers) {
1495
+ const kind = headers.get(CLIENT_IDENTITY_HEADERS.kind);
1496
+ if (kind === null || !isClientKind(kind)) {
1497
+ return null;
1498
+ }
1499
+ return {
1500
+ kind,
1501
+ version: headers.get(CLIENT_IDENTITY_HEADERS.version),
1502
+ contractVersion: headers.get(CLIENT_IDENTITY_HEADERS.contractVersion)
1503
+ };
1504
+ }
1505
+ function isClientKind(value) {
1506
+ return CLIENT_KINDS.includes(value);
1507
+ }
1508
+ function isContractVersionSupported(clientVersion) {
1509
+ const client = parseVersion(clientVersion);
1510
+ if (client === null) {
1511
+ return false;
1512
+ }
1513
+ const server = parseVersion(CONTRACT_VERSION);
1514
+ if (server === null || client.major !== server.major) {
1515
+ return false;
1516
+ }
1517
+ return CONTRACT_MAJOR > 0 || client.minor === server.minor;
1518
+ }
1519
+ function parseVersion(raw) {
1520
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(raw);
1521
+ if (match === null) {
1522
+ return null;
1523
+ }
1524
+ return { major: Number(match[1]), minor: Number(match[2]) };
1525
+ }
1526
+ function judgeClientIdentity(identity) {
1527
+ if (identity === null || !isAppKind(identity.kind)) {
1528
+ return null;
1529
+ }
1530
+ if (identity.contractVersion === null) {
1531
+ return ClientProofRefusal.contractVersionMissing();
1532
+ }
1533
+ if (!isContractVersionSupported(identity.contractVersion)) {
1534
+ return ClientProofRefusal.contractVersionUnsupported();
1535
+ }
1536
+ return null;
1537
+ }
1538
+ function applyServerContractHeaders(headers) {
1539
+ headers.set(SERVER_CONTRACT_HEADERS.version, CONTRACT_VERSION);
1540
+ headers.set(SERVER_CONTRACT_HEADERS.supportedRange, CONTRACT_SUPPORTED_RANGE);
1541
+ }
1542
+ function serverContractHeaders() {
1543
+ return {
1544
+ [SERVER_CONTRACT_HEADERS.version]: CONTRACT_VERSION,
1545
+ [SERVER_CONTRACT_HEADERS.supportedRange]: CONTRACT_SUPPORTED_RANGE
1546
+ };
1547
+ }
1548
+
1549
+ // src/server/client-proof/dev-handler.ts
1550
+ var MAX_BODY_BYTES = 1 << 20;
1551
+ var HTTP_OK2 = 200;
1552
+ var DEV_CATALOGUE = [
1553
+ { id: "item-0001", name: "alpha", updatedAtMillis: 1750000000001n },
1554
+ { id: "item-0002", name: "bravo", updatedAtMillis: 1750000000002n },
1555
+ { id: "item-0003", name: "charlie", updatedAtMillis: 1750000000003n },
1556
+ { id: "item-0004", name: "delta", updatedAtMillis: 1750000000004n },
1557
+ { id: "item-0005", name: "echo", updatedAtMillis: 1750000000005n }
1558
+ ];
1559
+ var DEV_MAX_LIMIT = 100n;
1560
+ function createClientProofDevHandler(options) {
1561
+ const state = new ClientProofState(options);
1562
+ const controlToken = options.controlToken ?? newHexId();
1563
+ const enableControl = options.enableControl ?? true;
1564
+ const log = options.log ?? (() => void 0);
1565
+ async function dispatch(request) {
1566
+ state.recordRequest();
1567
+ const url = new URL(request.url);
1568
+ if (enableControl && url.pathname.startsWith(CONTROL_PREFIX)) {
1569
+ return handleControlRequest(state, controlToken, url.pathname, request);
1570
+ }
1571
+ const operation = url.search === "" ? CONTRACT_OPERATIONS.find((op) => op.path === url.pathname && op.method === request.method) : void 0;
1572
+ if (operation === void 0) {
1573
+ return refuse(ClientProofRefusal.unroutable());
1574
+ }
1575
+ const body = await readBodyCapped(request);
1576
+ if (body === null) {
1577
+ return refuse(ClientProofRefusal.bodyTooLarge());
1578
+ }
1579
+ await waitOutHold(url.pathname);
1580
+ const admission = admitClientProofRequest({
1581
+ state,
1582
+ headers: request.headers,
1583
+ method: operation.method,
1584
+ path: operation.path,
1585
+ requiresSession: operation.requiresSession,
1586
+ body
1587
+ });
1588
+ if (!admission.admitted) {
1589
+ return refuse(admission.refusal);
1590
+ }
1591
+ return apply(operation, admission);
1592
+ }
1593
+ function apply(operation, admission) {
1594
+ let value;
1595
+ try {
1596
+ if (operation.id === "auth.clientProof.handshake") {
1597
+ const request = decodeHandshakeRequest(admission.value);
1598
+ if (request.clientId !== admission.credentials.clientId || request.keyId !== admission.credentials.keyId) {
1599
+ return refuse(ClientProofRefusal.bodyNotTheDeclaredType());
1600
+ }
1601
+ const opened = state.openSession(request.clientId, request.keyId);
1602
+ value = encodeHandshakeResponse(opened.sessionId, BigInt(opened.expiresAtMillis));
1603
+ } else if (operation.id === "echo.send") {
1604
+ const request = decodeEchoRequest(admission.value);
1605
+ value = encodeEchoResponse(request.message, request.sequence, BigInt(state.nowMillis()));
1606
+ } else {
1607
+ const listed = listItems(decodeListItemsRequest(admission.value));
1608
+ if (listed === null) {
1609
+ return refuse(ClientProofRefusal.bodyNotTheDeclaredType());
1610
+ }
1611
+ value = listed;
1612
+ }
1613
+ } catch (error) {
1614
+ if (error instanceof ContractTypeError) {
1615
+ return refuse(ClientProofRefusal.bodyNotTheDeclaredType());
1616
+ }
1617
+ return refuse(ClientProofRefusal.unprocessable());
1618
+ }
1619
+ state.recordOperation(operation.id);
1620
+ return contractResponse(HTTP_OK2, encodeCanonicalJson(value));
1621
+ }
1622
+ function refuse(refusal) {
1623
+ state.recordRefusal();
1624
+ return contractResponse(refusal.httpStatus, refusal.envelopeBytes(newHexId()));
1625
+ }
1626
+ async function waitOutHold(path) {
1627
+ const millis = state.takeHoldMillis(path);
1628
+ if (millis > 0) {
1629
+ await new Promise((resolve) => setTimeout(resolve, millis));
1630
+ }
1631
+ }
1632
+ return {
1633
+ state,
1634
+ controlToken,
1635
+ fetch: async (request) => {
1636
+ try {
1637
+ const response = await dispatch(request);
1638
+ log(`${request.method} ${new URL(request.url).pathname} -> ${response.status}`);
1639
+ return response;
1640
+ } catch {
1641
+ return refuse(ClientProofRefusal.unprocessable());
1642
+ }
1643
+ }
1644
+ };
1645
+ }
1646
+ function listItems(request) {
1647
+ if (request.limit < 1n || request.limit > DEV_MAX_LIMIT) {
1648
+ return null;
1649
+ }
1650
+ let start = 0;
1651
+ if (request.cursor !== void 0) {
1652
+ const index = DEV_CATALOGUE.findIndex((item) => item.id === request.cursor);
1653
+ if (index < 0) {
1654
+ return null;
1655
+ }
1656
+ start = index + 1;
1657
+ }
1658
+ const end = Math.min(DEV_CATALOGUE.length, start + Number(request.limit));
1659
+ const page = DEV_CATALOGUE.slice(start, end);
1660
+ const nextCursor = end < DEV_CATALOGUE.length && page.length > 0 ? page[page.length - 1].id : null;
1661
+ return encodeListItemsResponse([...page], nextCursor);
1662
+ }
1663
+ function contractResponse(status, body) {
1664
+ return new Response(toArrayBuffer(body), {
1665
+ status,
1666
+ headers: { "content-type": "application/json", ...serverContractHeaders() }
1667
+ });
1668
+ }
1669
+ async function readBodyCapped(request) {
1670
+ if (request.body === null) {
1671
+ return new Uint8Array(0);
1672
+ }
1673
+ const reader = request.body.getReader();
1674
+ const chunks = [];
1675
+ let total = 0;
1676
+ for (; ; ) {
1677
+ const { done, value } = await reader.read();
1678
+ if (done) {
1679
+ break;
1680
+ }
1681
+ total += value.length;
1682
+ if (total > MAX_BODY_BYTES) {
1683
+ await reader.cancel();
1684
+ return null;
1685
+ }
1686
+ chunks.push(value);
1687
+ }
1688
+ const body = new Uint8Array(total);
1689
+ let offset = 0;
1690
+ for (const chunk of chunks) {
1691
+ body.set(chunk, offset);
1692
+ offset += chunk.length;
1693
+ }
1694
+ return body;
1695
+ }
1696
+ function toArrayBuffer(bytes) {
1697
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
1698
+ }
1699
+
1700
+ // src/server/client-proof/guard.ts
1701
+ function createClientProofGuard(state, options = {}) {
1702
+ return async (c, next) => {
1703
+ const body = new Uint8Array(await c.req.arrayBuffer());
1704
+ const admission = admitClientProofRequest({
1705
+ state,
1706
+ headers: c.req.raw.headers,
1707
+ method: c.req.method,
1708
+ path: options.contractPath ?? c.req.path,
1709
+ requiresSession: true,
1710
+ body
1711
+ });
1712
+ if (!admission.admitted) {
1713
+ state.recordRefusal();
1714
+ const bytes = admission.refusal.envelopeBytes(newHexId());
1715
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
1716
+ return c.newResponse(buffer, admission.refusal.httpStatus, {
1717
+ "content-type": "application/json",
1718
+ ...serverContractHeaders()
1719
+ });
1720
+ }
1721
+ c.set("clientType", "mobile");
1722
+ c.set("clientProof", {
1723
+ credentials: admission.credentials,
1724
+ value: admission.value
1725
+ });
1726
+ await next();
1727
+ return void 0;
1728
+ };
1729
+ }
1730
+
1731
+ // src/server/client-proof/version-middleware.ts
1732
+ var CLIENT_IDENTITY_CONTEXT_KEY = "clientIdentity";
1733
+ function createClientVersionMiddleware() {
1734
+ return async (c, next) => {
1735
+ const identity = readClientIdentity(c.req.raw.headers);
1736
+ const refusal = judgeClientIdentity(identity);
1737
+ if (refusal !== null) {
1738
+ const bytes = refusal.envelopeBytes(newHexId());
1739
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
1740
+ const response = c.newResponse(buffer, refusal.httpStatus, {
1741
+ "content-type": "application/json"
1742
+ });
1743
+ applyServerContractHeaders(response.headers);
1744
+ return response;
1745
+ }
1746
+ if (identity !== null) {
1747
+ c.set(CLIENT_IDENTITY_CONTEXT_KEY, identity);
1748
+ }
1749
+ await next();
1750
+ applyServerContractHeaders(c.res.headers);
1751
+ return void 0;
1752
+ };
1753
+ }
1754
+ export {
1755
+ ABSENT_BODY_SHA256,
1756
+ AUTH_SURFACE_OPERATIONS,
1757
+ CLIENT_IDENTITY_CONTEXT_KEY,
1758
+ CLIENT_IDENTITY_HEADERS,
1759
+ CLIENT_KINDS,
1760
+ CLIENT_PROOF_CONTENT_TYPE,
1761
+ CLIENT_PROOF_HEADERS,
1762
+ CLIENT_PROOF_PROFILE,
1763
+ CONTRACT_OPERATIONS,
1764
+ CONTROL_PREFIX,
1765
+ CONTROL_TOKEN_HEADER,
1766
+ CanonicalJsonError,
1767
+ ClientProofRefusal,
1768
+ ClientProofState,
1769
+ ContractTypeError,
1770
+ DEFAULT_REPLAY_WINDOW_MILLIS,
1771
+ DEFAULT_SESSION_TTL_MILLIS,
1772
+ DEV_CATALOGUE,
1773
+ DEV_MAX_LIMIT,
1774
+ MemoryReplayLedger,
1775
+ MemoryReplayStore,
1776
+ PROOF_SIGNATURE_BYTES,
1777
+ PROOF_SIGNATURE_HEX_LENGTH,
1778
+ ProofInputError,
1779
+ RedisReplayStore,
1780
+ SERVER_CONTRACT_HEADERS,
1781
+ TestClock,
1782
+ admitClientProofRequest,
1783
+ applyServerContractHeaders,
1784
+ canonicalProofInput,
1785
+ configureClientProofReplayStore,
1786
+ createClientProofDevHandler,
1787
+ createClientProofGuard,
1788
+ createClientVersionMiddleware,
1789
+ decodeEchoRequest,
1790
+ decodeHandshakeRequest,
1791
+ decodeListItemsRequest,
1792
+ encodeCanonicalJson,
1793
+ encodeEchoResponse,
1794
+ encodeHandshakeResponse,
1795
+ encodeListItemsResponse,
1796
+ getClientProofReplayStore,
1797
+ isAppKind,
1798
+ isCanonicalBytes,
1799
+ isContractVersionSupported,
1800
+ isRequestContentType,
1801
+ judgeClientIdentity,
1802
+ newHexId,
1803
+ parseCanonicalJson,
1804
+ parseClientProofPublicKey,
1805
+ readClientIdentity,
1806
+ readCredentials,
1807
+ replayLedgerKey,
1808
+ serverContractHeaders,
1809
+ sha256Hex,
1810
+ signClientProof,
1811
+ systemClock,
1812
+ verifyClientProof
1813
+ };
1814
+ //# sourceMappingURL=client-proof.js.map