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.
- package/CHANGELOG.md +5 -0
- package/build/backend/.tsbuildinfo +1 -1
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +9 -5
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/auth.js +1 -1
- package/build/client/components/Image.d.ts +1 -1
- package/build/client/dev/devtools.js +3 -1
- package/build/client/index.d.ts +2 -2
- package/build/client/index.js +2 -2
- package/build/client/routing/Router.js +1 -1
- package/build/client/routing/mount.js +1 -1
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/docs.js +1 -1
- package/build/compiler/seo.js +1 -3
- package/build/compiler/template-build.js +1 -1
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/cache.js +0 -0
- package/build/devserver/crypto.js +45 -17
- package/build/devserver/database.d.ts +8 -0
- package/build/devserver/database.js +416 -0
- package/build/devserver/email/caps.js +0 -0
- package/build/devserver/email/config.js +7 -2
- package/build/devserver/email/validate.js +1 -4
- package/build/devserver/host.d.ts +2 -0
- package/build/devserver/host.js +3 -2
- package/build/devserver/index.d.ts +1 -1
- package/build/devserver/index.js +3 -2
- package/build/devserver/module.js +52 -7
- package/build/devserver/proxy.js +2 -1
- package/build/io/.tsbuildinfo +1 -1
- package/build/io/codec.d.ts +5 -5
- package/build/io/codec.js +193 -77
- package/examples/basic/client/components/HoneycombBackground.tsx +1 -1
- package/examples/basic/client/public/images/logo.svg +37 -34
- package/examples/basic/client/public/index.html +14 -14
- package/examples/basic/client/routes/auth.tsx +18 -10
- package/examples/basic/client/routes/cookies.tsx +15 -24
- package/examples/basic/client/routes/crypto.tsx +4 -5
- package/examples/basic/client/routes/features/template/template.tsx +1 -1
- package/examples/basic/client/routes/hello.tsx +1 -1
- package/examples/basic/client/routes/pq.tsx +14 -14
- package/examples/basic/client/routes/rest.tsx +50 -1
- package/examples/basic/client/styles/main.css +25 -22
- package/examples/basic/client/toil.tsx +1 -1
- package/examples/basic/server/README.md +8 -8
- package/examples/basic/server/core/AppHandler.ts +4 -7
- package/examples/basic/server/main.ts +1 -0
- package/examples/basic/server/models/GuestEntry.ts +12 -0
- package/examples/basic/server/models/GuestbookView.ts +10 -0
- package/examples/basic/server/models/NewMessage.ts +6 -0
- package/examples/basic/server/routes/Auth.ts +50 -106
- package/examples/basic/server/routes/EnvDemo.ts +9 -3
- package/examples/basic/server/routes/Guestbook.ts +62 -0
- package/package.json +2 -2
- package/server/globals/auth.ts +3 -3
- package/server/globals/twofactor.ts +2 -1
- package/server/runtime/http/securecookies.ts +3 -2
- package/src/backend/index.ts +4 -2
- package/src/cli/doctor.ts +10 -3
- package/src/cli/notify.ts +1 -6
- package/src/cli/ui.ts +3 -3
- package/src/cli/version-check.ts +5 -1
- package/src/client/auth.ts +33 -10
- package/src/client/components/Form.tsx +2 -2
- package/src/client/components/Image.tsx +1 -1
- package/src/client/components/Script.tsx +1 -1
- package/src/client/components/Slot.tsx +1 -1
- package/src/client/dev/devtools.tsx +121 -54
- package/src/client/dev/error-overlay.tsx +7 -1
- package/src/client/head/metadata.ts +1 -1
- package/src/client/index.ts +13 -2
- package/src/client/routing/Router.tsx +2 -2
- package/src/client/routing/error-boundary.tsx +1 -1
- package/src/client/routing/loader.ts +2 -2
- package/src/client/routing/mount.tsx +5 -6
- package/src/compiler/docs.ts +1 -1
- package/src/compiler/email-preview.ts +1 -1
- package/src/compiler/generate.ts +1 -1
- package/src/compiler/seo.ts +1 -3
- package/src/compiler/ssg.ts +10 -4
- package/src/compiler/template-build.ts +2 -7
- package/src/compiler/template.ts +1 -4
- package/src/compiler/vite.ts +1 -1
- package/src/devserver/cache.ts +0 -0
- package/src/devserver/crypto.ts +140 -51
- package/src/devserver/database.ts +600 -0
- package/src/devserver/dotenv.ts +10 -2
- package/src/devserver/email/caps.ts +0 -0
- package/src/devserver/email/config.ts +8 -2
- package/src/devserver/email/index.ts +3 -3
- package/src/devserver/email/validate.ts +1 -4
- package/src/devserver/envelope.ts +3 -3
- package/src/devserver/host.ts +22 -9
- package/src/devserver/index.ts +15 -6
- package/src/devserver/module.ts +59 -11
- package/src/devserver/proxy.ts +5 -7
- package/src/io/codec.ts +226 -83
- package/test/devserver-database.test.ts +364 -0
- package/test/devserver-pqauth.test.ts +5 -65
- package/test/example-guestbook.test.ts +78 -0
- package/test/pqauth-e2e.test.ts +6 -6
- package/build/devserver/kv.d.ts +0 -3
- package/build/devserver/kv.js +0 -53
- package/src/devserver/kv.ts +0 -93
package/src/devserver/crypto.ts
CHANGED
|
@@ -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,
|
|
29
|
-
|
|
30
|
-
|
|
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(
|
|
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
|
-
|
|
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:
|
|
119
|
-
|
|
120
|
-
case ALG.
|
|
121
|
-
|
|
122
|
-
case ALG.
|
|
123
|
-
|
|
124
|
-
case ALG.
|
|
125
|
-
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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,
|
|
247
|
-
|
|
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,
|
|
271
|
-
|
|
272
|
-
|
|
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 ||
|
|
313
|
-
alg === ALG.
|
|
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,
|
|
343
|
-
|
|
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'),
|
|
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'),
|
|
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,
|
|
399
|
-
|
|
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,
|
|
430
|
-
|
|
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(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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,
|
|
462
|
-
|
|
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(
|
|
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)
|
|
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,
|