lambder 6.0.1 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/CHANGELOG.md +2316 -0
  2. package/README.md +60 -33
  3. package/dist/api/LambderApiAnswer.d.ts +40 -0
  4. package/dist/api/LambderApiAnswer.js +19 -0
  5. package/dist/api/LambderApiCallContext.d.ts +38 -0
  6. package/dist/api/LambderApiCallContext.js +13 -0
  7. package/dist/api/LambderApiDefinition.d.ts +18 -0
  8. package/dist/api/LambderApiDefinition.js +1 -0
  9. package/dist/api/LambderApiEnvelope.d.ts +67 -0
  10. package/dist/api/LambderApiEnvelope.js +180 -0
  11. package/dist/api/LambderApiGuards.d.ts +302 -0
  12. package/dist/api/LambderApiGuards.js +134 -0
  13. package/dist/api/LambderApiIdempotency.d.ts +122 -0
  14. package/dist/api/LambderApiIdempotency.js +330 -0
  15. package/dist/api/LambderApiPipeline.d.ts +134 -0
  16. package/dist/api/LambderApiPipeline.js +221 -0
  17. package/dist/api/LambderApiPolicyEngine.d.ts +36 -0
  18. package/dist/api/LambderApiPolicyEngine.js +77 -0
  19. package/dist/api/LambderApiRateLimits.d.ts +206 -0
  20. package/dist/api/LambderApiRateLimits.js +239 -0
  21. package/dist/api/LambderApiRequest.d.ts +101 -0
  22. package/dist/api/LambderApiRequest.js +129 -0
  23. package/dist/api/LambderApiValidationRefusal.d.ts +32 -0
  24. package/dist/api/LambderApiValidationRefusal.js +40 -0
  25. package/dist/client/LambderCaller.d.ts +62 -55
  26. package/dist/client/LambderCaller.js +147 -90
  27. package/dist/client/lambderFetchTransport.d.ts +9 -0
  28. package/dist/client/lambderFetchTransport.js +71 -0
  29. package/dist/client.d.ts +20 -10
  30. package/dist/client.js +11 -5
  31. package/dist/core/Lambder.d.ts +117 -253
  32. package/dist/core/Lambder.js +374 -341
  33. package/dist/core/LambderContext.d.ts +54 -44
  34. package/dist/core/LambderContext.js +41 -110
  35. package/dist/core/LambderCreateOptions.d.ts +285 -0
  36. package/dist/core/LambderCreateOptions.js +44 -0
  37. package/dist/core/LambderFiles.d.ts +1 -45
  38. package/dist/core/LambderFiles.js +18 -38
  39. package/dist/core/LambderIndexHtml.d.ts +37 -0
  40. package/dist/core/LambderIndexHtml.js +87 -0
  41. package/dist/core/LambderPolicyBuilders.d.ts +17 -0
  42. package/dist/core/LambderPolicyBuilders.js +16 -0
  43. package/dist/core/LambderPublicFiles.d.ts +5 -2
  44. package/dist/core/LambderPublicFiles.js +7 -2
  45. package/dist/core/LambderResolver.d.ts +8 -6
  46. package/dist/core/LambderResponse.d.ts +29 -11
  47. package/dist/core/LambderResponse.js +96 -49
  48. package/dist/core/LambderResponseBuilder.d.ts +18 -14
  49. package/dist/core/LambderResponseBuilder.js +19 -25
  50. package/dist/core/LambderRouting.d.ts +18 -7
  51. package/dist/core/LambderRouting.js +17 -7
  52. package/dist/core/LambderTemplatingEngine.d.ts +0 -62
  53. package/dist/core/LambderTemplatingEngine.js +7 -3
  54. package/dist/index.d.ts +85 -32
  55. package/dist/index.js +44 -16
  56. package/dist/invoke/LambderInvokeCaller.d.ts +46 -139
  57. package/dist/invoke/LambderInvokeCaller.js +140 -335
  58. package/dist/invoke/LambderInvokeOutcome.d.ts +165 -0
  59. package/dist/invoke/LambderInvokeOutcome.js +129 -0
  60. package/dist/invoke/LambderLambdaEvent.d.ts +81 -0
  61. package/dist/invoke/LambderLambdaEvent.js +187 -0
  62. package/dist/invoke/lambderHandlerTransport.d.ts +36 -0
  63. package/dist/invoke/lambderHandlerTransport.js +89 -0
  64. package/dist/mock/LambderMockApp.d.ts +352 -0
  65. package/dist/mock/LambderMockApp.js +815 -0
  66. package/dist/mock/LambderMockBrowserCookies.d.ts +55 -0
  67. package/dist/mock/LambderMockBrowserCookies.js +76 -0
  68. package/dist/mock/LambderMockCallRecorder.d.ts +85 -0
  69. package/dist/mock/LambderMockCallRecorder.js +183 -0
  70. package/dist/mock/LambderMockCreateOptions.d.ts +161 -0
  71. package/dist/mock/LambderMockCreateOptions.js +9 -0
  72. package/dist/mock/LambderMockEntryRegistry.d.ts +52 -0
  73. package/dist/mock/LambderMockEntryRegistry.js +126 -0
  74. package/dist/mock/LambderMockFailureInjector.d.ts +60 -0
  75. package/dist/mock/LambderMockFailureInjector.js +138 -0
  76. package/dist/mock/LambderMockTypes.d.ts +421 -0
  77. package/dist/mock/LambderMockTypes.js +8 -0
  78. package/dist/mock/lambderMockConsoleLogger.d.ts +16 -0
  79. package/dist/mock/lambderMockConsoleLogger.js +35 -0
  80. package/dist/mock/lambderMockInvokeTransport.d.ts +50 -0
  81. package/dist/mock/lambderMockInvokeTransport.js +52 -0
  82. package/dist/mock/lambderMockMswHandler.d.ts +99 -0
  83. package/dist/mock/lambderMockMswHandler.js +126 -0
  84. package/dist/mock.d.ts +34 -0
  85. package/dist/mock.js +27 -0
  86. package/dist/session/LambderSessionController.d.ts +199 -30
  87. package/dist/session/LambderSessionController.js +396 -82
  88. package/dist/session/LambderSessionCrypto.d.ts +66 -0
  89. package/dist/session/LambderSessionCrypto.js +101 -0
  90. package/dist/session/LambderSessionManager.d.ts +118 -80
  91. package/dist/session/LambderSessionManager.js +212 -184
  92. package/dist/shared/LambderI18n.d.ts +6 -6
  93. package/dist/shared/LambderI18n.js +1 -1
  94. package/dist/shared/contracts/LambderFileSource.d.ts +33 -0
  95. package/dist/shared/contracts/LambderFileSource.js +19 -0
  96. package/dist/shared/contracts/LambderIdempotencyStore.d.ts +66 -0
  97. package/dist/shared/contracts/LambderIdempotencyStore.js +12 -0
  98. package/dist/shared/contracts/LambderRateLimiter.d.ts +71 -0
  99. package/dist/shared/contracts/LambderRateLimiter.js +24 -0
  100. package/dist/shared/contracts/LambderSessionStore.d.ts +72 -0
  101. package/dist/shared/contracts/LambderSessionStore.js +13 -0
  102. package/dist/shared/transport/LambderApiTransport.d.ts +139 -0
  103. package/dist/shared/transport/LambderApiTransport.js +65 -0
  104. package/dist/shared/transport/LambderCookieJar.d.ts +121 -0
  105. package/dist/shared/transport/LambderCookieJar.js +246 -0
  106. package/dist/shared/transport/lambderCookieJarTransport.d.ts +30 -0
  107. package/dist/shared/transport/lambderCookieJarTransport.js +60 -0
  108. package/dist/shared/util/LambderBase64.d.ts +10 -0
  109. package/dist/shared/util/LambderBase64.js +27 -0
  110. package/dist/shared/util/LambderCallAbort.d.ts +62 -0
  111. package/dist/shared/util/LambderCallAbort.js +80 -0
  112. package/dist/shared/util/LambderClientIp.d.ts +32 -0
  113. package/dist/shared/util/LambderClientIp.js +56 -0
  114. package/dist/shared/util/LambderExpiringMap.d.ts +119 -0
  115. package/dist/shared/util/LambderExpiringMap.js +217 -0
  116. package/dist/shared/util/LambderKeyFields.d.ts +32 -0
  117. package/dist/shared/util/LambderKeyFields.js +34 -0
  118. package/dist/shared/util/LambderNodeModules.d.ts +9 -0
  119. package/dist/shared/util/LambderNodeModules.js +39 -0
  120. package/dist/shared/util/LambderOptionChecks.d.ts +17 -0
  121. package/dist/shared/util/LambderOptionChecks.js +33 -0
  122. package/dist/shared/util/LambderResponseBrand.d.ts +20 -0
  123. package/dist/shared/util/LambderResponseBrand.js +18 -0
  124. package/dist/shared/util/LambderTextDigest.d.ts +17 -0
  125. package/dist/shared/util/LambderTextDigest.js +34 -0
  126. package/dist/shared/util/LambderTypeUtilities.d.ts +33 -0
  127. package/dist/shared/util/LambderTypeUtilities.js +8 -0
  128. package/dist/shared/wire/LambderAnswerHeaders.d.ts +60 -0
  129. package/dist/shared/wire/LambderAnswerHeaders.js +94 -0
  130. package/dist/shared/wire/LambderApiContract.d.ts +129 -0
  131. package/dist/shared/wire/LambderApiOptionValues.d.ts +39 -0
  132. package/dist/shared/wire/LambderApiOptionValues.js +11 -0
  133. package/dist/shared/wire/LambderApiOutcome.d.ts +128 -0
  134. package/dist/shared/{LambderApiOutcome.js → wire/LambderApiOutcome.js} +16 -9
  135. package/dist/shared/{LambderApiError.d.ts → wire/LambderApiRefusal.d.ts} +48 -26
  136. package/dist/shared/{LambderApiError.js → wire/LambderApiRefusal.js} +13 -11
  137. package/dist/shared/wire/LambderCallOptions.d.ts +171 -0
  138. package/dist/shared/wire/LambderCallOptions.js +17 -0
  139. package/dist/shared/{LambderCompressionCodec.d.ts → wire/LambderCompressionCodec.d.ts} +10 -6
  140. package/dist/shared/{LambderCompressionCodec.js → wire/LambderCompressionCodec.js} +67 -23
  141. package/dist/shared/{LambderCompressionOption.d.ts → wire/LambderCompressionOption.d.ts} +1 -1
  142. package/dist/shared/{LambderCompressionOption.js → wire/LambderCompressionOption.js} +3 -4
  143. package/dist/shared/{LambderCrashDetail.d.ts → wire/LambderCrashDetail.d.ts} +10 -0
  144. package/dist/shared/{LambderCrashDetail.js → wire/LambderCrashDetail.js} +30 -0
  145. package/dist/shared/wire/LambderHttpStatus.d.ts +12 -0
  146. package/dist/shared/wire/LambderHttpStatus.js +1 -0
  147. package/dist/shared/{LambderRequestPayload.d.ts → wire/LambderRequestPayload.d.ts} +25 -17
  148. package/dist/shared/{LambderRequestPayload.js → wire/LambderRequestPayload.js} +29 -52
  149. package/dist/shared/wire/LambderSessionCookieNames.d.ts +9 -0
  150. package/dist/shared/wire/LambderSessionCookieNames.js +9 -0
  151. package/dist/stores/LambderDdbCache.d.ts +12 -9
  152. package/dist/stores/LambderDdbCache.js +56 -47
  153. package/dist/stores/{LambderDdbIdempotency.d.ts → LambderDdbIdempotencyStore.d.ts} +41 -31
  154. package/dist/stores/LambderDdbIdempotencyStore.js +319 -0
  155. package/dist/stores/LambderDdbRateLimiter.d.ts +30 -49
  156. package/dist/stores/LambderDdbRateLimiter.js +47 -45
  157. package/dist/stores/LambderDdbSdk.d.ts +83 -6
  158. package/dist/stores/LambderDdbSdk.js +83 -2
  159. package/dist/stores/LambderDdbSessionStore.d.ts +65 -0
  160. package/dist/stores/LambderDdbSessionStore.js +161 -0
  161. package/dist/stores/LambderHttpFileSource.d.ts +1 -1
  162. package/dist/stores/LambderHttpFileSource.js +10 -1
  163. package/dist/stores/LambderLocalFileSource.d.ts +15 -0
  164. package/dist/stores/LambderLocalFileSource.js +28 -0
  165. package/dist/stores/LambderMemoryIdempotencyStore.d.ts +63 -0
  166. package/dist/stores/LambderMemoryIdempotencyStore.js +113 -0
  167. package/dist/stores/LambderMemoryRateLimiter.d.ts +34 -0
  168. package/dist/stores/LambderMemoryRateLimiter.js +64 -0
  169. package/dist/stores/LambderMemorySessionStore.d.ts +48 -0
  170. package/dist/stores/LambderMemorySessionStore.js +74 -0
  171. package/dist/stores/LambderS3FileSource.d.ts +1 -1
  172. package/dist/stores/LambderS3FileSource.js +1 -1
  173. package/package.json +26 -24
  174. package/dist/client/LambderMSW.d.ts +0 -69
  175. package/dist/client/LambderMSW.js +0 -121
  176. package/dist/policies/LambderApiGuards.d.ts +0 -256
  177. package/dist/policies/LambderApiGuards.js +0 -94
  178. package/dist/policies/LambderApiIdempotency.d.ts +0 -58
  179. package/dist/policies/LambderApiIdempotency.js +0 -219
  180. package/dist/policies/LambderApiPolicies.d.ts +0 -42
  181. package/dist/policies/LambderApiPolicies.js +0 -52
  182. package/dist/policies/LambderApiRateLimits.d.ts +0 -132
  183. package/dist/policies/LambderApiRateLimits.js +0 -119
  184. package/dist/shared/LambderApiContract.d.ts +0 -57
  185. package/dist/shared/LambderApiOutcome.d.ts +0 -69
  186. package/dist/shared/LambderCallOptions.d.ts +0 -71
  187. package/dist/shared/LambderCallOptions.js +0 -16
  188. package/dist/shared/node-polyfills.d.ts +0 -4
  189. package/dist/shared/node-polyfills.js +0 -58
  190. package/dist/stores/LambderDdbIdempotency.js +0 -229
  191. package/dist/testing.d.ts +0 -9
  192. package/dist/testing.js +0 -8
  193. /package/dist/shared/{LambderApiContract.js → wire/LambderApiContract.js} +0 -0
  194. /package/dist/{core → shared/wire}/LambderCookie.d.ts +0 -0
  195. /package/dist/{core → shared/wire}/LambderCookie.js +0 -0
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The cryptography the session model runs on, behind an interface so the
3
+ * manager itself has no Node dependency: the bearer secrets are hashed at
4
+ * rest, compared in constant time, and minted from a cryptographic random
5
+ * source.
6
+ *
7
+ * LambderWebCrypto is the default and runs on Node 20+, every browser on a
8
+ * secure context, and edge runtimes. LambderPlainSessionCrypto is the
9
+ * stand-in the mock runtime picks on its own where crypto.subtle is missing
10
+ * (a plain-http page during device testing on a LAN): its memory store is
11
+ * not a table anybody can leak, so hashing there protects nothing.
12
+ */
13
+ import { getCrypto } from "../shared/util/LambderNodeModules.js";
14
+ import { bytesToHexString, resolveWebCrypto, sha256HexOf } from "../shared/util/LambderTextDigest.js";
15
+ /** Length-aware, timing-neutral string comparison: no early exit on the first differing character. */
16
+ const constantTimeEqual = (a, b) => {
17
+ if (a.length !== b.length)
18
+ return false;
19
+ let difference = 0;
20
+ for (let i = 0; i < a.length; i += 1)
21
+ difference |= a.charCodeAt(i) ^ b.charCodeAt(i);
22
+ return difference === 0;
23
+ };
24
+ /** True when this runtime offers WebCrypto's subtle API (secure contexts in browsers; Node 20+). */
25
+ export const isWebCryptoAvailable = () => typeof globalThis.crypto?.subtle?.digest === "function" && typeof globalThis.crypto.getRandomValues === "function";
26
+ /** sha256 through crypto.subtle and randomness through getRandomValues: the default. */
27
+ export class LambderWebCrypto {
28
+ isCryptographic = true;
29
+ cryptoPromise;
30
+ /**
31
+ * Node's crypto where the runtime has it, kept for timingSafeEqual.
32
+ * Warmed by ready(), because the comparison itself is synchronous and a
33
+ * session is always hashed before anything is compared against it.
34
+ */
35
+ nodeCrypto = null;
36
+ /**
37
+ * The runtime's WebCrypto, through the resolver every layer shares, with
38
+ * Node's crypto warmed alongside it.
39
+ *
40
+ * The availability question is asked here, before the shared resolver,
41
+ * only because of the answer a session has to it: a runtime with neither
42
+ * a global crypto nor Node's webcrypto can still run sessions over
43
+ * LambderPlainSessionCrypto and a memory store, which is this layer's own
44
+ * way out and not something the shared message can know about.
45
+ */
46
+ ready() {
47
+ this.cryptoPromise ??= (async () => {
48
+ const nodeCrypto = await getCrypto();
49
+ this.nodeCrypto = nodeCrypto;
50
+ const nodeWebCrypto = nodeCrypto?.webcrypto;
51
+ if (!isWebCryptoAvailable() && !nodeWebCrypto?.subtle) {
52
+ throw new Error("Lambder sessions need WebCrypto (crypto.subtle). In a browser that means a secure context (https or localhost); pass `crypto: new LambderPlainSessionCrypto()` where none is available and the store holds nothing worth hashing.");
53
+ }
54
+ return await resolveWebCrypto();
55
+ })();
56
+ return this.cryptoPromise;
57
+ }
58
+ async sha256Hex(value) {
59
+ // ready() first, for the message above and for the warmed Node
60
+ // crypto; the digest itself is the one every layer shares.
61
+ await this.ready();
62
+ return await sha256HexOf(value);
63
+ }
64
+ async randomHex(bytes) {
65
+ const webCrypto = await this.ready();
66
+ return bytesToHexString(webCrypto.getRandomValues(new Uint8Array(bytes)));
67
+ }
68
+ constantTimeEqual(a, b) {
69
+ // node's timingSafeEqual where the runtime has it: a primitive built
70
+ // for this beats a JS loop the engine is free to optimize. The loop is
71
+ // the fallback everywhere else, which is every browser.
72
+ const nodeCrypto = this.nodeCrypto;
73
+ if (nodeCrypto && a.length === b.length) {
74
+ const left = Buffer.from(a, "utf8");
75
+ const right = Buffer.from(b, "utf8");
76
+ if (left.length === right.length)
77
+ return nodeCrypto.timingSafeEqual(left, right);
78
+ }
79
+ return constantTimeEqual(a, b);
80
+ }
81
+ }
82
+ /**
83
+ * No hashing and no cryptographic randomness: values are hex-encoded as
84
+ * they are and secrets come from Math.random. Only for an in-memory store
85
+ * in a runtime without WebCrypto; never for anything at rest.
86
+ */
87
+ export class LambderPlainSessionCrypto {
88
+ isCryptographic = false;
89
+ async sha256Hex(value) {
90
+ return bytesToHexString(new TextEncoder().encode(value));
91
+ }
92
+ async randomHex(bytes) {
93
+ const random = new Uint8Array(bytes);
94
+ for (let i = 0; i < bytes; i += 1)
95
+ random[i] = Math.floor(Math.random() * 256);
96
+ return bytesToHexString(random);
97
+ }
98
+ constantTimeEqual(a, b) {
99
+ return constantTimeEqual(a, b);
100
+ }
101
+ }
@@ -1,34 +1,13 @@
1
- import { type LambderCompressionOption } from "../shared/LambderCompressionOption.js";
2
- export type LambderSessionContext<SessionData = any> = {
3
- [x: string]: any;
4
- /**
5
- * sha256 of the raw CSRF token. The raw bearer secrets are never stored:
6
- * they exist only in the client's cookies (and, at creation, on the
7
- * LambderCreatedSession result), so a read of the session table yields
8
- * no usable credentials.
9
- */
10
- csrfTokenHash: string;
11
- sessionKey: string;
12
- data: SessionData;
13
- createdAt: number;
14
- expiresAt: number;
15
- lastAccessedAt: number;
16
- ttlInSeconds: number;
17
- /**
18
- * When `data` must be renewed via the dataRefresh callback (epoch seconds).
19
- * Only present when dataRefresh is configured; independent of the
20
- * session's own expiresAt.
21
- */
22
- dataExpiresAt?: number;
23
- };
1
+ import type { LambderSessionCrypto } from "./LambderSessionCrypto.js";
2
+ import type { LambderSessionRecord, LambderSessionStore } from "../shared/contracts/LambderSessionStore.js";
24
3
  /**
25
4
  * A freshly created (or regenerated) session: the persisted record plus the
26
5
  * RAW cookie secrets, which exist only here and in the cookies the caller
27
6
  * sets. At rest the record carries hashes of both.
28
7
  */
