@happyvertical/smrt-users 0.53.3 → 0.53.5

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 (61) hide show
  1. package/AGENTS.md +43 -2
  2. package/README.md +185 -2
  3. package/agents/retention.md +14 -7
  4. package/dist/chunks/{TerminalAuthService-DoadwiqV.js → TerminalAuthService-BdB_0vDA.js} +3373 -2121
  5. package/dist/chunks/TerminalAuthService-BdB_0vDA.js.map +1 -0
  6. package/dist/collections/CliAuthApproveLimitCollection.d.ts +4 -0
  7. package/dist/collections/CliAuthApproveLimitCollection.d.ts.map +1 -1
  8. package/dist/collections/CliAuthRequestCollection.d.ts.map +1 -1
  9. package/dist/collections/LoginAttemptCollection.d.ts +105 -0
  10. package/dist/collections/LoginAttemptCollection.d.ts.map +1 -0
  11. package/dist/collections/LoginAuditEventCollection.d.ts +19 -0
  12. package/dist/collections/LoginAuditEventCollection.d.ts.map +1 -0
  13. package/dist/collections/PinCredentialCollection.d.ts +26 -0
  14. package/dist/collections/PinCredentialCollection.d.ts.map +1 -0
  15. package/dist/collections/SessionCollection.d.ts +33 -1
  16. package/dist/collections/SessionCollection.d.ts.map +1 -1
  17. package/dist/collections/index.d.ts +3 -0
  18. package/dist/collections/index.d.ts.map +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +144 -31
  22. package/dist/index.js.map +1 -1
  23. package/dist/manifest.json +3245 -1990
  24. package/dist/models/CliAuthApproveLimit.d.ts +8 -1
  25. package/dist/models/CliAuthApproveLimit.d.ts.map +1 -1
  26. package/dist/models/LoginAttempt.d.ts +59 -0
  27. package/dist/models/LoginAttempt.d.ts.map +1 -0
  28. package/dist/models/LoginAuditEvent.d.ts +49 -0
  29. package/dist/models/LoginAuditEvent.d.ts.map +1 -0
  30. package/dist/models/PinCredential.d.ts +31 -0
  31. package/dist/models/PinCredential.d.ts.map +1 -0
  32. package/dist/models/Session.d.ts +69 -1
  33. package/dist/models/Session.d.ts.map +1 -1
  34. package/dist/models/index.d.ts +4 -1
  35. package/dist/models/index.d.ts.map +1 -1
  36. package/dist/retention.d.ts +7 -1
  37. package/dist/retention.d.ts.map +1 -1
  38. package/dist/services/DeviceCredentialService.d.ts +348 -0
  39. package/dist/services/DeviceCredentialService.d.ts.map +1 -0
  40. package/dist/services/LoginAttemptLimiter.d.ts +227 -0
  41. package/dist/services/LoginAttemptLimiter.d.ts.map +1 -0
  42. package/dist/services/MobileAuthService.d.ts.map +1 -1
  43. package/dist/services/OperationPermissionService.d.ts.map +1 -1
  44. package/dist/services/SessionPermissionContext.d.ts.map +1 -1
  45. package/dist/services/SessionService.d.ts +56 -1
  46. package/dist/services/SessionService.d.ts.map +1 -1
  47. package/dist/services/TerminalAuthService.d.ts +18 -3
  48. package/dist/services/TerminalAuthService.d.ts.map +1 -1
  49. package/dist/services/index.d.ts +4 -2
  50. package/dist/services/index.d.ts.map +1 -1
  51. package/dist/smrt-knowledge.json +679 -9
  52. package/dist/sveltekit/device-credential-handlers.d.ts +60 -0
  53. package/dist/sveltekit/device-credential-handlers.d.ts.map +1 -0
  54. package/dist/sveltekit/index.d.ts +10 -0
  55. package/dist/sveltekit/index.d.ts.map +1 -1
  56. package/dist/sveltekit/types.d.ts +6 -0
  57. package/dist/sveltekit/types.d.ts.map +1 -1
  58. package/dist/sveltekit.js +184 -4
  59. package/dist/sveltekit.js.map +1 -1
  60. package/package.json +12 -12
  61. package/dist/chunks/TerminalAuthService-DoadwiqV.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,18 +1,18 @@
