@effect-auth/core 0.1.0-alpha.2 → 0.1.0-alpha.4

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 (149) hide show
  1. package/README.md +148 -13
  2. package/dist/ApiKey.d.ts +207 -0
  3. package/dist/ApiKey.d.ts.map +1 -0
  4. package/dist/ApiKey.js +275 -0
  5. package/dist/ApiKey.js.map +1 -0
  6. package/dist/AuditLog.d.ts +204 -1
  7. package/dist/AuditLog.d.ts.map +1 -1
  8. package/dist/AuditLog.js +170 -0
  9. package/dist/AuditLog.js.map +1 -1
  10. package/dist/AuthConfig.d.ts +0 -9
  11. package/dist/AuthConfig.d.ts.map +1 -1
  12. package/dist/AuthConfig.js +1 -14
  13. package/dist/AuthConfig.js.map +1 -1
  14. package/dist/AuthFlow.d.ts +220 -51
  15. package/dist/AuthFlow.d.ts.map +1 -1
  16. package/dist/AuthFlow.js +550 -22
  17. package/dist/AuthFlow.js.map +1 -1
  18. package/dist/AuthKernel.d.ts +1 -1
  19. package/dist/Challenge.d.ts +5 -1
  20. package/dist/Challenge.d.ts.map +1 -1
  21. package/dist/Challenge.js +58 -48
  22. package/dist/Challenge.js.map +1 -1
  23. package/dist/Client.d.ts +242 -1
  24. package/dist/Client.d.ts.map +1 -1
  25. package/dist/Client.js +489 -1
  26. package/dist/Client.js.map +1 -1
  27. package/dist/D1Kysely.d.ts +1 -1
  28. package/dist/D1Kysely.d.ts.map +1 -1
  29. package/dist/EffectQbSqliteStorage.d.ts +156 -41
  30. package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
  31. package/dist/EffectQbSqliteStorage.js +336 -33
  32. package/dist/EffectQbSqliteStorage.js.map +1 -1
  33. package/dist/EmailOtp.d.ts +5 -2
  34. package/dist/EmailOtp.d.ts.map +1 -1
  35. package/dist/EmailOtp.js +11 -4
  36. package/dist/EmailOtp.js.map +1 -1
  37. package/dist/EmailVerification.d.ts +2 -2
  38. package/dist/EmailVerification.d.ts.map +1 -1
  39. package/dist/EmailVerification.js +2 -3
  40. package/dist/EmailVerification.js.map +1 -1
  41. package/dist/HttpApi/Api.d.ts +1161 -19
  42. package/dist/HttpApi/Api.d.ts.map +1 -1
  43. package/dist/HttpApi/Api.js +1433 -20
  44. package/dist/HttpApi/Api.js.map +1 -1
  45. package/dist/HttpApi/Endpoints.d.ts +611 -0
  46. package/dist/HttpApi/Endpoints.d.ts.map +1 -1
  47. package/dist/HttpApi/Endpoints.js +242 -1
  48. package/dist/HttpApi/Endpoints.js.map +1 -1
  49. package/dist/HttpApi/Schemas.d.ts +704 -0
  50. package/dist/HttpApi/Schemas.d.ts.map +1 -1
  51. package/dist/HttpApi/Schemas.js +435 -1
  52. package/dist/HttpApi/Schemas.js.map +1 -1
  53. package/dist/HttpApi/Security.d.ts +13 -1
  54. package/dist/HttpApi/Security.d.ts.map +1 -1
  55. package/dist/HttpApi/Security.js +107 -16
  56. package/dist/HttpApi/Security.js.map +1 -1
  57. package/dist/HttpApi/Transport.d.ts +36 -2
  58. package/dist/HttpApi/Transport.d.ts.map +1 -1
  59. package/dist/HttpApi/Transport.js +151 -9
  60. package/dist/HttpApi/Transport.js.map +1 -1
  61. package/dist/Jwt.d.ts +271 -0
  62. package/dist/Jwt.d.ts.map +1 -0
  63. package/dist/Jwt.js +512 -0
  64. package/dist/Jwt.js.map +1 -0
  65. package/dist/KyselyStorage.d.ts +134 -1
  66. package/dist/KyselyStorage.d.ts.map +1 -1
  67. package/dist/KyselyStorage.js +918 -4
  68. package/dist/KyselyStorage.js.map +1 -1
  69. package/dist/LoginApproval.d.ts +314 -0
  70. package/dist/LoginApproval.d.ts.map +1 -0
  71. package/dist/LoginApproval.js +551 -0
  72. package/dist/LoginApproval.js.map +1 -0
  73. package/dist/LoginNotification.d.ts +183 -0
  74. package/dist/LoginNotification.d.ts.map +1 -0
  75. package/dist/LoginNotification.js +329 -0
  76. package/dist/LoginNotification.js.map +1 -0
  77. package/dist/LoginRisk.d.ts +73 -0
  78. package/dist/LoginRisk.d.ts.map +1 -0
  79. package/dist/LoginRisk.js +57 -0
  80. package/dist/LoginRisk.js.map +1 -0
  81. package/dist/MagicLink.d.ts +85 -0
  82. package/dist/MagicLink.d.ts.map +1 -0
  83. package/dist/MagicLink.js +206 -0
  84. package/dist/MagicLink.js.map +1 -0
  85. package/dist/Mailer.d.ts +33 -2
  86. package/dist/Mailer.d.ts.map +1 -1
  87. package/dist/Mailer.js +107 -0
  88. package/dist/Mailer.js.map +1 -1
  89. package/dist/Passkey.d.ts +334 -0
  90. package/dist/Passkey.d.ts.map +1 -0
  91. package/dist/Passkey.js +422 -0
  92. package/dist/Passkey.js.map +1 -0
  93. package/dist/Password.d.ts +138 -8
  94. package/dist/Password.d.ts.map +1 -1
  95. package/dist/Password.js +397 -8
  96. package/dist/Password.js.map +1 -1
  97. package/dist/RecoveryCode.d.ts +169 -0
  98. package/dist/RecoveryCode.d.ts.map +1 -0
  99. package/dist/RecoveryCode.js +299 -0
  100. package/dist/RecoveryCode.js.map +1 -0
  101. package/dist/RefreshToken.d.ts +222 -0
  102. package/dist/RefreshToken.d.ts.map +1 -0
  103. package/dist/RefreshToken.js +342 -0
  104. package/dist/RefreshToken.js.map +1 -0
  105. package/dist/Sessions.d.ts +52 -2
  106. package/dist/Sessions.d.ts.map +1 -1
  107. package/dist/Sessions.js +123 -1
  108. package/dist/Sessions.js.map +1 -1
  109. package/dist/StepUp.d.ts +44 -0
  110. package/dist/StepUp.d.ts.map +1 -0
  111. package/dist/StepUp.js +82 -0
  112. package/dist/StepUp.js.map +1 -0
  113. package/dist/Storage.d.ts +14 -1
  114. package/dist/Storage.d.ts.map +1 -1
  115. package/dist/Storage.js.map +1 -1
  116. package/dist/StorageMigrations.d.ts +9 -1
  117. package/dist/StorageMigrations.d.ts.map +1 -1
  118. package/dist/StorageMigrations.js +159 -0
  119. package/dist/StorageMigrations.js.map +1 -1
  120. package/dist/Testing.d.ts +1 -1
  121. package/dist/Totp.d.ts +220 -0
  122. package/dist/Totp.d.ts.map +1 -0
  123. package/dist/Totp.js +477 -0
  124. package/dist/Totp.js.map +1 -0
  125. package/dist/TrustedDevice.d.ts +115 -0
  126. package/dist/TrustedDevice.d.ts.map +1 -0
  127. package/dist/TrustedDevice.js +222 -0
  128. package/dist/TrustedDevice.js.map +1 -0
  129. package/dist/alchemy/cloudflare/Email.d.ts +25 -0
  130. package/dist/alchemy/cloudflare/Email.d.ts.map +1 -0
  131. package/dist/alchemy/cloudflare/Email.js +117 -0
  132. package/dist/alchemy/cloudflare/Email.js.map +1 -0
  133. package/dist/cloudflare/Email.d.ts +25 -0
  134. package/dist/cloudflare/Email.d.ts.map +1 -0
  135. package/dist/cloudflare/Email.js +153 -0
  136. package/dist/cloudflare/Email.js.map +1 -0
  137. package/dist/index.d.ts +12 -0
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +12 -0
  140. package/dist/index.js.map +1 -1
  141. package/migrations/0003_auth_login_approval_review.sql +22 -0
  142. package/migrations/0004_auth_trusted_device.sql +12 -0
  143. package/migrations/0005_auth_passkey.sql +17 -0
  144. package/migrations/0006_auth_totp_factor.sql +17 -0
  145. package/migrations/0007_auth_recovery_code.sql +13 -0
  146. package/migrations/0008_auth_api_key.sql +17 -0
  147. package/migrations/0009_auth_refresh_token.sql +21 -0
  148. package/migrations/0010_auth_jwt_revocation.sql +8 -0
  149. package/package.json +57 -1