29
8
  export type LambderCreatedSession<SessionData = any> = {
30
- session: LambderSessionContext<SessionData>;
31
- /** Raw bearer token for the session cookie (`pkHash:secret`). */
9
+ session: LambderSessionRecord<SessionData>;
10
+ /** Raw bearer token for the session cookie (`sessionKeyHash:secret`). */
32
11
  sessionToken: string;
33
12
  /** Raw CSRF token for the client-readable csrf cookie. */
34
13
  csrfToken: string;
@@ -39,7 +18,7 @@ export type LambderCreatedSession<SessionData = any> = {
39
18
  * checks dataExpiresAt and calls `refresh` past it, persisting the result
40
19
  * onto the same session record: same tokens, same cookies, the session
41
20
  * itself is untouched. The refresh write and the sliding-expiration write
42
- * share a single DynamoDB put when both are due.
21
+ * share a single store write when both are due.
43
22
  */
44
23
  export type LambderSessionDataRefreshConfig<SessionData = any> = {
45
24
  /** Seconds session.data stays valid before refresh() runs on read. */
@@ -52,7 +31,7 @@ export type LambderSessionDataRefreshConfig<SessionData = any> = {
52
31
  * LambderSessionDataRefreshError and leave the session untouched; catch
53
32
  * inside and return session.data to explicitly serve stale instead.
54
33
  */
55
- refresh: (session: LambderSessionContext<SessionData>) => Promise<SessionData | null>;
34
+ refresh: (session: LambderSessionRecord<SessionData>) => Promise<SessionData | null>;
56
35
  };
57
36
  /**
58
37
  * Wraps errors thrown by the dataRefresh callback so they stay
@@ -64,91 +43,150 @@ export declare class LambderSessionDataRefreshError extends Error {
64
43
  constructor(cause: unknown);
65
44
  }
66
45
  /**
67
- * Wraps DynamoDB failures during a session read so they stay distinguishable
46
+ * Wraps store failures during a session read so they stay distinguishable
68
47
  * from "no session": fetchSessionIfExists() swallows missing or invalid
69
- * sessions but rethrows this. Without the distinction a transient DynamoDB
48
+ * sessions but rethrows this. Without the distinction a transient store
70
49
  * error would answer sessionExpired, and the caller would then clear the
71
50
  * client's session cookies: an infra blip forcing a real logout.
72
51
  */
73
52
  export declare class LambderSessionReadError extends Error {
74
53
  constructor(cause: unknown);
75
54
  }
76
- export default class LambderSessionManager {
77
- private tableName;
78
- private sessionSalt;
79
- private partitionKey;
80
- private sortKey;
81
- private tableRegion;
82
- /** The document client and the SDK it came from, created the first time the table is touched. */
83
- private readyPromise;
84
- private enableSlidingExpiration;
85
- private slidingWriteIntervalSeconds;
86
- private dataRefresh;
87
- private compression;
88
- constructor({ tableName, tableRegion, partitionKey, sortKey, sessionSalt, enableSlidingExpiration, slidingWriteIntervalSeconds, dataRefresh, compression, }: {
89
- tableName: string;
90
- tableRegion: string;
91
- partitionKey: string;
92
- sortKey: string;
93
- sessionSalt: string;
94
- enableSlidingExpiration?: boolean;
95
- slidingWriteIntervalSeconds?: number;
96
- dataRefresh?: LambderSessionDataRefreshConfig;
97
- compression?: LambderCompressionOption;
98
- });
99
- /** The SDK and the client, loaded and created the first time the table is touched (see LambderDdbSdk). */
100
- private ready;
101
- private sessionUserKeyHasher;
55
+ export type LambderSessionManagerOptions<SessionData = any> = {
56
+ /** Where sessions rest: LambderDdbSessionStore, LambderMemorySessionStore, or your own. */
57
+ store: LambderSessionStore<SessionData>;
58
+ /** Salts the sessionKey hash that partitions the store, so a table read does not reveal which subject a session belongs to. */
59
+ sessionSalt: string;
60
+ enableSlidingExpiration?: boolean;
61
+ /** Min seconds between sliding-expiration writes. Default: max(60, 5% of TTL). */
62
+ slidingWriteIntervalSeconds?: number;
63
+ dataRefresh?: LambderSessionDataRefreshConfig<SessionData>;
64
+ /** Hashing and randomness. Default: WebCrypto. */
65
+ crypto?: LambderSessionCrypto;
66
+ };
67
+ /**
68
+ * Whether a string has the shape a minted session token has:
69
+ * `sessionKeyHash:secret`, both hex, neither longer than 1024 characters.
70
+ *
71
+ * It lives beside the code that mints and splits that format rather than
72
+ * beside the cookie reader, so the ceiling is a property of the model and
73
+ * anyone handing lookupSession a token they did not mint can ask the same
74
+ * question. The session controller asks it of every candidate cookie before
75
+ * any store read, so a malformed candidate is "no session" and never a read
76
+ * error. Nothing a browser legitimately holds fails it, because the only
77
+ * writer of these cookies is the code that mints them.
78
+ */
79
+ export declare const isMintedSessionToken: (token: string) => boolean;
80
+ /**
81
+ * The session model: how a session is minted, what its tokens look like,
82
+ * how it is found from a presented token, when it expires, when its data is
83
+ * renewed, and how it is rotated or ended. Storage and cryptography are
84
+ * injected, so the same class runs on Lambda over DynamoDB, in a test over
85
+ * a Map, and in a browser under the mock runtime.
86
+ *
87
+ * Token format: the session cookie carries `sessionKeyHash:secret`, where
88
+ * the hash locates the partition and only the secret's hash is stored as
89
+ * the sort key, so the lookup itself proves possession of the raw secret
90
+ * and a store read yields no usable token. The CSRF token is a second
91
+ * random value the client sends back in the request body; its hash is
92
+ * stored too.
93
+ */
94
+ export default class LambderSessionManager<SessionData = any> {
95
+ private readonly store;
96
+ private readonly sessionSalt;
97
+ private readonly enableSlidingExpiration;
98
+ private readonly slidingWriteIntervalSeconds;
99
+ private readonly dataRefresh;
100
+ private readonly crypto;
101
+ constructor({ store, sessionSalt, enableSlidingExpiration, slidingWriteIntervalSeconds, dataRefresh, crypto, }: LambderSessionManagerOptions<SessionData>);
102
+ /**
103
+ * The salted partition hash of a sessionKey: sha256 of the key followed
104
+ * by the salt, with NO separator between them.
105
+ *
106
+ * The missing separator is frozen by the wire guarantee, not chosen
107
+ * again here: every live session in every deployed table was partitioned
108
+ * under this exact string, so inserting a separator would relocate every
109
+ * partition key at once and read to everyone signed in as being logged
110
+ * out. What it costs is worth naming so nobody reintroduces it by
111
+ * accident: without a separator the split between key and salt is not
112
+ * recoverable from the string, so two deployments SHARING one table
113
+ * collide when the difference between their salts can be absorbed into a
114
+ * sessionKey ("ab" + "cd" and "a" + "bcd" hash alike). Two deployments
115
+ * over one table must therefore not stand in a prefix relationship over
116
+ * their salts. Separate tables, or salts that are independent random
117
+ * strings, both rule it out.
118
+ */
119
+ private sessionKeyHashOf;
102
120
  /**
103
121
  * At-rest hash for the bearer secrets (session sort-key secret, CSRF
104
122
  * token). Fast unsalted sha256 is the right construction here: the
105
123
  * inputs are 256-bit random values, so there is nothing to brute-force;
106
- * hashing just ensures a leaked table read yields no usable cookies.
124
+ * hashing just ensures a leaked store read yields no usable cookies.
107
125
  */
108
126
  private hashToken;
109
- private constantTimeCompare;
110
- private ddbGetItem;
111
- /**
112
- * Persists a session record. With compression on, `data` is stored as
113
- * Brotli bytes (`dataBr`) beside its JSON byte length (`dataBytes`)
114
- * once the JSON reaches minBytes; otherwise it stays a plain attribute.
115
- */
116
- private ddbPutItem;
117
- private ddbDeleteItem;
118
- /** Sort keys of every session under a partition (the callers only need the keys). */
119
- private ddbQueryAllByPartitionKey;
120
- private ddbDeleteAllByPartitionKey;
121
- createSession(sessionKey: string, data?: any, ttlInSeconds?: number, options?: {
127
+ createSession(sessionKey: string, data?: SessionData, ttlInSeconds?: number, options?: {
122
128
  /** Carries an existing data freshness stamp over (used by regenerateSession). */
123
129
  dataExpiresAt?: number;
124
- }): Promise<LambderCreatedSession>;
125
- updateSessionData(session: LambderSessionContext, newData?: any): Promise<LambderSessionContext>;
126
- getSession(sessionToken: string): Promise<LambderSessionContext | null>;
130
+ }): Promise<LambderCreatedSession<SessionData>>;
131
+ updateSessionData(session: LambderSessionRecord<SessionData>, newData: SessionData): Promise<LambderSessionRecord<SessionData>>;
132
+ /**
133
+ * The record a token names, read and structurally checked, with nothing
134
+ * renewed. Split out of getSession so a caller weighing several candidate
135
+ * cookies can decide which one is this visitor's BEFORE anything is
136
+ * written on their behalf: renewing slides an expiry and may run the
137
+ * app's dataRefresh callback, and a cookie a sibling host planted must
138
+ * not get either from the victim's traffic.
139
+ */
140
+ lookupSession(sessionToken: string): Promise<LambderSessionRecord<SessionData> | null>;
141
+ /**
142
+ * The renewal half of a session read: the dataRefresh callback once its
143
+ * shelf life has passed, and the sliding-expiration write. Returns null
144
+ * when a refresh says the session is over (a deleted or disabled login),
145
+ * which ends it the same way a missing record does.
146
+ */
147
+ renewSession(session: LambderSessionRecord<SessionData>): Promise<LambderSessionRecord<SessionData> | null>;
127
148
  /**
128
149
  * Runs the dataRefresh callback now, regardless of dataExpiresAt, and
129
150
  * persists the result onto the same record. Returns the updated session,
130
151
  * or null when the callback ended it (the record is deleted). Requires
131
152
  * dataRefresh to be configured.
132
153
  */
133
- refreshSessionData(session: LambderSessionContext): Promise<LambderSessionContext | null>;
134
- isSessionValid(session: any, sessionToken: any, csrfToken: any, skipCsrfTokenCheck?: boolean): boolean;
135
- deleteSession(session: Record<string, any>): Promise<boolean>;
136
- deleteSessionAll(session: Record<string, any>): Promise<boolean>;
154
+ refreshSessionData(session: LambderSessionRecord<SessionData>): Promise<LambderSessionRecord<SessionData> | null>;
155
+ /**
156
+ * Checks a record against the session token presented with it: the
157
+ * partition hash and the bearer secret the cookie carries, plus the
158
+ * structural checks and the expiry. This is the half a route needs, and
159
+ * the half lookupSession has already proved for a record it just found by
160
+ * that token's own hash, so the read path does not ask it again.
161
+ */
162
+ isSessionTokenValid(session: LambderSessionRecord<SessionData> | null, sessionToken: string | null): Promise<boolean>;
163
+ /**
164
+ * Checks a record against the CSRF token the request posted: the other
165
+ * half, asked of an API call and not of a route. Separate methods rather
166
+ * than one with a skip flag, because a boolean at the call site says
167
+ * nothing about which half it turns off, and the two are asked in
168
+ * different places for different reasons.
169
+ */
170
+ isSessionCsrfTokenValid(session: LambderSessionRecord<SessionData> | null, csrfToken: string | null): Promise<boolean>;
171
+ deleteSession(session: LambderSessionRecord<SessionData>): Promise<boolean>;
172
+ /** Deletes every session that shares the record's subject: "log this subject out everywhere". */
173
+ deleteSessionAll(session: LambderSessionRecord<SessionData>): Promise<boolean>;
137
174
  /**
138
175
  * Deletes every session created for the given sessionKey (e.g. a user
139
176
  * id): "log this subject out everywhere", without needing a fetched
140
177
  * session record.
141
178
  */
142
179
  deleteSessionAllByKey(sessionKey: string): Promise<boolean>;
180
+ private deleteAllUnder;
143
181
  /**
144
182
  * Marks the data of every session of the given sessionKey stale, so each
145
183
  * renews via dataRefresh on its next read: "this subject's roles or
146
184
  * permissions changed, apply it now", without logging the subject out
147
185
  * (deleteSessionAllByKey) and without waiting for the data TTL. Stamps
148
- * dataExpiresAt only, conditionally on the record still existing, so it
149
- * neither resurrects a session deleted in between nor overwrites a
150
- * concurrent write. Requires dataRefresh to be configured.
186
+ * dataExpiresAt only, on records that still exist, so it neither
187
+ * resurrects a session deleted in between nor overwrites a concurrent
188
+ * write. Requires dataRefresh to be configured.
151
189
  */
152
190
  expireSessionDataAllByKey(sessionKey: string): Promise<boolean>;
153
- regenerateSession(session: LambderSessionContext): Promise<LambderCreatedSession>;
191
+ regenerateSession(session: LambderSessionRecord<SessionData>): Promise<LambderCreatedSession<SessionData>>;
154
192
  }