1
- import { $ as deriveOperationPermissionSlug, A as DEFAULT_ANCESTOR_READ_MAX_DEPTH, B as TenantPermissionOverride, C as OidcLoginService, Ct as UsersCliAuthRequestCollection, D as resolveOidcProviderConfig, Dt as generateSessionId, E as getUsersOidcConfig, Et as Session, F as UserCollection, G as applyTenantHierarchyUpdates, H as Tenant, J as recordTenantHierarchyChanges, K as assertTenantTableName, L as UserEmailKeyBackfillError, M as isAncestorReadableSlug, N as normalizeAncestorReadPolicy, Ot as UsersCliAuthRequest, P as OidcProvisioningError, Q as deriveOperationPermissionCollectionName, R as backfillUserEmailKeys, S as OidcLoginError, St as normalizeEmail, T as encodeOidcTransaction, Tt as DEFAULT_SESSION_TTL, U as MAX_TENANT_HIERARCHY_DEPTH, V as TenantCollection, W as TenantHierarchyError, X as RolePermissionCollection, Y as DEFAULT_ROLE_PERMISSION_PATTERNS, Z as PermissionCatalogService, _ as readMobileBearerToken, _t as Group, a as TerminalAuthRateLimitError, at as Role, b as PermissionResolver, bt as User, c as assertOperationPermission, ct as MembershipOverrideCollection, d as getCurrentSessionPermissionContext, dt as Membership, et as normalizeOperationPermissionAction, f as getRequestScopedDatabase, ft as GroupRoleCollection, g as MobileAuthService, gt as GroupCollection, h as MobileAuthError, ht as GroupMember, i as TerminalAuthError, it as RoleCollection, j as getConfiguredAncestorReadPolicy, k as ANCESTOR_READ_ACTION, l as checkOperationPermission, lt as MembershipOverride, m as withSessionPermissionContext, mt as GroupMemberCollection, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, nt as syncPermissionCatalog, o as TerminalAuthService, ot as PermissionCollection, p as withPrincipalPermissionContext, pt as GroupRole, q as planTenantHierarchy, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rt as RolePermission, s as OperationPermissionError, st as Permission, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tt as registerPermissionDefinitions, u as hasOperationPermission, ut as MembershipCollection, v as validateMobileRedirectUri, vt as UsersCliAuthApproveLimitCollection, w as decodeOidcTransaction, wt as SessionCollection, x as withoutListBounds, xt as isValidEmail, y as SessionService, yt as UsersCliAuthApproveLimit, z as TenantPermissionOverrideCollection } from "./chunks/TerminalAuthService-DoadwiqV.js";
1
+ import { $ as normalizeAncestorReadPolicy, $t as UsersCliAuthRequest, A as DeviceCredentialForbiddenError, At as MembershipOverrideCollection, B as DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, Bt as User, C as encodeOidcTransaction, Ct as RolePermission, D as DEFAULT_DEVICE_PERSON_IDLE_SECONDS, Dt as UsersPinCredential, Et as UsersPinCredentialCollection, F as PinVerifier, Ft as GroupRole, G as InvalidCredentialsError, Gt as UsersLoginAttemptCollection, H as DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, Ht as normalizeEmail, I as ensurePinPermissionsRegistered, It as GroupMemberCollection, J as withoutListBounds, Jt as SessionCollection, K as LoginAttemptLimiter, Kt as UsersLoginAttempt, L as SessionService, Lt as GroupMember, M as DeviceCredentialService, Mt as MembershipCollection, N as PIN_LOGIN_KIND, Nt as Membership, O as DEFAULT_PIN_MANAGE_PERMISSION, Ot as PermissionCollection, P as PIN_PERMISSION_DEFINITIONS, Pt as GroupRoleCollection, Q as isAncestorReadableSlug, Qt as generateSessionId, R as PermissionResolver, Rt as GroupCollection, S as decodeOidcTransaction, St as syncPermissionCatalog, T as resolveOidcProviderConfig, Tt as Role, U as DEFAULT_LOGIN_MAX_ATTEMPTS, Ut as UsersLoginAuditEventCollection, V as DEFAULT_LOGIN_LOCKOUT_FACTOR, Vt as isValidEmail, W as DurableLoginAuditSink, Wt as UsersLoginAuditEvent, X as DEFAULT_ANCESTOR_READ_MAX_DEPTH, Xt as SESSION_DATA_KEYS, Y as ANCESTOR_READ_ACTION, Yt as DEFAULT_SESSION_TTL, Z as getConfiguredAncestorReadPolicy, Zt as Session, _ as MobileAuthService, _t as PermissionCatalogService, a as TerminalAuthError, at as TenantPermissionOverrideCollection, b as OidcLoginError, bt as normalizeOperationPermissionAction, c as OperationPermissionError, ct as Tenant, d as hasOperationPermission, dt as applyTenantHierarchyUpdates, et as OidcProvisioningError, f as getCurrentSessionPermissionContext, ft as assertTenantTableName, g as MobileAuthError, gt as RolePermissionCollection, h as withSessionPermissionContext, ht as DEFAULT_ROLE_PERMISSION_PATTERNS, i as TERMINAL_APPROVE_LOGIN_KIND, it as backfillUserEmailKeys, j as DeviceCredentialPolicyError, jt as MembershipOverride, k as DeviceCredentialError, kt as Permission, l as assertOperationPermission, lt as MAX_TENANT_HIERARCHY_DEPTH, m as withPrincipalPermissionContext, mt as recordTenantHierarchyChanges, n as DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, o as TerminalAuthRateLimitError, ot as TenantPermissionOverride, p as getRequestScopedDatabase, pt as planTenantHierarchy, q as LoginRateLimitError, qt as UsersCliAuthRequestCollection, r as DEFAULT_CLI_SESSION_TTL_SECONDS, rt as UserEmailKeyBackfillError, s as TerminalAuthService, st as TenantCollection, t as DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, tt as UserCollection, u as checkOperationPermission, ut as TenantHierarchyError, v as readMobileBearerToken, vt as deriveOperationPermissionCollectionName, w as getUsersOidcConfig, wt as RoleCollection, x as OidcLoginService, xt as registerPermissionDefinitions, y as validateMobileRedirectUri, yt as deriveOperationPermissionSlug, z as DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, zt as Group } from "./chunks/TerminalAuthService-BdB_0vDA.js";
2
2
  import { a as MembershipStatus, c as TenantPermissionEffect, i as DEFAULT_TENANT_POLICY, l as TenantStatus, n as DEFAULT_ROLES, o as OverrideEffect, r as DEFAULT_ROLE_SLUGS, s as SessionStatus, t as AccessRequestStatus, u as UserStatus } from "./chunks/types-DFqeph0R.js";