@@ -1,7 +1,11 @@
1
1
  import { Data, Effect, Layer, Option } from "effect";
2
- import { Column, Executor, Query, Table } from "effect-qb/sqlite";
3
- import { ChallengeId, CredentialId, Email, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
2
+ import { Column, Executor, Query, Scalar, Table } from "effect-qb/sqlite";
3
+ import { AuthFlowId, ChallengeId, CredentialId, Email, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
4
+ import { JwtError, JwtRevocationStore } from "./Jwt.js";
5
+ import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
6
+ import { LoginRiskContext, LoginRiskSignal } from "./LoginRisk.js";
4
7
  import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
8
+ import { TrustedDeviceError, TrustedDeviceStore } from "./TrustedDevice.js";
5
9
  export class D1EffectQbError extends Data.TaggedError("D1EffectQbError") {
6
10
  }
7
11
  const makeAuthUserTable = (name) => Table.make(name, {
@@ -48,6 +52,38 @@ const makeAuthVerificationTable = (name) => Table.make(name, {
48
52
  metadata: Column.text().pipe(Column.nullable),
49
53
  consumed_at: Column.int().pipe(Column.nullable),
50
54
  });
55
+ const makeAuthLoginApprovalReviewTable = (name) => Table.make(name, {
56
+ approval_challenge_id: Column.text().pipe(Column.primaryKey),
57
+ flow_id: Column.text(),
58
+ user_id: Column.text(),
59
+ channel: Column.text(),
60
+ reason: Column.text(),
61
+ session_binding: Column.text(),
62
+ same_device_required: Column.int(),
63
+ status: Column.text(),
64
+ created_at: Column.int(),
65
+ expires_at: Column.int(),
66
+ reviewed_at: Column.int().pipe(Column.nullable),
67
+ reviewed_by: Column.text().pipe(Column.nullable),
68
+ denied_reason: Column.text().pipe(Column.nullable),
69
+ risk: Column.text().pipe(Column.nullable),
70
+ metadata: Column.text().pipe(Column.nullable),
71
+ review_metadata: Column.text().pipe(Column.nullable),
72
+ });
73
+ const makeAuthTrustedDeviceTable = (name) => Table.make(name, {
74
+ user_id: Column.text(),
75
+ token_hash: Column.text(),
76
+ created_at: Column.int(),
77
+ last_seen_at: Column.int(),
78
+ expires_at: Column.int(),
79
+ metadata: Column.text().pipe(Column.nullable),
80
+ });
81
+ const makeAuthJwtRevocationTable = (name) => Table.make(name, {
82
+ jwt_id: Column.text().pipe(Column.primaryKey),
83
+ revoked_at: Column.int(),
84
+ expires_at: Column.int().pipe(Column.nullable),
85
+ reason: Column.text().pipe(Column.nullable),
86
+ });
51
87
  const authSqliteTablesTypeId = Symbol.for("@effect-auth/core/EffectQbSqliteStorage/AuthSqliteTables");
52
88
  const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
53
89
  const jsonDecode = (value) => {
@@ -73,14 +109,8 @@ const decodeBoolean = (value) => {
73
109
  return value !== null && value !== undefined;
74
110
  };
75
111
  const mutationValues = (value) => value;
76
- // Concrete effect-qb table generics are erased after `defineEffectQbAuthTables`;
77
- // mapping definitions keep typed encoders at the app boundary.
78
- const querySelect = (selection) => Query.select(selection);
79
- const queryReturning = (selection) => Query.returning(selection);
80
- const queryFrom = (table) => Query.from(table);
81
112
  const queryInsert = (table, values) => Query.insert(table, values);
82
113
  const queryUpdate = (table, values) => Query.update(table, values);
83
- const queryDelete = (table) => Query.delete(table);
84
114
  const queryEq = (left, right) => Query.eq(left, right);
85
115
  const queryGt = (left, right) => Query.gt(left, right);
86
116
  const queryLte = (left, right) => Query.lte(left, right);
@@ -90,6 +120,45 @@ const executePlan = (executor, plan) => executor.execute(plan);
90
120
  const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
91
121
  const sessionMetadataEnvelopeKey = "__effectAuthSession";
92
122
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
123
+ const isLoginRiskLevel = (value) => value === "unknown" ||
124
+ value === "low" ||
125
+ value === "medium" ||
126
+ value === "high" ||
127
+ value === "critical";
128
+ const decodeLoginRiskSignal = (value) => {
129
+ const record = isRecord(value) ? value : {};
130
+ const level = isLoginRiskLevel(record.level) ? record.level : undefined;
131
+ return LoginRiskSignal.make({
132
+ type: typeof record.type === "string" ? record.type : "unknown",
133
+ ...(level === undefined ? {} : { level }),
134
+ ...(isRecord(record.metadata) ? { metadata: record.metadata } : {}),
135
+ });
136
+ };
137
+ const decodeLoginRiskContext = (value) => {
138
+ const decoded = jsonDecode(value);
139
+ if (!isRecord(decoded)) {
140
+ return undefined;
141
+ }
142
+ return LoginRiskContext.make({
143
+ level: isLoginRiskLevel(decoded.level) ? decoded.level : "unknown",
144
+ signals: Array.isArray(decoded.signals)
145
+ ? decoded.signals.map(decodeLoginRiskSignal)
146
+ : [],
147
+ ...(isRecord(decoded.metadata) ? { metadata: decoded.metadata } : {}),
148
+ });
149
+ };
150
+ const encodeLoginRiskSignal = (signal) => omitUndefined({
151
+ type: signal.type,
152
+ level: signal.level,
153
+ metadata: signal.metadata,
154
+ });
155
+ const encodeLoginRiskContext = (risk) => risk === undefined
156
+ ? null
157
+ : jsonEncode(omitUndefined({
158
+ level: risk.level,
159
+ signals: risk.signals.map(encodeLoginRiskSignal),
160
+ metadata: risk.metadata,
161
+ }));
93
162
  const decodeSessionMetadata = (value) => {
94
163
  const decoded = jsonDecode(value);
95
164
  const envelope = isRecord(decoded)
@@ -176,6 +245,49 @@ const decodeDefaultVerificationRow = (row) => omitUndefined({
176
245
  const decodeVerificationRow = (tables, row) => tables.verification.decode === undefined
177
246
  ? decodeDefaultVerificationRow(row)
178
247
  : tables.verification.decode(row);
248
+ const decodeDefaultLoginApprovalReviewRow = (row) => omitUndefined({
249
+ approvalChallengeId: ChallengeId(row.approvalChallengeId),
250
+ flowId: AuthFlowId(row.flowId),
251
+ userId: UserId(row.userId),
252
+ channel: row.channel,
253
+ reason: row.reason,
254
+ sessionBinding: row.sessionBinding,
255
+ sameDeviceRequired: decodeBoolean(row.sameDeviceRequired),
256
+ status: row.status,
257
+ createdAt: UnixMillis(Number(row.createdAt)),
258
+ expiresAt: UnixMillis(Number(row.expiresAt)),
259
+ reviewedAt: optionalUnixMillis(row.reviewedAt),
260
+ reviewedBy: row.reviewedBy === null
261
+ ? undefined
262
+ : row.reviewedBy,
263
+ deniedReason: row.deniedReason === null ? undefined : row.deniedReason,
264
+ risk: decodeLoginRiskContext(row.risk),
265
+ metadata: jsonDecode(row.metadata),
266
+ reviewMetadata: jsonDecode(row.reviewMetadata),
267
+ });
268
+ const decodeLoginApprovalReviewRow = (tables, row) => tables.loginApprovalReview.decode === undefined
269
+ ? decodeDefaultLoginApprovalReviewRow(row)
270
+ : tables.loginApprovalReview.decode(row);
271
+ const decodeDefaultTrustedDeviceRow = (row) => omitUndefined({
272
+ userId: UserId(row.userId),
273
+ tokenHash: row.tokenHash,
274
+ createdAt: UnixMillis(Number(row.createdAt)),
275
+ lastSeenAt: UnixMillis(Number(row.lastSeenAt)),
276
+ expiresAt: UnixMillis(Number(row.expiresAt)),
277
+ metadata: jsonDecode(row.metadata),
278
+ });
279
+ const decodeTrustedDeviceRow = (tables, row) => tables.trustedDevice.decode === undefined
280
+ ? decodeDefaultTrustedDeviceRow(row)
281
+ : tables.trustedDevice.decode(row);
282
+ const decodeDefaultJwtRevocationRow = (row) => omitUndefined({
283
+ jwtId: row.jwtId,
284
+ revokedAt: UnixMillis(Number(row.revokedAt)),
285
+ expiresAt: optionalUnixMillis(row.expiresAt),
286
+ reason: row.reason ?? undefined,
287
+ });
288
+ const decodeJwtRevocationRow = (tables, row) => tables.jwtRevocation.decode === undefined
289
+ ? decodeDefaultJwtRevocationRow(row)
290
+ : tables.jwtRevocation.decode(row);
179
291
  const userInsert = (row) => ({
180
292
  id: row.id,
181
293
  email: row.email,
@@ -195,6 +307,10 @@ const passwordCredentialInsert = (row) => ({
195
307
  revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
196
308
  metadata: jsonEncode(row.metadata),
197
309
  });
310
+ const passwordCredentialUpdatePassword = (input) => ({
311
+ password_hash: input.passwordHash,
312
+ updated_at: Number(input.updatedAt),
313
+ });
198
314
  const sessionInsert = (row) => ({
199
315
  id: row.id,
200
316
  user_id: row.userId,
@@ -220,6 +336,24 @@ const verificationInsert = (row) => ({
220
336
  metadata: jsonEncode(row.metadata),
221
337
  consumed_at: null,
222
338
  });
339
+ const loginApprovalReviewInsert = (row) => ({
340
+ approval_challenge_id: row.approvalChallengeId,
341
+ flow_id: row.flowId,
342
+ user_id: row.userId,
343
+ channel: row.channel,
344
+ reason: row.reason,
345
+ session_binding: row.sessionBinding,
346
+ same_device_required: row.sameDeviceRequired ? 1 : 0,
347
+ status: row.status,
348
+ created_at: Number(row.createdAt),
349
+ expires_at: Number(row.expiresAt),
350
+ reviewed_at: row.reviewedAt === undefined ? null : Number(row.reviewedAt),
351
+ reviewed_by: row.reviewedBy ?? null,
352
+ denied_reason: row.deniedReason ?? null,
353
+ risk: encodeLoginRiskContext(row.risk),
354
+ metadata: jsonEncode(row.metadata),
355
+ review_metadata: jsonEncode(row.reviewMetadata),
356
+ });
223
357
  const userMarkEmailVerifiedUpdate = (input) => ({
224
358
  email_verified: 1,
225
359
  updated_at: Number(input.updatedAt),
@@ -233,10 +367,13 @@ const sessionRotateUpdate = (input) => omitUndefined({
233
367
  rotated_at: Number(input.rotatedAt),
234
368
  expires_at: input.expiresAt === undefined ? undefined : Number(input.expiresAt),
235
369
  });
236
- const sessionUpdateClaimsUpdate = (row, input) => ({
370
+ const sessionUpdateClaimsUpdate = (row, input) => omitUndefined({
371
+ aal: input.aal,
372
+ amr: input.amr === undefined ? undefined : JSON.stringify(input.amr),
373
+ mfa_verified_at: input.mfaVerifiedAt === undefined ? undefined : Number(input.mfaVerifiedAt),
237
374
  metadata: encodeSessionMetadata({
238
375
  ...row,
239
- claims: input.claims,
376
+ claims: input.claims ?? row.claims,
240
377
  }),
241
378
  });
242
379
  const sessionRevokeUpdate = (input) => ({
@@ -245,11 +382,51 @@ const sessionRevokeUpdate = (input) => ({
245
382
  const verificationConsumeUpdate = (input) => ({
246
383
  consumed_at: Number(input.consumedAt),
247
384
  });
385
+ const loginApprovalReviewApproveUpdate = (input) => ({
386
+ status: "approved",
387
+ reviewed_at: Number(input.reviewedAt),
388
+ reviewed_by: input.reviewedBy ?? null,
389
+ review_metadata: jsonEncode(input.metadata),
390
+ });
391
+ const loginApprovalReviewDenyUpdate = (input) => ({
392
+ status: "denied",
393
+ reviewed_at: Number(input.reviewedAt),
394
+ reviewed_by: input.reviewedBy ?? null,
395
+ denied_reason: input.reason ?? null,
396
+ review_metadata: jsonEncode(input.metadata),
397
+ });
398
+ const trustedDeviceInsert = (input, createdAt) => ({
399
+ user_id: input.userId,
400
+ token_hash: input.tokenHash,
401
+ created_at: Number(createdAt),
402
+ last_seen_at: Number(input.now),
403
+ expires_at: Number(input.expiresAt),
404
+ metadata: jsonEncode(input.metadata),
405
+ });
406
+ const trustedDeviceUpdate = (input, existing) => ({
407
+ last_seen_at: Number(input.now),
408
+ expires_at: Number(input.expiresAt),
409
+ metadata: jsonEncode(input.metadata ?? existing.metadata),
410
+ });
411
+ const jwtRevocationInsert = (record) => ({
412
+ jwt_id: record.jwtId,
413
+ revoked_at: Number(record.revokedAt),
414
+ expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
415
+ reason: record.reason ?? null,
416
+ });
417
+ const jwtRevocationUpdate = (record) => ({
418
+ revoked_at: Number(record.revokedAt),
419
+ expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
420
+ reason: record.reason ?? null,
421
+ });
248
422
  const makeInternalAuthSqliteTables = (names = {}) => {
249
423
  const user = makeAuthUserTable(names.user ?? "auth_user");
250
424
  const credential = makeAuthCredentialTable(names.credential ?? "auth_credential");
251
425
  const session = makeAuthSessionTable(names.session ?? "auth_session");
252
426
  const verification = makeAuthVerificationTable(names.verification ?? "auth_verification");
427
+ const loginApprovalReview = makeAuthLoginApprovalReviewTable(names.loginApprovalReview ?? "auth_login_approval_review");
428
+ const trustedDevice = makeAuthTrustedDeviceTable(names.trustedDevice ?? "auth_trusted_device");
429
+ const jwtRevocation = makeAuthJwtRevocationTable(names.jwtRevocation ?? "auth_jwt_revocation");
253
430
  return {
254
431
  user: {
255
432
  table: user,
@@ -278,6 +455,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
278
455
  metadata: credential.metadata,
279
456
  },
280
457
  insertPassword: passwordCredentialInsert,
458
+ updatePassword: passwordCredentialUpdatePassword,
281
459
  },
282
460
  session: {
283
461
  table: session,
@@ -317,6 +495,54 @@ const makeInternalAuthSqliteTables = (names = {}) => {
317
495
  insert: verificationInsert,
318
496
  consume: verificationConsumeUpdate,
319
497
  },
498
+ loginApprovalReview: {
499
+ table: loginApprovalReview,
500
+ select: {
501
+ approvalChallengeId: loginApprovalReview.approval_challenge_id,
502
+ flowId: loginApprovalReview.flow_id,
503
+ userId: loginApprovalReview.user_id,
504
+ channel: loginApprovalReview.channel,
505
+ reason: loginApprovalReview.reason,
506
+ sessionBinding: loginApprovalReview.session_binding,
507
+ sameDeviceRequired: loginApprovalReview.same_device_required,
508
+ status: loginApprovalReview.status,
509
+ createdAt: loginApprovalReview.created_at,
510
+ expiresAt: loginApprovalReview.expires_at,
511
+ reviewedAt: loginApprovalReview.reviewed_at,
512
+ reviewedBy: loginApprovalReview.reviewed_by,
513
+ deniedReason: loginApprovalReview.denied_reason,
514
+ risk: loginApprovalReview.risk,
515
+ metadata: loginApprovalReview.metadata,
516
+ reviewMetadata: loginApprovalReview.review_metadata,
517
+ },
518
+ insertPending: loginApprovalReviewInsert,
519
+ approve: loginApprovalReviewApproveUpdate,
520
+ deny: loginApprovalReviewDenyUpdate,
521
+ },
522
+ trustedDevice: {
523
+ table: trustedDevice,
524
+ select: {
525
+ userId: trustedDevice.user_id,
526
+ tokenHash: trustedDevice.token_hash,
527
+ createdAt: trustedDevice.created_at,
528
+ lastSeenAt: trustedDevice.last_seen_at,
529
+ expiresAt: trustedDevice.expires_at,
530
+ metadata: trustedDevice.metadata,
531
+ },
532
+ insert: trustedDeviceInsert,
533
+ update: trustedDeviceUpdate,
534
+ },
535
+ jwtRevocation: {
536
+ table: jwtRevocation,
537
+ select: {
538
+ jwtId: jwtRevocation.jwt_id,
539
+ revokedAt: jwtRevocation.revoked_at,
540
+ expiresAt: jwtRevocation.expires_at,
541
+ reason: jwtRevocation.reason,
542
+ },
543
+ insert: jwtRevocationInsert,
544
+ update: jwtRevocationUpdate,
545
+ },
320
546
  };
321
547
  };
322
548
  const asAuthSqliteTables = (tables) => tables;
@@ -327,30 +553,59 @@ const selectUserColumns = (tables) => tables.user.select;
327
553
  const selectCredentialColumns = (tables) => tables.credential.select;
328
554
  const selectSessionColumns = (tables) => tables.session.select;
329
555
  const selectVerificationColumns = (tables) => tables.verification.select;
556
+ const selectLoginApprovalReviewColumns = (tables) => tables.loginApprovalReview.select;
557
+ const selectTrustedDeviceColumns = (tables) => tables.trustedDevice.select;
558
+ const selectJwtRevocationColumns = (tables) => tables.jwtRevocation.select;
330
559
  const storageError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
331
- const runEffectQb = (entity, operation, effect) => Effect.mapError(effect, (cause) => storageError(entity, operation, cause));
332
- const findUserById = (executor, tables, id, operation = "find") => runEffectQb("user", operation, executePlan(executor, querySelect(selectUserColumns(tables)).pipe(queryFrom(tables.user.table), Query.where(queryEq(tables.user.select.id, id))))).pipe(Effect.map((rows) => rows[0] === undefined
560
+ const loginApprovalReviewStoreError = (operation, cause) => new LoginApprovalReviewError({
561
+ message: `Login approval review ${operation} failed`,
562
+ cause,
563
+ });
564
+ const trustedDeviceStoreError = (operation, cause) => new TrustedDeviceError({
565
+ operation,
566
+ message: `Trusted device ${operation} failed`,
567
+ cause,
568
+ });
569
+ const jwtRevocationStoreError = (operation, cause) => new JwtError({
570
+ operation: "revocation",
571
+ message: `JWT revocation ${operation} failed`,
572
+ cause,
573
+ });
574
+ const findUserById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.id, id)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
333
575
  ? Option.none()
334
576
  : Option.some(decodeUserRow(tables, rows[0]))));
335
- const findUserByEmail = (executor, tables, email, operation = "find") => runEffectQb("user", operation, executePlan(executor, querySelect(selectUserColumns(tables)).pipe(queryFrom(tables.user.table), Query.where(queryEq(tables.user.select.email, email))))).pipe(Effect.map((rows) => rows[0] === undefined
577
+ const findUserByEmail = (executor, tables, email, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.email, email)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
336
578
  ? Option.none()
337
579
  : Option.some(decodeUserRow(tables, rows[0]))));
338
- const findPasswordCredentialByUserId = (executor, tables, userId, operation = "find") => runEffectQb("credential", operation, executePlan(executor, querySelect(selectCredentialColumns(tables)).pipe(queryFrom(tables.credential.table), Query.where(queryEq(tables.credential.select.userId, userId)), Query.where(queryEq(tables.credential.select.type, "password"))))).pipe(Effect.map((rows) => rows[0] === undefined
580
+ const findPasswordCredentialByUserId = (executor, tables, userId, operation = "find") => executePlan(executor, Query.select(selectCredentialColumns(tables)).pipe(Query.from(tables.credential.table), Query.where(queryEq(tables.credential.select.userId, userId)), Query.where(queryEq(tables.credential.select.type, "password")))).pipe(Effect.mapError((cause) => storageError("credential", operation, cause)), Effect.map((rows) => rows[0] === undefined
339
581
  ? Option.none()
340
582
  : Option.some(decodePasswordCredentialRow(tables, rows[0]))));
341
- const findSessionById = (executor, tables, id, operation = "find") => runEffectQb("session", operation, executePlan(executor, querySelect(selectSessionColumns(tables)).pipe(queryFrom(tables.session.table), Query.where(queryEq(tables.session.select.id, id))))).pipe(Effect.map((rows) => rows[0] === undefined
583
+ const findJwtRevocation = (executor, tables, jwtId, operation = "find") => executePlan(executor, Query.select(selectJwtRevocationColumns(tables)).pipe(Query.from(tables.jwtRevocation.table), Query.where(queryEq(tables.jwtRevocation.select.jwtId, jwtId)))).pipe(Effect.mapError((cause) => jwtRevocationStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
584
+ ? Option.none()
585
+ : Option.some(decodeJwtRevocationRow(tables, rows[0]))));
586
+ const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows[0] === undefined
342
587
  ? Option.none()
343
588
  : Option.some(decodeSessionRow(tables, rows[0]))));
344
- const findVerificationById = (executor, tables, id, operation = "find") => runEffectQb("verification", operation, executePlan(executor, querySelect(selectVerificationColumns(tables)).pipe(queryFrom(tables.verification.table), Query.where(queryEq(tables.verification.select.id, id))))).pipe(Effect.map((rows) => rows[0] === undefined
589
+ const listSessionsByUser = (executor, tables, input, operation = "listByUser") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows.map((row) => decodeSessionRow(tables, row))));
590
+ const findVerificationById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectVerificationColumns(tables)).pipe(Query.from(tables.verification.table), Query.where(queryEq(tables.verification.select.id, id)))).pipe(Effect.mapError((cause) => storageError("verification", operation, cause)), Effect.map((rows) => rows[0] === undefined
345
591
  ? Option.none()
346
592
  : Option.some(decodeVerificationRow(tables, rows[0]))));
593
+ const findLoginApprovalReviewByApprovalChallengeId = (executor, tables, input, operation = "find") => executePlan(executor, Query.select(selectLoginApprovalReviewColumns(tables)).pipe(Query.from(tables.loginApprovalReview.table), Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
594
+ ? Option.none()
595
+ : Option.some(decodeLoginApprovalReviewRow(tables, rows[0]))));
596
+ const findTrustedDevice = (executor, tables, input, operation = "status") => executePlan(executor, Query.select(selectTrustedDeviceColumns(tables)).pipe(Query.from(tables.trustedDevice.table), Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)), Query.where(queryGt(tables.trustedDevice.select.expiresAt, Number(input.now))))).pipe(Effect.mapError((cause) => trustedDeviceStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
597
+ ? Option.none()
598
+ : Option.some(decodeTrustedDeviceRow(tables, rows[0]))));
599
+ const findTrustedDeviceIncludingExpired = (executor, tables, input) => executePlan(executor, Query.select(selectTrustedDeviceColumns(tables)).pipe(Query.from(tables.trustedDevice.table), Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.map((rows) => rows[0] === undefined
600
+ ? Option.none()
601
+ : Option.some(decodeTrustedDeviceRow(tables, rows[0]))));
347
602
  export const makeEffectQbSqliteUserStore = (executor, options = {}) => {
348
603
  const tables = options.tables ?? authSqliteTables;
349
604
  return UserStore.make({
350
605
  findById: (id) => findUserById(executor, tables, id),
351
606
  findByEmail: (email) => findUserByEmail(executor, tables, email),
352
- insert: (row) => runEffectQb("user", "insert", executePlan(executor, queryInsert(tables.user.table, mutationValues(tables.user.insert(row))))).pipe(Effect.asVoid),
353
- markEmailVerified: (input) => runEffectQb("user", "markEmailVerified", executePlan(executor, queryUpdate(tables.user.table, mutationValues(tables.user.markEmailVerified(input))).pipe(Query.where(queryEq(tables.user.select.id, input.userId)), queryReturning(selectUserColumns(tables))))).pipe(Effect.map((rows) => rows[0] === undefined
607
+ insert: (row) => executePlan(executor, queryInsert(tables.user.table, mutationValues(tables.user.insert(row)))).pipe(Effect.mapError((cause) => storageError("user", "insert", cause)), Effect.asVoid),
608
+ markEmailVerified: (input) => executePlan(executor, queryUpdate(tables.user.table, mutationValues(tables.user.markEmailVerified(input))).pipe(Query.where(queryEq(tables.user.select.id, input.userId)), Query.returning(selectUserColumns(tables)))).pipe(Effect.mapError((cause) => storageError("user", "markEmailVerified", cause)), Effect.map((rows) => rows[0] === undefined
354
609
  ? Option.none()
355
610
  : Option.some(decodeUserRow(tables, rows[0])))),
356
611
  });
@@ -359,23 +614,27 @@ export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
359
614
  const tables = options.tables ?? authSqliteTables;
360
615
  return CredentialStore.make({
361
616
  findPasswordByUserId: (userId) => findPasswordCredentialByUserId(executor, tables, userId),
362
- insertPassword: (row) => runEffectQb("credential", "insert", executePlan(executor, queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(row))))).pipe(Effect.asVoid),
617
+ insertPassword: (row) => executePlan(executor, queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(row)))).pipe(Effect.mapError((cause) => storageError("credential", "insert", cause)), Effect.asVoid),
618
+ updatePassword: (input) => executePlan(executor, queryUpdate(tables.credential.table, mutationValues(tables.credential.updatePassword(input))).pipe(Query.where(queryEq(tables.credential.select.userId, input.userId)), Query.where(queryEq(tables.credential.select.type, "password")), Query.where(queryIsNull(tables.credential.select.revokedAt)), Query.returning({ id: tables.credential.select.id }))).pipe(Effect.mapError((cause) => storageError("credential", "updatePassword", cause)), Effect.flatMap((rows) => rows[0] === undefined
619
+ ? Effect.succeed(Option.none())
620
+ : findPasswordCredentialByUserId(executor, tables, input.userId, "updatePassword"))),
363
621
  });
364
622
  };
365
623
  export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
366
624
  const tables = options.tables ?? authSqliteTables;
367
625
  return SessionStore.make({
368
626
  findById: (id) => findSessionById(executor, tables, id),
369
- insert: (row) => runEffectQb("session", "insert", executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row))))).pipe(Effect.asVoid),
627
+ listByUser: (input) => listSessionsByUser(executor, tables, input),
628
+ insert: (row) => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row)))).pipe(Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
370
629
  refresh: (input) => {
371
- const query = queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), queryReturning(selectSessionColumns(tables)));
372
- return runEffectQb("session", "refresh", executePlan(executor, query)).pipe(Effect.map((rows) => rows[0] === undefined
630
+ const query = queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
631
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "refresh", cause)), Effect.map((rows) => rows[0] === undefined
373
632
  ? Option.none()
374
633
  : Option.some(decodeSessionRow(tables, rows[0]))));
375
634
  },
376
635
  rotate: (input) => {
377
- const query = queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), queryReturning(selectSessionColumns(tables)));
378
- return runEffectQb("session", "rotate", executePlan(executor, query)).pipe(Effect.map((rows) => rows[0] === undefined
636
+ const query = queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
637
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.map((rows) => rows[0] === undefined
379
638
  ? Option.none()
380
639
  : Option.some(decodeSessionRow(tables, rows[0]))));
381
640
  },
@@ -383,49 +642,93 @@ export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
383
642
  if (Option.isNone(rowOption)) {
384
643
  return Effect.succeed(Option.none());
385
644
  }
386
- return runEffectQb("session", "updateClaims", executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), queryReturning(selectSessionColumns(tables))))).pipe(Effect.map((rows) => rows[0] === undefined
645
+ return executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "updateClaims", cause)), Effect.map((rows) => rows[0] === undefined
387
646
  ? Option.none()
388
647
  : Option.some(decodeSessionRow(tables, rows[0]))));
389
648
  })),
