@effect-auth/core 0.1.0-alpha.7 → 0.1.0-alpha.8

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.
@@ -1,1383 +0,0 @@
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";
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";
14
- const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
15
- const jsonDecode = (value) => value === null ? undefined : JSON.parse(value);
16
- const optionalUnixMillis = (value) => value === null ? undefined : UnixMillis(Number(value));
17
- const decodeAmr = (value) => JSON.parse(value);
18
- const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
19
- const sessionMetadataEnvelopeKey = "__effectAuthSession";
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
- }));
60
- const decodeSessionMetadata = (value) => {
61
- const decoded = jsonDecode(value);
62
- const envelope = isRecord(decoded)
63
- ? decoded[sessionMetadataEnvelopeKey]
64
- : undefined;
65
- if (!isRecord(envelope) || envelope.version !== 1) {
66
- return omitUndefined({
67
- metadata: decoded,
68
- });
69
- }
70
- return omitUndefined({
71
- metadata: envelope.metadata,
72
- claims: envelope.claims,
73
- });
74
- };
75
- const encodeSessionMetadata = (row) => {
76
- if (row.claims === undefined) {
77
- return jsonEncode(row.metadata);
78
- }
79
- return jsonEncode({
80
- [sessionMetadataEnvelopeKey]: omitUndefined({
81
- version: 1,
82
- metadata: row.metadata,
83
- claims: row.claims,
84
- }),
85
- });
86
- };
87
- const decodeUserRow = (row) => omitUndefined({
88
- id: UserId(row.id),
89
- email: Email(row.email),
90
- emailVerified: row.email_verified === 1,
91
- createdAt: UnixMillis(Number(row.created_at)),
92
- updatedAt: UnixMillis(Number(row.updated_at)),
93
- disabledAt: optionalUnixMillis(row.disabled_at),
94
- metadata: jsonDecode(row.metadata),
95
- });
96
- const decodePasswordCredentialRow = (row) => omitUndefined({
97
- id: CredentialId(row.id),
98
- userId: UserId(row.user_id),
99
- passwordHash: row.password_hash ?? "",
100
- createdAt: UnixMillis(Number(row.created_at)),
101
- updatedAt: UnixMillis(Number(row.updated_at)),
102
- revokedAt: optionalUnixMillis(row.revoked_at),
103
- metadata: jsonDecode(row.metadata),
104
- });
105
- const decodeSessionRow = (row) => {
106
- const sessionMetadata = decodeSessionMetadata(row.metadata);
107
- return omitUndefined({
108
- id: SessionId(row.id),
109
- userId: UserId(row.user_id),
110
- secretHash: row.secret_hash,
111
- createdAt: UnixMillis(Number(row.created_at)),
112
- expiresAt: UnixMillis(Number(row.expires_at)),
113
- authTime: UnixMillis(Number(row.auth_time)),
114
- aal: row.aal,
115
- amr: decodeAmr(row.amr),
116
- mfaVerifiedAt: optionalUnixMillis(row.mfa_verified_at),
117
- metadata: sessionMetadata.metadata,
118
- claims: sessionMetadata.claims,
119
- revokedAt: optionalUnixMillis(row.revoked_at),
120
- lastSeenAt: optionalUnixMillis(row.last_seen_at),
121
- rotatedAt: optionalUnixMillis(row.rotated_at),
122
- });
123
- };
124
- const decodeVerificationRow = (row) => omitUndefined({
125
- id: ChallengeId(row.id),
126
- type: row.type,
127
- subject: row.subject,
128
- secretHash: row.secret_hash === null ? undefined : row.secret_hash,
129
- createdAt: UnixMillis(Number(row.created_at)),
130
- expiresAt: UnixMillis(Number(row.expires_at)),
131
- metadata: jsonDecode(row.metadata),
132
- consumedAt: optionalUnixMillis(row.consumed_at),
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
- });
243
- const sessionInsert = (row) => ({
244
- id: row.id,
245
- user_id: row.userId,
246
- secret_hash: row.secretHash,
247
- created_at: Number(row.createdAt),
248
- expires_at: Number(row.expiresAt),
249
- auth_time: Number(row.authTime),
250
- aal: row.aal,
251
- amr: JSON.stringify(row.amr),
252
- mfa_verified_at: row.mfaVerifiedAt === undefined ? null : Number(row.mfaVerifiedAt),
253
- metadata: encodeSessionMetadata(row),
254
- revoked_at: null,
255
- last_seen_at: null,
256
- rotated_at: null,
257
- });
258
- const userInsert = (row) => ({
259
- id: row.id,
260
- email: row.email,
261
- email_verified: row.emailVerified ? 1 : 0,
262
- created_at: Number(row.createdAt),
263
- updated_at: Number(row.updatedAt),
264
- disabled_at: row.disabledAt === undefined ? null : Number(row.disabledAt),
265
- metadata: jsonEncode(row.metadata),
266
- });
267
- const passwordCredentialInsert = (row) => ({
268
- id: row.id,
269
- user_id: row.userId,
270
- type: "password",
271
- password_hash: row.passwordHash,
272
- created_at: Number(row.createdAt),
273
- updated_at: Number(row.updatedAt),
274
- revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
275
- metadata: jsonEncode(row.metadata),
276
- });
277
- const verificationInsert = (row) => ({
278
- id: row.id,
279
- type: row.type,
280
- subject: row.subject,
281
- secret_hash: row.secretHash ?? null,
282
- created_at: Number(row.createdAt),
283
- expires_at: Number(row.expiresAt),
284
- metadata: jsonEncode(row.metadata),
285
- consumed_at: null,
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
- });
392
- const kyselyError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
393
- const runKysely = (entity, operation, evaluate) => Effect.tryPromise({
394
- try: evaluate,
395
- catch: (cause) => kyselyError(entity, operation, cause),
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
- });
477
- const asAuthDb = (db) => db;
478
- const findUserById = (db, id, operation = "find") => {
479
- const authDb = asAuthDb(db);
480
- return runKysely("user", operation, async () => {
481
- const row = await authDb
482
- .selectFrom("auth_user")
483
- .selectAll()
484
- .where("id", "=", id)
485
- .executeTakeFirst();
486
- return row === undefined
487
- ? Option.none()
488
- : Option.some(decodeUserRow(row));
489
- });
490
- };
491
- const findUserByEmail = (db, email, operation = "find") => {
492
- const authDb = asAuthDb(db);
493
- return runKysely("user", operation, async () => {
494
- const row = await authDb
495
- .selectFrom("auth_user")
496
- .selectAll()
497
- .where("email", "=", email)
498
- .executeTakeFirst();
499
- return row === undefined
500
- ? Option.none()
501
- : Option.some(decodeUserRow(row));
502
- });
503
- };
504
- const findPasswordCredentialByUserId = (db, userId, operation = "find") => {
505
- const authDb = asAuthDb(db);
506
- return runKysely("credential", operation, async () => {
507
- const row = await authDb
508
- .selectFrom("auth_credential")
509
- .selectAll()
510
- .where("user_id", "=", userId)
511
- .where("type", "=", "password")
512
- .executeTakeFirst();
513
- return row === undefined
514
- ? Option.none()
515
- : Option.some(decodePasswordCredentialRow(row));
516
- });
517
- };
518
- const findSessionById = (db, id, operation = "find") => {
519
- const authDb = asAuthDb(db);
520
- return runKysely("session", operation, async () => {
521
- const row = await authDb
522
- .selectFrom("auth_session")
523
- .selectAll()
524
- .where("id", "=", id)
525
- .executeTakeFirst();
526
- return row === undefined
527
- ? Option.none()
528
- : Option.some(decodeSessionRow(row));
529
- });
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
- };
542
- const findVerificationById = (db, id, operation = "find") => {
543
- const authDb = asAuthDb(db);
544
- return runKysely("verification", operation, async () => {
545
- const row = await authDb
546
- .selectFrom("auth_verification")
547
- .selectAll()
548
- .where("id", "=", id)
549
- .executeTakeFirst();
550
- return row === undefined
551
- ? Option.none()
552
- : Option.some(decodeVerificationRow(row));
553
- });
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
- };
583
- export const makeKyselyUserStore = (db) => {
584
- const authDb = asAuthDb(db);
585
- return UserStore.make({
586
- findById: (id) => findUserById(db, id),
587
- findByEmail: (email) => findUserByEmail(db, email),
588
- insert: (row) => runKysely("user", "insert", async () => {
589
- await authDb
590
- .insertInto("auth_user")
591
- .values(userInsert(row))
592
- .executeTakeFirst();
593
- }),
594
- markEmailVerified: (input) => runKysely("user", "markEmailVerified", () => authDb
595
- .updateTable("auth_user")
596
- .set({
597
- email_verified: 1,
598
- updated_at: Number(input.updatedAt),
599
- })
600
- .where("id", "=", input.userId)
601
- .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
602
- ? findUserById(db, input.userId, "markEmailVerified")
603
- : Effect.succeed(Option.none()))),
604
- });
605
- };
606
- export const makeKyselyCredentialStore = (db) => {
607
- const authDb = asAuthDb(db);
608
- return CredentialStore.make({
609
- findPasswordByUserId: (userId) => findPasswordCredentialByUserId(db, userId),
610
- insertPassword: (row) => runKysely("credential", "insert", async () => {
611
- await authDb
612
- .insertInto("auth_credential")
613
- .values(passwordCredentialInsert(row))
614
- .executeTakeFirst();
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()))),
628
- });
629
- };
630
- export const makeKyselySessionStore = (db) => {
631
- const authDb = asAuthDb(db);
632
- return SessionStore.make({
633
- findById: (id) => findSessionById(db, id),
634
- listByUser: (input) => listSessionsByUser(db, input),
635
- insert: (row) => runKysely("session", "insert", async () => {
636
- await authDb
637
- .insertInto("auth_session")
638
- .values(sessionInsert(row))
639
- .executeTakeFirst();
640
- }),
641
- refresh: (input) => {
642
- const update = {
643
- expires_at: Number(input.expiresAt),
644
- };
645
- if (input.lastSeenAt !== undefined) {
646
- update.last_seen_at = Number(input.lastSeenAt);
647
- }
648
- return runKysely("session", "refresh", () => authDb
649
- .updateTable("auth_session")
650
- .set(update)
651
- .where("id", "=", input.sessionId)
652
- .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
653
- ? findSessionById(db, input.sessionId, "refresh")
654
- : Effect.succeed(Option.none())));
655
- },
656
- rotate: (input) => {
657
- const update = {
658
- secret_hash: input.secretHash,
659
- rotated_at: Number(input.rotatedAt),
660
- };
661
- if (input.expiresAt !== undefined) {
662
- update.expires_at = Number(input.expiresAt);
663
- }
664
- return runKysely("session", "rotate", () => authDb
665
- .updateTable("auth_session")
666
- .set(update)
667
- .where("id", "=", input.sessionId)
668
- .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
669
- ? findSessionById(db, input.sessionId, "rotate")
670
- : Effect.succeed(Option.none())));
671
- },
672
- updateClaims: (input) => findSessionById(db, input.sessionId, "updateClaims").pipe(Effect.flatMap((rowOption) => {
673
- if (Option.isNone(rowOption)) {
674
- return Effect.succeed(Option.none());
675
- }
676
- return runKysely("session", "updateClaims", () => authDb
677
- .updateTable("auth_session")
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) }),
686
- metadata: encodeSessionMetadata({
687
- ...rowOption.value,
688
- claims: input.claims ?? rowOption.value.claims,
689
- }),
690
- })
691
- .where("id", "=", input.sessionId)
692
- .executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
693
- ? findSessionById(db, input.sessionId, "updateClaims")
694
- : Effect.succeed(Option.none())));
695
- })),
696
- revoke: (input) => runKysely("session", "revoke", async () => {
697
- await authDb
698
- .updateTable("auth_session")
699
- .set({ revoked_at: Number(input.revokedAt) })
700
- .where("id", "=", input.sessionId)
701
- .executeTakeFirst();
702
- }),
703
- revokeAllForUser: (input) => runKysely("session", "revokeAllForUser", async () => {
704
- await authDb
705
- .updateTable("auth_session")
706
- .set({ revoked_at: Number(input.revokedAt) })
707
- .where("user_id", "=", input.userId)
708
- .executeTakeFirst();
709
- }),
710
- });
711
- };
712
- export const makeKyselyVerificationStore = (db) => {
713
- const authDb = asAuthDb(db);
714
- return VerificationStore.make({
715
- findById: (id) => findVerificationById(db, id),
716
- insert: (row) => runKysely("verification", "insert", async () => {
717
- await authDb
718
- .insertInto("auth_verification")
719
- .values(verificationInsert(row))
720
- .executeTakeFirst();
721
- }),
722
- consume: (input) => {
723
- let query = authDb
724
- .updateTable("auth_verification")
725
- .set({ consumed_at: Number(input.consumedAt) })
726
- .where("id", "=", input.id)
727
- .where("consumed_at", "is", null)
728
- .where("expires_at", ">", Number(input.consumedAt));
729
- if (input.type !== undefined) {
730
- query = query.where("type", "=", input.type);
731
- }
732
- return runKysely("verification", "consume", () => query.executeTakeFirst()).pipe(Effect.flatMap((result) => Number(result.numUpdatedRows) > 0
733
- ? findVerificationById(db, input.id, "consume")
734
- : Effect.succeed(Option.none())));
735
- },
736
- deleteExpired: (input) => runKysely("verification", "deleteExpired", async () => {
737
- if (input.limit === undefined) {
738
- const result = await authDb
739
- .deleteFrom("auth_verification")
740
- .where("expires_at", "<=", Number(input.now))
741
- .executeTakeFirst();
742
- return Number(result.numDeletedRows);
743
- }
744
- const rows = await authDb
745
- .selectFrom("auth_verification")
746
- .select("id")
747
- .where("expires_at", "<=", Number(input.now))
748
- .limit(input.limit)
749
- .execute();
750
- if (rows.length === 0) {
751
- return 0;
752
- }
753
- const result = await authDb
754
- .deleteFrom("auth_verification")
755
- .where("id", "in", rows.map((row) => row.id))
756
- .executeTakeFirst();
757
- return Number(result.numDeletedRows);
758
- }),
759
- });
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
- };
1367
- export const makeKyselyStorage = (db) => ({
1368
- userStore: makeKyselyUserStore(db),
1369
- credentialStore: makeKyselyCredentialStore(db),
1370
- sessionStore: makeKyselySessionStore(db),
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),
1381
- });
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)));
1383
- //# sourceMappingURL=KyselyStorage.js.map