@getstrata/core 2.2.32 → 2.2.34

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 (34) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/core/auth/accountState.d.ts +9 -0
  3. package/dist/core/auth/accountStateMigration.d.ts +7 -0
  4. package/dist/core/auth/authContext.d.ts +3 -0
  5. package/dist/core/auth/guard.d.ts +1 -0
  6. package/dist/core/auth/jwt.d.ts +1 -0
  7. package/dist/core/auth/oneTimeToken.d.ts +2 -0
  8. package/dist/core/auth/passwordConfirmCookie.d.ts +2 -2
  9. package/dist/core/auth/passwordLogin.d.ts +2 -2
  10. package/dist/core/auth/pendingMfaCookie.d.ts +9 -0
  11. package/dist/core/auth/sessionCookie.d.ts +3 -0
  12. package/dist/core/contracts/authUserDirectory.d.ts +5 -0
  13. package/dist/entries/audit/exportAuditLogs.js +14 -0
  14. package/dist/entries/auth/accountState.js +18 -0
  15. package/dist/entries/auth/accountStateMigration.js +923 -0
  16. package/dist/entries/auth/basicAuthGuard.js +33 -1
  17. package/dist/entries/auth/jwtGuard.js +71 -23
  18. package/dist/entries/auth/oneTimeToken.js +16 -12
  19. package/dist/entries/auth/passwordConfirmCookie.js +63 -22
  20. package/dist/entries/auth/passwordLogin.js +15 -0
  21. package/dist/entries/auth/pendingMfaCookie.js +100 -0
  22. package/dist/entries/auth/scimAuthMiddleware.js +2330 -194
  23. package/dist/entries/auth/sessionCookie.js +49 -21
  24. package/dist/entries/auth/sessionGuard.js +66 -22
  25. package/dist/entries/http/cookies.js +5 -1
  26. package/dist/entries/http/csrfMiddleware.js +5 -1
  27. package/dist/entries/http/csrfToken.js +5 -1
  28. package/dist/entries/http/requirePasswordConfirmMiddleware.js +1 -1
  29. package/dist/entries/jobs/exportAuditLogsJob.js +14 -0
  30. package/dist/entries/queue/queueMetrics.js +14 -0
  31. package/dist/entries/tenant/databaseTenantContext.js +14 -0
  32. package/dist/entries/tenant/tenantDatabaseScope.js +14 -0
  33. package/dist/index.js +2564 -2535
  34. package/package.json +17 -2
@@ -2,6 +2,38 @@
2
2
  // ../../src/core/auth/sessionCookie.ts
3
3
  import { createHmac, timingSafeEqual } from "crypto";
4
4
 