390
- revoke: (input) => runEffectQb("session", "revoke", executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId))))).pipe(Effect.asVoid),
391
- revokeAllForUser: (input) => runEffectQb("session", "revokeAllForUser", executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.userId, input.userId))))).pipe(Effect.asVoid),
649
+ revoke: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)))).pipe(Effect.mapError((cause) => storageError("session", "revoke", cause)), Effect.asVoid),
650
+ revokeAllForUser: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", "revokeAllForUser", cause)), Effect.asVoid),
392
651
  });
393
652
  };
394
653
  export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
395
654
  const tables = options.tables ?? authSqliteTables;
396
655
  return VerificationStore.make({
397
656
  findById: (id) => findVerificationById(executor, tables, id),
398
- insert: (row) => runEffectQb("verification", "insert", executePlan(executor, queryInsert(tables.verification.table, mutationValues(tables.verification.insert(row))))).pipe(Effect.asVoid),
657
+ insert: (row) => executePlan(executor, queryInsert(tables.verification.table, mutationValues(tables.verification.insert(row)))).pipe(Effect.mapError((cause) => storageError("verification", "insert", cause)), Effect.asVoid),
399
658
  consume: (input) => {
400
659
  const base = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume(input))).pipe(Query.where(queryEq(tables.verification.select.id, input.id)), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))));
