toiljs 0.0.119 → 0.0.121
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 +10 -0
- package/README.md +1 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/index.js +1106 -91
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/head/head.js +1 -1
- package/build/client/routing/error-boundary.js +2 -0
- package/build/client/routing/loader.js +2 -2
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/index.js +4 -2
- package/build/compiler/toil-docs.generated.js +10 -10
- package/build/devserver/.tsbuildinfo +1 -1
- package/build/devserver/daemon/index.js +3 -2
- package/build/devserver/db/database.d.ts +5 -0
- package/build/devserver/db/database.js +107 -23
- package/build/devserver/db/types.d.ts +1 -0
- package/build/devserver/db/types.js +1 -1
- package/build/devserver/email/caps.js +0 -0
- package/build/devserver/http/cache.js +0 -0
- package/build/devserver/runtime/module.js +8 -1
- package/package.json +4 -3
- package/server/auth/AuthController.ts +4 -5
- package/server/auth/AuthReads.ts +15 -0
- package/server/auth/AuthStateDb.ts +51 -0
- package/server/auth/FactorSettings.ts +6 -27
- package/server/auth/SessionEpochs.ts +6 -24
- package/server/globals/auth.ts +30 -4
- package/server/runtime/request.ts +6 -0
- package/server/auth/SessionRevocations.ts +0 -13
package/server/globals/auth.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AuthReads } from 'toiljs/server/auth/AuthReads';
|
|
1
2
|
// AuthService: the server half of the post-quantum auth primitive, available
|
|
2
3
|
// as a no-import global (registered via the toilscript `--lib` mechanism, the
|
|
3
4
|
// same way `crypto` is a global). The client derives an ML-DSA-44 keypair from
|
|
@@ -11,11 +12,12 @@
|
|
|
11
12
|
|
|
12
13
|
import { DataWriter, DataReader } from 'data';
|
|
13
14
|
import { SessionEpochs } from 'toiljs/server/auth/SessionEpochs';
|
|
14
|
-
import {
|
|
15
|
+
import { AuthStateDb, SessionDigest, RevokedSession } from 'toiljs/server/auth/AuthStateDb';
|
|
15
16
|
import { HmacImportParams, HmacParams, ALG_SHA_256, FMT_RAW, USAGE_SIGN, USAGE_VERIFY } from 'crypto';
|
|
16
17
|
|
|
17
18
|
import {
|
|
18
19
|
Server,
|
|
20
|
+
Request,
|
|
19
21
|
SecureCookies,
|
|
20
22
|
Cookie,
|
|
21
23
|
SameSite,
|
|
@@ -265,7 +267,7 @@ export namespace AuthService {
|
|
|
265
267
|
const SESSION_VERSION: u8 = 2;
|
|
266
268
|
|
|
267
269
|
/** Default session lifetime if `mintSession` is called without a ttl. */
|
|
268
|
-
export const DEFAULT_SESSION_TTL_SECS: u64 =
|
|
270
|
+
export const DEFAULT_SESSION_TTL_SECS: u64 = 604800; // 7 days
|
|
269
271
|
|
|
270
272
|
/**
|
|
271
273
|
* Override the session-signing secret programmatically. OPTIONAL: by default
|
|
@@ -274,6 +276,7 @@ export namespace AuthService {
|
|
|
274
276
|
* the env value for the current request; keep it out of any client bundle.
|
|
275
277
|
*/
|
|
276
278
|
export function setSecret(secret: Uint8Array): void {
|
|
279
|
+
invalidateSession();
|
|
277
280
|
__sessionSecret = secret;
|
|
278
281
|
}
|
|
279
282
|
|
|
@@ -286,13 +289,33 @@ export namespace AuthService {
|
|
|
286
289
|
export function getSessionBytes(): Uint8Array | null {
|
|
287
290
|
const req = Server.currentRequest;
|
|
288
291
|
if (req == null) return null;
|
|
292
|
+
if (!req.__authChecked) {
|
|
293
|
+
// Publish only after verification completes. A host fault must never
|
|
294
|
+
// turn into a cached anonymous result if the application handles it.
|
|
295
|
+
req.__authBytes = verifySession(req);
|
|
296
|
+
req.__authChecked = true;
|
|
297
|
+
}
|
|
298
|
+
if (Time.nowSeconds() >= req.__authExpires) return null;
|
|
299
|
+
const bytes = req.__authBytes;
|
|
300
|
+
// Callers may decode/mutate their copy without changing later auth checks.
|
|
301
|
+
return bytes == null ? null : bytes.slice();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function invalidateSession(): void {
|
|
305
|
+
const req = Server.currentRequest;
|
|
306
|
+
if (req == null) return;
|
|
307
|
+
req.__authChecked = false;
|
|
308
|
+
req.__authBytes = null;
|
|
309
|
+
req.__authExpires = 0;
|
|
310
|
+
}
|
|
289
311
|
|
|
312
|
+
function verifySession(req: Request): Uint8Array | null {
|
|
290
313
|
const sealed = SecureCookies.signed(__resolveSessionSecret()).open(
|
|
291
314
|
req.cookies(),
|
|
292
315
|
sessionCookieName(__reqIsSecure()),
|
|
293
316
|
);
|
|
294
317
|
if (sealed == null) return null;
|
|
295
|
-
if (
|
|
318
|
+
if (AuthReads.owner(AuthStateDb.revokedSessions, new SessionDigest(crypto.toHex(crypto.sha256Text(sealed)))) != null) return null;
|
|
296
319
|
|
|
297
320
|
const payload = base64UrlDecode(sealed);
|
|
298
321
|
if (payload == null) return null;
|
|
@@ -310,6 +333,7 @@ export namespace AuthService {
|
|
|
310
333
|
if (Time.nowSeconds() >= exp) return null; // expired
|
|
311
334
|
if (!SessionEpochs.allowed(userBytes, generation)) return null;
|
|
312
335
|
|
|
336
|
+
req.__authExpires = exp;
|
|
313
337
|
return userBytes;
|
|
314
338
|
}
|
|
315
339
|
|
|
@@ -397,6 +421,7 @@ export namespace AuthService {
|
|
|
397
421
|
|
|
398
422
|
/** Invalidate all browser sessions authorized under the prior session generation. */
|
|
399
423
|
export function revokeUserSessions(userData: Uint8Array): void {
|
|
424
|
+
invalidateSession();
|
|
400
425
|
SessionEpochs.revoke(userData);
|
|
401
426
|
}
|
|
402
427
|
|
|
@@ -406,7 +431,8 @@ export namespace AuthService {
|
|
|
406
431
|
const req = Server.currentRequest!;
|
|
407
432
|
const sealed = SecureCookies.signed(__resolveSessionSecret()).open(req.cookies(), sessionCookieName(__reqIsSecure()));
|
|
408
433
|
if (sealed == null) return;
|
|
409
|
-
|
|
434
|
+
invalidateSession();
|
|
435
|
+
AuthStateDb.revokedSessions.claim(new SessionDigest(crypto.toHex(crypto.sha256Text(sealed))), new RevokedSession());
|
|
410
436
|
}
|
|
411
437
|
|
|
412
438
|
/** A `Set-Cookie` that immediately clears the session (logout). */
|
|
@@ -33,6 +33,12 @@ export class Request {
|
|
|
33
33
|
headers: Array<Header>;
|
|
34
34
|
body: Uint8Array;
|
|
35
35
|
|
|
36
|
+
// Auth verification belongs to this request object, never a process/tenant cache.
|
|
37
|
+
// Internal AuthService state; cleared before local revocations or secret changes.
|
|
38
|
+
__authChecked: bool = false;
|
|
39
|
+
__authBytes: Uint8Array | null = null;
|
|
40
|
+
__authExpires: u64 = 0;
|
|
41
|
+
|
|
36
42
|
// Lazily parsed `Cookie` header, cached for the life of the request.
|
|
37
43
|
private _cookies: CookieMap | null = null;
|
|
38
44
|
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
@data
|
|
2
|
-
export class SessionDigest {
|
|
3
|
-
digest: string = '';
|
|
4
|
-
constructor(digest: string = '') { this.digest = digest; }
|
|
5
|
-
}
|
|
6
|
-
@data
|
|
7
|
-
export class RevokedSession {
|
|
8
|
-
revoked: bool = true;
|
|
9
|
-
}
|
|
10
|
-
@database
|
|
11
|
-
export class SessionRevocations {
|
|
12
|
-
@collection static sessions: Unique<SessionDigest, RevokedSession>;
|
|
13
|
-
}
|