ioredis-toolkit 0.0.10 → 0.5.0

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 (267) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/LICENSE +1 -1
  3. package/README.md +68 -1613
  4. package/dist/cache/cache.d.ts +30 -0
  5. package/dist/cache/cache.d.ts.map +1 -0
  6. package/dist/cache/cache.js +59 -0
  7. package/dist/cache/cache.js.map +1 -0
  8. package/dist/cache/config.d.ts +12 -0
  9. package/dist/cache/config.d.ts.map +1 -0
  10. package/dist/cache/config.js +13 -0
  11. package/dist/cache/config.js.map +1 -0
  12. package/dist/cache/types.d.ts +32 -0
  13. package/dist/cache/types.d.ts.map +1 -0
  14. package/dist/cache/types.js +5 -0
  15. package/dist/cache/types.js.map +1 -0
  16. package/dist/index.d.ts +43 -51
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +32 -44
  19. package/dist/index.js.map +1 -0
  20. package/dist/lock/config.d.ts +12 -0
  21. package/dist/lock/config.d.ts.map +1 -0
  22. package/dist/lock/config.js +8 -0
  23. package/dist/lock/config.js.map +1 -0
  24. package/dist/lock/lock.d.ts +20 -0
  25. package/dist/lock/lock.d.ts.map +1 -0
  26. package/dist/lock/lock.js +44 -0
  27. package/dist/lock/lock.js.map +1 -0
  28. package/dist/lock/types.d.ts +19 -0
  29. package/dist/lock/types.d.ts.map +1 -0
  30. package/dist/lock/types.js +2 -0
  31. package/dist/lock/types.js.map +1 -0
  32. package/dist/modules-config.d.ts +3 -0
  33. package/dist/modules-config.d.ts.map +1 -0
  34. package/dist/modules-config.js +2 -0
  35. package/dist/modules-config.js.map +1 -0
  36. package/dist/pubsub/config.d.ts +11 -0
  37. package/dist/pubsub/config.d.ts.map +1 -0
  38. package/dist/pubsub/config.js +6 -0
  39. package/dist/pubsub/config.js.map +1 -0
  40. package/dist/pubsub/pubsub.d.ts +20 -0
  41. package/dist/pubsub/pubsub.d.ts.map +1 -0
  42. package/dist/pubsub/pubsub.js +54 -0
  43. package/dist/pubsub/pubsub.js.map +1 -0
  44. package/dist/pubsub/types.d.ts +24 -0
  45. package/dist/pubsub/types.d.ts.map +1 -0
  46. package/dist/pubsub/types.js +2 -0
  47. package/dist/pubsub/types.js.map +1 -0
  48. package/dist/rate-limit/config.d.ts +12 -0
  49. package/dist/rate-limit/config.d.ts.map +1 -0
  50. package/dist/rate-limit/config.js +6 -0
  51. package/dist/rate-limit/config.js.map +1 -0
  52. package/dist/rate-limit/rate-limiter.d.ts +18 -0
  53. package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
  54. package/dist/rate-limit/rate-limiter.js +37 -0
  55. package/dist/rate-limit/rate-limiter.js.map +1 -0
  56. package/dist/rate-limit/types.d.ts +27 -0
  57. package/dist/rate-limit/types.d.ts.map +1 -0
  58. package/dist/rate-limit/types.js +2 -0
  59. package/dist/rate-limit/types.js.map +1 -0
  60. package/dist/redis/client-facade.d.ts +77 -0
  61. package/dist/redis/client-facade.d.ts.map +1 -0
  62. package/dist/redis/client-facade.js +102 -0
  63. package/dist/redis/client-facade.js.map +1 -0
  64. package/dist/redis/client.d.ts +10 -0
  65. package/dist/redis/client.d.ts.map +1 -0
  66. package/dist/redis/client.js +29 -0
  67. package/dist/redis/client.js.map +1 -0
  68. package/dist/redis/cluster.d.ts +7 -0
  69. package/dist/redis/cluster.d.ts.map +1 -0
  70. package/dist/redis/cluster.js +47 -0
  71. package/dist/redis/cluster.js.map +1 -0
  72. package/dist/redis/config.d.ts +39 -0
  73. package/dist/redis/config.d.ts.map +1 -0
  74. package/dist/redis/config.js +52 -0
  75. package/dist/redis/config.js.map +1 -0
  76. package/dist/redis/errors.d.ts +5 -0
  77. package/dist/redis/errors.d.ts.map +1 -0
  78. package/dist/redis/errors.js +5 -0
  79. package/dist/redis/errors.js.map +1 -0
  80. package/dist/redis/types.d.ts +135 -0
  81. package/dist/redis/types.d.ts.map +1 -0
  82. package/dist/redis/types.js +2 -0
  83. package/dist/redis/types.js.map +1 -0
  84. package/dist/redis/wrapper.d.ts +88 -0
  85. package/dist/redis/wrapper.d.ts.map +1 -0
  86. package/dist/redis/wrapper.js +206 -0
  87. package/dist/redis/wrapper.js.map +1 -0
  88. package/dist/session/config.d.ts +47 -0
  89. package/dist/session/config.d.ts.map +1 -0
  90. package/dist/session/config.js +101 -0
  91. package/dist/session/config.js.map +1 -0
  92. package/dist/session/cookie.d.ts +16 -0
  93. package/dist/session/cookie.d.ts.map +1 -0
  94. package/dist/session/cookie.js +28 -0
  95. package/dist/session/cookie.js.map +1 -0
  96. package/dist/session/errors.d.ts +56 -0
  97. package/dist/session/errors.d.ts.map +1 -0
  98. package/dist/session/errors.js +58 -0
  99. package/dist/session/errors.js.map +1 -0
  100. package/dist/session/factory.d.ts +21 -0
  101. package/dist/session/factory.d.ts.map +1 -0
  102. package/dist/session/factory.js +30 -0
  103. package/dist/session/factory.js.map +1 -0
  104. package/dist/session/health.d.ts +12 -0
  105. package/dist/session/health.d.ts.map +1 -0
  106. package/dist/session/health.js +23 -0
  107. package/dist/session/health.js.map +1 -0
  108. package/dist/session/keys.d.ts +23 -0
  109. package/dist/session/keys.d.ts.map +1 -0
  110. package/dist/session/keys.js +27 -0
  111. package/dist/session/keys.js.map +1 -0
  112. package/dist/session/manager.d.ts +34 -0
  113. package/dist/session/manager.d.ts.map +1 -0
  114. package/dist/session/manager.js +31 -0
  115. package/dist/session/manager.js.map +1 -0
  116. package/dist/session/metrics.d.ts +11 -0
  117. package/dist/session/metrics.d.ts.map +1 -0
  118. package/dist/session/metrics.js +10 -0
  119. package/dist/session/metrics.js.map +1 -0
  120. package/dist/session/repository.d.ts +49 -0
  121. package/dist/session/repository.d.ts.map +1 -0
  122. package/dist/session/repository.js +203 -0
  123. package/dist/session/repository.js.map +1 -0
  124. package/dist/session/revocation.d.ts +22 -0
  125. package/dist/session/revocation.d.ts.map +1 -0
  126. package/dist/session/revocation.js +41 -0
  127. package/dist/session/revocation.js.map +1 -0
  128. package/dist/session/script-sources.d.ts +11 -0
  129. package/dist/session/script-sources.d.ts.map +1 -0
  130. package/dist/session/script-sources.js +140 -0
  131. package/dist/session/script-sources.js.map +1 -0
  132. package/dist/session/scripts.d.ts +15 -0
  133. package/dist/session/scripts.d.ts.map +1 -0
  134. package/dist/session/scripts.js +41 -0
  135. package/dist/session/scripts.js.map +1 -0
  136. package/dist/session/serializer.d.ts +12 -0
  137. package/dist/session/serializer.d.ts.map +1 -0
  138. package/dist/session/serializer.js +77 -0
  139. package/dist/session/serializer.js.map +1 -0
  140. package/dist/session/service.d.ts +48 -0
  141. package/dist/session/service.d.ts.map +1 -0
  142. package/dist/session/service.js +235 -0
  143. package/dist/session/service.js.map +1 -0
  144. package/dist/session/token.d.ts +16 -0
  145. package/dist/session/token.d.ts.map +1 -0
  146. package/dist/session/token.js +32 -0
  147. package/dist/session/token.js.map +1 -0
  148. package/dist/session/types.d.ts +134 -0
  149. package/dist/session/types.d.ts.map +1 -0
  150. package/dist/session/types.js +2 -0
  151. package/dist/session/types.js.map +1 -0
  152. package/dist/streams/config.d.ts +12 -0
  153. package/dist/streams/config.d.ts.map +1 -0
  154. package/dist/streams/config.js +6 -0
  155. package/dist/streams/config.js.map +1 -0
  156. package/dist/streams/streams.d.ts +24 -0
  157. package/dist/streams/streams.d.ts.map +1 -0
  158. package/dist/streams/streams.js +55 -0
  159. package/dist/streams/streams.js.map +1 -0
  160. package/dist/streams/types.d.ts +32 -0
  161. package/dist/streams/types.d.ts.map +1 -0
  162. package/dist/streams/types.js +2 -0
  163. package/dist/streams/types.js.map +1 -0
  164. package/docs/ACCEPTANCE-REPORT.md +70 -0
  165. package/docs/ARCHITECTURE.md +61 -0
  166. package/docs/CAPACITY.md +33 -0
  167. package/docs/DEPLOYMENT.md +22 -0
  168. package/docs/README-API.md +15 -0
  169. package/docs/STATE-MACHINE.md +38 -0
  170. package/docs/TESTING.md +37 -0
  171. package/docs/THREAT-MODEL.md +23 -0
  172. package/docs/TYPE-SAFETY.md +34 -0
  173. package/docs/modules/cache/README.md +7 -0
  174. package/docs/modules/cache/usage.md +156 -0
  175. package/docs/modules/lock/README.md +7 -0
  176. package/docs/modules/lock/usage.md +105 -0
  177. package/docs/modules/pubsub/README.md +7 -0
  178. package/docs/modules/pubsub/usage.md +106 -0
  179. package/docs/modules/rate-limit/README.md +7 -0
  180. package/docs/modules/rate-limit/usage.md +100 -0
  181. package/docs/modules/sessions/README.md +7 -0
  182. package/docs/modules/sessions/usage.md +262 -0
  183. package/docs/modules/streams/README.md +7 -0
  184. package/docs/modules/streams/usage.md +141 -0
  185. package/package.json +50 -60
  186. package/src/scripts/cleanup-index.lua +4 -0
  187. package/src/scripts/conditional-update.lua +21 -0
  188. package/src/scripts/consume-session.lua +21 -0
  189. package/src/scripts/create-session.lua +28 -0
  190. package/src/scripts/delete.lua +2 -0
  191. package/src/scripts/destroy-user.lua +13 -0
  192. package/src/scripts/enforce-limit.lua +17 -0
  193. package/src/scripts/revoke-session.lua +13 -0
  194. package/src/scripts/rotate.lua +24 -0
  195. package/src/scripts/touch-session.lua +28 -0
  196. package/src/scripts/update-session.lua +18 -0
  197. package/dist/cache.d.ts +0 -797
  198. package/dist/cache.js +0 -1115
  199. package/dist/client.d.ts +0 -287
  200. package/dist/client.js +0 -1113
  201. package/dist/cluster-slot.d.ts +0 -4
  202. package/dist/cluster-slot.js +0 -31
  203. package/dist/cluster.d.ts +0 -79
  204. package/dist/cluster.js +0 -156
  205. package/dist/errors.d.ts +0 -30
  206. package/dist/errors.js +0 -63
  207. package/dist/health.d.ts +0 -180
  208. package/dist/health.js +0 -239
  209. package/dist/lock.d.ts +0 -233
  210. package/dist/lock.js +0 -440
  211. package/dist/logger.d.ts +0 -12
  212. package/dist/logger.js +0 -40
  213. package/dist/pubsub.d.ts +0 -423
  214. package/dist/pubsub.js +0 -537
  215. package/dist/ratelimiter.d.ts +0 -441
  216. package/dist/ratelimiter.js +0 -539
  217. package/dist/session/index.d.ts +0 -23
  218. package/dist/session/index.js +0 -16
  219. package/dist/session/revocation-store.d.ts +0 -176
  220. package/dist/session/revocation-store.js +0 -318
  221. package/dist/session/scripts/cleanup-index.lua +0 -21
  222. package/dist/session/scripts/conditional-update-encrypted.lua +0 -60
  223. package/dist/session/scripts/conditional-update.lua +0 -63
  224. package/dist/session/scripts/create.lua +0 -83
  225. package/dist/session/scripts/delete-by-user.lua +0 -29
  226. package/dist/session/scripts/delete.lua +0 -15
  227. package/dist/session/scripts/enforce-limit.lua +0 -38
  228. package/dist/session/scripts/revoke.lua +0 -61
  229. package/dist/session/scripts/rotate-encrypted.lua +0 -149
  230. package/dist/session/scripts/rotate.lua +0 -167
  231. package/dist/session/scripts/touch-encrypted.lua +0 -89
  232. package/dist/session/scripts/touch.lua +0 -72
  233. package/dist/session/scripts/validate.lua +0 -90
  234. package/dist/session/session-circuit-breaker.d.ts +0 -42
  235. package/dist/session/session-circuit-breaker.js +0 -129
  236. package/dist/session/session-config.d.ts +0 -355
  237. package/dist/session/session-config.js +0 -171
  238. package/dist/session/session-cookie.d.ts +0 -72
  239. package/dist/session/session-cookie.js +0 -101
  240. package/dist/session/session-encryption.d.ts +0 -87
  241. package/dist/session/session-encryption.js +0 -139
  242. package/dist/session/session-errors.d.ts +0 -85
  243. package/dist/session/session-errors.js +0 -145
  244. package/dist/session/session-health.d.ts +0 -38
  245. package/dist/session/session-health.js +0 -60
  246. package/dist/session/session-keys.d.ts +0 -64
  247. package/dist/session/session-keys.js +0 -128
  248. package/dist/session/session-manager.d.ts +0 -73
  249. package/dist/session/session-manager.js +0 -94
  250. package/dist/session/session-metrics.d.ts +0 -41
  251. package/dist/session/session-metrics.js +0 -135
  252. package/dist/session/session-repository.d.ts +0 -184
  253. package/dist/session/session-repository.js +0 -763
  254. package/dist/session/session-scripts.d.ts +0 -36
  255. package/dist/session/session-scripts.js +0 -130
  256. package/dist/session/session-serializer.d.ts +0 -42
  257. package/dist/session/session-serializer.js +0 -267
  258. package/dist/session/session-service.d.ts +0 -123
  259. package/dist/session/session-service.js +0 -670
  260. package/dist/session/session-token.d.ts +0 -38
  261. package/dist/session/session-token.js +0 -86
  262. package/dist/session/session-types.d.ts +0 -281
  263. package/dist/session/session-types.js +0 -16
  264. package/dist/types.d.ts +0 -924
  265. package/dist/types.js +0 -151
  266. package/dist/utils/deepmerge.d.ts +0 -9
  267. package/dist/utils/deepmerge.js +0 -61
