@effect-auth/core 0.1.0-alpha.3 → 0.1.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) 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 +263 -1
  24. package/dist/Client.d.ts.map +1 -1
  25. package/dist/Client.js +533 -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 +115 -3
  30. package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
  31. package/dist/EffectQbSqliteStorage.js +314 -4
  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 +1363 -19
  42. package/dist/HttpApi/Api.d.ts.map +1 -1
  43. package/dist/HttpApi/Api.js +1625 -20
  44. package/dist/HttpApi/Api.js.map +1 -1
  45. package/dist/HttpApi/Endpoints.d.ts +682 -0
  46. package/dist/HttpApi/Endpoints.d.ts.map +1 -1
  47. package/dist/HttpApi/Endpoints.js +252 -1
  48. package/dist/HttpApi/Endpoints.js.map +1 -1
  49. package/dist/HttpApi/Schemas.d.ts +841 -0
  50. package/dist/HttpApi/Schemas.d.ts.map +1 -1
  51. package/dist/HttpApi/Schemas.js +513 -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 +151 -1
  66. package/dist/KyselyStorage.d.ts.map +1 -1
  67. package/dist/KyselyStorage.js +1021 -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/OAuth.d.ts +742 -0
  90. package/dist/OAuth.d.ts.map +1 -0
  91. package/dist/OAuth.js +1387 -0
  92. package/dist/OAuth.js.map +1 -0
  93. package/dist/Passkey.d.ts +334 -0
  94. package/dist/Passkey.d.ts.map +1 -0
  95. package/dist/Passkey.js +422 -0
  96. package/dist/Passkey.js.map +1 -0
  97. package/dist/Password.d.ts +138 -8
  98. package/dist/Password.d.ts.map +1 -1
  99. package/dist/Password.js +397 -8
  100. package/dist/Password.js.map +1 -1
  101. package/dist/RecoveryCode.d.ts +169 -0
  102. package/dist/RecoveryCode.d.ts.map +1 -0
  103. package/dist/RecoveryCode.js +299 -0
  104. package/dist/RecoveryCode.js.map +1 -0
  105. package/dist/RefreshToken.d.ts +222 -0
  106. package/dist/RefreshToken.d.ts.map +1 -0
  107. package/dist/RefreshToken.js +342 -0
  108. package/dist/RefreshToken.js.map +1 -0
  109. package/dist/Sessions.d.ts +52 -2
  110. package/dist/Sessions.d.ts.map +1 -1
  111. package/dist/Sessions.js +123 -1
  112. package/dist/Sessions.js.map +1 -1
  113. package/dist/StepUp.d.ts +44 -0
  114. package/dist/StepUp.d.ts.map +1 -0
  115. package/dist/StepUp.js +82 -0
  116. package/dist/StepUp.js.map +1 -0
  117. package/dist/Storage.d.ts +14 -1
  118. package/dist/Storage.d.ts.map +1 -1
  119. package/dist/Storage.js.map +1 -1
  120. package/dist/StorageMigrations.d.ts +10 -1
  121. package/dist/StorageMigrations.d.ts.map +1 -1
  122. package/dist/StorageMigrations.js +179 -0
  123. package/dist/StorageMigrations.js.map +1 -1
  124. package/dist/Testing.d.ts +1 -1
  125. package/dist/Totp.d.ts +220 -0
  126. package/dist/Totp.d.ts.map +1 -0
  127. package/dist/Totp.js +477 -0
  128. package/dist/Totp.js.map +1 -0
  129. package/dist/TrustedDevice.d.ts +115 -0
  130. package/dist/TrustedDevice.d.ts.map +1 -0
  131. package/dist/TrustedDevice.js +222 -0
  132. package/dist/TrustedDevice.js.map +1 -0
  133. package/dist/Webhook.d.ts +72 -0
  134. package/dist/Webhook.d.ts.map +1 -0
  135. package/dist/Webhook.js +82 -0
  136. package/dist/Webhook.js.map +1 -0
  137. package/dist/alchemy/cloudflare/Email.d.ts +25 -0
  138. package/dist/alchemy/cloudflare/Email.d.ts.map +1 -0
  139. package/dist/alchemy/cloudflare/Email.js +117 -0
  140. package/dist/alchemy/cloudflare/Email.js.map +1 -0
  141. package/dist/cloudflare/Email.d.ts +25 -0
  142. package/dist/cloudflare/Email.d.ts.map +1 -0
  143. package/dist/cloudflare/Email.js +153 -0
  144. package/dist/cloudflare/Email.js.map +1 -0
  145. package/dist/index.d.ts +14 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +14 -0
  148. package/dist/index.js.map +1 -1
  149. package/migrations/0003_auth_login_approval_review.sql +22 -0
  150. package/migrations/0004_auth_trusted_device.sql +12 -0
  151. package/migrations/0005_auth_passkey.sql +17 -0
  152. package/migrations/0006_auth_totp_factor.sql +17 -0
  153. package/migrations/0007_auth_recovery_code.sql +13 -0
  154. package/migrations/0008_auth_api_key.sql +17 -0
  155. package/migrations/0009_auth_refresh_token.sql +21 -0
  156. package/migrations/0010_auth_jwt_revocation.sql +8 -0
  157. package/migrations/0011_auth_oauth_account.sql +16 -0
  158. package/package.json +65 -1
@@ -1,6 +1,16 @@
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, OAuthAccountId, OAuthProviderId, 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 { OAuthAccountStore, OAuthAccountStoreError, } from "./OAuth.js";
8
+ import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
9
+ import { RecoveryCodeHash, RecoveryCodeStore, RecoveryCodeStoreError, } from "./RecoveryCode.js";
10
+ import { RefreshTokenFamilyId, RefreshTokenId, RefreshTokenSecretHash, RefreshTokenStore, RefreshTokenStoreError, } from "./RefreshToken.js";
3
11
  import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
12
+ import { TotpFactorStore, TotpFactorStoreError, TotpSecret, } from "./Totp.js";
13
+ import { TrustedDeviceError, TrustedDeviceStore } from "./TrustedDevice.js";
4
14
  const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
5
15
  const jsonDecode = (value) => value === null ? undefined : JSON.parse(value);
6
16
  const optionalUnixMillis = (value) => value === null ? undefined : UnixMillis(Number(value));
@@ -8,6 +18,45 @@ const decodeAmr = (value) => JSON.parse(value);
8
18
  const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
9
19
  const sessionMetadataEnvelopeKey = "__effectAuthSession";
