toiljs 0.0.54 → 0.0.56

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 (105) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/build/backend/.tsbuildinfo +1 -1
  3. package/build/cli/.tsbuildinfo +1 -1
  4. package/build/cli/index.js +9 -5
  5. package/build/client/.tsbuildinfo +1 -1
  6. package/build/client/auth.js +1 -1
  7. package/build/client/components/Image.d.ts +1 -1
  8. package/build/client/dev/devtools.js +3 -1
  9. package/build/client/index.d.ts +2 -2
  10. package/build/client/index.js +2 -2
  11. package/build/client/routing/Router.js +1 -1
  12. package/build/client/routing/mount.js +1 -1
  13. package/build/compiler/.tsbuildinfo +1 -1
  14. package/build/compiler/docs.js +1 -1
  15. package/build/compiler/seo.js +1 -3
  16. package/build/compiler/template-build.js +1 -1
  17. package/build/devserver/.tsbuildinfo +1 -1
  18. package/build/devserver/cache.js +0 -0
  19. package/build/devserver/crypto.js +45 -17
  20. package/build/devserver/database.d.ts +8 -0
  21. package/build/devserver/database.js +416 -0
  22. package/build/devserver/email/caps.js +0 -0
  23. package/build/devserver/email/config.js +7 -2
  24. package/build/devserver/email/validate.js +1 -4
  25. package/build/devserver/host.d.ts +2 -0
  26. package/build/devserver/host.js +3 -2
  27. package/build/devserver/index.d.ts +1 -1
  28. package/build/devserver/index.js +3 -2
  29. package/build/devserver/module.js +52 -7
  30. package/build/devserver/proxy.js +2 -1
  31. package/build/io/.tsbuildinfo +1 -1
  32. package/build/io/codec.d.ts +5 -5
  33. package/build/io/codec.js +193 -77
  34. package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
  35. package/examples/basic/client/public/images/logo.svg +37 -34
  36. package/examples/basic/client/public/index.html +14 -14
  37. package/examples/basic/client/routes/auth.tsx +18 -10
  38. package/examples/basic/client/routes/cookies.tsx +15 -24
  39. package/examples/basic/client/routes/crypto.tsx +4 -5
  40. package/examples/basic/client/routes/features/template/template.tsx +1 -1
  41. package/examples/basic/client/routes/hello.tsx +1 -1
  42. package/examples/basic/client/routes/pq.tsx +14 -14
  43. package/examples/basic/client/routes/rest.tsx +50 -1
  44. package/examples/basic/client/styles/main.css +25 -22
  45. package/examples/basic/client/toil.tsx +1 -1
  46. package/examples/basic/server/README.md +8 -8
  47. package/examples/basic/server/core/AppHandler.ts +4 -7
  48. package/examples/basic/server/main.ts +1 -0
  49. package/examples/basic/server/models/GuestEntry.ts +12 -0
  50. package/examples/basic/server/models/GuestbookView.ts +10 -0
  51. package/examples/basic/server/models/NewMessage.ts +6 -0
  52. package/examples/basic/server/routes/Auth.ts +50 -106
  53. package/examples/basic/server/routes/EnvDemo.ts +9 -3
  54. package/examples/basic/server/routes/Guestbook.ts +62 -0
  55. package/package.json +2 -2
  56. package/server/globals/auth.ts +3 -3
  57. package/server/globals/twofactor.ts +2 -1
  58. package/server/runtime/http/securecookies.ts +3 -2
  59. package/src/backend/index.ts +4 -2
  60. package/src/cli/doctor.ts +10 -3
  61. package/src/cli/notify.ts +1 -6
  62. package/src/cli/ui.ts +3 -3
  63. package/src/cli/version-check.ts +5 -1
  64. package/src/client/auth.ts +33 -10
  65. package/src/client/components/Form.tsx +2 -2
  66. package/src/client/components/Image.tsx +1 -1
  67. package/src/client/components/Script.tsx +1 -1
  68. package/src/client/components/Slot.tsx +1 -1
  69. package/src/client/dev/devtools.tsx +121 -54
  70. package/src/client/dev/error-overlay.tsx +7 -1
  71. package/src/client/head/metadata.ts +1 -1
  72. package/src/client/index.ts +13 -2
  73. package/src/client/routing/Router.tsx +2 -2
  74. package/src/client/routing/error-boundary.tsx +1 -1
  75. package/src/client/routing/loader.ts +2 -2
  76. package/src/client/routing/mount.tsx +5 -6
  77. package/src/compiler/docs.ts +1 -1
  78. package/src/compiler/email-preview.ts +1 -1
  79. package/src/compiler/generate.ts +1 -1
  80. package/src/compiler/seo.ts +1 -3
  81. package/src/compiler/ssg.ts +10 -4
  82. package/src/compiler/template-build.ts +2 -7
  83. package/src/compiler/template.ts +1 -4
  84. package/src/compiler/vite.ts +1 -1
  85. package/src/devserver/cache.ts +0 -0
  86. package/src/devserver/crypto.ts +140 -51
  87. package/src/devserver/database.ts +600 -0
  88. package/src/devserver/dotenv.ts +10 -2
  89. package/src/devserver/email/caps.ts +0 -0
  90. package/src/devserver/email/config.ts +8 -2
  91. package/src/devserver/email/index.ts +3 -3
  92. package/src/devserver/email/validate.ts +1 -4
  93. package/src/devserver/envelope.ts +3 -3
  94. package/src/devserver/host.ts +22 -9
  95. package/src/devserver/index.ts +15 -6
  96. package/src/devserver/module.ts +59 -11
  97. package/src/devserver/proxy.ts +5 -7
  98. package/src/io/codec.ts +226 -83
  99. package/test/devserver-database.test.ts +364 -0
  100. package/test/devserver-pqauth.test.ts +5 -65
  101. package/test/example-guestbook.test.ts +78 -0
  102. package/test/pqauth-e2e.test.ts +6 -6
  103. package/build/devserver/kv.d.ts +0 -3
  104. package/build/devserver/kv.js +0 -53
  105. package/src/devserver/kv.ts +0 -93
