@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,6 +1,15 @@
1
- import { Effect, Layer, Option } from "effect";
2
- import { ChallengeId, CredentialId, Email, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
1
+ import { Effect, Layer, Option, Redacted } from "effect";
2
+ import { ApiKeyId, ApiKeyPrefix, ApiKeySecretHash, ApiKeyStore, ApiKeyStoreError, } from "./ApiKey.js";
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";
7
+ import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
8
+ import { RecoveryCodeHash, RecoveryCodeStore, RecoveryCodeStoreError, } from "./RecoveryCode.js";
9
+ import { RefreshTokenFamilyId, RefreshTokenId, RefreshTokenSecretHash, RefreshTokenStore, RefreshTokenStoreError, } from "./RefreshToken.js";
3
10
  import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
11
+ import { TotpFactorStore, TotpFactorStoreError, TotpSecret, } from "./Totp.js";
12
+ import { TrustedDeviceError, TrustedDeviceStore } from "./TrustedDevice.js";
4
13
  const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
5
14
  const jsonDecode = (value) => value === null ? undefined : JSON.parse(value);
6
15
  const optionalUnixMillis = (value) => value === null ? undefined : UnixMillis(Number(value));
@@ -8,6 +17,45 @@ const decodeAmr = (value) => JSON.parse(value);
8
17
  const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
9
18
  const sessionMetadataEnvelopeKey = "__effectAuthSession";
10
19
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20
+ const isLoginRiskLevel = (value) => value === "unknown" ||
21
+ value === "low" ||
22
+ value === "medium" ||
23
+ value === "high" ||
24
+ value === "critical";
25
+ const decodeLoginRiskSignal = (value) => {
26
+ const record = isRecord(value) ? value : {};
27
+ const level = isLoginRiskLevel(record.level) ? record.level : undefined;
28
+ return LoginRiskSignal.make({
29
+ type: typeof record.type === "string" ? record.type : "unknown",
30
+ ...(level === undefined ? {} : { level }),
31
+ ...(isRecord(record.metadata) ? { metadata: record.metadata } : {}),
32
+ });
33
+ };
34
+ const decodeLoginRiskContext = (value) => {
35
+ const decoded = jsonDecode(value);
36
+ if (!isRecord(decoded)) {
37
+ return undefined;
38
+ }
39
+ return LoginRiskContext.make({
40
+ level: isLoginRiskLevel(decoded.level) ? decoded.level : "unknown",
41
+ signals: Array.isArray(decoded.signals)
42
+ ? decoded.signals.map(decodeLoginRiskSignal)
43
+ : [],
44
+ ...(isRecord(decoded.metadata) ? { metadata: decoded.metadata } : {}),
45
+ });
46
+ };
47
+ const encodeLoginRiskSignal = (signal) => omitUndefined({
48
+ type: signal.type,
49
+ level: signal.level,
50
+ metadata: signal.metadata,
51
+ });
52
+ const encodeLoginRiskContext = (risk) => risk === undefined
53
+ ? null
54
+ : jsonEncode(omitUndefined({
55
+ level: risk.level,
56
+ signals: risk.signals.map(encodeLoginRiskSignal),
57
+ metadata: risk.metadata,
58
+ }));
11
59
  const decodeSessionMetadata = (value) => {
12
60
  const decoded = jsonDecode(value);
13
61
  const envelope = isRecord(decoded)
@@ -82,6 +130,103 @@ const decodeVerificationRow = (row) => omitUndefined({
82
130
  metadata: jsonDecode(row.metadata),
83
131
  consumedAt: optionalUnixMillis(row.consumed_at),
84
132
  });
133
+ const decodeLoginApprovalReviewRow = (row) => omitUndefined({
134
+ approvalChallengeId: ChallengeId(row.approval_challenge_id),
135
+ flowId: AuthFlowId(row.flow_id),
136
+ userId: UserId(row.user_id),
137
+ channel: row.channel,
138
+ reason: row.reason,
139
+ sessionBinding: row.session_binding,
140
+ sameDeviceRequired: row.same_device_required === 1,
141
+ status: row.status,
142
+ createdAt: UnixMillis(Number(row.created_at)),
143
+ expiresAt: UnixMillis(Number(row.expires_at)),
144
+ reviewedAt: optionalUnixMillis(row.reviewed_at),
145
+ reviewedBy: row.reviewed_by === null
146
+ ? undefined
147
+ : row.reviewed_by,
148
+ deniedReason: row.denied_reason === null ? undefined : row.denied_reason,
149
+ risk: decodeLoginRiskContext(row.risk),
150
+ metadata: jsonDecode(row.metadata),
151
+ reviewMetadata: jsonDecode(row.review_metadata),
152
+ });
153
+ const decodeTrustedDeviceRow = (row) => omitUndefined({
154
+ userId: UserId(row.user_id),
155
+ tokenHash: row.token_hash,
156
+ createdAt: UnixMillis(Number(row.created_at)),
157
+ lastSeenAt: UnixMillis(Number(row.last_seen_at)),
158
+ expiresAt: UnixMillis(Number(row.expires_at)),
159
+ metadata: jsonDecode(row.metadata),
160
+ });
161
+ const decodePasskeyTransports = (value) => value === null ? undefined : JSON.parse(value);
162
+ const decodePasskeyCredentialRow = (row) => omitUndefined({
163
+ id: CredentialId(row.id),
164
+ userId: UserId(row.user_id),
165
+ credentialId: PasskeyCredentialId(row.credential_id),
166
+ publicKey: row.public_key,
167
+ signCount: Number(row.sign_count),
168
+ transports: decodePasskeyTransports(row.transports),
169
+ backedUp: row.backed_up === null ? undefined : row.backed_up === 1,
170
+ createdAt: UnixMillis(Number(row.created_at)),
171
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
172
+ revokedAt: optionalUnixMillis(row.revoked_at),
173
+ metadata: jsonDecode(row.metadata),
174
+ });
175
+ const decodeTotpFactorRow = (row) => omitUndefined({
176
+ id: CredentialId(row.id),
177
+ userId: UserId(row.user_id),
178
+ secret: Redacted.make(TotpSecret(row.secret)),
179
+ algorithm: row.algorithm,
180
+ digits: Number(row.digits),
181
+ period: Number(row.period),
182
+ createdAt: UnixMillis(Number(row.created_at)),
183
+ confirmedAt: optionalUnixMillis(row.confirmed_at),
184
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
185
+ revokedAt: optionalUnixMillis(row.revoked_at),
186
+ metadata: jsonDecode(row.metadata),
187
+ });
188
+ const decodeRecoveryCodeRow = (row) => omitUndefined({
189
+ id: CredentialId(row.id),
190
+ userId: UserId(row.user_id),
191
+ codeHash: RecoveryCodeHash(row.code_hash),
192
+ createdAt: UnixMillis(Number(row.created_at)),
193
+ usedAt: optionalUnixMillis(row.used_at),
194
+ revokedAt: optionalUnixMillis(row.revoked_at),
195
+ metadata: jsonDecode(row.metadata),
196
+ });
197
+ const decodeApiKeyScopes = (value) => JSON.parse(value);
198
+ const decodeApiKeyRow = (row) => omitUndefined({
199
+ id: ApiKeyId(row.id),
200
+ userId: UserId(row.user_id),
201
+ prefix: ApiKeyPrefix(row.prefix),
202
+ secretHash: ApiKeySecretHash(row.secret_hash),
203
+ scopes: decodeApiKeyScopes(row.scopes),
204
+ createdAt: UnixMillis(Number(row.created_at)),
205
+ expiresAt: optionalUnixMillis(row.expires_at),
206
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
207
+ revokedAt: optionalUnixMillis(row.revoked_at),
208
+ metadata: jsonDecode(row.metadata),
209
+ });
210
+ const decodeRefreshTokenRow = (row) => omitUndefined({
211
+ id: RefreshTokenId(row.id),
212
+ familyId: RefreshTokenFamilyId(row.family_id),
213
+ userId: UserId(row.user_id),
214
+ secretHash: RefreshTokenSecretHash(row.secret_hash),
215
+ createdAt: UnixMillis(Number(row.created_at)),
216
+ expiresAt: UnixMillis(Number(row.expires_at)),
217
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
218
+ rotatedAt: optionalUnixMillis(row.rotated_at),
219
+ replacedById: row.replaced_by_id === null ? undefined : RefreshTokenId(row.replaced_by_id),
220
+ revokedAt: optionalUnixMillis(row.revoked_at),
221
+ reuseDetectedAt: optionalUnixMillis(row.reuse_detected_at),
222
+ metadata: jsonDecode(row.metadata),
223
+ });
224
+ const decodeJwtRevocationRow = (row) => omitUndefined({
225
+ jwtId: row.jwt_id,
226
+ revokedAt: UnixMillis(Number(row.revoked_at)),
227
+ expiresAt: optionalUnixMillis(row.expires_at),
228
+ reason: row.reason ?? undefined,
229
+ });
85
230
  const sessionInsert = (row) => ({
86
231
  id: row.id,
87
232
  user_id: row.userId,
@@ -126,11 +271,175 @@ const verificationInsert = (row) => ({
126
271
  metadata: jsonEncode(row.metadata),
127
272
  consumed_at: null,
128
273
  });
274
+ const loginApprovalReviewInsert = (row) => ({
275
+ approval_challenge_id: row.approvalChallengeId,
276
+ flow_id: row.flowId,
277
+ user_id: row.userId,
278
+ channel: row.channel,
279
+ reason: row.reason,
280
+ session_binding: row.sessionBinding,
281
+ same_device_required: row.sameDeviceRequired ? 1 : 0,
282
+ status: row.status,
283
+ created_at: Number(row.createdAt),
284
+ expires_at: Number(row.expiresAt),
285
+ reviewed_at: row.reviewedAt === undefined ? null : Number(row.reviewedAt),
286
+ reviewed_by: row.reviewedBy ?? null,
287
+ denied_reason: row.deniedReason ?? null,
288
+ risk: encodeLoginRiskContext(row.risk),
289
+ metadata: jsonEncode(row.metadata),
290
+ review_metadata: jsonEncode(row.reviewMetadata),
291
+ });
292
+ const trustedDeviceInsert = (input, createdAt) => ({
293
+ user_id: input.userId,
294
+ token_hash: input.tokenHash,
295
+ created_at: Number(createdAt),
296
+ last_seen_at: Number(input.now),
297
+ expires_at: Number(input.expiresAt),
298
+ metadata: jsonEncode(input.metadata),
299
+ });
300
+ const passkeyCredentialInsert = (row) => ({
301
+ id: row.id,
302
+ user_id: row.userId,
303
+ credential_id: row.credentialId,
304
+ public_key: row.publicKey,
305
+ sign_count: row.signCount,
306
+ transports: jsonEncode(row.transports),
307
+ backed_up: row.backedUp === undefined ? null : row.backedUp ? 1 : 0,
308
+ created_at: Number(row.createdAt),
309
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
310
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
311
+ metadata: jsonEncode(row.metadata),
312
+ });
313
+ const totpFactorInsert = (row) => ({
314
+ id: row.id,
315
+ user_id: row.userId,
316
+ secret: Redacted.value(row.secret),
317
+ algorithm: row.algorithm,
318
+ digits: row.digits,
319
+ period: row.period,
320
+ created_at: Number(row.createdAt),
321
+ confirmed_at: row.confirmedAt === undefined ? null : Number(row.confirmedAt),
322
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
323
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
324
+ metadata: jsonEncode(row.metadata),
325
+ });
326
+ const recoveryCodeInsert = (row) => ({
327
+ id: row.id,
328
+ user_id: row.userId,
329
+ code_hash: row.codeHash,
330
+ created_at: Number(row.createdAt),
331
+ used_at: row.usedAt === undefined ? null : Number(row.usedAt),
332
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
333
+ metadata: jsonEncode(row.metadata),
334
+ });
335
+ const apiKeyInsert = (row) => ({
336
+ id: row.id,
337
+ user_id: row.userId,
338
+ prefix: row.prefix,
339
+ secret_hash: row.secretHash,
340
+ scopes: JSON.stringify(row.scopes),
341
+ created_at: Number(row.createdAt),
342
+ expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
343
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
344
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
345
+ metadata: jsonEncode(row.metadata),
346
+ });
347
+ const refreshTokenInsert = (row) => ({
348
+ id: row.id,
349
+ family_id: row.familyId,
350
+ user_id: row.userId,
351
+ secret_hash: row.secretHash,
352
+ created_at: Number(row.createdAt),
353
+ expires_at: Number(row.expiresAt),
354
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
355
+ rotated_at: row.rotatedAt === undefined ? null : Number(row.rotatedAt),
356
+ replaced_by_id: row.replacedById ?? null,
357
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
358
+ reuse_detected_at: row.reuseDetectedAt === undefined ? null : Number(row.reuseDetectedAt),
359
+ metadata: jsonEncode(row.metadata),
360
+ });
361
+ const jwtRevocationInsert = (row) => ({
362
+ jwt_id: row.jwtId,
363
+ revoked_at: Number(row.revokedAt),
364
+ expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
365
+ reason: row.reason ?? null,
366
+ });
129
367
  const kyselyError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
130
368
  const runKysely = (entity, operation, evaluate) => Effect.tryPromise({
131
369
  try: evaluate,
132
370
  catch: (cause) => kyselyError(entity, operation, cause),
133
371
  });
372
+ const loginApprovalReviewStoreError = (operation, cause) => new LoginApprovalReviewError({
373
+ message: `Login approval review ${operation} failed`,
374
+ cause,
375
+ });
376
+ const runLoginApprovalReviewKysely = (operation, evaluate) => Effect.tryPromise({
377
+ try: evaluate,
378
+ catch: (cause) => loginApprovalReviewStoreError(operation, cause),
379
+ });
380
+ const trustedDeviceStoreError = (operation, cause) => new TrustedDeviceError({
381
+ operation,
382
+ message: `Trusted device ${operation} failed`,
383
+ cause,
384
+ });
385
+ const runTrustedDeviceKysely = (operation, evaluate) => Effect.tryPromise({
386
+ try: evaluate,
387
+ catch: (cause) => trustedDeviceStoreError(operation, cause),
388
+ });
389
+ const passkeyCredentialStoreError = (operation, cause) => new PasskeyCredentialStoreError({
390
+ operation,
391
+ message: `Passkey credential ${operation} failed`,
392
+ cause,
393
+ });
394
+ const runPasskeyCredentialKysely = (operation, evaluate) => Effect.tryPromise({
395
+ try: evaluate,
396
+ catch: (cause) => passkeyCredentialStoreError(operation, cause),
397
+ });
398
+ const totpFactorStoreError = (operation, cause) => new TotpFactorStoreError({
399
+ operation,
400
+ message: `TOTP factor ${operation} failed`,
401
+ cause,
402
+ });
403
+ const runTotpFactorKysely = (operation, evaluate) => Effect.tryPromise({
404
+ try: evaluate,
405
+ catch: (cause) => totpFactorStoreError(operation, cause),
406
+ });
407
+ const recoveryCodeStoreError = (operation, cause) => new RecoveryCodeStoreError({
408
+ operation,
409
+ message: `Recovery code ${operation} failed`,
410
+ cause,
411
+ });
412
+ const runRecoveryCodeKysely = (operation, evaluate) => Effect.tryPromise({
413
+ try: evaluate,
414
+ catch: (cause) => recoveryCodeStoreError(operation, cause),
415
+ });
416
+ const apiKeyStoreError = (operation, cause) => new ApiKeyStoreError({
417
+ operation,
418
+ message: `API key ${operation} failed`,
419
+ cause,
420
+ });
421
+ const runApiKeyKysely = (operation, evaluate) => Effect.tryPromise({
422
+ try: evaluate,
423
+ catch: (cause) => apiKeyStoreError(operation, cause),
424
+ });
425
+ const refreshTokenStoreError = (operation, cause) => new RefreshTokenStoreError({
426
+ operation,
427
+ message: `Refresh token ${operation} failed`,
428
+ cause,
429
+ });
430
+ const runRefreshTokenKysely = (operation, evaluate) => Effect.tryPromise({
431
+ try: evaluate,
432
+ catch: (cause) => refreshTokenStoreError(operation, cause),
433
+ });
434
+ const jwtRevocationStoreError = (operation, cause) => new JwtError({
435
+ operation: "revocation",
436
+ message: `JWT revocation ${operation} failed`,
437
+ cause,
438
+ });
439
+ const runJwtRevocationKysely = (operation, evaluate) => Effect.tryPromise({
440
+ try: evaluate,
441
+ catch: (cause) => jwtRevocationStoreError(operation, cause),
442
+ });
134
443
  const asAuthDb = (db) => db;
135
444
  const findUserById = (db, id, operation = "find") => {
136
445
  const authDb = asAuthDb(db);
@@ -185,6 +494,17 @@ const findSessionById = (db, id, operation = "find") => {
185
494
  : Option.some(decodeSessionRow(row));
186
495
  });
187
496
  };
497
+ const listSessionsByUser = (db, input, operation = "listByUser") => {
498
+ const authDb = asAuthDb(db);
499
+ return runKysely("session", operation, async () => {
500
+ const rows = await authDb
501
+ .selectFrom("auth_session")
502
+ .selectAll()
503
+ .where("user_id", "=", input.userId)
504
+ .execute();
505
+ return rows.map(decodeSessionRow);
506
+ });
507
+ };
188
508
  const findVerificationById = (db, id, operation = "find") => {
189
509
  const authDb = asAuthDb(db);
190
510
  return runKysely("verification", operation, async () => {
@@ -198,6 +518,34 @@ const findVerificationById = (db, id, operation = "find") => {
198
518
  : Option.some(decodeVerificationRow(row));
199
519
  });
200
520
  };
521
+ const findLoginApprovalReviewByApprovalChallengeId = (db, input, operation = "find") => {
522
+ const authDb = asAuthDb(db);
523
+ return runLoginApprovalReviewKysely(operation, async () => {
524
+ const row = await authDb
525
+ .selectFrom("auth_login_approval_review")
526
+ .selectAll()
527
+ .where("approval_challenge_id", "=", input.approvalChallengeId)
528
+ .executeTakeFirst();
529
+ return row === undefined
530
+ ? Option.none()
531
+ : Option.some(decodeLoginApprovalReviewRow(row));
532
+ });
533
+ };
534
+ const findTrustedDevice = (db, input, operation = "status") => {
535
+ const authDb = asAuthDb(db);
536
+ return runTrustedDeviceKysely(operation, async () => {
537
+ const row = await authDb
538
+ .selectFrom("auth_trusted_device")
539
+ .selectAll()
540
+ .where("user_id", "=", input.userId)
541
+ .where("token_hash", "=", input.tokenHash)
542
+ .where("expires_at", ">", Number(input.now))
543
+ .executeTakeFirst();
544
+ return row === undefined
545
+ ? Option.none()
546
+ : Option.some(decodeTrustedDeviceRow(row));
547
+ });
548
+ };
201
549
  export const makeKyselyUserStore = (db) => {
202
550
  const authDb = asAuthDb(db);
203
551
  return UserStore.make({
@@ -231,12 +579,25 @@ export const makeKyselyCredentialStore = (db) => {
231
579
  .values(passwordCredentialInsert(row))
232
580
  .executeTakeFirst();
233
581
  }),
582
+ updatePassword: (input) => runKysely("credential", "updatePassword", () => authDb
583
+ .updateTable("auth_credential")
584
+ .set({
585
+ password_hash: input.passwordHash,
586
+ updated_at: Number(input.updatedAt),
587
+ })
588
+ .where("user_id", "=", input.userId)
589
+ .where("type", "=", "password")
590
+ .where("revoked_at", "is", null)
591
+ .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
592
+ ? findPasswordCredentialByUserId(db, input.userId, "updatePassword")
593
+ : Effect.succeed(Option.none()))),
234
594
  });
235
595
  };
236
596
  export const makeKyselySessionStore = (db) => {
237
597
  const authDb = asAuthDb(db);
238
598
  return SessionStore.make({
239
599
  findById: (id) => findSessionById(db, id),
600
+ listByUser: (input) => listSessionsByUser(db, input),
240
601
  insert: (row) => runKysely("session", "insert", async () => {
241
602
  await authDb
242
603
  .insertInto("auth_session")
@@ -281,9 +642,16 @@ export const makeKyselySessionStore = (db) => {
281
642
  return runKysely("session", "updateClaims", () => authDb
282
643
  .updateTable("auth_session")
283
644
  .set({
645
+ ...(input.aal === undefined ? {} : { aal: input.aal }),
646
+ ...(input.amr === undefined
647
+ ? {}
648
+ : { amr: JSON.stringify(input.amr) }),
649
+ ...(input.mfaVerifiedAt === undefined
650
+ ? {}
651
+ : { mfa_verified_at: Number(input.mfaVerifiedAt) }),
284
652
  metadata: encodeSessionMetadata({
285
653
  ...rowOption.value,
286
- claims: input.claims,
654
+ claims: input.claims ?? rowOption.value.claims,
287
655
  }),
288
656
  })
289
657
  .where("id", "=", input.sessionId)
@@ -356,11 +724,557 @@ export const makeKyselyVerificationStore = (db) => {
356
724
  }),
357
725
  });
358
726
  };
727
+ export const makeKyselyLoginApprovalReviewStore = (db) => {
728
+ const authDb = asAuthDb(db);
729
+ return LoginApprovalReviewStore.make({
730
+ insertPending: (input) => runLoginApprovalReviewKysely("insert", async () => {
731
+ await authDb
732
+ .insertInto("auth_login_approval_review")
733
+ .values(loginApprovalReviewInsert(input))
734
+ .executeTakeFirst();
735
+ return input;
736
+ }),
737
+ findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(db, input),
738
+ approve: (input) => runLoginApprovalReviewKysely("approve", () => authDb
739
+ .updateTable("auth_login_approval_review")
740
+ .set({
741
+ status: "approved",
742
+ reviewed_at: Number(input.reviewedAt),
743
+ reviewed_by: input.reviewedBy ?? null,
744
+ review_metadata: jsonEncode(input.metadata),
745
+ })
746
+ .where("approval_challenge_id", "=", input.approvalChallengeId)
747
+ .where("status", "=", "pending")
748
+ .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
749
+ ? findLoginApprovalReviewByApprovalChallengeId(db, input, "approve")
750
+ : Effect.succeed(Option.none()))),
751
+ deny: (input) => runLoginApprovalReviewKysely("deny", () => authDb
752
+ .updateTable("auth_login_approval_review")
753
+ .set({
754
+ status: "denied",
755
+ reviewed_at: Number(input.reviewedAt),
756
+ reviewed_by: input.reviewedBy ?? null,
757
+ denied_reason: input.reason ?? null,
758
+ review_metadata: jsonEncode(input.metadata),
759
+ })
760
+ .where("approval_challenge_id", "=", input.approvalChallengeId)
761
+ .where("status", "=", "pending")
762
+ .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
763
+ ? findLoginApprovalReviewByApprovalChallengeId(db, input, "deny")
764
+ : Effect.succeed(Option.none()))),
765
+ });
766
+ };
767
+ export const makeKyselyTrustedDeviceStore = (db) => {
768
+ const authDb = asAuthDb(db);
769
+ return TrustedDeviceStore.make({
770
+ find: (input) => findTrustedDevice(db, input),
771
+ upsert: (input) => runTrustedDeviceKysely("trust", async () => {
772
+ const existing = await authDb
773
+ .selectFrom("auth_trusted_device")
774
+ .selectAll()
775
+ .where("user_id", "=", input.userId)
776
+ .where("token_hash", "=", input.tokenHash)
777
+ .executeTakeFirst();
778
+ const existingRow = existing === undefined ? undefined : decodeTrustedDeviceRow(existing);
779
+ await (existingRow === undefined
780
+ ? authDb
781
+ .insertInto("auth_trusted_device")
782
+ .values(trustedDeviceInsert(input, input.now))
783
+ : authDb
784
+ .updateTable("auth_trusted_device")
785
+ .set({
786
+ last_seen_at: Number(input.now),
787
+ expires_at: Number(input.expiresAt),
788
+ metadata: jsonEncode(input.metadata ?? existingRow.metadata),
789
+ })
790
+ .where("user_id", "=", input.userId)
791
+ .where("token_hash", "=", input.tokenHash)).executeTakeFirst();
792
+ const row = await authDb
793
+ .selectFrom("auth_trusted_device")
794
+ .selectAll()
795
+ .where("user_id", "=", input.userId)
796
+ .where("token_hash", "=", input.tokenHash)
797
+ .executeTakeFirstOrThrow();
798
+ return decodeTrustedDeviceRow(row);
799
+ }),
800
+ });
801
+ };
802
+ export const makeKyselyPasskeyCredentialStore = (db) => {
803
+ const authDb = asAuthDb(db);
804
+ return PasskeyCredentialStore.make({
805
+ insert: (row) => runPasskeyCredentialKysely("insert", () => authDb
806
+ .insertInto("auth_passkey_credential")
807
+ .values(passkeyCredentialInsert(row))
808
+ .executeTakeFirst()).pipe(Effect.asVoid),
809
+ findByCredentialId: (credentialId) => runPasskeyCredentialKysely("find", () => authDb
810
+ .selectFrom("auth_passkey_credential")
811
+ .selectAll()
812
+ .where("credential_id", "=", credentialId)
813
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined
814
+ ? Option.none()
815
+ : Option.some(decodePasskeyCredentialRow(row)))),
816
+ listByUser: (input) => runPasskeyCredentialKysely("list", () => {
817
+ let query = authDb
818
+ .selectFrom("auth_passkey_credential")
819
+ .selectAll()
820
+ .where("user_id", "=", input.userId);
821
+ if (input.includeRevoked !== true) {
822
+ query = query.where("revoked_at", "is", null);
823
+ }
824
+ return query.execute();
825
+ }).pipe(Effect.map((rows) => rows.map(decodePasskeyCredentialRow))),
826
+ updateSignCount: (input) => runPasskeyCredentialKysely("update-sign-count", () => authDb
827
+ .updateTable("auth_passkey_credential")
828
+ .set({
829
+ sign_count: input.signCount,
830
+ last_used_at: Number(input.lastUsedAt),
831
+ ...(input.metadata === undefined
832
+ ? {}
833
+ : { metadata: jsonEncode(input.metadata) }),
834
+ })
835
+ .where("credential_id", "=", input.credentialId)
836
+ .where("revoked_at", "is", null)
837
+ .executeTakeFirst()).pipe(Effect.flatMap(() => runPasskeyCredentialKysely("find", () => authDb
838
+ .selectFrom("auth_passkey_credential")
839
+ .selectAll()
840
+ .where("credential_id", "=", input.credentialId)
841
+ .where("revoked_at", "is", null)
842
+ .executeTakeFirst())), Effect.map((row) => row === undefined
843
+ ? Option.none()
844
+ : Option.some(decodePasskeyCredentialRow(row)))),
845
+ revoke: (input) => runPasskeyCredentialKysely("revoke", async () => {
846
+ const metadata = input.reason === undefined
847
+ ? undefined
848
+ : await authDb
849
+ .selectFrom("auth_passkey_credential")
850
+ .select("metadata")
851
+ .where("credential_id", "=", input.credentialId)
852
+ .executeTakeFirst()
853
+ .then((row) => {
854
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
855
+ return jsonEncode({
856
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
857
+ revokeReason: input.reason,
858
+ });
859
+ });
860
+ return authDb
861
+ .updateTable("auth_passkey_credential")
862
+ .set({
863
+ revoked_at: Number(input.revokedAt),
864
+ ...(metadata === undefined ? {} : { metadata }),
865
+ })
866
+ .where("credential_id", "=", input.credentialId)
867
+ .executeTakeFirst();
868
+ }).pipe(Effect.asVoid),
869
+ });
870
+ };
871
+ export const makeKyselyTotpFactorStore = (db) => {
872
+ const authDb = asAuthDb(db);
873
+ return TotpFactorStore.make({
874
+ insert: (row) => runTotpFactorKysely("insert", () => authDb
875
+ .insertInto("auth_totp_factor")
876
+ .values(totpFactorInsert(row))
877
+ .executeTakeFirst()).pipe(Effect.asVoid),
878
+ findById: (id) => runTotpFactorKysely("find", () => authDb
879
+ .selectFrom("auth_totp_factor")
880
+ .selectAll()
881
+ .where("id", "=", id)
882
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeTotpFactorRow(row)))),
883
+ listByUser: (input) => runTotpFactorKysely("list", () => {
884
+ let query = authDb
885
+ .selectFrom("auth_totp_factor")
886
+ .selectAll()
887
+ .where("user_id", "=", input.userId);
888
+ if (input.includeRevoked !== true) {
889
+ query = query.where("revoked_at", "is", null);
890
+ }
891
+ if (input.includeUnconfirmed !== true) {
892
+ query = query.where("confirmed_at", "is not", null);
893
+ }
894
+ return query.execute();
895
+ }).pipe(Effect.map((rows) => rows.map(decodeTotpFactorRow))),
896
+ confirm: (input) => runTotpFactorKysely("confirm", () => authDb
897
+ .updateTable("auth_totp_factor")
898
+ .set({
899
+ confirmed_at: Number(input.confirmedAt),
900
+ ...(input.metadata === undefined
901
+ ? {}
902
+ : { metadata: jsonEncode(input.metadata) }),
903
+ })
904
+ .where("id", "=", input.id)
905
+ .where("revoked_at", "is", null)
906
+ .executeTakeFirst()).pipe(Effect.flatMap(() => runTotpFactorKysely("find", () => authDb
907
+ .selectFrom("auth_totp_factor")
908
+ .selectAll()
909
+ .where("id", "=", input.id)
910
+ .where("revoked_at", "is", null)
911
+ .executeTakeFirst())), Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeTotpFactorRow(row)))),
912
+ markUsed: (input) => runTotpFactorKysely("mark-used", () => authDb
913
+ .updateTable("auth_totp_factor")
914
+ .set({
915
+ last_used_at: Number(input.lastUsedAt),
916
+ ...(input.metadata === undefined
917
+ ? {}
918
+ : { metadata: jsonEncode(input.metadata) }),
919
+ })
920
+ .where("id", "=", input.id)
921
+ .where("revoked_at", "is", null)
922
+ .where("confirmed_at", "is not", null)
923
+ .executeTakeFirst()).pipe(Effect.flatMap(() => runTotpFactorKysely("find", () => authDb
924
+ .selectFrom("auth_totp_factor")
925
+ .selectAll()
926
+ .where("id", "=", input.id)
927
+ .where("revoked_at", "is", null)
928
+ .where("confirmed_at", "is not", null)
929
+ .executeTakeFirst())), Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeTotpFactorRow(row)))),
930
+ revoke: (input) => runTotpFactorKysely("revoke", async () => {
931
+ const metadata = input.reason === undefined
932
+ ? undefined
933
+ : await authDb
934
+ .selectFrom("auth_totp_factor")
935
+ .select("metadata")
936
+ .where("id", "=", input.id)
937
+ .executeTakeFirst()
938
+ .then((row) => {
939
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
940
+ return jsonEncode({
941
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
942
+ revokeReason: input.reason,
943
+ });
944
+ });
945
+ return authDb
946
+ .updateTable("auth_totp_factor")
947
+ .set({
948
+ revoked_at: Number(input.revokedAt),
949
+ ...(metadata === undefined ? {} : { metadata }),
950
+ })
951
+ .where("id", "=", input.id)
952
+ .executeTakeFirst();
953
+ }).pipe(Effect.asVoid),
954
+ });
955
+ };
956
+ export const makeKyselyRecoveryCodeStore = (db) => {
957
+ const authDb = asAuthDb(db);
958
+ return RecoveryCodeStore.make({
959
+ insertMany: (rows) => rows.length === 0
960
+ ? Effect.void
961
+ : runRecoveryCodeKysely("insert", () => authDb
962
+ .insertInto("auth_recovery_code")
963
+ .values(rows.map(recoveryCodeInsert))
964
+ .execute()).pipe(Effect.asVoid),
965
+ findById: (id) => runRecoveryCodeKysely("find", () => authDb
966
+ .selectFrom("auth_recovery_code")
967
+ .selectAll()
968
+ .where("id", "=", id)
969
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined
970
+ ? Option.none()
971
+ : Option.some(decodeRecoveryCodeRow(row)))),
972
+ listByUser: (input) => runRecoveryCodeKysely("list", () => {
973
+ let query = authDb
974
+ .selectFrom("auth_recovery_code")
975
+ .selectAll()
976
+ .where("user_id", "=", input.userId);
977
+ if (input.includeUsed !== true) {
978
+ query = query.where("used_at", "is", null);
979
+ }
980
+ if (input.includeRevoked !== true) {
981
+ query = query.where("revoked_at", "is", null);
982
+ }
983
+ return query.execute();
984
+ }).pipe(Effect.map((rows) => rows.map(decodeRecoveryCodeRow))),
985
+ markUsed: (input) => runRecoveryCodeKysely("mark-used", async () => {
986
+ const result = await authDb
987
+ .updateTable("auth_recovery_code")
988
+ .set({
989
+ used_at: Number(input.usedAt),
990
+ ...(input.metadata === undefined
991
+ ? {}
992
+ : { metadata: jsonEncode(input.metadata) }),
993
+ })
994
+ .where("id", "=", input.id)
995
+ .where("used_at", "is", null)
996
+ .where("revoked_at", "is", null)
997
+ .executeTakeFirst();
998
+ if (Number(result.numUpdatedRows) === 0) {
999
+ return undefined;
1000
+ }
1001
+ return authDb
1002
+ .selectFrom("auth_recovery_code")
1003
+ .selectAll()
1004
+ .where("id", "=", input.id)
1005
+ .executeTakeFirst();
1006
+ }).pipe(Effect.map((row) => row === undefined
1007
+ ? Option.none()
1008
+ : Option.some(decodeRecoveryCodeRow(row)))),
1009
+ revoke: (input) => runRecoveryCodeKysely("revoke", async () => {
1010
+ const metadata = input.reason === undefined
1011
+ ? undefined
1012
+ : await authDb
1013
+ .selectFrom("auth_recovery_code")
1014
+ .select("metadata")
1015
+ .where("id", "=", input.id)
1016
+ .executeTakeFirst()
1017
+ .then((row) => {
1018
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
1019
+ return jsonEncode({
1020
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
1021
+ revokeReason: input.reason,
1022
+ });
1023
+ });
1024
+ return authDb
1025
+ .updateTable("auth_recovery_code")
1026
+ .set({
1027
+ revoked_at: Number(input.revokedAt),
1028
+ ...(metadata === undefined ? {} : { metadata }),
1029
+ })
1030
+ .where("id", "=", input.id)
1031
+ .executeTakeFirst();
1032
+ }).pipe(Effect.asVoid),
1033
+ });
1034
+ };
1035
+ export const makeKyselyApiKeyStore = (db) => {
1036
+ const authDb = asAuthDb(db);
1037
+ return ApiKeyStore.make({
1038
+ insert: (row) => runApiKeyKysely("insert", () => authDb.insertInto("auth_api_key").values(apiKeyInsert(row)).executeTakeFirst()).pipe(Effect.asVoid),
1039
+ findById: (id) => runApiKeyKysely("find", () => authDb
1040
+ .selectFrom("auth_api_key")
1041
+ .selectAll()
1042
+ .where("id", "=", id)
1043
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeApiKeyRow(row)))),
1044
+ findByPrefix: (prefix) => runApiKeyKysely("find", () => authDb
1045
+ .selectFrom("auth_api_key")
1046
+ .selectAll()
1047
+ .where("prefix", "=", prefix)
1048
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeApiKeyRow(row)))),
1049
+ listByUser: (input) => runApiKeyKysely("list", () => {
1050
+ let query = authDb
1051
+ .selectFrom("auth_api_key")
1052
+ .selectAll()
1053
+ .where("user_id", "=", input.userId);
1054
+ if (input.includeRevoked !== true) {
1055
+ query = query.where("revoked_at", "is", null);
1056
+ }
1057
+ return query.execute();
1058
+ }).pipe(Effect.map((rows) => rows.map(decodeApiKeyRow))),
1059
+ markUsed: (input) => runApiKeyKysely("mark-used", async () => {
1060
+ const result = await authDb
1061
+ .updateTable("auth_api_key")
1062
+ .set({
1063
+ last_used_at: Number(input.lastUsedAt),
1064
+ ...(input.metadata === undefined
1065
+ ? {}
1066
+ : { metadata: jsonEncode(input.metadata) }),
1067
+ })
1068
+ .where("id", "=", input.id)
1069
+ .where("revoked_at", "is", null)
1070
+ .executeTakeFirst();
1071
+ if (Number(result.numUpdatedRows) === 0) {
1072
+ return undefined;
1073
+ }
1074
+ return authDb
1075
+ .selectFrom("auth_api_key")
1076
+ .selectAll()
1077
+ .where("id", "=", input.id)
1078
+ .executeTakeFirst();
1079
+ }).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeApiKeyRow(row)))),
1080
+ revoke: (input) => runApiKeyKysely("revoke", async () => {
1081
+ const metadata = input.reason === undefined
1082
+ ? undefined
1083
+ : await authDb
1084
+ .selectFrom("auth_api_key")
1085
+ .select("metadata")
1086
+ .where("id", "=", input.id)
1087
+ .executeTakeFirst()
1088
+ .then((row) => {
1089
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
1090
+ return jsonEncode({
1091
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
1092
+ revokeReason: input.reason,
1093
+ });
1094
+ });
1095
+ return authDb
1096
+ .updateTable("auth_api_key")
1097
+ .set({
1098
+ revoked_at: Number(input.revokedAt),
1099
+ ...(metadata === undefined ? {} : { metadata }),
1100
+ })
1101
+ .where("id", "=", input.id)
1102
+ .executeTakeFirst();
1103
+ }).pipe(Effect.asVoid),
1104
+ });
1105
+ };
1106
+ export const makeKyselyRefreshTokenStore = (db) => {
1107
+ const authDb = asAuthDb(db);
1108
+ const metadataWithReason = (metadata, key, reason) => {
1109
+ if (reason === undefined) {
1110
+ return metadata;
1111
+ }
1112
+ const existing = jsonDecode(metadata);
1113
+ return jsonEncode({
1114
+ ...(isRecord(existing) ? existing : {}),
1115
+ [key]: reason,
1116
+ });
1117
+ };
1118
+ return RefreshTokenStore.make({
1119
+ insert: (row) => runRefreshTokenKysely("insert", () => authDb
1120
+ .insertInto("auth_refresh_token")
1121
+ .values(refreshTokenInsert(row))
1122
+ .executeTakeFirst()).pipe(Effect.asVoid),
1123
+ findById: (id) => runRefreshTokenKysely("find", () => authDb
1124
+ .selectFrom("auth_refresh_token")
1125
+ .selectAll()
1126
+ .where("id", "=", id)
1127
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeRefreshTokenRow(row)))),
1128
+ findBySecretHash: (hash) => runRefreshTokenKysely("find", () => authDb
1129
+ .selectFrom("auth_refresh_token")
1130
+ .selectAll()
1131
+ .where("secret_hash", "=", hash)
1132
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeRefreshTokenRow(row)))),
1133
+ listByUser: (input) => runRefreshTokenKysely("list", () => {
1134
+ let query = authDb
1135
+ .selectFrom("auth_refresh_token")
1136
+ .selectAll()
1137
+ .where("user_id", "=", input.userId);
1138
+ if (input.includeRevoked !== true) {
1139
+ query = query.where("revoked_at", "is", null);
1140
+ }
1141
+ return query.execute();
1142
+ }).pipe(Effect.map((rows) => rows.map(decodeRefreshTokenRow))),
1143
+ listByFamily: (input) => runRefreshTokenKysely("list", () => {
1144
+ let query = authDb
1145
+ .selectFrom("auth_refresh_token")
1146
+ .selectAll()
1147
+ .where("family_id", "=", input.familyId);
1148
+ if (input.includeRevoked !== true) {
1149
+ query = query.where("revoked_at", "is", null);
1150
+ }
1151
+ return query.execute();
1152
+ }).pipe(Effect.map((rows) => rows.map(decodeRefreshTokenRow))),
1153
+ rotate: (input) => runRefreshTokenKysely("rotate", async () => authDb.transaction().execute(async (transaction) => {
1154
+ const tx = transaction;
1155
+ const updated = await tx
1156
+ .updateTable("auth_refresh_token")
1157
+ .set({
1158
+ last_used_at: Number(input.rotatedAt),
1159
+ rotated_at: Number(input.rotatedAt),
1160
+ replaced_by_id: input.replacement.id,
1161
+ })
1162
+ .where("id", "=", input.id)
1163
+ .where("rotated_at", "is", null)
1164
+ .where("revoked_at", "is", null)
1165
+ .executeTakeFirst();
1166
+ if (Number(updated.numUpdatedRows) === 0) {
1167
+ return undefined;
1168
+ }
1169
+ await tx
1170
+ .insertInto("auth_refresh_token")
1171
+ .values(refreshTokenInsert(input.replacement))
1172
+ .executeTakeFirst();
1173
+ const previous = await tx
1174
+ .selectFrom("auth_refresh_token")
1175
+ .selectAll()
1176
+ .where("id", "=", input.id)
1177
+ .executeTakeFirst();
1178
+ const replacement = await tx
1179
+ .selectFrom("auth_refresh_token")
1180
+ .selectAll()
1181
+ .where("id", "=", input.replacement.id)
1182
+ .executeTakeFirst();
1183
+ return previous === undefined || replacement === undefined
1184
+ ? undefined
1185
+ : {
1186
+ previous: decodeRefreshTokenRow(previous),
1187
+ replacement: decodeRefreshTokenRow(replacement),
1188
+ };
1189
+ })).pipe(Effect.map((row) => (row === undefined ? Option.none() : Option.some(row)))),
1190
+ markReuseDetected: (input) => runRefreshTokenKysely("reuse", async () => {
1191
+ const result = await authDb
1192
+ .updateTable("auth_refresh_token")
1193
+ .set({ reuse_detected_at: Number(input.reuseDetectedAt) })
1194
+ .where("id", "=", input.id)
1195
+ .executeTakeFirst();
1196
+ if (Number(result.numUpdatedRows) === 0) {
1197
+ return undefined;
1198
+ }
1199
+ return authDb
1200
+ .selectFrom("auth_refresh_token")
1201
+ .selectAll()
1202
+ .where("id", "=", input.id)
1203
+ .executeTakeFirst();
1204
+ }).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeRefreshTokenRow(row)))),
1205
+ revoke: (input) => runRefreshTokenKysely("revoke", async () => {
1206
+ const row = await authDb
1207
+ .selectFrom("auth_refresh_token")
1208
+ .select(["metadata"])
1209
+ .where("id", "=", input.id)
1210
+ .executeTakeFirst();
1211
+ return authDb
1212
+ .updateTable("auth_refresh_token")
1213
+ .set({
1214
+ revoked_at: Number(input.revokedAt),
1215
+ metadata: metadataWithReason(row?.metadata ?? null, "revokeReason", input.reason),
1216
+ })
1217
+ .where("id", "=", input.id)
1218
+ .executeTakeFirst();
1219
+ }).pipe(Effect.asVoid),
1220
+ revokeFamily: (input) => runRefreshTokenKysely("revoke", async () => {
1221
+ const rows = await authDb
1222
+ .selectFrom("auth_refresh_token")
1223
+ .select(["id", "metadata"])
1224
+ .where("family_id", "=", input.familyId)
1225
+ .execute();
1226
+ await Promise.all(rows.map((row) => authDb
1227
+ .updateTable("auth_refresh_token")
1228
+ .set({
1229
+ revoked_at: Number(input.revokedAt),
1230
+ metadata: metadataWithReason(row.metadata, "familyRevokeReason", input.reason),
1231
+ })
1232
+ .where("id", "=", row.id)
1233
+ .executeTakeFirst()));
1234
+ }).pipe(Effect.asVoid),
1235
+ });
1236
+ };
1237
+ export const makeKyselyJwtRevocationStore = (db) => {
1238
+ const authDb = asAuthDb(db);
1239
+ return JwtRevocationStore.make({
1240
+ revoke: (record) => runJwtRevocationKysely("revoke", async () => {
1241
+ const row = jwtRevocationInsert(record);
1242
+ const updated = await authDb
1243
+ .updateTable("auth_jwt_revocation")
1244
+ .set({
1245
+ revoked_at: row.revoked_at,
1246
+ expires_at: row.expires_at,
1247
+ reason: row.reason,
1248
+ })
1249
+ .where("jwt_id", "=", record.jwtId)
1250
+ .executeTakeFirst();
1251
+ if (Number(updated.numUpdatedRows) === 0) {
1252
+ await authDb
1253
+ .insertInto("auth_jwt_revocation")
1254
+ .values(row)
1255
+ .executeTakeFirst();
1256
+ }
1257
+ }).pipe(Effect.asVoid),
1258
+ find: (jwtId) => runJwtRevocationKysely("find", () => authDb
1259
+ .selectFrom("auth_jwt_revocation")
1260
+ .selectAll()
1261
+ .where("jwt_id", "=", jwtId)
1262
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeJwtRevocationRow(row)))),
1263
+ });
1264
+ };
359
1265
  export const makeKyselyStorage = (db) => ({
360
1266
  userStore: makeKyselyUserStore(db),
361
1267
  credentialStore: makeKyselyCredentialStore(db),
362
1268
  sessionStore: makeKyselySessionStore(db),
363
1269
  verificationStore: makeKyselyVerificationStore(db),
1270
+ loginApprovalReviewStore: makeKyselyLoginApprovalReviewStore(db),
1271
+ trustedDeviceStore: makeKyselyTrustedDeviceStore(db),
1272
+ passkeyCredentialStore: makeKyselyPasskeyCredentialStore(db),
1273
+ totpFactorStore: makeKyselyTotpFactorStore(db),
1274
+ recoveryCodeStore: makeKyselyRecoveryCodeStore(db),
1275
+ apiKeyStore: makeKyselyApiKeyStore(db),
1276
+ refreshTokenStore: makeKyselyRefreshTokenStore(db),
1277
+ jwtRevocationStore: makeKyselyJwtRevocationStore(db),
364
1278
  });