@@ -1,171 +0,0 @@
1
- import { z } from 'zod';
2
- import { SessionConfigurationError } from './session-errors.js';
3
- /* -------------------------------------------------------------------------- */
4
- /* Session configuration. */
5
- /* */
6
- /* The public types below (SessionConfig + nested configs) are documented */
7
- /* interfaces: they are what editors show in intellisense. The Zod schemas */
8
- /* are the runtime validator; a compile-time shape guard (see bottom) keeps */
9
- /* the interfaces and schemas in sync. */
10
- /* */
11
- /* Secrets (encryption keys, Redis credentials) NEVER live in this config. */
12
- /* Encryption keys are injected via a SessionKeyProvider at construction. */
13
- /* -------------------------------------------------------------------------- */
14
- /** Default absolute session lifetime: 7 days. */
15
- export const TTL = 7 * 24 * 60 * 60;
16
- /** Default idle timeout: 24 hours. */
17
- export const IDLE_TIMEOUT = 24 * 60 * 60;
18
- /** Default touch throttle interval: 5 minutes. */
19
- export const TOUCH_INTERVAL = 5 * 60;
20
- export const SessionStatusSchema = z.enum(['active', 'consumed', 'revoked']);
21
- /** How strictly session binding metadata (IP/UA/device) is enforced. */
22
- export const SessionBindingPolicySchema = z.enum(['disabled', 'advisory', 'strict']);
23
- export const SessionCircuitBreakerConfigSchema = z
24
- .object({
25
- enabled: z.boolean().default(false),
26
- failureThreshold: z.number().int().min(1).default(10),
27
- resetTimeoutMs: z.number().int().min(1000).default(30_000),
28
- halfOpenMaxRequests: z.number().int().min(1).default(5),
29
- })
30
- .prefault({});
31
- export const SessionEncryptionConfigSchema = z.object({
32
- enabled: z.boolean().default(false),
33
- reEncryptOnWrite: z.boolean().default(true),
34
- });
35
- export const SessionMetricsConfigSchema = z.object({
36
- enabled: z.boolean().default(true),
37
- });
38
- export const SessionHealthConfigSchema = z.object({
39
- latencyThresholdMs: z.number().int().min(1).default(200),
40
- errorRateThreshold: z.number().min(0).max(1).default(0.1),
41
- errorWindowSize: z.number().int().min(1).default(100),
42
- });
43
- export const SessionCookieConfigSchema = z
44
- .object({
45
- name: z.string().min(1).max(128).default('sid'),
46
- path: z.string().min(1).default('/'),
47
- domain: z.string().optional(),
48
- httpOnly: z.boolean().default(true),
49
- secure: z.boolean().default(true),
50
- sameSite: z.enum(['strict', 'lax', 'none']).default('lax'),
51
- maxAge: z.number().int().min(1).optional(),
52
- })
53
- .superRefine((data, ctx) => {
54
- // SameSite=None is rejected by browsers unless Secure is set.
55
- if (data.sameSite === 'none' && !data.secure) {
56
- ctx.addIssue({
57
- code: z.ZodIssueCode.custom,
58
- message: 'SameSite=None requires secure: true',
59
- path: ['sameSite'],
60
- });
61
- }
62
- });
63
- export const SessionLimitsConfigSchema = z.object({
64
- maxMetadataSize: z.number().int().min(0).default(4096),
65
- maxListPageSize: z.number().int().min(1).default(100),
66
- maxBatchSize: z.number().int().min(1).max(500).default(100),
67
- maxFanOutConcurrency: z.number().int().min(1).max(64).default(8),
68
- maxEvictionsPerCall: z.number().int().min(1).max(5000).default(1000),
69
- maxSessionsPerUserHardCap: z.number().int().min(0).default(10_000),
70
- });
71
- export const SessionConfigSchema = z
72
- .object({
73
- enabled: z.boolean().default(false),
74
- namespace: z.string().min(1).max(64).default('authcore'),
75
- tokenBytes: z.number().int().min(16).max(64).default(32),
76
- ttl: z.number().int().min(1).default(TTL),
77
- idleTimeout: z.number().int().min(1).nullable().default(IDLE_TIMEOUT),
78
- rolling: z.boolean().default(true),
79
- touchInterval: z.number().int().min(0).default(TOUCH_INTERVAL),
80
- maxSessionsPerUser: z.number().int().min(0).default(20),
81
- storeDeviceId: z.boolean().default(false),
82
- storeIpAddress: z.boolean().default(false),
83
- storeUserAgent: z.boolean().default(false),
84
- bindingPolicy: SessionBindingPolicySchema.default('disabled'),
85
- securityVersion: z
86
- .object({
87
- enabled: z.boolean().default(false),
88
- })
89
- .prefault({}),
90
- jtiIndex: z
91
- .object({
92
- enabled: z.boolean().default(false),
93
- })
94
- .prefault({}),
95
- checkRevocationStore: z.boolean().default(false),
96
- encryption: SessionEncryptionConfigSchema.prefault({}),
97
- circuitBreaker: SessionCircuitBreakerConfigSchema,
98
- metrics: SessionMetricsConfigSchema.prefault({}),
99
- health: SessionHealthConfigSchema.prefault({}),
100
- cookie: SessionCookieConfigSchema.prefault({}),
101
- limits: SessionLimitsConfigSchema.prefault({}),
102
- enableCreateIdempotency: z.boolean().default(false),
103
- retainConsumedTombstones: z.boolean().default(true),
104
- revokeFamilyOnReplay: z.boolean().default(false),
105
- })
106
- .superRefine((data, ctx) => {
107
- if (data.idleTimeout !== null && data.idleTimeout > data.ttl) {
108
- ctx.addIssue({
109
- code: z.ZodIssueCode.custom,
110
- message: 'idleTimeout must not exceed ttl (absolute lifetime)',
111
- path: ['idleTimeout'],
112
- });
113
- }
114
- if (data.touchInterval > data.ttl) {
115
- ctx.addIssue({
116
- code: z.ZodIssueCode.custom,
117
- message: 'touchInterval must not exceed ttl',
118
- path: ['touchInterval'],
119
- });
120
- }
121
- if (data.revokeFamilyOnReplay && !data.retainConsumedTombstones) {
122
- ctx.addIssue({
123
- code: z.ZodIssueCode.custom,
124
- message: 'revokeFamilyOnReplay requires retainConsumedTombstones: replay can only be ' +
125
- 'detected while a consumed tombstone still exists to be replayed against.',
126
- path: ['revokeFamilyOnReplay'],
127
- });
128
- }
129
- });
130
- /**
131
- * Parses and validates session configuration.
132
- *
133
- * @throws {SessionConfigurationError} with a safe message on invalid config.
134
- */
135
- export function parseSessionConfig(input = {}) {
136
- try {
137
- return SessionConfigSchema.parse(input);
138
- }
139
- catch (error) {
140
- if (error instanceof z.ZodError) {
141
- const first = error.issues[0];
142
- const where = first?.path.length ? ` at "${first.path.join('.')}"` : '';
143
- throw new SessionConfigurationError(`Invalid session configuration${where}: ${first?.message ?? 'unknown error'}`);
144
- }
145
- throw error;
146
- }
147
- }
148
- /**
149
- * Returns a redacted copy of the config suitable for logging.
150
- * Strips nothing by default (no secrets are allowed in config), but the
151
- * serializer is explicit so future secret-bearing fields cannot leak.
152
- */
153
- export function redactSessionConfig(config) {
154
- return {
155
- enabled: config.enabled,
156
- namespace: config.namespace,
157
- tokenBytes: config.tokenBytes,
158
- ttl: config.ttl,
159
- idleTimeout: config.idleTimeout,
160
- rolling: config.rolling,
161
- touchInterval: config.touchInterval,
162
- maxSessionsPerUser: config.maxSessionsPerUser,
163
- bindingPolicy: config.bindingPolicy,
164
- securityVersion: config.securityVersion.enabled,
165
- jtiIndex: config.jtiIndex.enabled,
166
- checkRevocationStore: config.checkRevocationStore,
167
- encryption: { enabled: config.encryption.enabled },
168
- circuitBreaker: { enabled: config.circuitBreaker.enabled },
169
- cookie: { name: config.cookie.name, secure: config.cookie.secure },
170
- };
171
- }
@@ -1,72 +0,0 @@
1
- import type { SessionCookieConfig } from './session-config.js';
2
- export interface SerializeCookieOptions {
3
- /** Overrides the Max-Age (seconds). Defaults to config.maxAge. */
4
- maxAge?: number;
5
- /** Overrides the Path attribute. */
6
- path?: string;
7
- }
8
- /** Structured Set-Cookie attributes, mirroring the serialized header. */
9
- export interface SerializedCookieAttributes {
10
- path: string;
11
- domain?: string;
12
- httpOnly: boolean;
13
- secure: boolean;
14
- sameSite: 'strict' | 'lax' | 'none';
15
- maxAge?: number;
16
- }
17
- /**
18
- * A serialized cookie: the `Set-Cookie` header string plus the same
19
- * attributes as structured fields for frameworks that need the pieces
20
- * (or for tests asserting the exact shape).
21
- */
22
- export interface SerializedCookie {
23
- /** The full `Set-Cookie` header value. */
24
- header: string;
25
- /** The cookie name. */
26
- name: string;
27
- /** The cookie value — the raw session token. */
28
- value: string;
29
- /** Structured attributes mirroring the header. */
30
- attributes: SerializedCookieAttributes;
31
- }
32
- /**
33
- * Builds Set-Cookie values and reads Cookie headers for session tokens.
34
- */
35
- export declare class SessionCookieManager {
36
- private readonly config;
37
- constructor(config: SessionCookieConfig);
38
- /** The configured cookie name. */
39
- get name(): string;
40
- /**
41
- * Builds the `Set-Cookie` header value for a freshly created session.
42
- *
43
- * @example
44
- * ```ts
45
- * const header = cookies.serialize(token, { maxAge: ttlSeconds });
46
- * res.setHeader('Set-Cookie', header);
47
- * ```
48
- */
49
- serialize(token: string, options?: SerializeCookieOptions): string;
50
- /**
51
- * Like {@link serialize}, but returns the header string together with the
52
- * structured cookie object.
53
- *
54
- * @example
55
- * ```ts
56
- * const cookie = cookies.serializeWithAttributes(token, { maxAge: ttlSeconds });
57
- * res.setHeader('Set-Cookie', cookie.header);
58
- * console.log(cookie.attributes.sameSite);
59
- * ```
60
- */
61
- serializeWithAttributes(token: string, options?: SerializeCookieOptions): SerializedCookie;
62
- private buildHeader;
63
- /**
64
- * Builds the `Set-Cookie` header value that expires the cookie immediately.
65
- */
66
- clear(options?: SerializeCookieOptions): string;
67
- /**
68
- * Extracts the session token from a `Cookie` request header, or null when
69
- * the cookie is absent or its value is empty.
70
- */
71
- parse(header: string | null | undefined): string | null;
72
- }
@@ -1,101 +0,0 @@
1
- /**
2
- * Builds Set-Cookie values and reads Cookie headers for session tokens.
3
- */
4
- export class SessionCookieManager {
5
- config;
6
- constructor(config) {
7
- this.config = config;
8
- }
9
- /** The configured cookie name. */
10
- get name() {
11
- return this.config.name;
12
- }
13
- /**
14
- * Builds the `Set-Cookie` header value for a freshly created session.
15
- *
16
- * @example
17
- * ```ts
18
- * const header = cookies.serialize(token, { maxAge: ttlSeconds });
19
- * res.setHeader('Set-Cookie', header);
20
- * ```
21
- */
22
- serialize(token, options = {}) {
23
- return this.serializeWithAttributes(token, options).header;
24
- }
25
- /**
26
- * Like {@link serialize}, but returns the header string together with the
27
- * structured cookie object.
28
- *
29
- * @example
30
- * ```ts
31
- * const cookie = cookies.serializeWithAttributes(token, { maxAge: ttlSeconds });
32
- * res.setHeader('Set-Cookie', cookie.header);
33
- * console.log(cookie.attributes.sameSite);
34
- * ```
35
- */
36
- serializeWithAttributes(token, options = {}) {
37
- const path = options.path ?? this.config.path;
38
- const maxAge = options.maxAge ?? this.config.maxAge;
39
- const header = this.buildHeader(token, path, maxAge);
40
- const attributes = {
41
- path,
42
- httpOnly: this.config.httpOnly,
43
- secure: this.config.secure,
44
- sameSite: this.config.sameSite,
45
- };
46
- if (this.config.domain)
47
- attributes.domain = this.config.domain;
48
- if (maxAge !== undefined)
49
- attributes.maxAge = Math.max(0, Math.floor(maxAge));
50
- return { header, name: this.config.name, value: token, attributes };
51
- }
52
- buildHeader(token, path, maxAge) {
53
- const parts = [`${this.config.name}=${token}`];
54
- parts.push(`Path=${path}`);
55
- if (this.config.domain) {
56
- parts.push(`Domain=${this.config.domain}`);
57
- }
58
- if (this.config.httpOnly)
59
- parts.push('HttpOnly');
60
- if (this.config.secure)
61
- parts.push('Secure');
62
- parts.push(`SameSite=${capitalize(this.config.sameSite)}`);
63
- if (maxAge !== undefined) {
64
- parts.push(`Max-Age=${Math.max(0, Math.floor(maxAge))}`);
65
- }
66
- return parts.join('; ');
67
- }
68
- /**
69
- * Builds the `Set-Cookie` header value that expires the cookie immediately.
70
- */
71
- clear(options = {}) {
72
- const parts = [`${this.config.name}=`];
73
- parts.push(`Path=${options.path ?? this.config.path}`);
74
- if (this.config.domain) {
75
- parts.push(`Domain=${this.config.domain}`);
76
- }
77
- parts.push('Max-Age=0');
78
- parts.push('Expires=Thu, 01 Jan 1970 00:00:00 GMT');
79
- return parts.join('; ');
80
- }
81
- /**
82
- * Extracts the session token from a `Cookie` request header, or null when
83
- * the cookie is absent or its value is empty.
84
- */
85
- parse(header) {
86
- if (!header)
87
- return null;
88
- const prefix = `${this.config.name}=`;
89
- for (const part of header.split(';')) {
90
- const trimmed = part.trim();
91
- if (trimmed.startsWith(prefix)) {
92
- const value = trimmed.slice(prefix.length);
93
- return value.length > 0 ? value : null;
94
- }
95
- }
96
- return null;
97
- }
98
- }
99
- function capitalize(value) {
100
- return value.charAt(0).toUpperCase() + value.slice(1);
101
- }
@@ -1,87 +0,0 @@
1
- import type { EncryptedSessionEnvelope } from './session-types.js';
2
- /**
3
- * Key management abstraction. Applications provide their own implementation
4
- * (KMS, secret store, env-based rotation), or a simple in-memory map of
5
- * versions to 32-byte keys for single-process deployments.
6
- */
7
- export interface SessionKeyProvider {
8
- /**
9
- * Returns the encryption key used for all WRITES (new sessions, updates,
10
- * re-encryption on touch). Called on every encryption.
11
- *
12
- * The version labels which key produced the ciphertext: it is stored in
13
- * the session envelope (`k` field) so the corresponding key can be looked
14
- * up later on read. Always return the current key here — never an
15
- * arbitrary one.
16
- *
17
- * The key may be a 32-byte Buffer or a string (see {@link toKeyBuffer} for
18
- * the accepted encodings). Whatever form is returned here must also be
19
- * resolvable via {@link getKey} for the same version.
20
- */
21
- getCurrentKey(): {
22
- keyVersion: number;
23
- key: Buffer | string;
24
- };
25
- /**
26
- * Returns the key for a specific version, or null when that version is no
27
- * longer available. Called on every READ (decryption).
28
- *
29
- * Every session records the version it was encrypted with; this lookup
30
- * resolves it. Returning null makes sessions encrypted with that version
31
- * undecryptable — they surface as {@link SessionSerializationError}.
32
- *
33
- * This is what enables safe key rotation: when the current key changes,
34
- * old keys must stay available here so previously written sessions keep
35
- * decrypting. Only drop a version once every session using it has expired
36
- * or been re-encrypted with the current key.
37
- */
38
- getKey(keyVersion: number): Buffer | string | null;
39
- }
40
- /**
41
- * Normalizes a key to a Buffer for use with AES-256-GCM.
42
- *
43
- * Buffers are returned as-is. Strings are decoded in priority order:
44
- * - 64 hex characters -> hex
45
- * - base64 / base64url text that decodes to exactly 32 bytes -> base64
46
- * - anything else -> utf8 (a 32-character passphrase)
47
- *
48
- * The decoded key must be 32 bytes (AES-256) for Node's crypto to accept
49
- * it; {@link StaticSessionKeyProvider} validates this at construction.
50
- */
51
- export declare function toKeyBuffer(key: Buffer | string): Buffer;
52
- /** A simple key provider for single-process deployments (env/CLI injection). */
53
- export declare class StaticSessionKeyProvider implements SessionKeyProvider {
54
- private readonly currentVersion;
55
- private readonly keys;
56
- constructor(keys: ReadonlyMap<number, Buffer | string>, currentVersion: number);
57
- getCurrentKey(): {
58
- keyVersion: number;
59
- key: Buffer;
60
- };
61
- getKey(keyVersion: number): Buffer | null;
62
- }
63
- /**
64
- * Creates a {@link StaticSessionKeyProvider} with one freshly generated
65
- * 32-byte key. Convenience for local development and tests; production
66
- * deployments should derive keys from a KMS/vault instead.
67
- *
68
- * @param keyVersion - Version label for the generated key (default 1).
69
- */
70
- export declare function createRandomSessionKeyProvider(keyVersion?: number): StaticSessionKeyProvider;
71
- /**
72
- * Encrypts a plaintext payload with AES-256-GCM using the current key.
73
- * Returns a fresh random IV + auth tag per call.
74
- */
75
- export declare function encryptPayload(plaintext: Buffer, provider: SessionKeyProvider): Pick<EncryptedSessionEnvelope, 'k' | 'i' | 't' | 'c'>;
76
- /**
77
- * Decrypts an envelope, verifying the GCM auth tag.
78
- *
79
- * @throws {SessionSerializationError} when the key version is unknown, the
80
- * IV/auth tag/ciphertext are malformed, or authentication fails (tampered
81
- * or corrupt data).
82
- */
83
- export declare function decryptPayload(envelope: Pick<EncryptedSessionEnvelope, 'k' | 'i' | 't' | 'c'>, provider: SessionKeyProvider): Buffer;
84
- /** Encrypts a JSON string into an encrypted envelope body. */
85
- export declare function encryptJson(json: string, provider: SessionKeyProvider): Pick<EncryptedSessionEnvelope, 'k' | 'i' | 't' | 'c'>;
86
- /** Decrypts an envelope body and parses the JSON inside. */
87
- export declare function decryptJson<T>(envelope: Pick<EncryptedSessionEnvelope, 'k' | 'i' | 't' | 'c'>, provider: SessionKeyProvider): T;
@@ -1,139 +0,0 @@
1
- import { createCipheriv, createDecipheriv, randomBytes, } from 'node:crypto';
2
- import { SessionConfigurationError, SessionSerializationError } from './session-errors.js';
3
- /**
4
- * Normalizes a key to a Buffer for use with AES-256-GCM.
5
- *
6
- * Buffers are returned as-is. Strings are decoded in priority order:
7
- * - 64 hex characters -> hex
8
- * - base64 / base64url text that decodes to exactly 32 bytes -> base64
9
- * - anything else -> utf8 (a 32-character passphrase)
10
- *
11
- * The decoded key must be 32 bytes (AES-256) for Node's crypto to accept
12
- * it; {@link StaticSessionKeyProvider} validates this at construction.
13
- */
14
- export function toKeyBuffer(key) {
15
- if (Buffer.isBuffer(key))
16
- return key;
17
- const trimmed = key.trim();
18
- if (/^[0-9a-fA-F]{64}$/.test(trimmed)) {
19
- return Buffer.from(trimmed, 'hex');
20
- }
21
- if (/^[A-Za-z0-9+/_-]+={0,2}$/.test(trimmed)) {
22
- const decoded = Buffer.from(trimmed.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
23
- if (decoded.length === 32)
24
- return decoded;
25
- }
26
- return Buffer.from(key, 'utf8');
27
- }
28
- /** A simple key provider for single-process deployments (env/CLI injection). */
29
- export class StaticSessionKeyProvider {
30
- currentVersion;
31
- keys;
32
- constructor(keys, currentVersion) {
33
- this.currentVersion = currentVersion;
34
- if (keys.size === 0) {
35
- throw new SessionConfigurationError('At least one encryption key is required.');
36
- }
37
- if (!keys.has(currentVersion)) {
38
- throw new SessionConfigurationError('currentVersion must be present in the key map.');
39
- }
40
- const normalized = new Map();
41
- for (const [version, raw] of keys) {
42
- const key = toKeyBuffer(raw);
43
- if (key.length !== 32) {
44
- throw new SessionConfigurationError(`Encryption key version ${version} must be exactly 32 bytes (AES-256).`);
45
- }
46
- normalized.set(version, key);
47
- }
48
- this.keys = normalized;
49
- }
50
- getCurrentKey() {
51
- return { keyVersion: this.currentVersion, key: this.keys.get(this.currentVersion) };
52
- }
53
- getKey(keyVersion) {
54
- return this.keys.get(keyVersion) ?? null;
55
- }
56
- }
57
- /**
58
- * Creates a {@link StaticSessionKeyProvider} with one freshly generated
59
- * 32-byte key. Convenience for local development and tests; production
60
- * deployments should derive keys from a KMS/vault instead.
61
- *
62
- * @param keyVersion - Version label for the generated key (default 1).
63
- */
64
- export function createRandomSessionKeyProvider(keyVersion = 1) {
65
- return new StaticSessionKeyProvider(new Map([[keyVersion, randomBytes(32)]]), keyVersion);
66
- }
67
- const IV_BYTES = 12;
68
- const TAG_BYTES = 16;
69
- /**
70
- * Encrypts a plaintext payload with AES-256-GCM using the current key.
71
- * Returns a fresh random IV + auth tag per call.
72
- */
73
- export function encryptPayload(plaintext, provider) {
74
- const { keyVersion, key } = provider.getCurrentKey();
75
- const iv = randomBytes(IV_BYTES);
76
- const cipher = createCipheriv('aes-256-gcm', toKeyBuffer(key), iv);
77
- const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
78
- const authTag = cipher.getAuthTag();
79
- return {
80
- k: keyVersion,
81
- i: iv.toString('base64url'),
82
- t: authTag.toString('base64url'),
83
- c: ciphertext.toString('base64url'),
84
- };
85
- }
86
- /**
87
- * Decrypts an envelope, verifying the GCM auth tag.
88
- *
89
- * @throws {SessionSerializationError} when the key version is unknown, the
90
- * IV/auth tag/ciphertext are malformed, or authentication fails (tampered
91
- * or corrupt data).
92
- */
93
- export function decryptPayload(envelope, provider) {
94
- const raw = provider.getKey(envelope.k);
95
- if (!raw) {
96
- throw new SessionSerializationError({
97
- reason: 'unknown_key_version',
98
- keyVersion: envelope.k,
99
- });
100
- }
101
- const key = toKeyBuffer(raw);
102
- let iv;
103
- let tag;
104
- let ciphertext;
105
- try {
106
- iv = Buffer.from(envelope.i, 'base64url');
107
- tag = Buffer.from(envelope.t, 'base64url');
108
- ciphertext = Buffer.from(envelope.c, 'base64url');
109
- }
110
- catch {
111
- throw new SessionSerializationError({ reason: 'malformed_encrypted_fields' });
112
- }
113
- if (iv.length !== IV_BYTES || tag.length !== TAG_BYTES) {
114
- throw new SessionSerializationError({ reason: 'malformed_encrypted_fields' });
115
- }
116
- try {
117
- const decipher = createDecipheriv('aes-256-gcm', key, iv);
118
- decipher.setAuthTag(tag);
119
- return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
120
- }
121
- catch {
122
- // GCM auth failure = tampered or corrupted data. Treat as invalid.
123
- throw new SessionSerializationError({ reason: 'authentication_failed' });
124
- }
125
- }
126
- /** Encrypts a JSON string into an encrypted envelope body. */
127
- export function encryptJson(json, provider) {
128
- return encryptPayload(Buffer.from(json, 'utf8'), provider);
129
- }
130
- /** Decrypts an envelope body and parses the JSON inside. */
131
- export function decryptJson(envelope, provider) {
132
- const plaintext = decryptPayload(envelope, provider);
133
- try {
134
- return JSON.parse(plaintext.toString('utf8'));
135
- }
136
- catch {
137
- throw new SessionSerializationError({ reason: 'malformed_plaintext' });
138
- }
139
- }
@@ -1,85 +0,0 @@
1
- import { RedisError } from '../errors.js';
2
- /**
3
- * Base class for every session subsystem error.
4
- * Extends {@link RedisError} so existing Redis error handling keeps working.
5
- */
6
- export declare class SessionError extends RedisError {
7
- constructor(message: string, code?: string, details?: Record<string, unknown>);
8
- }
9
- /** The session does not exist (or no longer exists). */
10
- export declare class SessionNotFoundError extends SessionError {
11
- constructor(details?: Record<string, unknown>);
12
- }
13
- /** The session expired (absolute or idle timeout), or was created in the past. */
14
- export declare class SessionExpiredError extends SessionError {
15
- constructor(details?: Record<string, unknown>);
16
- }
17
- /** The session was revoked or consumed by a rotation (replay detected). */
18
- export declare class SessionRevokedError extends SessionError {
19
- constructor(details?: Record<string, unknown>);
20
- }
21
- /** The session record exists but is invalid (corrupt, tampered, mismatched). */
22
- export declare class SessionInvalidError extends SessionError {
23
- constructor(details?: Record<string, unknown>);
24
- }
25
- /** A security-sensitive session transition (rotation) failed. */
26
- export declare class SessionRotationError extends SessionError {
27
- constructor(details?: Record<string, unknown>);
28
- }
29
- /** Reuse of an already-consumed session token was detected. */
30
- export declare class SessionReplayError extends SessionError {
31
- constructor(details?: Record<string, unknown>);
32
- }
33
- /**
34
- * Redis (or the session storage backend) is unavailable.
35
- *
36
- * Authentication MUST fail closed on this error: never treat it as an
37
- * invalid session, and never fall back to assuming the session is valid.
38
- */
39
- export declare class SessionStorageError extends SessionError {
40
- constructor(message?: string, details?: Record<string, unknown>);
41
- }
42
- /** Stored session data could not be deserialized/decrypted. */
43
- export declare class SessionSerializationError extends SessionError {
44
- constructor(details?: Record<string, unknown>);
45
- }
46
- /** Session configuration is invalid (fails at manager construction). */
47
- export declare class SessionConfigurationError extends SessionError {
48
- constructor(message: string, details?: Record<string, unknown>);
49
- }
50
- /** Optimistic-concurrency conflict on a session update. */
51
- export declare class SessionConcurrencyError extends SessionError {
52
- constructor(details?: Record<string, unknown>);
53
- }
54
- /** A revocation could not be persisted (fail closed, do not swallow). */
55
- export declare class RevocationError extends SessionError {
56
- constructor(details?: Record<string, unknown>);
57
- }
58
- /** A batch revocation partially failed; check `failures` for details. */
59
- export declare class RevocationBatchError extends SessionError {
60
- /** Safe identifiers of the entries whose pipeline command failed. */
61
- readonly failures: Array<{
62
- jti: string;
63
- error: unknown;
64
- }>;
65
- constructor(failures: Array<{
66
- jti: string;
67
- error: unknown;
68
- }>);
69
- /** Compatibility alias: the affected jtis. */
70
- get ids(): string[];
71
- }
72
- /** The circuit breaker is open; requests fail closed without touching Redis. */
73
- export declare class CircuitBreakerOpenError extends SessionError {
74
- constructor(details?: Record<string, unknown>);
75
- }
76
- /**
77
- * Redacts an identifier for safe inclusion in logs/errors/metrics labels.
78
- *
79
- * Only the length and a short opaque suffix are revealed; never the full
80
- * value. Use for jti/userId/deviceId/ipAddress in structured details.
81
- *
82
- * @example
83
- * redactIdentifier('dG9rZW5oYXNo...') // => 'token#c3V'
84
- */
85
- export declare function redactIdentifier(value: string | null | undefined): string;