@@ -25,9 +25,24 @@ import type { MemoryRef } from './host.js';
25
25
 
26
26
  // --- ABI id tables (must match the std + Rust backend) ----------------------
27
27
  const ALG = {
28
- SHA1: 1, SHA256: 2, SHA384: 3, SHA512: 4, SHA3_256: 5, SHA3_384: 6, SHA3_512: 7,
29
- AES_GCM: 10, AES_CBC: 11, AES_CTR: 12, AES_KW: 13, HMAC: 20,
30
- ECDSA: 32, ED25519: 33, ECDH: 50, X25519: 51, HKDF: 52, PBKDF2: 53,
28
+ SHA1: 1,
29
+ SHA256: 2,
30
+ SHA384: 3,
31
+ SHA512: 4,
32
+ SHA3_256: 5,
33
+ SHA3_384: 6,
34
+ SHA3_512: 7,
35
+ AES_GCM: 10,
36
+ AES_CBC: 11,
37
+ AES_CTR: 12,
38
+ AES_KW: 13,
39
+ HMAC: 20,
40
+ ECDSA: 32,
41
+ ED25519: 33,
42
+ ECDH: 50,
43
+ X25519: 51,
44
+ HKDF: 52,
45
+ PBKDF2: 53,
31
46
  } as const;
32
47
  const FMT = { RAW: 0, PKCS8: 1, SPKI: 2, JWK: 3 } as const;
33
48
 