401
660
  const plan = input.type === undefined
402
661
  ? base
403
662
  : base.pipe(Query.where(queryEq(tables.verification.select.type, input.type)));
404
- return runEffectQb("verification", "consume", executePlan(executor, plan.pipe(queryReturning({ id: tables.verification.select.id })))).pipe(Effect.flatMap((rows) => rows[0] === undefined
663
+ return executePlan(executor, plan.pipe(Query.returning({ id: tables.verification.select.id }))).pipe(Effect.mapError((cause) => storageError("verification", "consume", cause)), Effect.flatMap((rows) => rows[0] === undefined
405
664
  ? Effect.succeed(Option.none())
406
665
  : findVerificationById(executor, tables, input.id, "consume")));
407
666
  },
408
667
  deleteExpired: (input) => {
409
668
  if (input.limit === undefined) {
410
- return runEffectQb("verification", "deleteExpired", executePlan(executor, queryDelete(tables.verification.table).pipe(Query.where(queryLte(tables.verification.select.expiresAt, Number(input.now))), queryReturning({ id: tables.verification.select.id })))).pipe(Effect.map((rows) => rows.length));
669
+ return executePlan(executor, Query.delete(tables.verification.table).pipe(Query.where(queryLte(tables.verification.select.expiresAt, Number(input.now))), Query.returning({ id: tables.verification.select.id }))).pipe(Effect.mapError((cause) => storageError("verification", "deleteExpired", cause)), Effect.map((rows) => rows.length));
411
670
  }
412
- return runEffectQb("verification", "deleteExpired", executePlan(executor, querySelect({ id: tables.verification.select.id }).pipe(queryFrom(tables.verification.table), Query.where(queryLte(tables.verification.select.expiresAt, Number(input.now))), Query.limit(input.limit)))).pipe(Effect.flatMap((rows) => {
671
+ return executePlan(executor, Query.select({ id: tables.verification.select.id }).pipe(Query.from(tables.verification.table), Query.where(queryLte(tables.verification.select.expiresAt, Number(input.now))), Query.limit(input.limit))).pipe(Effect.mapError((cause) => storageError("verification", "deleteExpired", cause)), Effect.flatMap((rows) => {
413
672
  const [first, ...rest] = rows.map((row) => row.id);
414
673
  if (first === undefined) {
415
674
  return Effect.succeed(0);
416
675
  }
417
- return runEffectQb("verification", "deleteExpired", executePlan(executor, queryDelete(tables.verification.table).pipe(Query.where(queryIn(tables.verification.select.id, first, ...rest)), queryReturning({ id: tables.verification.select.id })))).pipe(Effect.map((deleted) => deleted.length));
676
+ return executePlan(executor, Query.delete(tables.verification.table).pipe(Query.where(queryIn(tables.verification.select.id, first, ...rest)), Query.returning({ id: tables.verification.select.id }))).pipe(Effect.mapError((cause) => storageError("verification", "deleteExpired", cause)), Effect.map((deleted) => deleted.length));
418
677
  }));
419
678
  },
420
679
  });
421
680
  };
681
+ export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {}) => {
682
+ const tables = options.tables ?? authSqliteTables;
683
+ return LoginApprovalReviewStore.make({
684
+ insertPending: (input) => executePlan(executor, queryInsert(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.insertPending(input)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("insert", cause)), Effect.as(input)),
685
+ findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(executor, tables, input),
686
+ approve: (input) => executePlan(executor, queryUpdate(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.approve(input))).pipe(Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)), Query.where(queryEq(tables.loginApprovalReview.select.status, "pending")), Query.returning(selectLoginApprovalReviewColumns(tables)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("approve", cause)), Effect.map((rows) => rows[0] === undefined
687
+ ? Option.none()
688
+ : Option.some(decodeLoginApprovalReviewRow(tables, rows[0])))),
689
+ deny: (input) => executePlan(executor, queryUpdate(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.deny(input))).pipe(Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)), Query.where(queryEq(tables.loginApprovalReview.select.status, "pending")), Query.returning(selectLoginApprovalReviewColumns(tables)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("deny", cause)), Effect.map((rows) => rows[0] === undefined
690
+ ? Option.none()
691
+ : Option.some(decodeLoginApprovalReviewRow(tables, rows[0])))),
692
+ });
693
+ };
694
+ export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) => {
695
+ const tables = options.tables ?? authSqliteTables;
696
+ return TrustedDeviceStore.make({
697
+ find: (input) => findTrustedDevice(executor, tables, input),
698
+ upsert: (input) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => {
699
+ if (Option.isNone(existing)) {
700
+ return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
701
+ }
702
+ return executePlan(executor, queryUpdate(tables.trustedDevice.table, mutationValues(tables.trustedDevice.update(input, existing.value))).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
703
+ }), Effect.flatMap(() => findTrustedDeviceIncludingExpired(executor, tables, input)), Effect.flatMap((stored) => Option.isSome(stored)
704
+ ? Effect.succeed(stored.value)
705
+ : Effect.fail(trustedDeviceStoreError("trust", "missing row")))),
706
+ });
707
+ };
708
+ export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) => {
709
+ const tables = options.tables ?? authSqliteTables;
710
+ return JwtRevocationStore.make({
711
+ revoke: (record) => findJwtRevocation(executor, tables, record.jwtId, "revoke").pipe(Effect.flatMap((existing) => {
712
+ if (Option.isNone(existing)) {
713
+ return executePlan(executor, queryInsert(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.insert(record))));
714
+ }
715
+ return executePlan(executor, queryUpdate(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.update(record))).pipe(Query.where(queryEq(tables.jwtRevocation.select.jwtId, record.jwtId))));
716
+ }), Effect.mapError((cause) => cause instanceof JwtError
717
+ ? cause
718
+ : jwtRevocationStoreError("revoke", cause)), Effect.asVoid),
719
+ find: (jwtId) => findJwtRevocation(executor, tables, jwtId),
720
+ });
721
+ };
422
722
  export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
423
723
  userStore: makeEffectQbSqliteUserStore(executor, options),
424
724
  credentialStore: makeEffectQbSqliteCredentialStore(executor, options),
425
725
  sessionStore: makeEffectQbSqliteSessionStore(executor, options),
426
726
  verificationStore: makeEffectQbSqliteVerificationStore(executor, options),
727
+ loginApprovalReviewStore: makeEffectQbSqliteLoginApprovalReviewStore(executor, options),
728
+ trustedDeviceStore: makeEffectQbSqliteTrustedDeviceStore(executor, options),
729
+ jwtRevocationStore: makeEffectQbSqliteJwtRevocationStore(executor, options),
427
730
  });
428
- export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)));
731
+ export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)));
429
732
  export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
430
733
  driver: Executor.driver((query) => Effect.tryPromise({
431
734
  try: () => database