10
20
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21
+ const isLoginRiskLevel = (value) => value === "unknown" ||
22
+ value === "low" ||
23
+ value === "medium" ||
24
+ value === "high" ||
25
+ value === "critical";
26
+ const decodeLoginRiskSignal = (value) => {
27
+ const record = isRecord(value) ? value : {};
28
+ const level = isLoginRiskLevel(record.level) ? record.level : undefined;
29
+ return LoginRiskSignal.make({
30
+ type: typeof record.type === "string" ? record.type : "unknown",
31
+ ...(level === undefined ? {} : { level }),
32
+ ...(isRecord(record.metadata) ? { metadata: record.metadata } : {}),
33
+ });
34
+ };
35
+ const decodeLoginRiskContext = (value) => {
36
+ const decoded = jsonDecode(value);
37
+ if (!isRecord(decoded)) {
38
+ return undefined;
39
+ }
40
+ return LoginRiskContext.make({
41
+ level: isLoginRiskLevel(decoded.level) ? decoded.level : "unknown",
42
+ signals: Array.isArray(decoded.signals)
43
+ ? decoded.signals.map(decodeLoginRiskSignal)
44
+ : [],
45
+ ...(isRecord(decoded.metadata) ? { metadata: decoded.metadata } : {}),
46
+ });
47
+ };
48
+ const encodeLoginRiskSignal = (signal) => omitUndefined({
49
+ type: signal.type,
50
+ level: signal.level,
51
+ metadata: signal.metadata,
52
+ });
53
+ const encodeLoginRiskContext = (risk) => risk === undefined
54
+ ? null
55
+ : jsonEncode(omitUndefined({
56
+ level: risk.level,
57
+ signals: risk.signals.map(encodeLoginRiskSignal),
58
+ metadata: risk.metadata,
59
+ }));
11
60
  const decodeSessionMetadata = (value) => {
12
61
  const decoded = jsonDecode(value);
13
62
  const envelope = isRecord(decoded)
@@ -82,6 +131,115 @@ const decodeVerificationRow = (row) => omitUndefined({
82
131
  metadata: jsonDecode(row.metadata),
83
132
  consumedAt: optionalUnixMillis(row.consumed_at),
84
133
  });
134
+ const decodeLoginApprovalReviewRow = (row) => omitUndefined({
135
+ approvalChallengeId: ChallengeId(row.approval_challenge_id),
136
+ flowId: AuthFlowId(row.flow_id),
137
+ userId: UserId(row.user_id),
138
+ channel: row.channel,
139
+ reason: row.reason,
140
+ sessionBinding: row.session_binding,
141
+ sameDeviceRequired: row.same_device_required === 1,
142
+ status: row.status,
143
+ createdAt: UnixMillis(Number(row.created_at)),
144
+ expiresAt: UnixMillis(Number(row.expires_at)),
145
+ reviewedAt: optionalUnixMillis(row.reviewed_at),
146
+ reviewedBy: row.reviewed_by === null
147
+ ? undefined
148
+ : row.reviewed_by,
149
+ deniedReason: row.denied_reason === null ? undefined : row.denied_reason,
150
+ risk: decodeLoginRiskContext(row.risk),
151
+ metadata: jsonDecode(row.metadata),
152
+ reviewMetadata: jsonDecode(row.review_metadata),
153
+ });
154
+ const decodeTrustedDeviceRow = (row) => omitUndefined({
155
+ userId: UserId(row.user_id),
156
+ tokenHash: row.token_hash,
157
+ createdAt: UnixMillis(Number(row.created_at)),
158
+ lastSeenAt: UnixMillis(Number(row.last_seen_at)),
159
+ expiresAt: UnixMillis(Number(row.expires_at)),
160
+ metadata: jsonDecode(row.metadata),
161
+ });
162
+ const decodePasskeyTransports = (value) => value === null ? undefined : JSON.parse(value);
163
+ const decodePasskeyCredentialRow = (row) => omitUndefined({
164
+ id: CredentialId(row.id),
165
+ userId: UserId(row.user_id),
166
+ credentialId: PasskeyCredentialId(row.credential_id),
167
+ publicKey: row.public_key,
168
+ signCount: Number(row.sign_count),
169
+ transports: decodePasskeyTransports(row.transports),
170
+ backedUp: row.backed_up === null ? undefined : row.backed_up === 1,
171
+ createdAt: UnixMillis(Number(row.created_at)),
172
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
173
+ revokedAt: optionalUnixMillis(row.revoked_at),
174
+ metadata: jsonDecode(row.metadata),
175
+ });
176
+ const decodeTotpFactorRow = (row) => omitUndefined({
177
+ id: CredentialId(row.id),
178
+ userId: UserId(row.user_id),
179
+ secret: Redacted.make(TotpSecret(row.secret)),
180
+ algorithm: row.algorithm,
181
+ digits: Number(row.digits),
182
+ period: Number(row.period),
183
+ createdAt: UnixMillis(Number(row.created_at)),
184
+ confirmedAt: optionalUnixMillis(row.confirmed_at),
185
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
186
+ revokedAt: optionalUnixMillis(row.revoked_at),
187
+ metadata: jsonDecode(row.metadata),
188
+ });
189
+ const decodeRecoveryCodeRow = (row) => omitUndefined({
190
+ id: CredentialId(row.id),
191
+ userId: UserId(row.user_id),
192
+ codeHash: RecoveryCodeHash(row.code_hash),
193
+ createdAt: UnixMillis(Number(row.created_at)),
194
+ usedAt: optionalUnixMillis(row.used_at),
195
+ revokedAt: optionalUnixMillis(row.revoked_at),
196
+ metadata: jsonDecode(row.metadata),
197
+ });
198
+ const decodeApiKeyScopes = (value) => JSON.parse(value);
199
+ const decodeApiKeyRow = (row) => omitUndefined({
200
+ id: ApiKeyId(row.id),
201
+ userId: UserId(row.user_id),
202
+ prefix: ApiKeyPrefix(row.prefix),
203
+ secretHash: ApiKeySecretHash(row.secret_hash),
204
+ scopes: decodeApiKeyScopes(row.scopes),
205
+ createdAt: UnixMillis(Number(row.created_at)),
206
+ expiresAt: optionalUnixMillis(row.expires_at),
207
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
208
+ revokedAt: optionalUnixMillis(row.revoked_at),
209
+ metadata: jsonDecode(row.metadata),
210
+ });
211
+ const decodeRefreshTokenRow = (row) => omitUndefined({
212
+ id: RefreshTokenId(row.id),
213
+ familyId: RefreshTokenFamilyId(row.family_id),
214
+ userId: UserId(row.user_id),
215
+ secretHash: RefreshTokenSecretHash(row.secret_hash),
216
+ createdAt: UnixMillis(Number(row.created_at)),
217
+ expiresAt: UnixMillis(Number(row.expires_at)),
218
+ lastUsedAt: optionalUnixMillis(row.last_used_at),
219
+ rotatedAt: optionalUnixMillis(row.rotated_at),
220
+ replacedById: row.replaced_by_id === null ? undefined : RefreshTokenId(row.replaced_by_id),
221
+ revokedAt: optionalUnixMillis(row.revoked_at),
222
+ reuseDetectedAt: optionalUnixMillis(row.reuse_detected_at),
223
+ metadata: jsonDecode(row.metadata),
224
+ });
225
+ const decodeJwtRevocationRow = (row) => omitUndefined({
226
+ jwtId: row.jwt_id,
227
+ revokedAt: UnixMillis(Number(row.revoked_at)),
228
+ expiresAt: optionalUnixMillis(row.expires_at),
229
+ reason: row.reason ?? undefined,
230
+ });
231
+ const decodeOAuthAccountRow = (row) => omitUndefined({
232
+ id: OAuthAccountId(row.id),
233
+ providerId: OAuthProviderId(row.provider_id),
234
+ providerAccountId: row.provider_account_id,
235
+ userId: UserId(row.user_id),
236
+ email: row.email === null ? undefined : Email(row.email),
237
+ emailVerified: row.email_verified === null ? undefined : row.email_verified === 1,
238
+ createdAt: UnixMillis(Number(row.created_at)),
239
+ updatedAt: UnixMillis(Number(row.updated_at)),
240
+ unlinkedAt: optionalUnixMillis(row.unlinked_at),
241
+ metadata: jsonDecode(row.metadata),
242
+ });
85
243
  const sessionInsert = (row) => ({
86
244
  id: row.id,
87
245
  user_id: row.userId,
@@ -126,11 +284,196 @@ const verificationInsert = (row) => ({
126
284
  metadata: jsonEncode(row.metadata),
127
285
  consumed_at: null,
128
286
  });
287
+ const loginApprovalReviewInsert = (row) => ({
288
+ approval_challenge_id: row.approvalChallengeId,
289
+ flow_id: row.flowId,
290
+ user_id: row.userId,
291
+ channel: row.channel,
292
+ reason: row.reason,
293
+ session_binding: row.sessionBinding,
294
+ same_device_required: row.sameDeviceRequired ? 1 : 0,
295
+ status: row.status,
296
+ created_at: Number(row.createdAt),
297
+ expires_at: Number(row.expiresAt),
298
+ reviewed_at: row.reviewedAt === undefined ? null : Number(row.reviewedAt),
299
+ reviewed_by: row.reviewedBy ?? null,
300
+ denied_reason: row.deniedReason ?? null,
301
+ risk: encodeLoginRiskContext(row.risk),
302
+ metadata: jsonEncode(row.metadata),
303
+ review_metadata: jsonEncode(row.reviewMetadata),
304
+ });
305
+ const trustedDeviceInsert = (input, createdAt) => ({
306
+ user_id: input.userId,
307
+ token_hash: input.tokenHash,
308
+ created_at: Number(createdAt),
309
+ last_seen_at: Number(input.now),
310
+ expires_at: Number(input.expiresAt),
311
+ metadata: jsonEncode(input.metadata),
312
+ });
313
+ const passkeyCredentialInsert = (row) => ({
314
+ id: row.id,
315
+ user_id: row.userId,
316
+ credential_id: row.credentialId,
317
+ public_key: row.publicKey,
318
+ sign_count: row.signCount,
319
+ transports: jsonEncode(row.transports),
320
+ backed_up: row.backedUp === undefined ? null : row.backedUp ? 1 : 0,
321
+ created_at: Number(row.createdAt),
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 totpFactorInsert = (row) => ({
327
+ id: row.id,
328
+ user_id: row.userId,
329
+ secret: Redacted.value(row.secret),
330
+ algorithm: row.algorithm,
331
+ digits: row.digits,
332
+ period: row.period,
333
+ created_at: Number(row.createdAt),
334
+ confirmed_at: row.confirmedAt === undefined ? null : Number(row.confirmedAt),
335
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
336
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
337
+ metadata: jsonEncode(row.metadata),
338
+ });
339
+ const recoveryCodeInsert = (row) => ({
340
+ id: row.id,
341
+ user_id: row.userId,
342
+ code_hash: row.codeHash,
343
+ created_at: Number(row.createdAt),
344
+ used_at: row.usedAt === undefined ? null : Number(row.usedAt),
345
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
346
+ metadata: jsonEncode(row.metadata),
347
+ });
348
+ const apiKeyInsert = (row) => ({
349
+ id: row.id,
350
+ user_id: row.userId,
351
+ prefix: row.prefix,
352
+ secret_hash: row.secretHash,
353
+ scopes: JSON.stringify(row.scopes),
354
+ created_at: Number(row.createdAt),
355
+ expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
356
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
357
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
358
+ metadata: jsonEncode(row.metadata),
359
+ });
360
+ const refreshTokenInsert = (row) => ({
361
+ id: row.id,
362
+ family_id: row.familyId,
363
+ user_id: row.userId,
364
+ secret_hash: row.secretHash,
365
+ created_at: Number(row.createdAt),
366
+ expires_at: Number(row.expiresAt),
367
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
368
+ rotated_at: row.rotatedAt === undefined ? null : Number(row.rotatedAt),
369
+ replaced_by_id: row.replacedById ?? null,
370
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
371
+ reuse_detected_at: row.reuseDetectedAt === undefined ? null : Number(row.reuseDetectedAt),
372
+ metadata: jsonEncode(row.metadata),
373
+ });
374
+ const jwtRevocationInsert = (row) => ({
375
+ jwt_id: row.jwtId,
376
+ revoked_at: Number(row.revokedAt),
377
+ expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
378
+ reason: row.reason ?? null,
379
+ });
380
+ const oauthAccountInsert = (row) => ({
381
+ id: row.id,
382
+ provider_id: row.providerId,
383
+ provider_account_id: row.providerAccountId,
384
+ user_id: row.userId,
385
+ email: row.email ?? null,
386
+ email_verified: row.emailVerified === undefined ? null : row.emailVerified ? 1 : 0,
387
+ created_at: Number(row.createdAt),
388
+ updated_at: Number(row.updatedAt),
389
+ unlinked_at: row.unlinkedAt === undefined ? null : Number(row.unlinkedAt),
390
+ metadata: jsonEncode(row.metadata),
391
+ });
129
392
  const kyselyError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
130
393
  const runKysely = (entity, operation, evaluate) => Effect.tryPromise({
131
394
  try: evaluate,
132
395
  catch: (cause) => kyselyError(entity, operation, cause),
133
396
  });
397
+ const loginApprovalReviewStoreError = (operation, cause) => new LoginApprovalReviewError({
398
+ message: `Login approval review ${operation} failed`,
399
+ cause,
400
+ });
401
+ const runLoginApprovalReviewKysely = (operation, evaluate) => Effect.tryPromise({
402
+ try: evaluate,
403
+ catch: (cause) => loginApprovalReviewStoreError(operation, cause),
404
+ });
405
+ const trustedDeviceStoreError = (operation, cause) => new TrustedDeviceError({
406
+ operation,
407
+ message: `Trusted device ${operation} failed`,
408
+ cause,
409
+ });
410
+ const runTrustedDeviceKysely = (operation, evaluate) => Effect.tryPromise({
411
+ try: evaluate,
412
+ catch: (cause) => trustedDeviceStoreError(operation, cause),
413
+ });
414
+ const passkeyCredentialStoreError = (operation, cause) => new PasskeyCredentialStoreError({
415
+ operation,
416
+ message: `Passkey credential ${operation} failed`,
417
+ cause,
418
+ });
419
+ const runPasskeyCredentialKysely = (operation, evaluate) => Effect.tryPromise({
420
+ try: evaluate,
421
+ catch: (cause) => passkeyCredentialStoreError(operation, cause),
422
+ });
423
+ const totpFactorStoreError = (operation, cause) => new TotpFactorStoreError({
424
+ operation,
425
+ message: `TOTP factor ${operation} failed`,
426
+ cause,
427
+ });
428
+ const runTotpFactorKysely = (operation, evaluate) => Effect.tryPromise({
429
+ try: evaluate,
430
+ catch: (cause) => totpFactorStoreError(operation, cause),
431
+ });
432
+ const recoveryCodeStoreError = (operation, cause) => new RecoveryCodeStoreError({
433
+ operation,
434
+ message: `Recovery code ${operation} failed`,
435
+ cause,
436
+ });
437
+ const runRecoveryCodeKysely = (operation, evaluate) => Effect.tryPromise({
438
+ try: evaluate,
439
+ catch: (cause) => recoveryCodeStoreError(operation, cause),
440
+ });
441
+ const apiKeyStoreError = (operation, cause) => new ApiKeyStoreError({
442
+ operation,
443
+ message: `API key ${operation} failed`,
444
+ cause,
445
+ });
446
+ const runApiKeyKysely = (operation, evaluate) => Effect.tryPromise({
447
+ try: evaluate,
448
+ catch: (cause) => apiKeyStoreError(operation, cause),
449
+ });
450
+ const refreshTokenStoreError = (operation, cause) => new RefreshTokenStoreError({
451
+ operation,
452
+ message: `Refresh token ${operation} failed`,
453
+ cause,
454
+ });
455
+ const runRefreshTokenKysely = (operation, evaluate) => Effect.tryPromise({
456
+ try: evaluate,
457
+ catch: (cause) => refreshTokenStoreError(operation, cause),
458
+ });
459
+ const jwtRevocationStoreError = (operation, cause) => new JwtError({
460
+ operation: "revocation",
461
+ message: `JWT revocation ${operation} failed`,
462
+ cause,
463
+ });
464
+ const runJwtRevocationKysely = (operation, evaluate) => Effect.tryPromise({
465
+ try: evaluate,
466
+ catch: (cause) => jwtRevocationStoreError(operation, cause),
467
+ });
468
+ const oauthAccountStoreError = (operation, cause) => new OAuthAccountStoreError({
469
+ operation,
470
+ message: `OAuth account ${operation} failed`,
471
+ cause,
472
+ });
473
+ const runOAuthAccountKysely = (operation, evaluate) => Effect.tryPromise({
474
+ try: evaluate,
475
+ catch: (cause) => oauthAccountStoreError(operation, cause),
476
+ });
134
477
  const asAuthDb = (db) => db;
135
478
  const findUserById = (db, id, operation = "find") => {
136
479
  const authDb = asAuthDb(db);
@@ -185,6 +528,17 @@ const findSessionById = (db, id, operation = "find") => {
185
528
  : Option.some(decodeSessionRow(row));
186
529
  });
187
530
  };
531
+ const listSessionsByUser = (db, input, operation = "listByUser") => {
532
+ const authDb = asAuthDb(db);
533
+ return runKysely("session", operation, async () => {
534
+ const rows = await authDb
535
+ .selectFrom("auth_session")
536
+ .selectAll()
537
+ .where("user_id", "=", input.userId)
538
+ .execute();
539
+ return rows.map(decodeSessionRow);
540
+ });
541
+ };
188
542
  const findVerificationById = (db, id, operation = "find") => {
189
543
  const authDb = asAuthDb(db);
190
544
  return runKysely("verification", operation, async () => {
@@ -198,6 +552,34 @@ const findVerificationById = (db, id, operation = "find") => {
198
552
  : Option.some(decodeVerificationRow(row));
199
553
  });
200
554
  };
555
+ const findLoginApprovalReviewByApprovalChallengeId = (db, input, operation = "find") => {
556
+ const authDb = asAuthDb(db);
557
+ return runLoginApprovalReviewKysely(operation, async () => {
558
+ const row = await authDb
559
+ .selectFrom("auth_login_approval_review")
560
+ .selectAll()
561
+ .where("approval_challenge_id", "=", input.approvalChallengeId)
562
+ .executeTakeFirst();
563
+ return row === undefined
564
+ ? Option.none()
565
+ : Option.some(decodeLoginApprovalReviewRow(row));
566
+ });
567
+ };
568
+ const findTrustedDevice = (db, input, operation = "status") => {
569
+ const authDb = asAuthDb(db);
570
+ return runTrustedDeviceKysely(operation, async () => {
571
+ const row = await authDb
572
+ .selectFrom("auth_trusted_device")
573
+ .selectAll()
574
+ .where("user_id", "=", input.userId)
575
+ .where("token_hash", "=", input.tokenHash)
576
+ .where("expires_at", ">", Number(input.now))
577
+ .executeTakeFirst();
578
+ return row === undefined
579
+ ? Option.none()
580
+ : Option.some(decodeTrustedDeviceRow(row));
581
+ });
582
+ };
201
583
  export const makeKyselyUserStore = (db) => {
202
584
  const authDb = asAuthDb(db);
203
585
  return UserStore.make({
@@ -231,12 +613,25 @@ export const makeKyselyCredentialStore = (db) => {
231
613
  .values(passwordCredentialInsert(row))
232
614
  .executeTakeFirst();
233
615
  }),
616
+ updatePassword: (input) => runKysely("credential", "updatePassword", () => authDb
617
+ .updateTable("auth_credential")
618
+ .set({
619
+ password_hash: input.passwordHash,
620
+ updated_at: Number(input.updatedAt),
621
+ })
622
+ .where("user_id", "=", input.userId)
623
+ .where("type", "=", "password")
624
+ .where("revoked_at", "is", null)
625
+ .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
626
+ ? findPasswordCredentialByUserId(db, input.userId, "updatePassword")
627
+ : Effect.succeed(Option.none()))),
234
628
  });
235
629
  };
236
630
  export const makeKyselySessionStore = (db) => {
237
631
  const authDb = asAuthDb(db);
238
632
  return SessionStore.make({
239
633
  findById: (id) => findSessionById(db, id),
634
+ listByUser: (input) => listSessionsByUser(db, input),
240
635
  insert: (row) => runKysely("session", "insert", async () => {
241
636
  await authDb
242
637
  .insertInto("auth_session")
@@ -281,9 +676,16 @@ export const makeKyselySessionStore = (db) => {
281
676
  return runKysely("session", "updateClaims", () => authDb
282
677
  .updateTable("auth_session")
283
678
  .set({
679
+ ...(input.aal === undefined ? {} : { aal: input.aal }),
680
+ ...(input.amr === undefined
681
+ ? {}
682
+ : { amr: JSON.stringify(input.amr) }),
683
+ ...(input.mfaVerifiedAt === undefined
684
+ ? {}
685
+ : { mfa_verified_at: Number(input.mfaVerifiedAt) }),
284
686
  metadata: encodeSessionMetadata({
285
687
  ...rowOption.value,
286
- claims: input.claims,
688
+ claims: input.claims ?? rowOption.value.claims,
287
689
  }),
288
690
  })
289
691
  .where("id", "=", input.sessionId)
@@ -356,11 +758,626 @@ export const makeKyselyVerificationStore = (db) => {
356
758
  }),
357
759
  });
358
760
  };
761
+ export const makeKyselyLoginApprovalReviewStore = (db) => {
762
+ const authDb = asAuthDb(db);
763
+ return LoginApprovalReviewStore.make({
764
+ insertPending: (input) => runLoginApprovalReviewKysely("insert", async () => {
765
+ await authDb
766
+ .insertInto("auth_login_approval_review")
767
+ .values(loginApprovalReviewInsert(input))
768
+ .executeTakeFirst();
769
+ return input;
770
+ }),
771
+ findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(db, input),
772
+ approve: (input) => runLoginApprovalReviewKysely("approve", () => authDb
773
+ .updateTable("auth_login_approval_review")
774
+ .set({
775
+ status: "approved",
776
+ reviewed_at: Number(input.reviewedAt),
777
+ reviewed_by: input.reviewedBy ?? null,
778
+ review_metadata: jsonEncode(input.metadata),
779
+ })
780
+ .where("approval_challenge_id", "=", input.approvalChallengeId)
781
+ .where("status", "=", "pending")
782
+ .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
783
+ ? findLoginApprovalReviewByApprovalChallengeId(db, input, "approve")
784
+ : Effect.succeed(Option.none()))),
785
+ deny: (input) => runLoginApprovalReviewKysely("deny", () => authDb
786
+ .updateTable("auth_login_approval_review")
787
+ .set({
788
+ status: "denied",
789
+ reviewed_at: Number(input.reviewedAt),
790
+ reviewed_by: input.reviewedBy ?? null,
791
+ denied_reason: input.reason ?? null,
792
+ review_metadata: jsonEncode(input.metadata),
793
+ })
794
+ .where("approval_challenge_id", "=", input.approvalChallengeId)
795
+ .where("status", "=", "pending")
796
+ .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
797
+ ? findLoginApprovalReviewByApprovalChallengeId(db, input, "deny")
798
+ : Effect.succeed(Option.none()))),
799
+ });
800
+ };
801
+ export const makeKyselyTrustedDeviceStore = (db) => {
802
+ const authDb = asAuthDb(db);
803
+ return TrustedDeviceStore.make({
804
+ find: (input) => findTrustedDevice(db, input),
805
+ upsert: (input) => runTrustedDeviceKysely("trust", async () => {
806
+ const existing = await authDb
807
+ .selectFrom("auth_trusted_device")
808
+ .selectAll()
809
+ .where("user_id", "=", input.userId)
810
+ .where("token_hash", "=", input.tokenHash)
811
+ .executeTakeFirst();
812
+ const existingRow = existing === undefined ? undefined : decodeTrustedDeviceRow(existing);
813
+ await (existingRow === undefined
814
+ ? authDb
815
+ .insertInto("auth_trusted_device")
816
+ .values(trustedDeviceInsert(input, input.now))
817
+ : authDb
818
+ .updateTable("auth_trusted_device")
819
+ .set({
820
+ last_seen_at: Number(input.now),
821
+ expires_at: Number(input.expiresAt),
822
+ metadata: jsonEncode(input.metadata ?? existingRow.metadata),
823
+ })
824
+ .where("user_id", "=", input.userId)
825
+ .where("token_hash", "=", input.tokenHash)).executeTakeFirst();
826
+ const row = await authDb
827
+ .selectFrom("auth_trusted_device")
828
+ .selectAll()
829
+ .where("user_id", "=", input.userId)
830
+ .where("token_hash", "=", input.tokenHash)
831
+ .executeTakeFirstOrThrow();
832
+ return decodeTrustedDeviceRow(row);
833
+ }),
834
+ });
835
+ };
836
+ export const makeKyselyPasskeyCredentialStore = (db) => {
837
+ const authDb = asAuthDb(db);
838
+ return PasskeyCredentialStore.make({
839
+ insert: (row) => runPasskeyCredentialKysely("insert", () => authDb
840
+ .insertInto("auth_passkey_credential")
841
+ .values(passkeyCredentialInsert(row))
842
+ .executeTakeFirst()).pipe(Effect.asVoid),
843
+ findByCredentialId: (credentialId) => runPasskeyCredentialKysely("find", () => authDb
844
+ .selectFrom("auth_passkey_credential")
845
+ .selectAll()
846
+ .where("credential_id", "=", credentialId)
847
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined
848
+ ? Option.none()
849
+ : Option.some(decodePasskeyCredentialRow(row)))),
850
+ listByUser: (input) => runPasskeyCredentialKysely("list", () => {
851
+ let query = authDb
852
+ .selectFrom("auth_passkey_credential")
853
+ .selectAll()
854
+ .where("user_id", "=", input.userId);
855
+ if (input.includeRevoked !== true) {
856
+ query = query.where("revoked_at", "is", null);
857
+ }
858
+ return query.execute();
859
+ }).pipe(Effect.map((rows) => rows.map(decodePasskeyCredentialRow))),
860
+ updateSignCount: (input) => runPasskeyCredentialKysely("update-sign-count", () => authDb
861
+ .updateTable("auth_passkey_credential")
862
+ .set({
863
+ sign_count: input.signCount,
864
+ last_used_at: Number(input.lastUsedAt),
865
+ ...(input.metadata === undefined
866
+ ? {}
867
+ : { metadata: jsonEncode(input.metadata) }),
868
+ })
869
+ .where("credential_id", "=", input.credentialId)
870
+ .where("revoked_at", "is", null)
871
+ .executeTakeFirst()).pipe(Effect.flatMap(() => runPasskeyCredentialKysely("find", () => authDb
872
+ .selectFrom("auth_passkey_credential")
873
+ .selectAll()
874
+ .where("credential_id", "=", input.credentialId)
875
+ .where("revoked_at", "is", null)
876
+ .executeTakeFirst())), Effect.map((row) => row === undefined
877
+ ? Option.none()
878
+ : Option.some(decodePasskeyCredentialRow(row)))),
879
+ revoke: (input) => runPasskeyCredentialKysely("revoke", async () => {
880
+ const metadata = input.reason === undefined
881
+ ? undefined
882
+ : await authDb
883
+ .selectFrom("auth_passkey_credential")
884
+ .select("metadata")
885
+ .where("credential_id", "=", input.credentialId)
886
+ .executeTakeFirst()
887
+ .then((row) => {
888
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
889
+ return jsonEncode({
890
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
891
+ revokeReason: input.reason,
892
+ });
893
+ });
894
+ return authDb
895
+ .updateTable("auth_passkey_credential")
896
+ .set({
897
+ revoked_at: Number(input.revokedAt),
898
+ ...(metadata === undefined ? {} : { metadata }),
899
+ })
900
+ .where("credential_id", "=", input.credentialId)
901
+ .executeTakeFirst();
902
+ }).pipe(Effect.asVoid),
903
+ });
904
+ };
905
+ export const makeKyselyTotpFactorStore = (db) => {
906
+ const authDb = asAuthDb(db);
907
+ return TotpFactorStore.make({
908
+ insert: (row) => runTotpFactorKysely("insert", () => authDb
909
+ .insertInto("auth_totp_factor")
910
+ .values(totpFactorInsert(row))
911
+ .executeTakeFirst()).pipe(Effect.asVoid),
912
+ findById: (id) => runTotpFactorKysely("find", () => authDb
913
+ .selectFrom("auth_totp_factor")
914
+ .selectAll()
915
+ .where("id", "=", id)
916
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeTotpFactorRow(row)))),
917
+ listByUser: (input) => runTotpFactorKysely("list", () => {
918
+ let query = authDb
919
+ .selectFrom("auth_totp_factor")
920
+ .selectAll()
921
+ .where("user_id", "=", input.userId);
922
+ if (input.includeRevoked !== true) {
923
+ query = query.where("revoked_at", "is", null);
924
+ }
925
+ if (input.includeUnconfirmed !== true) {
926
+ query = query.where("confirmed_at", "is not", null);
927
+ }
928
+ return query.execute();
929
+ }).pipe(Effect.map((rows) => rows.map(decodeTotpFactorRow))),
930
+ confirm: (input) => runTotpFactorKysely("confirm", () => authDb
931
+ .updateTable("auth_totp_factor")
932
+ .set({
933
+ confirmed_at: Number(input.confirmedAt),
934
+ ...(input.metadata === undefined
935
+ ? {}
936
+ : { metadata: jsonEncode(input.metadata) }),
937
+ })
938
+ .where("id", "=", input.id)
939
+ .where("revoked_at", "is", null)
940
+ .executeTakeFirst()).pipe(Effect.flatMap(() => runTotpFactorKysely("find", () => authDb
941
+ .selectFrom("auth_totp_factor")
942
+ .selectAll()
943
+ .where("id", "=", input.id)
944
+ .where("revoked_at", "is", null)
945
+ .executeTakeFirst())), Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeTotpFactorRow(row)))),
946
+ markUsed: (input) => runTotpFactorKysely("mark-used", () => authDb
947
+ .updateTable("auth_totp_factor")
948
+ .set({
949
+ last_used_at: Number(input.lastUsedAt),
950
+ ...(input.metadata === undefined
951
+ ? {}
952
+ : { metadata: jsonEncode(input.metadata) }),
953
+ })
954
+ .where("id", "=", input.id)
955
+ .where("revoked_at", "is", null)
956
+ .where("confirmed_at", "is not", null)
957
+ .executeTakeFirst()).pipe(Effect.flatMap(() => runTotpFactorKysely("find", () => authDb
958
+ .selectFrom("auth_totp_factor")
959
+ .selectAll()
960
+ .where("id", "=", input.id)
961
+ .where("revoked_at", "is", null)
962
+ .where("confirmed_at", "is not", null)
963
+ .executeTakeFirst())), Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeTotpFactorRow(row)))),
964
+ revoke: (input) => runTotpFactorKysely("revoke", async () => {
965
+ const metadata = input.reason === undefined
966
+ ? undefined
967
+ : await authDb
968
+ .selectFrom("auth_totp_factor")
969
+ .select("metadata")
970
+ .where("id", "=", input.id)
971
+ .executeTakeFirst()
972
+ .then((row) => {
973
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
974
+ return jsonEncode({
975
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
976
+ revokeReason: input.reason,
977
+ });
978
+ });
979
+ return authDb
980
+ .updateTable("auth_totp_factor")
981
+ .set({
982
+ revoked_at: Number(input.revokedAt),
983
+ ...(metadata === undefined ? {} : { metadata }),
984
+ })
985
+ .where("id", "=", input.id)
986
+ .executeTakeFirst();
987
+ }).pipe(Effect.asVoid),
988
+ });
989
+ };
990
+ export const makeKyselyRecoveryCodeStore = (db) => {
991
+ const authDb = asAuthDb(db);
992
+ return RecoveryCodeStore.make({
993
+ insertMany: (rows) => rows.length === 0
994
+ ? Effect.void
995
+ : runRecoveryCodeKysely("insert", () => authDb
996
+ .insertInto("auth_recovery_code")
997
+ .values(rows.map(recoveryCodeInsert))
998
+ .execute()).pipe(Effect.asVoid),
999
+ findById: (id) => runRecoveryCodeKysely("find", () => authDb
1000
+ .selectFrom("auth_recovery_code")
1001
+ .selectAll()
1002
+ .where("id", "=", id)
1003
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined
1004
+ ? Option.none()
1005
+ : Option.some(decodeRecoveryCodeRow(row)))),
1006
+ listByUser: (input) => runRecoveryCodeKysely("list", () => {
1007
+ let query = authDb
1008
+ .selectFrom("auth_recovery_code")
1009
+ .selectAll()
1010
+ .where("user_id", "=", input.userId);
1011
+ if (input.includeUsed !== true) {
1012
+ query = query.where("used_at", "is", null);
1013
+ }
1014
+ if (input.includeRevoked !== true) {
1015
+ query = query.where("revoked_at", "is", null);
1016
+ }
1017
+ return query.execute();
1018
+ }).pipe(Effect.map((rows) => rows.map(decodeRecoveryCodeRow))),
1019
+ markUsed: (input) => runRecoveryCodeKysely("mark-used", async () => {
1020
+ const result = await authDb
1021
+ .updateTable("auth_recovery_code")
1022
+ .set({
1023
+ used_at: Number(input.usedAt),
1024
+ ...(input.metadata === undefined
1025
+ ? {}
1026
+ : { metadata: jsonEncode(input.metadata) }),
1027
+ })
1028
+ .where("id", "=", input.id)
1029
+ .where("used_at", "is", null)
1030
+ .where("revoked_at", "is", null)
1031
+ .executeTakeFirst();
1032
+ if (Number(result.numUpdatedRows) === 0) {
1033
+ return undefined;
1034
+ }
1035
+ return authDb
1036
+ .selectFrom("auth_recovery_code")
1037
+ .selectAll()
1038
+ .where("id", "=", input.id)
1039
+ .executeTakeFirst();
1040
+ }).pipe(Effect.map((row) => row === undefined
1041
+ ? Option.none()
1042
+ : Option.some(decodeRecoveryCodeRow(row)))),
1043
+ revoke: (input) => runRecoveryCodeKysely("revoke", async () => {
1044
+ const metadata = input.reason === undefined
1045
+ ? undefined
1046
+ : await authDb
1047
+ .selectFrom("auth_recovery_code")
1048
+ .select("metadata")
1049
+ .where("id", "=", input.id)
1050
+ .executeTakeFirst()
1051
+ .then((row) => {
1052
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
1053
+ return jsonEncode({
1054
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
1055
+ revokeReason: input.reason,
1056
+ });
1057
+ });
1058
+ return authDb
1059
+ .updateTable("auth_recovery_code")
1060
+ .set({
1061
+ revoked_at: Number(input.revokedAt),
1062
+ ...(metadata === undefined ? {} : { metadata }),
1063
+ })
1064
+ .where("id", "=", input.id)
1065
+ .executeTakeFirst();
1066
+ }).pipe(Effect.asVoid),
1067
+ });
1068
+ };
1069
+ export const makeKyselyApiKeyStore = (db) => {
1070
+ const authDb = asAuthDb(db);
1071
+ return ApiKeyStore.make({
1072
+ insert: (row) => runApiKeyKysely("insert", () => authDb.insertInto("auth_api_key").values(apiKeyInsert(row)).executeTakeFirst()).pipe(Effect.asVoid),
1073
+ findById: (id) => runApiKeyKysely("find", () => authDb
1074
+ .selectFrom("auth_api_key")
1075
+ .selectAll()
1076
+ .where("id", "=", id)
1077
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeApiKeyRow(row)))),
1078
+ findByPrefix: (prefix) => runApiKeyKysely("find", () => authDb
1079
+ .selectFrom("auth_api_key")
1080
+ .selectAll()
1081
+ .where("prefix", "=", prefix)
1082
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeApiKeyRow(row)))),
1083
+ listByUser: (input) => runApiKeyKysely("list", () => {
1084
+ let query = authDb
1085
+ .selectFrom("auth_api_key")
1086
+ .selectAll()
1087
+ .where("user_id", "=", input.userId);
1088
+ if (input.includeRevoked !== true) {
1089
+ query = query.where("revoked_at", "is", null);
1090
+ }
1091
+ return query.execute();
1092
+ }).pipe(Effect.map((rows) => rows.map(decodeApiKeyRow))),
1093
+ markUsed: (input) => runApiKeyKysely("mark-used", async () => {
1094
+ const result = await authDb
1095
+ .updateTable("auth_api_key")
1096
+ .set({
1097
+ last_used_at: Number(input.lastUsedAt),
1098
+ ...(input.metadata === undefined
1099
+ ? {}
1100
+ : { metadata: jsonEncode(input.metadata) }),
1101
+ })
1102
+ .where("id", "=", input.id)
1103
+ .where("revoked_at", "is", null)
1104
+ .executeTakeFirst();
1105
+ if (Number(result.numUpdatedRows) === 0) {
1106
+ return undefined;
1107
+ }
1108
+ return authDb
1109
+ .selectFrom("auth_api_key")
1110
+ .selectAll()
1111
+ .where("id", "=", input.id)
1112
+ .executeTakeFirst();
1113
+ }).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeApiKeyRow(row)))),
1114
+ revoke: (input) => runApiKeyKysely("revoke", async () => {
1115
+ const metadata = input.reason === undefined
1116
+ ? undefined
1117
+ : await authDb
1118
+ .selectFrom("auth_api_key")
1119
+ .select("metadata")
1120
+ .where("id", "=", input.id)
1121
+ .executeTakeFirst()
1122
+ .then((row) => {
1123
+ const existingMetadata = jsonDecode(row?.metadata ?? null);
1124
+ return jsonEncode({
1125
+ ...(isRecord(existingMetadata) ? existingMetadata : {}),
1126
+ revokeReason: input.reason,
1127
+ });
1128
+ });
1129
+ return authDb
1130
+ .updateTable("auth_api_key")
1131
+ .set({
1132
+ revoked_at: Number(input.revokedAt),
1133
+ ...(metadata === undefined ? {} : { metadata }),
1134
+ })
1135
+ .where("id", "=", input.id)
1136
+ .executeTakeFirst();
1137
+ }).pipe(Effect.asVoid),
1138
+ });
1139
+ };
1140
+ export const makeKyselyRefreshTokenStore = (db) => {
1141
+ const authDb = asAuthDb(db);
1142
+ const metadataWithReason = (metadata, key, reason) => {
1143
+ if (reason === undefined) {
1144
+ return metadata;
1145
+ }
1146
+ const existing = jsonDecode(metadata);
1147
+ return jsonEncode({
1148
+ ...(isRecord(existing) ? existing : {}),
1149
+ [key]: reason,
1150
+ });
1151
+ };
1152
+ return RefreshTokenStore.make({
1153
+ insert: (row) => runRefreshTokenKysely("insert", () => authDb
1154
+ .insertInto("auth_refresh_token")
1155
+ .values(refreshTokenInsert(row))
1156
+ .executeTakeFirst()).pipe(Effect.asVoid),
1157
+ findById: (id) => runRefreshTokenKysely("find", () => authDb
1158
+ .selectFrom("auth_refresh_token")
1159
+ .selectAll()
1160
+ .where("id", "=", id)
1161
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeRefreshTokenRow(row)))),
1162
+ findBySecretHash: (hash) => runRefreshTokenKysely("find", () => authDb
1163
+ .selectFrom("auth_refresh_token")
1164
+ .selectAll()
1165
+ .where("secret_hash", "=", hash)
1166
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeRefreshTokenRow(row)))),
1167
+ listByUser: (input) => runRefreshTokenKysely("list", () => {
1168
+ let query = authDb
1169
+ .selectFrom("auth_refresh_token")
1170
+ .selectAll()
1171
+ .where("user_id", "=", input.userId);
1172
+ if (input.includeRevoked !== true) {
1173
+ query = query.where("revoked_at", "is", null);
1174
+ }
1175
+ return query.execute();
1176
+ }).pipe(Effect.map((rows) => rows.map(decodeRefreshTokenRow))),
1177
+ listByFamily: (input) => runRefreshTokenKysely("list", () => {
1178
+ let query = authDb
1179
+ .selectFrom("auth_refresh_token")
1180
+ .selectAll()
1181
+ .where("family_id", "=", input.familyId);
1182
+ if (input.includeRevoked !== true) {
1183
+ query = query.where("revoked_at", "is", null);
1184
+ }
1185
+ return query.execute();
1186
+ }).pipe(Effect.map((rows) => rows.map(decodeRefreshTokenRow))),
1187
+ rotate: (input) => runRefreshTokenKysely("rotate", async () => authDb.transaction().execute(async (transaction) => {
1188
+ const tx = transaction;
1189
+ const updated = await tx
1190
+ .updateTable("auth_refresh_token")
1191
+ .set({
1192
+ last_used_at: Number(input.rotatedAt),
1193
+ rotated_at: Number(input.rotatedAt),
1194
+ replaced_by_id: input.replacement.id,
1195
+ })
1196
+ .where("id", "=", input.id)
1197
+ .where("rotated_at", "is", null)
1198
+ .where("revoked_at", "is", null)
1199
+ .executeTakeFirst();
1200
+ if (Number(updated.numUpdatedRows) === 0) {
1201
+ return undefined;
1202
+ }
1203
+ await tx
1204
+ .insertInto("auth_refresh_token")
1205
+ .values(refreshTokenInsert(input.replacement))
1206
+ .executeTakeFirst();
1207
+ const previous = await tx
1208
+ .selectFrom("auth_refresh_token")
1209
+ .selectAll()
1210
+ .where("id", "=", input.id)
1211
+ .executeTakeFirst();
1212
+ const replacement = await tx
1213
+ .selectFrom("auth_refresh_token")
1214
+ .selectAll()
1215
+ .where("id", "=", input.replacement.id)
1216
+ .executeTakeFirst();
1217
+ return previous === undefined || replacement === undefined
1218
+ ? undefined
1219
+ : {
1220
+ previous: decodeRefreshTokenRow(previous),
1221
+ replacement: decodeRefreshTokenRow(replacement),
1222
+ };
1223
+ })).pipe(Effect.map((row) => (row === undefined ? Option.none() : Option.some(row)))),
1224
+ markReuseDetected: (input) => runRefreshTokenKysely("reuse", async () => {
1225
+ const result = await authDb
1226
+ .updateTable("auth_refresh_token")
1227
+ .set({ reuse_detected_at: Number(input.reuseDetectedAt) })
1228
+ .where("id", "=", input.id)
1229
+ .executeTakeFirst();
1230
+ if (Number(result.numUpdatedRows) === 0) {
1231
+ return undefined;
1232
+ }
1233
+ return authDb
1234
+ .selectFrom("auth_refresh_token")
1235
+ .selectAll()
1236
+ .where("id", "=", input.id)
1237
+ .executeTakeFirst();
1238
+ }).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeRefreshTokenRow(row)))),
1239
+ revoke: (input) => runRefreshTokenKysely("revoke", async () => {
1240
+ const row = await authDb
1241
+ .selectFrom("auth_refresh_token")
1242
+ .select(["metadata"])
1243
+ .where("id", "=", input.id)
1244
+ .executeTakeFirst();
1245
+ return authDb
1246
+ .updateTable("auth_refresh_token")
1247
+ .set({
1248
+ revoked_at: Number(input.revokedAt),
1249
+ metadata: metadataWithReason(row?.metadata ?? null, "revokeReason", input.reason),
1250
+ })
1251
+ .where("id", "=", input.id)
1252
+ .executeTakeFirst();
1253
+ }).pipe(Effect.asVoid),
1254
+ revokeFamily: (input) => runRefreshTokenKysely("revoke", async () => {
1255
+ const rows = await authDb
1256
+ .selectFrom("auth_refresh_token")
1257
+ .select(["id", "metadata"])
1258
+ .where("family_id", "=", input.familyId)
1259
+ .execute();
1260
+ await Promise.all(rows.map((row) => authDb
1261
+ .updateTable("auth_refresh_token")
1262
+ .set({
1263
+ revoked_at: Number(input.revokedAt),
1264
+ metadata: metadataWithReason(row.metadata, "familyRevokeReason", input.reason),
1265
+ })
1266
+ .where("id", "=", row.id)
1267
+ .executeTakeFirst()));
1268
+ }).pipe(Effect.asVoid),
1269
+ });
1270
+ };
1271
+ export const makeKyselyJwtRevocationStore = (db) => {
1272
+ const authDb = asAuthDb(db);
1273
+ return JwtRevocationStore.make({
1274
+ revoke: (record) => runJwtRevocationKysely("revoke", async () => {
1275
+ const row = jwtRevocationInsert(record);
1276
+ const updated = await authDb
1277
+ .updateTable("auth_jwt_revocation")
1278
+ .set({
1279
+ revoked_at: row.revoked_at,
1280
+ expires_at: row.expires_at,
1281
+ reason: row.reason,
1282
+ })
1283
+ .where("jwt_id", "=", record.jwtId)
1284
+ .executeTakeFirst();
1285
+ if (Number(updated.numUpdatedRows) === 0) {
1286
+ await authDb
1287
+ .insertInto("auth_jwt_revocation")
1288
+ .values(row)
1289
+ .executeTakeFirst();
1290
+ }
1291
+ }).pipe(Effect.asVoid),
1292
+ find: (jwtId) => runJwtRevocationKysely("find", () => authDb
1293
+ .selectFrom("auth_jwt_revocation")
1294
+ .selectAll()
1295
+ .where("jwt_id", "=", jwtId)
1296
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeJwtRevocationRow(row)))),
1297
+ });
1298
+ };
1299
+ export const makeKyselyOAuthAccountStore = (db) => {
1300
+ const authDb = asAuthDb(db);
1301
+ const metadataWithReason = (metadata, reason) => {
1302
+ if (reason === undefined) {
1303
+ return metadata;
1304
+ }
1305
+ const existing = jsonDecode(metadata);
1306
+ return jsonEncode({
1307
+ ...(isRecord(existing) ? existing : {}),
1308
+ unlinkReason: reason,
1309
+ });
1310
+ };
1311
+ const activeFilter = (query, includeUnlinked) => includeUnlinked === true ? query : query.where("unlinked_at", "is", null);
1312
+ return OAuthAccountStore.make({
1313
+ insert: (row) => runOAuthAccountKysely("insert", () => authDb
1314
+ .insertInto("auth_oauth_account")
1315
+ .values(oauthAccountInsert(row))
1316
+ .executeTakeFirst()).pipe(Effect.asVoid),
1317
+ findById: (id) => runOAuthAccountKysely("find", () => authDb
1318
+ .selectFrom("auth_oauth_account")
1319
+ .selectAll()
1320
+ .where("id", "=", id)
1321
+ .executeTakeFirst()).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeOAuthAccountRow(row)))),
1322
+ findByProviderAccount: (input) => runOAuthAccountKysely("find", () => {
1323
+ const query = authDb
1324
+ .selectFrom("auth_oauth_account")
1325
+ .selectAll()
1326
+ .where("provider_id", "=", input.providerId)
1327
+ .where("provider_account_id", "=", input.providerAccountId);
1328
+ return activeFilter(query, input.includeUnlinked).executeTakeFirst();
1329
+ }).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeOAuthAccountRow(row)))),
1330
+ listByUser: (input) => runOAuthAccountKysely("list", () => {
1331
+ const query = authDb
1332
+ .selectFrom("auth_oauth_account")
1333
+ .selectAll()
1334
+ .where("user_id", "=", input.userId);
1335
+ return activeFilter(query, input.includeUnlinked).execute();
1336
+ }).pipe(Effect.map((rows) => rows.map(decodeOAuthAccountRow))),
1337
+ unlink: (input) => runOAuthAccountKysely("unlink", async () => {
1338
+ const current = await authDb
1339
+ .selectFrom("auth_oauth_account")
1340
+ .select(["id", "metadata"])
1341
+ .where("provider_id", "=", input.providerId)
1342
+ .where("provider_account_id", "=", input.providerAccountId)
1343
+ .where("unlinked_at", "is", null)
1344
+ .executeTakeFirst();
1345
+ if (current === undefined) {
1346
+ return undefined;
1347
+ }
1348
+ const updated = await authDb
1349
+ .updateTable("auth_oauth_account")
1350
+ .set({
1351
+ unlinked_at: Number(input.unlinkedAt),
1352
+ metadata: metadataWithReason(current.metadata, input.reason),
1353
+ })
1354
+ .where("id", "=", current.id)
1355
+ .executeTakeFirst();
1356
+ if (Number(updated.numUpdatedRows) === 0) {
1357
+ return undefined;
1358
+ }
1359
+ return authDb
1360
+ .selectFrom("auth_oauth_account")
1361
+ .selectAll()
1362
+ .where("id", "=", current.id)
1363
+ .executeTakeFirst();
1364
+ }).pipe(Effect.map((row) => row === undefined ? Option.none() : Option.some(decodeOAuthAccountRow(row)))),
1365
+ });
1366
+ };
359
1367
  export const makeKyselyStorage = (db) => ({
360
1368
  userStore: makeKyselyUserStore(db),
361
1369
  credentialStore: makeKyselyCredentialStore(db),
362
1370
  sessionStore: makeKyselySessionStore(db),
363
1371
  verificationStore: makeKyselyVerificationStore(db),
1372
+ loginApprovalReviewStore: makeKyselyLoginApprovalReviewStore(db),
1373
+ trustedDeviceStore: makeKyselyTrustedDeviceStore(db),
1374
+ passkeyCredentialStore: makeKyselyPasskeyCredentialStore(db),
1375
+ totpFactorStore: makeKyselyTotpFactorStore(db),
1376
+ recoveryCodeStore: makeKyselyRecoveryCodeStore(db),
1377
+ apiKeyStore: makeKyselyApiKeyStore(db),
1378
+ refreshTokenStore: makeKyselyRefreshTokenStore(db),
1379
+ jwtRevocationStore: makeKyselyJwtRevocationStore(db),
1380
+ oauthAccountStore: makeKyselyOAuthAccountStore(db),
364
1381
  });
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)));
1382
+ 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)), Layer.succeed(OAuthAccountStore)(makeKyselyOAuthAccountStore(db)));
366
1383
  //# sourceMappingURL=KyselyStorage.js.map