3
3
  import { ObjectRegistry, SmrtCollection, SmrtObject, bumpChangeFeed, ensureChangeFeedTable, field, findManifestEntryByQualifiedName, foreignKey, isEmbeddedDatabase, isUniqueViolationError, registerRetentionTask, smrt, unregisterRetentionTask, withEmbeddedWriteTransaction } from "@happyvertical/smrt-core";
4
+ import { createHash, randomUUID } from "node:crypto";
4
5
  import { PROFILE_EMAIL_KEY_BACKFILL_NAME, Person, ProfileCollection, ProfileTypeCollection, normalizeIdentityEmail } from "@happyvertical/smrt-profiles";
5
- import { createHash } from "node:crypto";
6
6
  import { createLogger } from "@happyvertical/logger";
7
7
  import { TenantScoped, tenantId, withSystemContext } from "@happyvertical/smrt-tenancy";
8
8
  import { BackfillTracker } from "@happyvertical/smrt-core/migrations";
9
9
  //#region src/models/MagicLinkToken.ts
10
- var __defProp$3 = Object.defineProperty;
11
- var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
12
- var __decorateClass$3 = (decorators, target, key, kind) => {
13
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
10
+ var __defProp$4 = Object.defineProperty;
11
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
12
+ var __decorateClass$4 = (decorators, target, key, kind) => {
13
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
14
14
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
15
- if (kind && result) __defProp$3(target, key, result);
15
+ if (kind && result) __defProp$4(target, key, result);
16
16
  return result;
17
17
  };
18
18
  var DEFAULT_TOKEN_EXPIRY_SECONDS = 600;
@@ -39,15 +39,15 @@ var UsersMagicLinkToken = class extends SmrtObject {
39
39
  return !this.used && !this.isExpired();
40
40
  }
41
41
  };