5
+ // ../../src/core/http/cookies.ts
6
+ function readRequestCookie(request, name) {
7
+ const cookies = request.cookies;
8
+ if (cookies && typeof cookies.get === "function") {
9
+ const value = cookies.get(name);
10
+ if (value) {
11
+ return value;
12
+ }
13
+ }
14
+ const header = request.headers.get("cookie");
15
+ if (!header) {
16
+ return null;
17
+ }
18
+ for (const part of header.split(";")) {
19
+ const idx = part.indexOf("=");
20
+ if (idx === -1)
21
+ continue;
22
+ const cookieName = part.slice(0, idx).trim();
23
+ if (cookieName !== name)
24
+ continue;
25
+ try {
26
+ return decodeURIComponent(part.slice(idx + 1).trim());
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ function readBunRequestCookie(request, name) {
34
+ return request.cookies.get(name) ?? readRequestCookie(request, name);
35
+ }
36
+
5
37
  // ../../src/core/runtime/appEnv.ts
6
38
  var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
7
39
  function normalizeEnvValue(value) {
@@ -108,25 +140,12 @@ function sessionRememberTtlSeconds() {
108
140
  function resolveSessionSecret() {
109
141
  return requireConfiguredSecret(["SESSION_SECRET"], "session-secret");
110
142
  }
111
- function signSession(userId, issuedAt, ttlSeconds) {
112
- const payload = ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
143
+ function signSession(userId, issuedAt, ttlSeconds, authVersion) {
144
+ const payload = authVersion !== undefined ? `v2.${userId}.${issuedAt}.${ttlSeconds}.${authVersion}` : ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
113
145
  const signature = createHmac("sha256", resolveSessionSecret()).update(payload).digest("hex");
114
146
  return `${payload}.${signature}`;
115
147
  }
116
- function readCookieValue(request, cookieName) {
117
- const cookieHeader = request.headers.get("cookie");
118
- if (!cookieHeader) {
119
- return null;
120
- }
121
- for (const part of cookieHeader.split(";")) {
122
- const [name, ...rest] = part.trim().split("=");
123
- if (name === cookieName) {
124
- return decodeURIComponent(rest.join("="));
125
- }
126
- }
127
- return null;
128
- }
129
- function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds, remember) {
148
+ function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds, remember, authVersion) {
130
149
  const userId = Number.parseInt(userIdRaw, 10);
131
150
  const issuedAt = Number.parseInt(issuedAtRaw, 10);
132
151
  if (!Number.isInteger(userId) || userId <= 0 || !Number.isFinite(issuedAt)) {
@@ -138,7 +157,7 @@ function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds,
138
157
  if (Date.now() - issuedAt > ttlSeconds * 1000) {
139
158
  return null;
140
159
  }
141
- const expectedSignature = (remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt)).split(".").pop();
160
+ const expectedSignature = (authVersion !== undefined ? signSession(userId, issuedAt, ttlSeconds, authVersion) : remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt)).split(".").pop();
142
161
  if (!expectedSignature || !cookieSignature) {
143
162
  return null;
144
163
  }
@@ -150,14 +169,20 @@ function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds,
150
169
  if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
151
170
  return null;
152
171
  }
153
- return { userId, issuedAt };
172
+ return { userId, issuedAt, ...authVersion !== undefined ? { authVersion } : {} };
154
173
  }
155
174
  function readSession(request) {
156
- const cookieValue = readCookieValue(request, sessionCookieName());
175
+ const cookieValue = readRequestCookie(request, sessionCookieName());
157
176
  if (!cookieValue) {
158
177
  return null;
159
178
  }
160
179
  const parts = cookieValue.split(".");
180
+ if (parts.length === 6 && parts[0] === "v2") {
181
+ const fields = parts.slice(1, 5);
182
+ if (fields.some((value) => !/^(0|[1-9][0-9]*)$/.test(value) || !Number.isSafeInteger(Number(value))))
183
+ return null;
184
+ return readSignedSession(String(parts[1]), String(parts[2]), parts[5], Number(parts[3]), true, Number(parts[4]));
185
+ }
161
186
  if (parts.length === 4) {
162
187
  const [userIdRaw, issuedAtRaw, ttlRaw, cookieSignature] = parts;
163
188
  return readSignedSession(String(userIdRaw), String(issuedAtRaw), cookieSignature, Number.parseInt(String(ttlRaw), 10), true);
@@ -182,15 +207,18 @@ function isSessionInvalidated(issuedAt, validAfter) {
182
207
  return issuedAt < timestamp;
183
208
  }
184
209
  function createSessionCookieDetails(userId, options = {}) {
210
+ if (options.authVersion !== undefined && (!Number.isSafeInteger(options.authVersion) || options.authVersion < 0))
211
+ throw new RangeError("authVersion must be a non-negative safe integer.");
185
212
  const issuedAt = Date.now();
186
213
  const ttlSeconds = options.remember ? sessionRememberTtlSeconds() : sessionTtlSeconds();
187
- const value = options.remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt);
214
+ const value = options.authVersion !== undefined ? signSession(userId, issuedAt, ttlSeconds, options.authVersion) : options.remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt);
188
215
  const secure = isProductionEnv() ? "; Secure" : "";
189
216
  return {
190
217
  header: `${sessionCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ttlSeconds}${secure}`,
191
218
  userId,
192
219
  issuedAt,
193
- ttlSeconds
220
+ ttlSeconds,
221
+ ...options.authVersion !== undefined ? { authVersion: options.authVersion } : {}
194
222
  };
195
223
  }
196
224
  function createSessionCookie(userId, options = {}) {
@@ -147,9 +147,54 @@ function resolveAbilitiesForRole(role) {
147
147
  return [...MEMBER_ABILITIES];
148
148
  }
149
149
 
150
+ // ../../src/core/auth/accountState.ts
151
+ function isAccountActive(record) {
152
+ return record.is_active === undefined || record.is_active === true;
153
+ }
154
+ function isAuthUserActive(user) {
155
+ return user.isActive === undefined || user.isActive === true;
156
+ }
157
+ function matchesAuthVersion(issued, current) {
158
+ if (current === undefined)
159
+ return true;
160
+ return Number.isSafeInteger(current) && current >= 0 && typeof issued === "number" && Number.isSafeInteger(issued) && issued >= 0 && issued === current;
161
+ }
162
+
150
163
  // ../../src/core/auth/sessionCookie.ts
151
164
  import { createHmac, timingSafeEqual } from "crypto";
152
165
 
166
+ // ../../src/core/http/cookies.ts
167
+ function readRequestCookie(request, name) {
168
+ const cookies = request.cookies;
169
+ if (cookies && typeof cookies.get === "function") {
170
+ const value = cookies.get(name);
171
+ if (value) {
172
+ return value;
173
+ }
174
+ }
175
+ const header = request.headers.get("cookie");
176
+ if (!header) {
177
+ return null;
178
+ }
179
+ for (const part of header.split(";")) {
180
+ const idx = part.indexOf("=");
181
+ if (idx === -1)
182
+ continue;
183
+ const cookieName = part.slice(0, idx).trim();
184
+ if (cookieName !== name)
185
+ continue;
186
+ try {
187
+ return decodeURIComponent(part.slice(idx + 1).trim());
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function readBunRequestCookie(request, name) {
195
+ return request.cookies.get(name) ?? readRequestCookie(request, name);
196
+ }
197
+
153
198
  // ../../src/core/runtime/appEnv.ts
154
199
  var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
155
200
  function normalizeEnvValue(value) {
@@ -256,25 +301,12 @@ function sessionRememberTtlSeconds() {
256
301
  function resolveSessionSecret() {
257
302
  return requireConfiguredSecret(["SESSION_SECRET"], "session-secret");
258
303
  }
259
- function signSession(userId, issuedAt, ttlSeconds) {
260
- const payload = ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
304
+ function signSession(userId, issuedAt, ttlSeconds, authVersion) {
305
+ const payload = authVersion !== undefined ? `v2.${userId}.${issuedAt}.${ttlSeconds}.${authVersion}` : ttlSeconds === undefined ? `${userId}.${issuedAt}` : `${userId}.${issuedAt}.${ttlSeconds}`;
261
306
  const signature = createHmac("sha256", resolveSessionSecret()).update(payload).digest("hex");
262
307
  return `${payload}.${signature}`;
263
308
  }
264
- function readCookieValue(request, cookieName) {
265
- const cookieHeader = request.headers.get("cookie");
266
- if (!cookieHeader) {
267
- return null;
268
- }
269
- for (const part of cookieHeader.split(";")) {
270
- const [name, ...rest] = part.trim().split("=");
271
- if (name === cookieName) {
272
- return decodeURIComponent(rest.join("="));
273
- }
274
- }
275
- return null;
276
- }
277
- function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds, remember) {
309
+ function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds, remember, authVersion) {
278
310
  const userId = Number.parseInt(userIdRaw, 10);
279
311
  const issuedAt = Number.parseInt(issuedAtRaw, 10);
280
312
  if (!Number.isInteger(userId) || userId <= 0 || !Number.isFinite(issuedAt)) {
@@ -286,7 +318,7 @@ function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds,
286
318
  if (Date.now() - issuedAt > ttlSeconds * 1000) {
287
319
  return null;
288
320
  }
289
- const expectedSignature = (remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt)).split(".").pop();
321
+ const expectedSignature = (authVersion !== undefined ? signSession(userId, issuedAt, ttlSeconds, authVersion) : remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt)).split(".").pop();
290
322
  if (!expectedSignature || !cookieSignature) {
291
323
  return null;
292
324
  }
@@ -298,14 +330,20 @@ function readSignedSession(userIdRaw, issuedAtRaw, cookieSignature, ttlSeconds,
298
330
  if (!timingSafeEqual(expectedBuffer, actualBuffer)) {
299
331
  return null;
300
332
  }
301
- return { userId, issuedAt };
333
+ return { userId, issuedAt, ...authVersion !== undefined ? { authVersion } : {} };
302
334
  }
303
335
  function readSession(request) {
304
- const cookieValue = readCookieValue(request, sessionCookieName());
336
+ const cookieValue = readRequestCookie(request, sessionCookieName());
305
337
  if (!cookieValue) {
306
338
  return null;
307
339
  }
308
340
  const parts = cookieValue.split(".");
341
+ if (parts.length === 6 && parts[0] === "v2") {
342
+ const fields = parts.slice(1, 5);
343
+ if (fields.some((value) => !/^(0|[1-9][0-9]*)$/.test(value) || !Number.isSafeInteger(Number(value))))
344
+ return null;
345
+ return readSignedSession(String(parts[1]), String(parts[2]), parts[5], Number(parts[3]), true, Number(parts[4]));
346
+ }
309
347
  if (parts.length === 4) {
310
348
  const [userIdRaw, issuedAtRaw, ttlRaw, cookieSignature] = parts;
311
349
  return readSignedSession(String(userIdRaw), String(issuedAtRaw), cookieSignature, Number.parseInt(String(ttlRaw), 10), true);
@@ -330,15 +368,18 @@ function isSessionInvalidated(issuedAt, validAfter) {
330
368
  return issuedAt < timestamp;
331
369
  }
332
370
  function createSessionCookieDetails(userId, options = {}) {
371
+ if (options.authVersion !== undefined && (!Number.isSafeInteger(options.authVersion) || options.authVersion < 0))
372
+ throw new RangeError("authVersion must be a non-negative safe integer.");
333
373
  const issuedAt = Date.now();
334
374
  const ttlSeconds = options.remember ? sessionRememberTtlSeconds() : sessionTtlSeconds();
335
- const value = options.remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt);
375
+ const value = options.authVersion !== undefined ? signSession(userId, issuedAt, ttlSeconds, options.authVersion) : options.remember ? signSession(userId, issuedAt, ttlSeconds) : signSession(userId, issuedAt);
336
376
  const secure = isProductionEnv() ? "; Secure" : "";
337
377
  return {
338
378
  header: `${sessionCookieName()}=${encodeURIComponent(value)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${ttlSeconds}${secure}`,
339
379
  userId,
340
380
  issuedAt,
341
- ttlSeconds
381
+ ttlSeconds,
382
+ ...options.authVersion !== undefined ? { authVersion: options.authVersion } : {}
342
383
  };
343
384
  }
344
385
  function createSessionCookie(userId, options = {}) {
@@ -366,7 +407,9 @@ class SessionGuard {
366
407
  }
367
408
  try {
368
409
  const user = await tokenService.findByIdOrThrow(session.userId);
369
- if (isSessionInvalidated(session.issuedAt, user.session_valid_after)) {
410
+ if (!isAccountActive(user) || !matchesAuthVersion(session.authVersion, user.auth_version))
411
+ return null;
412
+ if (user.auth_version === undefined && isSessionInvalidated(session.issuedAt, user.session_valid_after)) {
370
413
  return null;
371
414
  }
372
415
  if (typeof tokenService.hasActiveBrowserSession === "function") {
@@ -378,6 +421,7 @@ class SessionGuard {
378
421
  return {
379
422
  id: user.id,
380
423
  role: user.role,
424
+ ...user.auth_version !== undefined ? { authVersion: user.auth_version } : {},
381
425
  abilities: resolveAbilitiesForRole(user.role),
382
426
  emailVerifiedAt: user.email_verified_at ?? null
383
427
  };
@@ -19,7 +19,11 @@ function readRequestCookie(request, name) {
19
19
  const cookieName = part.slice(0, idx).trim();
20
20
  if (cookieName !== name)
21
21
  continue;
22
- return decodeURIComponent(part.slice(idx + 1).trim());
22
+ try {
23
+ return decodeURIComponent(part.slice(idx + 1).trim());
24
+ } catch {
25
+ return null;
26
+ }
23
27
  }
24
28
  return null;
25
29
  }
@@ -139,7 +139,11 @@ function readRequestCookie(request, name) {
139
139
  const cookieName = part.slice(0, idx).trim();
140
140
  if (cookieName !== name)
141
141
  continue;
142
- return decodeURIComponent(part.slice(idx + 1).trim());
142
+ try {
143
+ return decodeURIComponent(part.slice(idx + 1).trim());
144
+ } catch {
145
+ return null;
146
+ }
143
147
  }
144
148
  return null;
145
149
  }
@@ -135,7 +135,11 @@ function readRequestCookie(request, name) {
135
135
  const cookieName = part.slice(0, idx).trim();
136
136
  if (cookieName !== name)
137
137
  continue;
138
- return decodeURIComponent(part.slice(idx + 1).trim());
138
+ try {
139
+ return decodeURIComponent(part.slice(idx + 1).trim());
140
+ } catch {
141
+ return null;
142
+ }
139
143
  }
140
144
  return null;
141
145
  }
@@ -106,7 +106,7 @@ function createRequirePasswordConfirmMiddleware() {
106
106
  return Response.redirect("/login", 302);
107
107
  }
108
108
  const userId = resolveUserId(user);
109
- if (hasFreshPasswordConfirmation(request, userId)) {
109
+ if (hasFreshPasswordConfirmation(request, userId, user.authVersion)) {
110
110
  return await next();
111
111
  }
112
112
  if (requestPrefersJson(request)) {
@@ -306,6 +306,20 @@ async function dispatchQueuedModelEvents(queued) {
306
306
  }
307
307
  }
308
308
  }
309
+ async function dispatchModelEvent(event, payload) {
310
+ await eventBus.dispatchTransactional(event, payload);
311
+ const state = deferredModelEvents.getStore();
312
+ const frame = state?.frames[state.frames.length - 1];
313
+ if (frame) {
314
+ frame.push({
315
+ event,
316
+ payload,
317
+ tenant: currentTenant()
318
+ });
319
+ return;
320
+ }
321
+ await eventBus.dispatch(event, payload);
322
+ }
309
323
  async function runWithDeferredModelEvents(callback) {
310
324
  const existing = deferredModelEvents.getStore();
311
325
  if (existing) {
@@ -691,6 +691,20 @@ async function dispatchQueuedModelEvents(queued) {
691
691
  }
692
692
  }
693
693
  }
694
+ async function dispatchModelEvent(event, payload) {
695
+ await eventBus.dispatchTransactional(event, payload);
696
+ const state = deferredModelEvents.getStore();
697
+ const frame = state?.frames[state.frames.length - 1];
698
+ if (frame) {
699
+ frame.push({
700
+ event,
701
+ payload,
702
+ tenant: currentTenant()
703
+ });
704
+ return;
705
+ }
706
+ await eventBus.dispatch(event, payload);
707
+ }
694
708
  async function runWithDeferredModelEvents(callback) {
695
709
  const existing = deferredModelEvents.getStore();
696
710
  if (existing) {
@@ -306,6 +306,20 @@ async function dispatchQueuedModelEvents(queued) {
306
306
  }
307
307
  }
308
308
  }
309
+ async function dispatchModelEvent(event, payload) {
310
+ await eventBus.dispatchTransactional(event, payload);
311
+ const state = deferredModelEvents.getStore();
312
+ const frame = state?.frames[state.frames.length - 1];
313
+ if (frame) {
314
+ frame.push({
315
+ event,
316
+ payload,
317
+ tenant: currentTenant()
318
+ });
319
+ return;
320
+ }
321
+ await eventBus.dispatch(event, payload);
322
+ }
309
323
  async function runWithDeferredModelEvents(callback) {
310
324
  const existing = deferredModelEvents.getStore();
311
325
  if (existing) {
@@ -239,6 +239,20 @@ async function dispatchQueuedModelEvents(queued) {
239
239
  }
240
240
  }
241
241
  }
242
+ async function dispatchModelEvent(event, payload) {
243
+ await eventBus.dispatchTransactional(event, payload);
244
+ const state = deferredModelEvents.getStore();
245
+ const frame = state?.frames[state.frames.length - 1];
246
+ if (frame) {
247
+ frame.push({
248
+ event,
249
+ payload,
250
+ tenant: currentTenant()
251
+ });
252
+ return;
253
+ }
254
+ await eventBus.dispatch(event, payload);
255
+ }
242
256
  async function runWithDeferredModelEvents(callback) {
243
257
  const existing = deferredModelEvents.getStore();
244
258
  if (existing) {