365
- export const KyselyAuthStorageLive = (db) => Layer.mergeAll(Layer.succeed(UserStore)(makeKyselyUserStore(db)), Layer.succeed(CredentialStore)(makeKyselyCredentialStore(db)), Layer.succeed(SessionStore)(makeKyselySessionStore(db)), Layer.succeed(VerificationStore)(makeKyselyVerificationStore(db)));
1279
+ export const KyselyAuthStorageLive = (db) => Layer.mergeAll(Layer.succeed(UserStore)(makeKyselyUserStore(db)), Layer.succeed(CredentialStore)(makeKyselyCredentialStore(db)), Layer.succeed(SessionStore)(makeKyselySessionStore(db)), Layer.succeed(VerificationStore)(makeKyselyVerificationStore(db)), Layer.succeed(LoginApprovalReviewStore)(makeKyselyLoginApprovalReviewStore(db)), Layer.succeed(TrustedDeviceStore)(makeKyselyTrustedDeviceStore(db)), Layer.succeed(PasskeyCredentialStore)(makeKyselyPasskeyCredentialStore(db)), Layer.succeed(TotpFactorStore)(makeKyselyTotpFactorStore(db)), Layer.succeed(RecoveryCodeStore)(makeKyselyRecoveryCodeStore(db)), Layer.succeed(ApiKeyStore)(makeKyselyApiKeyStore(db)), Layer.succeed(RefreshTokenStore)(makeKyselyRefreshTokenStore(db)), Layer.succeed(JwtRevocationStore)(makeKyselyJwtRevocationStore(db)));
366
1280
  //# sourceMappingURL=KyselyStorage.js.map