42
- __decorateClass$3([field({
42
+ __decorateClass$4([field({
43
43
  required: true,
44
44
  unique: true
45
45
  })], UsersMagicLinkToken.prototype, "nonce", 2);
46
- __decorateClass$3([field({
46
+ __decorateClass$4([field({
47
47
  type: "datetime",
48
48
  indexed: true
49
49
  })], UsersMagicLinkToken.prototype, "expiresAt", 2);
50
- UsersMagicLinkToken = __decorateClass$3([smrt({
50
+ UsersMagicLinkToken = __decorateClass$4([smrt({
51
51
  tableName: "users_magic_link_tokens",
52
52
  sensitive: true,
53
53
  api: { include: [] },
@@ -103,10 +103,15 @@ var UsersMagicLinkTokenCollection = class extends SmrtCollection {
103
103
  var SESSIONS_RETENTION_TASK = "users-sessions";
104
104
  var MAGIC_LINK_RETENTION_TASK = "users-magic-link-tokens";
105
105
  var CLI_AUTH_RETENTION_TASK = "users-cli-auth-requests";
106
+ var LOGIN_ATTEMPTS_RETENTION_TASK = "users-login-attempts";
107
+ var LOGIN_AUDIT_RETENTION_TASK = "users-login-audit-events";
108
+ var DEFAULT_LOGIN_AUDIT_RETENTION_DAYS = 90;
106
109
  var USER_RETENTION_TASKS = [
107
110
  SESSIONS_RETENTION_TASK,
108
111
  MAGIC_LINK_RETENTION_TASK,
109
- CLI_AUTH_RETENTION_TASK
112
+ CLI_AUTH_RETENTION_TASK,
113
+ LOGIN_ATTEMPTS_RETENTION_TASK,
114
+ LOGIN_AUDIT_RETENTION_TASK
110
115
  ];
111
116
  function registerUserRetentionTasks() {
112
117
  registerRetentionTask({
@@ -130,18 +135,32 @@ function registerUserRetentionTasks() {
130
135
  return (await UsersCliAuthRequestCollection.create({ db })).deleteExpired({ dryRun: context.dryRun });
131
136
  }
132
137
  });
138
+ registerRetentionTask({
139
+ name: LOGIN_ATTEMPTS_RETENTION_TASK,
140
+ description: "Delete idle login-attempt limiter rows",
141
+ run: async (db, context) => {
142
+ return (await UsersLoginAttemptCollection.create({ db })).deleteIdle({ dryRun: context.dryRun });
143
+ }
144
+ });
145
+ registerRetentionTask({
146
+ name: LOGIN_AUDIT_RETENTION_TASK,
147
+ description: `Delete login audit events older than 90 days`,
148
+ run: async (db, context) => {
149
+ return (await UsersLoginAuditEventCollection.create({ db })).deleteOlderThan(2160 * 60 * 60 * 1e3, { dryRun: context.dryRun });
150
+ }
151
+ });
133
152
  }
134
153
  function unregisterUserRetentionTasks() {
135
154
  for (const name of USER_RETENTION_TASKS) unregisterRetentionTask(name);
136
155
  }
137
156
  //#endregion
138
157
  //#region src/models/AccessRequest.ts
139
- var __defProp$2 = Object.defineProperty;
140
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
141
- var __decorateClass$2 = (decorators, target, key, kind) => {
142
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
158
+ var __defProp$3 = Object.defineProperty;
159
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
160
+ var __decorateClass$3 = (decorators, target, key, kind) => {
161
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
143
162
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
144
- if (kind && result) __defProp$2(target, key, result);
163
+ if (kind && result) __defProp$3(target, key, result);
145
164
  return result;
146
165
  };
147
166
  var AccessRequest = class extends SmrtObject {
@@ -226,24 +245,24 @@ var AccessRequest = class extends SmrtObject {
226
245
  return this.status === AccessRequestStatus.DECLINED || this.status === AccessRequestStatus.GRADUATED || this.status === AccessRequestStatus.CANCELED;
227
246
  }
228
247
  };
229
- __decorateClass$2([field({
248
+ __decorateClass$3([field({
230
249
  required: true,
231
250
  indexed: true
232
251
  })], AccessRequest.prototype, "email", 2);
233
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "name", 2);
234
- __decorateClass$2([field({
252
+ __decorateClass$3([field({ nullable: true })], AccessRequest.prototype, "name", 2);
253
+ __decorateClass$3([field({
235
254
  type: "text",
236
255
  indexed: true
237
256
  })], AccessRequest.prototype, "status", 2);
238
- __decorateClass$2([field({ indexed: true })], AccessRequest.prototype, "source", 2);
239
- __decorateClass$2([field()], AccessRequest.prototype, "requestContext", 2);
240
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "note", 2);
241
- __decorateClass$2([field()], AccessRequest.prototype, "requestedAt", 2);
242
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "decidedAt", 2);
243
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "decidedBy", 2);
244
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "resultingUserId", 2);
245
- __decorateClass$2([field({ nullable: true })], AccessRequest.prototype, "tenantHint", 2);
246
- AccessRequest = __decorateClass$2([smrt({
257
+ __decorateClass$3([field({ indexed: true })], AccessRequest.prototype, "source", 2);
258
+ __decorateClass$3([field()], AccessRequest.prototype, "requestContext", 2);
259
+ __decorateClass$3([field({ nullable: true })], AccessRequest.prototype, "note", 2);
260
+ __decorateClass$3([field()], AccessRequest.prototype, "requestedAt", 2);
261
+ __decorateClass$3([field({ nullable: true })], AccessRequest.prototype, "decidedAt", 2);
262
+ __decorateClass$3([field({ nullable: true })], AccessRequest.prototype, "decidedBy", 2);
263
+ __decorateClass$3([field({ nullable: true })], AccessRequest.prototype, "resultingUserId", 2);
264
+ __decorateClass$3([field({ nullable: true })], AccessRequest.prototype, "tenantHint", 2);
265
+ AccessRequest = __decorateClass$3([smrt({
247
266
  tableName: "access_requests",
248
267
  conflictColumns: ["id"],
249
268
  api: { include: [] },
@@ -296,6 +315,100 @@ var AccessRequestCollection = class extends SmrtCollection {
296
315
  }
297
316
  };
298
317
  //#endregion
318
+ //#region src/models/CliAuthApproveLimit.ts
319
+ var __defProp$2 = Object.defineProperty;
320
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
321
+ var __decorateClass$2 = (decorators, target, key, kind) => {
322
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
323
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
324
+ if (kind && result) __defProp$2(target, key, result);
325
+ return result;
326
+ };
327
+ var UsersCliAuthApproveLimit = class extends SmrtObject {
328
+ userId = "";
329
+ attemptCount = 0;
330
+ windowStartedAt = /* @__PURE__ */ new Date();
331
+ };
332
+ __decorateClass$2([foreignKey("User", {
333
+ required: true,
334
+ unique: true
335
+ })], UsersCliAuthApproveLimit.prototype, "userId", 2);
336
+ __decorateClass$2([field({
337
+ type: "integer",
338
+ required: true,
339
+ default: 0
340
+ })], UsersCliAuthApproveLimit.prototype, "attemptCount", 2);
341
+ __decorateClass$2([field({
342
+ type: "datetime",
343
+ required: true
344
+ })], UsersCliAuthApproveLimit.prototype, "windowStartedAt", 2);
345
+ UsersCliAuthApproveLimit = __decorateClass$2([smrt({
346
+ tableName: "users_cli_auth_approve_limits",
347
+ api: false,
348
+ cli: false,
349
+ mcp: false
350
+ })], UsersCliAuthApproveLimit);
351
+ //#endregion
352
+ //#region src/collections/CliAuthApproveLimitCollection.ts
353
+ var UsersCliAuthApproveLimitCollection = class extends SmrtCollection {
354
+ static _itemClass = UsersCliAuthApproveLimit;
355
+ /**
356
+ * Atomically reserve one attempt across every process using this database.
357
+ * Failed attempts retain their reservation; successful attempts release it.
358
+ */
359
+ async reserveAttempt(input) {
360
+ const now = /* @__PURE__ */ new Date();
361
+ const nowIso = now.toISOString();
362
+ const windowFloorIso = new Date(now.getTime() - input.windowMs).toISOString();
363
+ const id = randomUUID();
364
+ const reserved = await this.db.query(`INSERT INTO ${this.tableName} (
365
+ id, slug, context, user_id, attempt_count, window_started_at,
366
+ created_at, updated_at
367
+ ) VALUES (?, ?, '', ?, 1, ?, ?, ?)
368
+ ON CONFLICT (user_id) DO UPDATE SET
369
+ attempt_count = CASE
370
+ WHEN ${this.tableName}.window_started_at <= ? THEN 1
371
+ ELSE ${this.tableName}.attempt_count + 1
372
+ END,
373
+ window_started_at = CASE
374
+ WHEN ${this.tableName}.window_started_at <= ?
375
+ THEN excluded.window_started_at
376
+ ELSE ${this.tableName}.window_started_at
377
+ END,
378
+ updated_at = excluded.updated_at
379
+ WHERE ${this.tableName}.window_started_at <= ?
380
+ OR ${this.tableName}.attempt_count < ?
381
+ RETURNING attempt_count, window_started_at`, id, `terminal-auth-${id}`, input.userId, nowIso, nowIso, nowIso, windowFloorIso, windowFloorIso, windowFloorIso, input.maxAttempts);
382
+ if (reserved.rows.length === 1) {
383
+ const reservedWindow = reserved.rows[0]?.window_started_at;
384
+ return {
385
+ allowed: true,
386
+ windowStartedAt: reservedWindow instanceof Date ? reservedWindow.toISOString() : new Date(String(reservedWindow)).toISOString()
387
+ };
388
+ }
389
+ const windowStartedAt = (await this.db.query(`SELECT window_started_at FROM ${this.tableName}
390
+ WHERE user_id = ? LIMIT 1`, input.userId)).rows[0]?.window_started_at;
391
+ const startedMs = windowStartedAt instanceof Date ? windowStartedAt.getTime() : new Date(String(windowStartedAt)).getTime();
392
+ const remainingMs = Number.isFinite(startedMs) ? startedMs + input.windowMs - now.getTime() : input.windowMs;
393
+ return {
394
+ allowed: false,
395
+ retryAfterSeconds: Math.max(1, Math.ceil(remainingMs / 1e3))
396
+ };
397
+ }
398
+ /** Release the reservation for an attempt that did not fail authentication. */
399
+ async releaseAttempt(userId, windowStartedAt) {
400
+ await this.db.query(`UPDATE ${this.tableName}
401
+ SET attempt_count = CASE
402
+ WHEN attempt_count > 0 THEN attempt_count - 1
403
+ ELSE 0
404
+ END,
405
+ updated_at = ?
406
+ WHERE user_id = ? AND window_started_at = ?`, (/* @__PURE__ */ new Date()).toISOString(), userId, windowStartedAt);
407
+ await this.db.query(`DELETE FROM ${this.tableName}
408
+ WHERE user_id = ? AND window_started_at = ? AND attempt_count = 0`, userId, windowStartedAt);
409
+ }
410
+ };
411
+ //#endregion
299
412
  //#region src/models/ResourceGrant.ts
300
413
  var __defProp$1 = Object.defineProperty;
301
414
  var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
@@ -1229,7 +1342,7 @@ var MagicLinkService = class MagicLinkService {
1229
1342
  * The caller is responsible for emailing the token to the user.
1230
1343
  */
1231
1344
  async generate(email) {
1232
- const { SignJWT } = await import("./chunks/TerminalAuthService-DoadwiqV.js").then((n) => n.O);
1345
+ const { SignJWT } = await import("./chunks/TerminalAuthService-BdB_0vDA.js").then((n) => n.E);
1233
1346
  const key = await this.getSigningKey();
1234
1347
  const nonce = crypto.randomUUID();
1235
1348
  const normalizedEmail = normalizeEmail(email);
@@ -1258,7 +1371,7 @@ var MagicLinkService = class MagicLinkService {
1258
1371
  * @throws {MagicLinkError} If the token is invalid, expired, or already used
1259
1372
  */
1260
1373
  async verify(token) {
1261
- const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-DoadwiqV.js").then((n) => n.O);
1374
+ const { jwtVerify, errors } = await import("./chunks/TerminalAuthService-BdB_0vDA.js").then((n) => n.E);
1262
1375
  const key = await this.getSigningKey();
1263
1376
  let payload;
1264
1377
  try {
@@ -1856,6 +1969,6 @@ var TenantService = class TenantService {
1856
1969
  //#region src/index.ts
1857
1970
  registerUserRetentionTasks();
1858
1971
  //#endregion
1859
- export { ACCESS_REQUEST_CAPABILITIES, ANCESTOR_READ_ACTION, AccessRequest, AccessRequestCollection, AccessRequestError, AccessRequestService, AccessRequestStatus, CLI_AUTH_RETENTION_TASK, UsersCliAuthRequest as CliAuthRequest, UsersCliAuthRequest, UsersCliAuthRequestCollection as CliAuthRequestCollection, UsersCliAuthRequestCollection, DEFAULT_ANCESTOR_READ_MAX_DEPTH, DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, DEFAULT_CLI_SESSION_TTL_SECONDS, DEFAULT_ROLES, DEFAULT_ROLE_PERMISSION_PATTERNS, DEFAULT_ROLE_SLUGS, DEFAULT_SESSION_TTL, DEFAULT_TENANT_POLICY, DEFAULT_TOKEN_EXPIRY_SECONDS, Group, GroupCollection, GroupMember, GroupMemberCollection, GroupRole, GroupRoleCollection, LEGACY_USER_PROFILE_BACKFILL_NAME, LegacyUserProfileBackfillError, MAGIC_LINK_RETENTION_TASK, MAX_RESOURCE_GRANT_DELEGATION_DEPTH, MAX_TENANT_HIERARCHY_DEPTH, MagicLinkError, MagicLinkService, UsersMagicLinkToken as MagicLinkToken, UsersMagicLinkToken, UsersMagicLinkTokenCollection as MagicLinkTokenCollection, UsersMagicLinkTokenCollection, Membership, MembershipCollection, MembershipOverride, MembershipOverrideCollection, MembershipStatus, MobileAuthError, MobileAuthService, OidcLoginError, OidcLoginService, OidcProvisioningError, OperationPermissionError, OverrideEffect, Permission, PermissionCatalogService, PermissionCollection, PermissionResolver, ResourceGrant, ResourceGrantCollection, ResourceGrantService, Role, RoleCollection, RolePermission, RolePermissionCollection, SESSIONS_RETENTION_TASK, Session, SessionCollection, SessionService, SessionStatus, Tenant, TenantCollection, TenantHierarchyError, TenantHierarchyMaterializationError, TenantIntegration, TenantIntegrationCollection, TenantPermissionEffect, TenantPermissionOverride, TenantPermissionOverrideCollection, TenantService, TenantStatus, TerminalAuthError, TerminalAuthRateLimitError, TerminalAuthService, USER_RETENTION_TASKS, User, UserCollection, UserEmailKeyBackfillError, UserStatus, UsersCliAuthApproveLimit, UsersCliAuthApproveLimitCollection, applyPostgresPermissionPolicies, assertOperationPermission, backfillLegacyUserProfiles, backfillUserEmailKeys, checkOperationPermission, checkResourceOperationPermission, decodeOidcTransaction, deduplicateRolePermissions, deriveOperationPermissionCollectionName, deriveOperationPermissionSlug, encodeOidcTransaction, generatePostgresPermissionSql, generateSessionId, getConfiguredAncestorReadPolicy, getCurrentSessionPermissionContext, getRequestScopedDatabase, getUsersOidcConfig, hasOperationPermission, isAncestorReadableSlug, materializeTenantHierarchy, normalizeAncestorReadPolicy, normalizeOperationPermissionAction, planTenantHierarchy, readMobileBearerToken, registerPermissionDefinitions, registerUserRetentionTasks, resolveOidcProviderConfig, syncPermissionCatalog, unregisterUserRetentionTasks, validateMobileRedirectUri, withPrincipalPermissionContext, withSessionPermissionContext };
1972
+ export { ACCESS_REQUEST_CAPABILITIES, ANCESTOR_READ_ACTION, AccessRequest, AccessRequestCollection, AccessRequestError, AccessRequestService, AccessRequestStatus, CLI_AUTH_RETENTION_TASK, UsersCliAuthRequest as CliAuthRequest, UsersCliAuthRequest, UsersCliAuthRequestCollection as CliAuthRequestCollection, UsersCliAuthRequestCollection, DEFAULT_ANCESTOR_READ_MAX_DEPTH, DEFAULT_CLI_AUTH_POLL_INTERVAL_SECONDS, DEFAULT_CLI_AUTH_REQUEST_TTL_SECONDS, DEFAULT_CLI_SESSION_TTL_SECONDS, DEFAULT_DEVICE_PERSON_IDLE_SECONDS, DEFAULT_LOGIN_ATTEMPT_WINDOW_SECONDS, DEFAULT_LOGIN_AUDIT_RETENTION_DAYS, DEFAULT_LOGIN_LOCKOUT_BASE_SECONDS, DEFAULT_LOGIN_LOCKOUT_FACTOR, DEFAULT_LOGIN_LOCKOUT_MAX_SECONDS, DEFAULT_LOGIN_MAX_ATTEMPTS, DEFAULT_PIN_MANAGE_PERMISSION, DEFAULT_ROLES, DEFAULT_ROLE_PERMISSION_PATTERNS, DEFAULT_ROLE_SLUGS, DEFAULT_SESSION_TTL, DEFAULT_TENANT_POLICY, DEFAULT_TOKEN_EXPIRY_SECONDS, DeviceCredentialError, DeviceCredentialForbiddenError, DeviceCredentialPolicyError, DeviceCredentialService, DurableLoginAuditSink, Group, GroupCollection, GroupMember, GroupMemberCollection, GroupRole, GroupRoleCollection, InvalidCredentialsError, LEGACY_USER_PROFILE_BACKFILL_NAME, LOGIN_ATTEMPTS_RETENTION_TASK, LOGIN_AUDIT_RETENTION_TASK, LegacyUserProfileBackfillError, LoginAttemptLimiter, LoginRateLimitError, MAGIC_LINK_RETENTION_TASK, MAX_RESOURCE_GRANT_DELEGATION_DEPTH, MAX_TENANT_HIERARCHY_DEPTH, MagicLinkError, MagicLinkService, UsersMagicLinkToken as MagicLinkToken, UsersMagicLinkToken, UsersMagicLinkTokenCollection as MagicLinkTokenCollection, UsersMagicLinkTokenCollection, Membership, MembershipCollection, MembershipOverride, MembershipOverrideCollection, MembershipStatus, MobileAuthError, MobileAuthService, OidcLoginError, OidcLoginService, OidcProvisioningError, OperationPermissionError, OverrideEffect, PIN_LOGIN_KIND, PIN_PERMISSION_DEFINITIONS, Permission, PermissionCatalogService, PermissionCollection, PermissionResolver, PinVerifier, ResourceGrant, ResourceGrantCollection, ResourceGrantService, Role, RoleCollection, RolePermission, RolePermissionCollection, SESSIONS_RETENTION_TASK, SESSION_DATA_KEYS, Session, SessionCollection, SessionService, SessionStatus, TERMINAL_APPROVE_LOGIN_KIND, Tenant, TenantCollection, TenantHierarchyError, TenantHierarchyMaterializationError, TenantIntegration, TenantIntegrationCollection, TenantPermissionEffect, TenantPermissionOverride, TenantPermissionOverrideCollection, TenantService, TenantStatus, TerminalAuthError, TerminalAuthRateLimitError, TerminalAuthService, USER_RETENTION_TASKS, User, UserCollection, UserEmailKeyBackfillError, UserStatus, UsersCliAuthApproveLimit, UsersCliAuthApproveLimitCollection, UsersLoginAttempt, UsersLoginAttemptCollection, UsersLoginAuditEvent, UsersLoginAuditEventCollection, UsersPinCredential, UsersPinCredentialCollection, applyPostgresPermissionPolicies, assertOperationPermission, backfillLegacyUserProfiles, backfillUserEmailKeys, checkOperationPermission, checkResourceOperationPermission, decodeOidcTransaction, deduplicateRolePermissions, deriveOperationPermissionCollectionName, deriveOperationPermissionSlug, encodeOidcTransaction, ensurePinPermissionsRegistered, generatePostgresPermissionSql, generateSessionId, getConfiguredAncestorReadPolicy, getCurrentSessionPermissionContext, getRequestScopedDatabase, getUsersOidcConfig, hasOperationPermission, isAncestorReadableSlug, materializeTenantHierarchy, normalizeAncestorReadPolicy, normalizeOperationPermissionAction, planTenantHierarchy, readMobileBearerToken, registerPermissionDefinitions, registerUserRetentionTasks, resolveOidcProviderConfig, syncPermissionCatalog, unregisterUserRetentionTasks, validateMobileRedirectUri, withPrincipalPermissionContext, withSessionPermissionContext };
1860
1973
 
1861
1974
  //# sourceMappingURL=index.js.map