@@ -77,7 +92,9 @@ function readBytes(ref: MemoryRef, ptr: number, len: number): Buffer {
77
92
  function writeBytes(ref: MemoryRef, ptr: number, bytes: Buffer | Uint8Array): void {
78
93
  const m = memBuf(ref);
79
94
  if (ptr < 0 || ptr + bytes.length > m.length)
80
- throw new Error(`crypto write out of bounds: ptr=${String(ptr)} len=${String(bytes.length)}`);
95
+ throw new Error(
96
+ `crypto write out of bounds: ptr=${String(ptr)} len=${String(bytes.length)}`,
97
+ );
81
98
  m.set(bytes, ptr);
82
99
  }
83
100
 
@@ -85,25 +102,21 @@ function writeBytes(ref: MemoryRef, ptr: number, bytes: Buffer | Uint8Array): vo
85
102
  class ParamReader {
86
103
  private pos = 0;
87
104
  constructor(private readonly buf: Buffer) {}
88
- /** Bounds-check before a read so a malformed buffer throws a controlled
89
- * error (trap-equivalent, caught by the dispatcher) rather than a raw
90
- * Node RangeError. */
91
- private need(n: number): void {
92
- if (n < 0 || this.pos + n > this.buf.length)
93
- throw new Error('crypto: malformed params buffer (truncated)');
94
- }
105
+
95
106
  readI32(): number {
96
107
  this.need(4);
97
108
  const v = this.buf.readInt32LE(this.pos);
98
109
  this.pos += 4;
99
110
  return v;
100
111
  }
112
+
101
113
  readU32(): number {
102
114
  this.need(4);
103
115
  const v = this.buf.readUInt32LE(this.pos);
104
116
  this.pos += 4;
105
117
  return v;
106
118
  }
119
+
107
120
  readBlob(): Buffer {
108
121
  const n = this.readU32();
109
122
  this.need(n);
@@ -111,18 +124,34 @@ class ParamReader {
111
124
  this.pos += n;
112
125
  return s;
113
126
  }
127
+
128
+ /** Bounds-check before a read so a malformed buffer throws a controlled
129
+ * error (trap-equivalent, caught by the dispatcher) rather than a raw
130
+ * Node RangeError. */
131
+ private need(n: number): void {
132
+ if (n < 0 || this.pos + n > this.buf.length)
133
+ throw new Error('crypto: malformed params buffer (truncated)');
134
+ }
114
135
  }
115
136
 
116
137
  function hashName(id: number): string {
117
138
  switch (id) {
118
- case ALG.SHA1: return 'sha1';
119
- case ALG.SHA256: return 'sha256';
120
- case ALG.SHA384: return 'sha384';
121
- case ALG.SHA512: return 'sha512';
122
- case ALG.SHA3_256: return 'sha3-256';
123
- case ALG.SHA3_384: return 'sha3-384';
124
- case ALG.SHA3_512: return 'sha3-512';
125
- default: throw new Error(`crypto: bad hash id ${String(id)}`);
139
+ case ALG.SHA1:
140
+ return 'sha1';
141
+ case ALG.SHA256:
142
+ return 'sha256';
143
+ case ALG.SHA384:
144
+ return 'sha384';
145
+ case ALG.SHA512:
146
+ return 'sha512';
147
+ case ALG.SHA3_256:
148
+ return 'sha3-256';
149
+ case ALG.SHA3_384:
150
+ return 'sha3-384';
151
+ case ALG.SHA3_512:
152
+ return 'sha3-512';
153
+ default:
154
+ throw new Error(`crypto: bad hash id ${String(id)}`);
126
155
  }
127
156
  }
128
157
 
@@ -151,8 +180,7 @@ export function buildCryptoImports(
151
180
 
152
181
  'crypto.take_result': (outPtr: number, outLen: number): number => {
153
182
  const r = cs.lastResult;
154
- if (!r || r.length !== outLen)
155
- throw new Error('crypto.take_result: length mismatch');
183
+ if (!r || r.length !== outLen) throw new Error('crypto.take_result: length mismatch');
156
184
  writeBytes(ref, outPtr, r);
157
185
  cs.lastResult = null;
158
186
  return r.length;
@@ -191,9 +219,15 @@ export function buildCryptoImports(
191
219
  if (e.raw && format === FMT.RAW) return stash(cs, e.raw);
192
220
  if (e.keyObject) {
193
221
  if (format === FMT.PKCS8 && e.isPrivate)
194
- return stash(cs, e.keyObject.export({ format: 'der', type: 'pkcs8' }) as Buffer);
222
+ return stash(
223
+ cs,
224
+ e.keyObject.export({ format: 'der', type: 'pkcs8' }) as Buffer,
225
+ );
195
226
  if (format === FMT.SPKI && !e.isPrivate)
196
- return stash(cs, e.keyObject.export({ format: 'der', type: 'spki' }) as Buffer);
227
+ return stash(
228
+ cs,
229
+ e.keyObject.export({ format: 'der', type: 'spki' }) as Buffer,
230
+ );
197
231
  }
198
232
  return ERR_UNSUPPORTED;
199
233
  } catch {
@@ -210,7 +244,13 @@ export function buildCryptoImports(
210
244
  signOp(cs, ref, h, pp, pl, dp, dl),
211
245
 
212
246
  'crypto.verify': (
213
- h: number, pp: number, pl: number, sp: number, sl: number, dp: number, dl: number,
247
+ h: number,
248
+ pp: number,
249
+ pl: number,
250
+ sp: number,
251
+ sl: number,
252
+ dp: number,
253
+ dl: number,
214
254
  ): number => verifyOp(cs, ref, h, pp, pl, sp, sl, dp, dl),
215
255
 
216
256
  'crypto.derive_bits': (h: number, pp: number, pl: number, lengthBits: number): number =>
@@ -220,10 +260,14 @@ export function buildCryptoImports(
220
260
  // host (`mldsa_verify_import.rs`): same size asserts, 1/0/neg result.
221
261
  // Backed by the same noble lib the client signs with, so dev == prod.
222
262
  'crypto.mldsa_verify': (
223
- pkPtr: number, pkLen: number,
224
- msgPtr: number, msgLen: number,
225
- sigPtr: number, sigLen: number,
226
- ctxPtr: number, ctxLen: number,
263
+ pkPtr: number,
264
+ pkLen: number,
265
+ msgPtr: number,
266
+ msgLen: number,
267
+ sigPtr: number,
268
+ sigLen: number,
269
+ ctxPtr: number,
270
+ ctxLen: number,
227
271
  ): number => {
228
272
  if (pkLen !== 1312 || sigLen !== 2420 || ctxLen > 255) return -4;
229
273
  try {
@@ -243,8 +287,10 @@ export function buildCryptoImports(
243
287
  // the server's static secret key, write it to `outPtr`, return 0 / neg.
244
288
  // Backed by the same noble lib the client encapsulates with (dev == prod).
245
289
  'crypto.mlkem_decapsulate': (
246
- ctPtr: number, ctLen: number,
247
- skPtr: number, skLen: number,
290
+ ctPtr: number,
291
+ ctLen: number,
292
+ skPtr: number,
293
+ skLen: number,
248
294
  outPtr: number,
249
295
  ): number => {
250
296
  if (ctLen !== 1088 || skLen !== 2400) return -4;
@@ -267,9 +313,12 @@ export function buildCryptoImports(
267
313
  // `@noble/curves` ristretto255_oprf, which matches the edge byte-for-byte
268
314
  // (both RFC 9497), so dev == prod.
269
315
  'crypto.voprf_evaluate': (
270
- seedPtr: number, seedLen: number,
271
- infoPtr: number, infoLen: number,
272
- blindedPtr: number, blindedLen: number,
316
+ seedPtr: number,
317
+ seedLen: number,
318
+ infoPtr: number,
319
+ infoLen: number,
320
+ blindedPtr: number,
321
+ blindedLen: number,
273
322
  outPtr: number,
274
323
  ): number => {
275
324
  // seedLen MUST be exactly 32 (RFC 9497 Ns; noble deriveKeyPair rejects
@@ -309,8 +358,13 @@ function importKey(
309
358
  try {
310
359
  // Symmetric / MAC / KDF: raw bytes.
311
360
  if (
312
- alg === ALG.AES_GCM || alg === ALG.AES_CBC || alg === ALG.AES_CTR ||
313
- alg === ALG.AES_KW || alg === ALG.HMAC || alg === ALG.PBKDF2 || alg === ALG.HKDF
361
+ alg === ALG.AES_GCM ||
362
+ alg === ALG.AES_CBC ||
363
+ alg === ALG.AES_CTR ||
364
+ alg === ALG.AES_KW ||
365
+ alg === ALG.HMAC ||
366
+ alg === ALG.PBKDF2 ||
367
+ alg === ALG.HKDF
314
368
  ) {
315
369
  if (format !== FMT.RAW) return ERR_UNSUPPORTED;
316
370
  return newEntry({ raw: key, keyObject: null, alg, hash, isPrivate: false });
@@ -339,8 +393,14 @@ function aesAlgName(keyLen: number, mode: 'gcm' | 'cbc' | 'ctr'): string {
339
393
  }
340
394
 
341
395
  function aesOp(
342
- cs: CryptoState, ref: MemoryRef, encrypt: boolean,
343
- handle: number, pp: number, pl: number, dp: number, dl: number,
396
+ cs: CryptoState,
397
+ ref: MemoryRef,
398
+ encrypt: boolean,
399
+ handle: number,
400
+ pp: number,
401
+ pl: number,
402
+ dp: number,
403
+ dl: number,
344
404
  ): number {
345
405
  const e = cs.keys.get(handle);
346
406
  if (!e || !e.raw) throw new Error('crypto: invalid AES key handle');
@@ -356,7 +416,9 @@ function aesOp(
356
416
  if (tagBits !== 0 && tagBits !== 128) return ERR_INVALID_PARAMS;
357
417
  if (encrypt) {
358
418
  const c = nodeCrypto.createCipheriv(
359
- aesAlgName(e.raw.length, 'gcm'), e.raw, iv,
419
+ aesAlgName(e.raw.length, 'gcm'),
420
+ e.raw,
421
+ iv,
360
422
  ) as nodeCrypto.CipherGCM;
361
423
  if (aad.length) c.setAAD(aad);
362
424
  const ct = Buffer.concat([c.update(data), c.final()]);
@@ -366,7 +428,9 @@ function aesOp(
366
428
  // never authenticate.
367
429
  if (data.length < 16) return ERR_OPERATION_FAILED;
368
430
  const d = nodeCrypto.createDecipheriv(
369
- aesAlgName(e.raw.length, 'gcm'), e.raw, iv,
431
+ aesAlgName(e.raw.length, 'gcm'),
432
+ e.raw,
433
+ iv,
370
434
  ) as nodeCrypto.DecipherGCM;
371
435
  if (aad.length) d.setAAD(aad);
372
436
  const tag = data.subarray(data.length - 16);
@@ -395,8 +459,13 @@ function aesOp(
395
459
  }
396
460
 
397
461
  function signOp(
398
- cs: CryptoState, ref: MemoryRef,
399
- handle: number, pp: number, pl: number, dp: number, dl: number,
462
+ cs: CryptoState,
463
+ ref: MemoryRef,
464
+ handle: number,
465
+ pp: number,
466
+ pl: number,
467
+ dp: number,
468
+ dl: number,
400
469
  ): number {
401
470
  const e = cs.keys.get(handle);
402
471
  if (!e) throw new Error('crypto.sign: invalid handle');
@@ -426,8 +495,15 @@ function signOp(
426
495
  }
427
496
 
428
497
  function verifyOp(
429
- cs: CryptoState, ref: MemoryRef,
430
- handle: number, pp: number, pl: number, sp: number, sl: number, dp: number, dl: number,
498
+ cs: CryptoState,
499
+ ref: MemoryRef,
500
+ handle: number,
501
+ pp: number,
502
+ pl: number,
503
+ sp: number,
504
+ sl: number,
505
+ dp: number,
506
+ dl: number,
431
507
  ): number {
432
508
  const e = cs.keys.get(handle);
433
509
  if (!e) throw new Error('crypto.verify: invalid handle');
@@ -442,10 +518,15 @@ function verifyOp(
442
518
  return mac.length === sig.length && nodeCrypto.timingSafeEqual(mac, sig) ? 1 : 0;
443
519
  }
444
520
  if (e.alg === ALG.ECDSA && e.keyObject) {
445
- const ok = nodeCrypto.verify(hashName(hash), data, {
446
- key: e.keyObject,
447
- dsaEncoding: 'ieee-p1363',
448
- }, sig);
521
+ const ok = nodeCrypto.verify(
522
+ hashName(hash),
523
+ data,
524
+ {
525
+ key: e.keyObject,
526
+ dsaEncoding: 'ieee-p1363',
527
+ },
528
+ sig,
529
+ );
449
530
  return ok ? 1 : 0;
450
531
  }
451
532
  if (e.alg === ALG.ED25519 && e.keyObject) {
@@ -458,8 +539,12 @@ function verifyOp(
458
539
  }
459
540
 
460
541
  function deriveBitsOp(
461
- cs: CryptoState, ref: MemoryRef,
462
- handle: number, pp: number, pl: number, lengthBits: number,
542
+ cs: CryptoState,
543
+ ref: MemoryRef,
544
+ handle: number,
545
+ pp: number,
546
+ pl: number,
547
+ lengthBits: number,
463
548
  ): number {
464
549
  if (lengthBits < 0 || lengthBits % 8 !== 0) return ERR_INVALID_PARAMS;
465
550
  const outLen = lengthBits / 8;
@@ -473,7 +558,10 @@ function deriveBitsOp(
473
558
  if (alg === ALG.PBKDF2 && e.raw) {
474
559
  const iterations = pr.readU32();
475
560
  const salt = pr.readBlob();
476
- return stash(cs, nodeCrypto.pbkdf2Sync(e.raw, salt, iterations, outLen, hashName(hash)));
561
+ return stash(
562
+ cs,
563
+ nodeCrypto.pbkdf2Sync(e.raw, salt, iterations, outLen, hashName(hash)),
564
+ );
477
565
  }
478
566
  if (alg === ALG.HKDF && e.raw) {
479
567
  const salt = pr.readBlob();
@@ -484,7 +572,8 @@ function deriveBitsOp(
484
572
  if ((alg === ALG.ECDH || alg === ALG.X25519) && e.keyObject) {
485
573
  const peerHandle = pr.readI32();
486
574
  const peer = cs.keys.get(peerHandle);
487
- if (!peer || !peer.keyObject) throw new Error('crypto.derive_bits: invalid peer handle');
575
+ if (!peer || !peer.keyObject)
576
+ throw new Error('crypto.derive_bits: invalid peer handle');
488
577
  const shared = nodeCrypto.diffieHellman({
489
578
  privateKey: e.keyObject,
490
579
  publicKey: peer.keyObject,