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

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,10 +1,16 @@
1
- import { Data, Effect, Layer, Option } from "effect";
1
+ import { Data, Effect, Layer, Option, Redacted } from "effect";
2
2
  import { Column, Executor, Query, Scalar, Table } from "effect-qb/sqlite";
3
- import { AuthFlowId, ChallengeId, CredentialId, Email, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
3
+ import { ApiKeyId, ApiKeyPrefix, ApiKeySecretHash, ApiKeyStore, ApiKeyStoreError, } from "./ApiKey.js";
4
+ import { AuthFlowId, ChallengeId, CredentialId, Email, OAuthAccountId, OAuthProviderId, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
4
5
  import { JwtError, JwtRevocationStore } from "./Jwt.js";
5
6
  import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
6
7
  import { LoginRiskContext, LoginRiskSignal } from "./LoginRisk.js";
8
+ import { OAuthAccountStore, OAuthAccountStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
9
+ import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
10
+ import { RecoveryCodeHash, RecoveryCodeStore, RecoveryCodeStoreError, } from "./RecoveryCode.js";
11
+ import { RefreshTokenFamilyId, RefreshTokenId, RefreshTokenSecretHash, RefreshTokenStore, RefreshTokenStoreError, } from "./RefreshToken.js";
7
12
  import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
13
+ import { TotpFactorStore, TotpFactorStoreError, TotpSecret, } from "./Totp.js";
8
14
  import { TrustedDeviceError, TrustedDeviceStore } from "./TrustedDevice.js";
9
15
  export class D1EffectQbError extends Data.TaggedError("D1EffectQbError") {
10
16
  }
@@ -78,12 +84,100 @@ const makeAuthTrustedDeviceTable = (name) => Table.make(name, {
78
84
  expires_at: Column.int(),
79
85
  metadata: Column.text().pipe(Column.nullable),
80
86
  });
87
+ const makeAuthPasskeyCredentialTable = (name) => Table.make(name, {
88
+ id: Column.text().pipe(Column.primaryKey),
89
+ user_id: Column.text(),
90
+ credential_id: Column.text(),
91
+ public_key: Column.text(),
92
+ sign_count: Column.int(),
93
+ transports: Column.text().pipe(Column.nullable),
94
+ backed_up: Column.int().pipe(Column.nullable),
95
+ created_at: Column.int(),
96
+ last_used_at: Column.int().pipe(Column.nullable),
97
+ revoked_at: Column.int().pipe(Column.nullable),
98
+ metadata: Column.text().pipe(Column.nullable),
99
+ });
100
+ const makeAuthTotpFactorTable = (name) => Table.make(name, {
101
+ id: Column.text().pipe(Column.primaryKey),
102
+ user_id: Column.text(),
103
+ secret: Column.text(),
104
+ algorithm: Column.text(),
105
+ digits: Column.int(),
106
+ period: Column.int(),
107
+ created_at: Column.int(),
108
+ confirmed_at: Column.int().pipe(Column.nullable),
109
+ last_used_at: Column.int().pipe(Column.nullable),
110
+ revoked_at: Column.int().pipe(Column.nullable),
111
+ metadata: Column.text().pipe(Column.nullable),
112
+ });
113
+ const makeAuthRecoveryCodeTable = (name) => Table.make(name, {
114
+ id: Column.text().pipe(Column.primaryKey),
115
+ user_id: Column.text(),
116
+ code_hash: Column.text(),
117
+ created_at: Column.int(),
118
+ used_at: Column.int().pipe(Column.nullable),
119
+ revoked_at: Column.int().pipe(Column.nullable),
120
+ metadata: Column.text().pipe(Column.nullable),
121
+ });
122
+ const makeAuthApiKeyTable = (name) => Table.make(name, {
123
+ id: Column.text().pipe(Column.primaryKey),
124
+ user_id: Column.text(),
125
+ prefix: Column.text(),
126
+ secret_hash: Column.text(),
127
+ scopes: Column.text(),
128
+ created_at: Column.int(),
129
+ expires_at: Column.int().pipe(Column.nullable),
130
+ last_used_at: Column.int().pipe(Column.nullable),
131
+ revoked_at: Column.int().pipe(Column.nullable),
132
+ metadata: Column.text().pipe(Column.nullable),
133
+ });
134
+ const makeAuthRefreshTokenTable = (name) => Table.make(name, {
135
+ id: Column.text().pipe(Column.primaryKey),
136
+ family_id: Column.text(),
137
+ user_id: Column.text(),
138
+ secret_hash: Column.text(),
139
+ created_at: Column.int(),
140
+ expires_at: Column.int(),
141
+ last_used_at: Column.int().pipe(Column.nullable),
142
+ rotated_at: Column.int().pipe(Column.nullable),
143
+ replaced_by_id: Column.text().pipe(Column.nullable),
144
+ revoked_at: Column.int().pipe(Column.nullable),
145
+ reuse_detected_at: Column.int().pipe(Column.nullable),
146
+ metadata: Column.text().pipe(Column.nullable),
147
+ });
81
148
  const makeAuthJwtRevocationTable = (name) => Table.make(name, {
82
149
  jwt_id: Column.text().pipe(Column.primaryKey),
83
150
  revoked_at: Column.int(),
84
151
  expires_at: Column.int().pipe(Column.nullable),
85
152
  reason: Column.text().pipe(Column.nullable),
86
153
  });
154
+ const makeAuthOAuthAccountTable = (name) => Table.make(name, {
155
+ id: Column.text().pipe(Column.primaryKey),
156
+ provider_id: Column.text(),
157
+ provider_account_id: Column.text(),
158
+ user_id: Column.text(),
159
+ email: Column.text().pipe(Column.nullable),
160
+ email_verified: Column.int().pipe(Column.nullable),
161
+ created_at: Column.int(),
162
+ updated_at: Column.int(),
163
+ unlinked_at: Column.int().pipe(Column.nullable),
164
+ metadata: Column.text().pipe(Column.nullable),
165
+ });
166
+ const makeAuthOAuthProviderTokenVaultTable = (name) => Table.make(name, {
167
+ account_id: Column.text().pipe(Column.primaryKey),
168
+ user_id: Column.text(),
169
+ provider_id: Column.text(),
170
+ provider_account_id: Column.text(),
171
+ access_token_ciphertext: Column.text(),
172
+ refresh_token_ciphertext: Column.text().pipe(Column.nullable),
173
+ id_token_ciphertext: Column.text().pipe(Column.nullable),
174
+ token_type: Column.text(),
175
+ scopes: Column.text().pipe(Column.nullable),
176
+ expires_at: Column.int().pipe(Column.nullable),
177
+ updated_at: Column.int(),
178
+ revoked_at: Column.int().pipe(Column.nullable),
179
+ revocation_reason: Column.text().pipe(Column.nullable),
180
+ });
87
181
  const authSqliteTablesTypeId = Symbol.for("@effect-auth/core/EffectQbSqliteStorage/AuthSqliteTables");
88
182
  const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
89
183
  const jsonDecode = (value) => {
@@ -120,6 +214,7 @@ const executePlan = (executor, plan) => executor.execute(plan);
120
214
  const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
121
215
  const sessionMetadataEnvelopeKey = "__effectAuthSession";
122
216
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
217
+ const metadataWithReason = (metadata, key, reason) => reason === undefined ? metadata : { ...metadata, [key]: reason };
123
218
  const isLoginRiskLevel = (value) => value === "unknown" ||
124
219
  value === "low" ||
125
220
  value === "medium" ||
@@ -279,6 +374,96 @@ const decodeDefaultTrustedDeviceRow = (row) => omitUndefined({
279
374
  const decodeTrustedDeviceRow = (tables, row) => tables.trustedDevice.decode === undefined
280
375
  ? decodeDefaultTrustedDeviceRow(row)
281
376
  : tables.trustedDevice.decode(row);
377
+ const decodePasskeyTransports = (value) => {
378
+ const decoded = jsonDecode(value);
379
+ return Array.isArray(decoded) ? decoded : undefined;
380
+ };
381
+ const decodeDefaultPasskeyCredentialRow = (row) => omitUndefined({
382
+ id: CredentialId(row.id),
383
+ userId: UserId(row.userId),
384
+ credentialId: PasskeyCredentialId(row.credentialId),
385
+ publicKey: row.publicKey,
386
+ signCount: Number(row.signCount),
387
+ transports: decodePasskeyTransports(row.transports),
388
+ backedUp: row.backedUp === null || row.backedUp === undefined
389
+ ? undefined
390
+ : decodeBoolean(row.backedUp),
391
+ createdAt: UnixMillis(Number(row.createdAt)),
392
+ lastUsedAt: optionalUnixMillis(row.lastUsedAt),
393
+ revokedAt: optionalUnixMillis(row.revokedAt),
394
+ metadata: jsonDecode(row.metadata),
395
+ });
396
+ const decodePasskeyCredentialRow = (tables, row) => tables.passkeyCredential.decode === undefined
397
+ ? decodeDefaultPasskeyCredentialRow(row)
398
+ : tables.passkeyCredential.decode(row);
399
+ const decodeDefaultTotpFactorRow = (row) => omitUndefined({
400
+ id: CredentialId(row.id),
401
+ userId: UserId(row.userId),
402
+ secret: Redacted.make(TotpSecret(row.secret)),
403
+ algorithm: row.algorithm,
404
+ digits: Number(row.digits),
405
+ period: Number(row.period),
406
+ createdAt: UnixMillis(Number(row.createdAt)),
407
+ confirmedAt: optionalUnixMillis(row.confirmedAt),
408
+ lastUsedAt: optionalUnixMillis(row.lastUsedAt),
409
+ revokedAt: optionalUnixMillis(row.revokedAt),
410
+ metadata: jsonDecode(row.metadata),
411
+ });
412
+ const decodeTotpFactorRow = (tables, row) => tables.totpFactor.decode === undefined
413
+ ? decodeDefaultTotpFactorRow(row)
414
+ : tables.totpFactor.decode(row);
415
+ const decodeDefaultRecoveryCodeRow = (row) => omitUndefined({
416
+ id: CredentialId(row.id),
417
+ userId: UserId(row.userId),
418
+ codeHash: RecoveryCodeHash(row.codeHash),
419
+ createdAt: UnixMillis(Number(row.createdAt)),
420
+ usedAt: optionalUnixMillis(row.usedAt),
421
+ revokedAt: optionalUnixMillis(row.revokedAt),
422
+ metadata: jsonDecode(row.metadata),
423
+ });
424
+ const decodeRecoveryCodeRow = (tables, row) => tables.recoveryCode.decode === undefined
425
+ ? decodeDefaultRecoveryCodeRow(row)
426
+ : tables.recoveryCode.decode(row);
427
+ const decodeStringArray = (value) => {
428
+ const decoded = jsonDecode(value);
429
+ return Array.isArray(decoded) ? decoded.map(String) : [];
430
+ };
431
+ const decodeOptionalStringArray = (value) => {
432
+ const decoded = jsonDecode(value);
433
+ return Array.isArray(decoded) ? decoded.map(String) : undefined;
434
+ };
435
+ const decodeDefaultApiKeyRow = (row) => omitUndefined({
436
+ id: ApiKeyId(row.id),
437
+ userId: UserId(row.userId),
438
+ prefix: ApiKeyPrefix(row.prefix),
439
+ secretHash: ApiKeySecretHash(row.secretHash),
440
+ scopes: decodeStringArray(row.scopes),
441
+ createdAt: UnixMillis(Number(row.createdAt)),
442
+ expiresAt: optionalUnixMillis(row.expiresAt),
443
+ lastUsedAt: optionalUnixMillis(row.lastUsedAt),
444
+ revokedAt: optionalUnixMillis(row.revokedAt),
445
+ metadata: jsonDecode(row.metadata),
446
+ });
447
+ const decodeApiKeyRow = (tables, row) => tables.apiKey.decode === undefined
448
+ ? decodeDefaultApiKeyRow(row)
449
+ : tables.apiKey.decode(row);
450
+ const decodeDefaultRefreshTokenRow = (row) => omitUndefined({
451
+ id: RefreshTokenId(row.id),
452
+ familyId: RefreshTokenFamilyId(row.familyId),
453
+ userId: UserId(row.userId),
454
+ secretHash: RefreshTokenSecretHash(row.secretHash),
455
+ createdAt: UnixMillis(Number(row.createdAt)),
456
+ expiresAt: UnixMillis(Number(row.expiresAt)),
457
+ lastUsedAt: optionalUnixMillis(row.lastUsedAt),
458
+ rotatedAt: optionalUnixMillis(row.rotatedAt),
459
+ replacedById: row.replacedById === null ? undefined : RefreshTokenId(row.replacedById),
460
+ revokedAt: optionalUnixMillis(row.revokedAt),
461
+ reuseDetectedAt: optionalUnixMillis(row.reuseDetectedAt),
462
+ metadata: jsonDecode(row.metadata),
463
+ });
464
+ const decodeRefreshTokenRow = (tables, row) => tables.refreshToken.decode === undefined
465
+ ? decodeDefaultRefreshTokenRow(row)
466
+ : tables.refreshToken.decode(row);
282
467
  const decodeDefaultJwtRevocationRow = (row) => omitUndefined({
283
468
  jwtId: row.jwtId,
284
469
  revokedAt: UnixMillis(Number(row.revokedAt)),
@@ -288,6 +473,53 @@ const decodeDefaultJwtRevocationRow = (row) => omitUndefined({
288
473
  const decodeJwtRevocationRow = (tables, row) => tables.jwtRevocation.decode === undefined
289
474
  ? decodeDefaultJwtRevocationRow(row)
290
475
  : tables.jwtRevocation.decode(row);
476
+ const decodeDefaultOAuthAccountRow = (row) => omitUndefined({
477
+ id: OAuthAccountId(row.id),
478
+ providerId: OAuthProviderId(row.providerId),
479
+ providerAccountId: row.providerAccountId,
480
+ userId: UserId(row.userId),
481
+ email: row.email === null ? undefined : Email(row.email),
482
+ emailVerified: row.emailVerified === null || row.emailVerified === undefined
483
+ ? undefined
484
+ : decodeBoolean(row.emailVerified),
485
+ createdAt: UnixMillis(Number(row.createdAt)),
486
+ updatedAt: UnixMillis(Number(row.updatedAt)),
487
+ unlinkedAt: optionalUnixMillis(row.unlinkedAt),
488
+ metadata: jsonDecode(row.metadata),
489
+ });
490
+ const decodeOAuthAccountRow = (tables, row) => tables.oauthAccount.decode === undefined
491
+ ? decodeDefaultOAuthAccountRow(row)
492
+ : tables.oauthAccount.decode(row);
493
+ const decodeDefaultOAuthProviderTokenVaultRow = (row) => omitUndefined({
494
+ accountId: OAuthAccountId(row.accountId),
495
+ userId: UserId(row.userId),
496
+ providerId: OAuthProviderId(row.providerId),
497
+ providerAccountId: row.providerAccountId,
498
+ accessTokenCiphertext: Redacted.make(row.accessTokenCiphertext),
499
+ refreshTokenCiphertext: row.refreshTokenCiphertext === null
500
+ ? undefined
501
+ : Redacted.make(row.refreshTokenCiphertext),
502
+ idTokenCiphertext: row.idTokenCiphertext === null
503
+ ? undefined
504
+ : Redacted.make(row.idTokenCiphertext),
505
+ tokenType: row.tokenType,
506
+ scopes: decodeOptionalStringArray(row.scopes),
507
+ expiresAt: optionalUnixMillis(row.expiresAt),
508
+ updatedAt: UnixMillis(Number(row.updatedAt)),
509
+ revokedAt: optionalUnixMillis(row.revokedAt),
510
+ revocationReason: row.revocationReason ?? undefined,
511
+ });
512
+ const decodeOAuthProviderTokenVaultRow = (tables, row) => tables.oauthProviderTokenVault.decode === undefined
513
+ ? decodeDefaultOAuthProviderTokenVaultRow(row)
514
+ : tables.oauthProviderTokenVault.decode(row);
515
+ const providerTokenVaultTokens = (record) => omitUndefined({
516
+ accessTokenCiphertext: record.accessTokenCiphertext,
517
+ refreshTokenCiphertext: record.refreshTokenCiphertext,
518
+ idTokenCiphertext: record.idTokenCiphertext,
519
+ tokenType: record.tokenType,
520
+ scopes: record.scopes === undefined ? undefined : [...record.scopes],
521
+ expiresAt: record.expiresAt,
522
+ });
291
523
  const userInsert = (row) => ({
292
524
  id: row.id,
293
525
  email: row.email,
@@ -408,12 +640,163 @@ const trustedDeviceUpdate = (input, existing) => ({
408
640
  expires_at: Number(input.expiresAt),
409
641
  metadata: jsonEncode(input.metadata ?? existing.metadata),
410
642
  });
643
+ const passkeyCredentialInsert = (row) => ({
644
+ id: row.id,
645
+ user_id: row.userId,
646
+ credential_id: row.credentialId,
647
+ public_key: row.publicKey,
648
+ sign_count: row.signCount,
649
+ transports: jsonEncode(row.transports),
650
+ backed_up: row.backedUp === undefined ? null : row.backedUp ? 1 : 0,
651
+ created_at: Number(row.createdAt),
652
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
653
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
654
+ metadata: jsonEncode(row.metadata),
655
+ });
656
+ const passkeyCredentialUpdateSignCount = (input) => omitUndefined({
657
+ sign_count: input.signCount,
658
+ last_used_at: Number(input.lastUsedAt),
659
+ metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
660
+ });
661
+ const passkeyCredentialRevoke = (input, row) => ({
662
+ revoked_at: Number(input.revokedAt),
663
+ metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
664
+ });
665
+ const totpFactorInsert = (row) => ({
666
+ id: row.id,
667
+ user_id: row.userId,
668
+ secret: Redacted.value(row.secret),
669
+ algorithm: row.algorithm,
670
+ digits: row.digits,
671
+ period: row.period,
672
+ created_at: Number(row.createdAt),
673
+ confirmed_at: row.confirmedAt === undefined ? null : Number(row.confirmedAt),
674
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
675
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
676
+ metadata: jsonEncode(row.metadata),
677
+ });
678
+ const totpFactorConfirm = (input) => omitUndefined({
679
+ confirmed_at: Number(input.confirmedAt),
680
+ metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
681
+ });
682
+ const totpFactorMarkUsed = (input) => omitUndefined({
683
+ last_used_at: Number(input.lastUsedAt),
684
+ metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
685
+ });
686
+ const totpFactorRevoke = (input, row) => ({
687
+ revoked_at: Number(input.revokedAt),
688
+ metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
689
+ });
690
+ const recoveryCodeInsert = (row) => ({
691
+ id: row.id,
692
+ user_id: row.userId,
693
+ code_hash: row.codeHash,
694
+ created_at: Number(row.createdAt),
695
+ used_at: row.usedAt === undefined ? null : Number(row.usedAt),
696
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
697
+ metadata: jsonEncode(row.metadata),
698
+ });
699
+ const recoveryCodeMarkUsed = (input) => omitUndefined({
700
+ used_at: Number(input.usedAt),
701
+ metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
702
+ });
703
+ const recoveryCodeRevoke = (input, row) => ({
704
+ revoked_at: Number(input.revokedAt),
705
+ metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
706
+ });
707
+ const apiKeyInsert = (row) => ({
708
+ id: row.id,
709
+ user_id: row.userId,
710
+ prefix: row.prefix,
711
+ secret_hash: row.secretHash,
712
+ scopes: JSON.stringify(row.scopes),
713
+ created_at: Number(row.createdAt),
714
+ expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
715
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
716
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
717
+ metadata: jsonEncode(row.metadata),
718
+ });
719
+ const apiKeyMarkUsed = (input) => omitUndefined({
720
+ last_used_at: Number(input.lastUsedAt),
721
+ metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
722
+ });
723
+ const apiKeyRevoke = (input, row) => ({
724
+ revoked_at: Number(input.revokedAt),
725
+ metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
726
+ });
727
+ const refreshTokenInsert = (row) => ({
728
+ id: row.id,
729
+ family_id: row.familyId,
730
+ user_id: row.userId,
731
+ secret_hash: row.secretHash,
732
+ created_at: Number(row.createdAt),
733
+ expires_at: Number(row.expiresAt),
734
+ last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
735
+ rotated_at: row.rotatedAt === undefined ? null : Number(row.rotatedAt),
736
+ replaced_by_id: row.replacedById ?? null,
737
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
738
+ reuse_detected_at: row.reuseDetectedAt === undefined ? null : Number(row.reuseDetectedAt),
739
+ metadata: jsonEncode(row.metadata),
740
+ });
741
+ const refreshTokenRotate = (input) => ({
742
+ last_used_at: Number(input.rotatedAt),
743
+ rotated_at: Number(input.rotatedAt),
744
+ replaced_by_id: input.replacement.id,
745
+ });
746
+ const refreshTokenMarkReuseDetected = (input) => ({
747
+ reuse_detected_at: Number(input.reuseDetectedAt),
748
+ });
749
+ const refreshTokenRevoke = (input, row) => ({
750
+ revoked_at: Number(input.revokedAt),
751
+ metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
752
+ });
753
+ const refreshTokenRevokeFamily = (input, row) => ({
754
+ revoked_at: Number(input.revokedAt),
755
+ metadata: jsonEncode(metadataWithReason(row.metadata, "familyRevokeReason", input.reason)),
756
+ });
411
757
  const jwtRevocationInsert = (record) => ({
412
758
  jwt_id: record.jwtId,
413
759
  revoked_at: Number(record.revokedAt),
414
760
  expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
415
761
  reason: record.reason ?? null,
416
762
  });
763
+ const oauthAccountInsert = (row) => ({
764
+ id: row.id,
765
+ provider_id: row.providerId,
766
+ provider_account_id: row.providerAccountId,
767
+ user_id: row.userId,
768
+ email: row.email ?? null,
769
+ email_verified: row.emailVerified === undefined ? null : row.emailVerified ? 1 : 0,
770
+ created_at: Number(row.createdAt),
771
+ updated_at: Number(row.updatedAt),
772
+ unlinked_at: row.unlinkedAt === undefined ? null : Number(row.unlinkedAt),
773
+ metadata: jsonEncode(row.metadata),
774
+ });
775
+ const oauthAccountUnlink = (input, row) => ({
776
+ unlinked_at: Number(input.unlinkedAt),
777
+ metadata: jsonEncode(metadataWithReason(row.metadata, "unlinkReason", input.reason)),
778
+ });
779
+ const oauthProviderTokenVaultValues = (row) => ({
780
+ account_id: row.accountId,
781
+ user_id: row.userId,
782
+ provider_id: row.providerId,
783
+ provider_account_id: row.providerAccountId,
784
+ access_token_ciphertext: Redacted.value(row.accessTokenCiphertext),
785
+ refresh_token_ciphertext: row.refreshTokenCiphertext === undefined
786
+ ? null
787
+ : Redacted.value(row.refreshTokenCiphertext),
788
+ id_token_ciphertext: row.idTokenCiphertext === undefined ? null : Redacted.value(row.idTokenCiphertext),
789
+ token_type: row.tokenType,
790
+ scopes: jsonEncode(row.scopes),
791
+ expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
792
+ updated_at: Number(row.updatedAt),
793
+ revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
794
+ revocation_reason: row.revocationReason ?? null,
795
+ });
796
+ const oauthProviderTokenVaultMarkRevoked = (input) => ({
797
+ revoked_at: Number(input.revokedAt),
798
+ revocation_reason: input.reason,
799
+ });
417
800
  const jwtRevocationUpdate = (record) => ({
418
801
  revoked_at: Number(record.revokedAt),
419
802
  expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
@@ -426,7 +809,14 @@ const makeInternalAuthSqliteTables = (names = {}) => {
426
809
  const verification = makeAuthVerificationTable(names.verification ?? "auth_verification");
427
810
  const loginApprovalReview = makeAuthLoginApprovalReviewTable(names.loginApprovalReview ?? "auth_login_approval_review");
428
811
  const trustedDevice = makeAuthTrustedDeviceTable(names.trustedDevice ?? "auth_trusted_device");
812
+ const passkeyCredential = makeAuthPasskeyCredentialTable(names.passkeyCredential ?? "auth_passkey_credential");
813
+ const totpFactor = makeAuthTotpFactorTable(names.totpFactor ?? "auth_totp_factor");
814
+ const recoveryCode = makeAuthRecoveryCodeTable(names.recoveryCode ?? "auth_recovery_code");
815
+ const apiKey = makeAuthApiKeyTable(names.apiKey ?? "auth_api_key");
816
+ const refreshToken = makeAuthRefreshTokenTable(names.refreshToken ?? "auth_refresh_token");
429
817
  const jwtRevocation = makeAuthJwtRevocationTable(names.jwtRevocation ?? "auth_jwt_revocation");
818
+ const oauthAccount = makeAuthOAuthAccountTable(names.oauthAccount ?? "auth_oauth_account");
819
+ const oauthProviderTokenVault = makeAuthOAuthProviderTokenVaultTable(names.oauthProviderTokenVault ?? "auth_oauth_provider_token_vault");
430
820
  return {
431
821
  user: {
432
822
  table: user,
@@ -532,6 +922,100 @@ const makeInternalAuthSqliteTables = (names = {}) => {
532
922
  insert: trustedDeviceInsert,
533
923
  update: trustedDeviceUpdate,
534
924
  },
925
+ passkeyCredential: {
926
+ table: passkeyCredential,
927
+ select: {
928
+ id: passkeyCredential.id,
929
+ userId: passkeyCredential.user_id,
930
+ credentialId: passkeyCredential.credential_id,
931
+ publicKey: passkeyCredential.public_key,
932
+ signCount: passkeyCredential.sign_count,
933
+ transports: passkeyCredential.transports,
934
+ backedUp: passkeyCredential.backed_up,
935
+ createdAt: passkeyCredential.created_at,
936
+ lastUsedAt: passkeyCredential.last_used_at,
937
+ revokedAt: passkeyCredential.revoked_at,
938
+ metadata: passkeyCredential.metadata,
939
+ },
940
+ insert: passkeyCredentialInsert,
941
+ updateSignCount: passkeyCredentialUpdateSignCount,
942
+ revoke: passkeyCredentialRevoke,
943
+ },
944
+ totpFactor: {
945
+ table: totpFactor,
946
+ select: {
947
+ id: totpFactor.id,
948
+ userId: totpFactor.user_id,
949
+ secret: totpFactor.secret,
950
+ algorithm: totpFactor.algorithm,
951
+ digits: totpFactor.digits,
952
+ period: totpFactor.period,
953
+ createdAt: totpFactor.created_at,
954
+ confirmedAt: totpFactor.confirmed_at,
955
+ lastUsedAt: totpFactor.last_used_at,
956
+ revokedAt: totpFactor.revoked_at,
957
+ metadata: totpFactor.metadata,
958
+ },
959
+ insert: totpFactorInsert,
960
+ confirm: totpFactorConfirm,
961
+ markUsed: totpFactorMarkUsed,
962
+ revoke: totpFactorRevoke,
963
+ },
964
+ recoveryCode: {
965
+ table: recoveryCode,
966
+ select: {
967
+ id: recoveryCode.id,
968
+ userId: recoveryCode.user_id,
969
+ codeHash: recoveryCode.code_hash,
970
+ createdAt: recoveryCode.created_at,
971
+ usedAt: recoveryCode.used_at,
972
+ revokedAt: recoveryCode.revoked_at,
973
+ metadata: recoveryCode.metadata,
974
+ },
975
+ insert: recoveryCodeInsert,
976
+ markUsed: recoveryCodeMarkUsed,
977
+ revoke: recoveryCodeRevoke,
978
+ },
979
+ apiKey: {
980
+ table: apiKey,
981
+ select: {
982
+ id: apiKey.id,
983
+ userId: apiKey.user_id,
984
+ prefix: apiKey.prefix,
985
+ secretHash: apiKey.secret_hash,
986
+ scopes: apiKey.scopes,
987
+ createdAt: apiKey.created_at,
988
+ expiresAt: apiKey.expires_at,
989
+ lastUsedAt: apiKey.last_used_at,
990
+ revokedAt: apiKey.revoked_at,
991
+ metadata: apiKey.metadata,
992
+ },
993
+ insert: apiKeyInsert,
994
+ markUsed: apiKeyMarkUsed,
995
+ revoke: apiKeyRevoke,
996
+ },
997
+ refreshToken: {
998
+ table: refreshToken,
999
+ select: {
1000
+ id: refreshToken.id,
1001
+ familyId: refreshToken.family_id,
1002
+ userId: refreshToken.user_id,
1003
+ secretHash: refreshToken.secret_hash,
1004
+ createdAt: refreshToken.created_at,
1005
+ expiresAt: refreshToken.expires_at,
1006
+ lastUsedAt: refreshToken.last_used_at,
1007
+ rotatedAt: refreshToken.rotated_at,
1008
+ replacedById: refreshToken.replaced_by_id,
1009
+ revokedAt: refreshToken.revoked_at,
1010
+ reuseDetectedAt: refreshToken.reuse_detected_at,
1011
+ metadata: refreshToken.metadata,
1012
+ },
1013
+ insert: refreshTokenInsert,
1014
+ rotate: refreshTokenRotate,
1015
+ markReuseDetected: refreshTokenMarkReuseDetected,
1016
+ revoke: refreshTokenRevoke,
1017
+ revokeFamily: refreshTokenRevokeFamily,
1018
+ },
535
1019
  jwtRevocation: {
536
1020
  table: jwtRevocation,
537
1021
  select: {
@@ -543,6 +1027,44 @@ const makeInternalAuthSqliteTables = (names = {}) => {
543
1027
  insert: jwtRevocationInsert,
544
1028
  update: jwtRevocationUpdate,
545
1029
  },
1030
+ oauthAccount: {
1031
+ table: oauthAccount,
1032
+ select: {
1033
+ id: oauthAccount.id,
1034
+ providerId: oauthAccount.provider_id,
1035
+ providerAccountId: oauthAccount.provider_account_id,
1036
+ userId: oauthAccount.user_id,
1037
+ email: oauthAccount.email,
1038
+ emailVerified: oauthAccount.email_verified,
1039
+ createdAt: oauthAccount.created_at,
1040
+ updatedAt: oauthAccount.updated_at,
1041
+ unlinkedAt: oauthAccount.unlinked_at,
1042
+ metadata: oauthAccount.metadata,
1043
+ },
1044
+ insert: oauthAccountInsert,
1045
+ unlink: oauthAccountUnlink,
1046
+ },
1047
+ oauthProviderTokenVault: {
1048
+ table: oauthProviderTokenVault,
1049
+ select: {
1050
+ accountId: oauthProviderTokenVault.account_id,
1051
+ userId: oauthProviderTokenVault.user_id,
1052
+ providerId: oauthProviderTokenVault.provider_id,
1053
+ providerAccountId: oauthProviderTokenVault.provider_account_id,
1054
+ accessTokenCiphertext: oauthProviderTokenVault.access_token_ciphertext,
1055
+ refreshTokenCiphertext: oauthProviderTokenVault.refresh_token_ciphertext,
1056
+ idTokenCiphertext: oauthProviderTokenVault.id_token_ciphertext,
1057
+ tokenType: oauthProviderTokenVault.token_type,
1058
+ scopes: oauthProviderTokenVault.scopes,
1059
+ expiresAt: oauthProviderTokenVault.expires_at,
1060
+ updatedAt: oauthProviderTokenVault.updated_at,
1061
+ revokedAt: oauthProviderTokenVault.revoked_at,
1062
+ revocationReason: oauthProviderTokenVault.revocation_reason,
1063
+ },
1064
+ insert: oauthProviderTokenVaultValues,
1065
+ update: oauthProviderTokenVaultValues,
1066
+ markRevoked: oauthProviderTokenVaultMarkRevoked,
1067
+ },
546
1068
  };
547
1069
  };
548
1070
  const asAuthSqliteTables = (tables) => tables;
@@ -555,7 +1077,14 @@ const selectSessionColumns = (tables) => tables.session.select;
555
1077
  const selectVerificationColumns = (tables) => tables.verification.select;
556
1078
  const selectLoginApprovalReviewColumns = (tables) => tables.loginApprovalReview.select;
557
1079
  const selectTrustedDeviceColumns = (tables) => tables.trustedDevice.select;
1080
+ const selectPasskeyCredentialColumns = (tables) => tables.passkeyCredential.select;
1081
+ const selectTotpFactorColumns = (tables) => tables.totpFactor.select;
1082
+ const selectRecoveryCodeColumns = (tables) => tables.recoveryCode.select;
1083
+ const selectApiKeyColumns = (tables) => tables.apiKey.select;
1084
+ const selectRefreshTokenColumns = (tables) => tables.refreshToken.select;
558
1085
  const selectJwtRevocationColumns = (tables) => tables.jwtRevocation.select;
1086
+ const selectOAuthAccountColumns = (tables) => tables.oauthAccount.select;
1087
+ const selectOAuthProviderTokenVaultColumns = (tables) => tables.oauthProviderTokenVault.select;
559
1088
  const storageError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
560
1089
  const loginApprovalReviewStoreError = (operation, cause) => new LoginApprovalReviewError({
561
1090
  message: `Login approval review ${operation} failed`,
@@ -566,11 +1095,46 @@ const trustedDeviceStoreError = (operation, cause) => new TrustedDeviceError({
566
1095
  message: `Trusted device ${operation} failed`,
567
1096
  cause,
568
1097
  });
1098
+ const passkeyCredentialStoreError = (operation, cause) => new PasskeyCredentialStoreError({
1099
+ operation,
1100
+ message: `Passkey credential ${operation} failed`,
1101
+ cause,
1102
+ });
1103
+ const totpFactorStoreError = (operation, cause) => new TotpFactorStoreError({
1104
+ operation,
1105
+ message: `TOTP factor ${operation} failed`,
1106
+ cause,
1107
+ });
1108
+ const recoveryCodeStoreError = (operation, cause) => new RecoveryCodeStoreError({
1109
+ operation,
1110
+ message: `Recovery code ${operation} failed`,
1111
+ cause,
1112
+ });
1113
+ const apiKeyStoreError = (operation, cause) => new ApiKeyStoreError({
1114
+ operation,
1115
+ message: `API key ${operation} failed`,
1116
+ cause,
1117
+ });
1118
+ const refreshTokenStoreError = (operation, cause) => new RefreshTokenStoreError({
1119
+ operation,
1120
+ message: `Refresh token ${operation} failed`,
1121
+ cause,
1122
+ });
569
1123
  const jwtRevocationStoreError = (operation, cause) => new JwtError({
570
1124
  operation: "revocation",
571
1125
  message: `JWT revocation ${operation} failed`,
572
1126
  cause,
573
1127
  });
1128
+ const oauthAccountStoreError = (operation, cause) => new OAuthAccountStoreError({
1129
+ operation,
1130
+ message: `OAuth account ${operation} failed`,
1131
+ cause,
1132
+ });
1133
+ const oauthProviderTokenVaultError = (operation, cause) => new OAuthProviderTokenVaultError({
1134
+ operation,
1135
+ message: `OAuth provider token vault ${operation} failed`,
1136
+ cause,
1137
+ });
574
1138
  const findUserById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.id, id)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
575
1139
  ? Option.none()
576
1140
  : Option.some(decodeUserRow(tables, rows[0]))));
@@ -599,6 +1163,48 @@ const findTrustedDevice = (executor, tables, input, operation = "status") => exe
599
1163
  const findTrustedDeviceIncludingExpired = (executor, tables, input) => executePlan(executor, Query.select(selectTrustedDeviceColumns(tables)).pipe(Query.from(tables.trustedDevice.table), Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.map((rows) => rows[0] === undefined
600
1164
  ? Option.none()
601
1165
  : Option.some(decodeTrustedDeviceRow(tables, rows[0]))));
1166
+ const findPasskeyCredentialByCredentialId = (executor, tables, credentialId, operation = "find") => executePlan(executor, Query.select(selectPasskeyCredentialColumns(tables)).pipe(Query.from(tables.passkeyCredential.table), Query.where(queryEq(tables.passkeyCredential.select.credentialId, credentialId)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1167
+ ? Option.none()
1168
+ : Option.some(decodePasskeyCredentialRow(tables, rows[0]))));
1169
+ const findTotpFactorById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectTotpFactorColumns(tables)).pipe(Query.from(tables.totpFactor.table), Query.where(queryEq(tables.totpFactor.select.id, id)))).pipe(Effect.mapError((cause) => totpFactorStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1170
+ ? Option.none()
1171
+ : Option.some(decodeTotpFactorRow(tables, rows[0]))));
1172
+ const findRecoveryCodeById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectRecoveryCodeColumns(tables)).pipe(Query.from(tables.recoveryCode.table), Query.where(queryEq(tables.recoveryCode.select.id, id)))).pipe(Effect.mapError((cause) => recoveryCodeStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1173
+ ? Option.none()
1174
+ : Option.some(decodeRecoveryCodeRow(tables, rows[0]))));
1175
+ const findApiKeyById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectApiKeyColumns(tables)).pipe(Query.from(tables.apiKey.table), Query.where(queryEq(tables.apiKey.select.id, id)))).pipe(Effect.mapError((cause) => apiKeyStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1176
+ ? Option.none()
1177
+ : Option.some(decodeApiKeyRow(tables, rows[0]))));
1178
+ const findApiKeyByPrefix = (executor, tables, prefix, operation = "find") => executePlan(executor, Query.select(selectApiKeyColumns(tables)).pipe(Query.from(tables.apiKey.table), Query.where(queryEq(tables.apiKey.select.prefix, prefix)))).pipe(Effect.mapError((cause) => apiKeyStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1179
+ ? Option.none()
1180
+ : Option.some(decodeApiKeyRow(tables, rows[0]))));
1181
+ const findRefreshTokenById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectRefreshTokenColumns(tables)).pipe(Query.from(tables.refreshToken.table), Query.where(queryEq(tables.refreshToken.select.id, id)))).pipe(Effect.mapError((cause) => refreshTokenStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1182
+ ? Option.none()
1183
+ : Option.some(decodeRefreshTokenRow(tables, rows[0]))));
1184
+ const findRefreshTokenBySecretHash = (executor, tables, hash, operation = "find") => executePlan(executor, Query.select(selectRefreshTokenColumns(tables)).pipe(Query.from(tables.refreshToken.table), Query.where(queryEq(tables.refreshToken.select.secretHash, hash)))).pipe(Effect.mapError((cause) => refreshTokenStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1185
+ ? Option.none()
1186
+ : Option.some(decodeRefreshTokenRow(tables, rows[0]))));
1187
+ const findOAuthAccountById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectOAuthAccountColumns(tables)).pipe(Query.from(tables.oauthAccount.table), Query.where(queryEq(tables.oauthAccount.select.id, id)))).pipe(Effect.mapError((cause) => oauthAccountStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1188
+ ? Option.none()
1189
+ : Option.some(decodeOAuthAccountRow(tables, rows[0]))));
1190
+ const findOAuthAccountByProviderAccount = (executor, tables, input, operation = "find") => {
1191
+ const base = Query.select(selectOAuthAccountColumns(tables)).pipe(Query.from(tables.oauthAccount.table), Query.where(queryEq(tables.oauthAccount.select.providerId, input.providerId)), Query.where(queryEq(tables.oauthAccount.select.providerAccountId, input.providerAccountId)));
1192
+ const query = input.includeUnlinked === true
1193
+ ? base
1194
+ : base.pipe(Query.where(queryIsNull(tables.oauthAccount.select.unlinkedAt)));
1195
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => oauthAccountStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1196
+ ? Option.none()
1197
+ : Option.some(decodeOAuthAccountRow(tables, rows[0]))));
1198
+ };
1199
+ const findOAuthProviderTokenVaultRecordByAccountId = (executor, tables, input, operation, includeRevoked = false) => {
1200
+ const base = Query.select(selectOAuthProviderTokenVaultColumns(tables)).pipe(Query.from(tables.oauthProviderTokenVault.table), Query.where(queryEq(tables.oauthProviderTokenVault.select.accountId, input.accountId)));
1201
+ const query = includeRevoked
1202
+ ? base
1203
+ : base.pipe(Query.where(queryIsNull(tables.oauthProviderTokenVault.select.revokedAt)));
1204
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => oauthProviderTokenVaultError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1205
+ ? Option.none()
1206
+ : Option.some(decodeOAuthProviderTokenVaultRow(tables, rows[0]))));
1207
+ };
602
1208
  export const makeEffectQbSqliteUserStore = (executor, options = {}) => {
603
1209
  const tables = options.tables ?? authSqliteTables;
604
1210
  return UserStore.make({
@@ -705,6 +1311,155 @@ export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) =>
705
1311
  : Effect.fail(trustedDeviceStoreError("trust", "missing row")))),
706
1312
  });
707
1313
  };
1314
+ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {}) => {
1315
+ const tables = options.tables ?? authSqliteTables;
1316
+ return PasskeyCredentialStore.make({
1317
+ insert: (row) => executePlan(executor, queryInsert(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.insert(row)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("insert", cause)), Effect.asVoid),
1318
+ findByCredentialId: (credentialId) => findPasskeyCredentialByCredentialId(executor, tables, credentialId),
1319
+ listByUser: (input) => {
1320
+ const base = Query.select(selectPasskeyCredentialColumns(tables)).pipe(Query.from(tables.passkeyCredential.table), Query.where(queryEq(tables.passkeyCredential.select.userId, input.userId)));
1321
+ const query = input.includeRevoked === true
1322
+ ? base
1323
+ : base.pipe(Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)));
1324
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodePasskeyCredentialRow(tables, row))));
1325
+ },
1326
+ updateSignCount: (input) => executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.updateSignCount(input))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)), Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)), Query.returning(selectPasskeyCredentialColumns(tables)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("update-sign-count", cause)), Effect.map((rows) => rows[0] === undefined
1327
+ ? Option.none()
1328
+ : Option.some(decodePasskeyCredentialRow(tables, rows[0])))),
1329
+ revoke: (input) => findPasskeyCredentialByCredentialId(executor, tables, input.credentialId, "revoke").pipe(Effect.flatMap((row) => {
1330
+ if (Option.isNone(row)) {
1331
+ return Effect.void;
1332
+ }
1333
+ return executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.revoke(input, row.value))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("revoke", cause)), Effect.asVoid);
1334
+ })),
1335
+ });
1336
+ };
1337
+ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
1338
+ const tables = options.tables ?? authSqliteTables;
1339
+ return TotpFactorStore.make({
1340
+ insert: (row) => executePlan(executor, queryInsert(tables.totpFactor.table, mutationValues(tables.totpFactor.insert(row)))).pipe(Effect.mapError((cause) => totpFactorStoreError("insert", cause)), Effect.asVoid),
1341
+ findById: (id) => findTotpFactorById(executor, tables, id),
1342
+ listByUser: (input) => {
1343
+ const byUser = Query.select(selectTotpFactorColumns(tables)).pipe(Query.from(tables.totpFactor.table), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)));
1344
+ const byRevoked = input.includeRevoked === true
1345
+ ? byUser
1346
+ : byUser.pipe(Query.where(queryIsNull(tables.totpFactor.select.revokedAt)));
1347
+ const query = input.includeUnconfirmed === true
1348
+ ? byRevoked
1349
+ : byRevoked.pipe(Query.where(queryGt(tables.totpFactor.select.confirmedAt, 0)));
1350
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => totpFactorStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeTotpFactorRow(tables, row))));
1351
+ },
1352
+ confirm: (input) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.confirm(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.returning(selectTotpFactorColumns(tables)))).pipe(Effect.mapError((cause) => totpFactorStoreError("confirm", cause)), Effect.map((rows) => rows[0] === undefined
1353
+ ? Option.none()
1354
+ : Option.some(decodeTotpFactorRow(tables, rows[0])))),
1355
+ markUsed: (input) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.markUsed(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryGt(tables.totpFactor.select.confirmedAt, 0)), Query.returning(selectTotpFactorColumns(tables)))).pipe(Effect.mapError((cause) => totpFactorStoreError("mark-used", cause)), Effect.map((rows) => rows[0] === undefined
1356
+ ? Option.none()
1357
+ : Option.some(decodeTotpFactorRow(tables, rows[0])))),
1358
+ revoke: (input) => findTotpFactorById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
1359
+ if (Option.isNone(row)) {
1360
+ return Effect.void;
1361
+ }
1362
+ return executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.revoke(input, row.value))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)))).pipe(Effect.mapError((cause) => totpFactorStoreError("revoke", cause)), Effect.asVoid);
1363
+ })),
1364
+ });
1365
+ };
1366
+ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
1367
+ const tables = options.tables ?? authSqliteTables;
1368
+ return RecoveryCodeStore.make({
1369
+ insertMany: (rows) => Effect.forEach(rows, (row) => executePlan(executor, queryInsert(tables.recoveryCode.table, mutationValues(tables.recoveryCode.insert(row)))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("insert", cause)), Effect.asVoid), { discard: true }),
1370
+ findById: (id) => findRecoveryCodeById(executor, tables, id),
1371
+ listByUser: (input) => {
1372
+ const byUser = Query.select(selectRecoveryCodeColumns(tables)).pipe(Query.from(tables.recoveryCode.table), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)));
1373
+ const byUsed = input.includeUsed === true
1374
+ ? byUser
1375
+ : byUser.pipe(Query.where(queryIsNull(tables.recoveryCode.select.usedAt)));
1376
+ const query = input.includeRevoked === true
1377
+ ? byUsed
1378
+ : byUsed.pipe(Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)));
1379
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => recoveryCodeStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeRecoveryCodeRow(tables, row))));
1380
+ },
1381
+ markUsed: (input) => executePlan(executor, queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed(input))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.id)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.returning({ id: tables.recoveryCode.select.id }))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("mark-used", cause)), Effect.flatMap((rows) => rows[0] === undefined
1382
+ ? Effect.succeed(Option.none())
1383
+ : findRecoveryCodeById(executor, tables, input.id, "mark-used"))),
1384
+ revoke: (input) => findRecoveryCodeById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
1385
+ if (Option.isNone(row)) {
1386
+ return Effect.void;
1387
+ }
1388
+ return executePlan(executor, queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.revoke(input, row.value))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.id)))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("revoke", cause)), Effect.asVoid);
1389
+ })),
1390
+ });
1391
+ };
1392
+ export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
1393
+ const tables = options.tables ?? authSqliteTables;
1394
+ return ApiKeyStore.make({
1395
+ insert: (row) => executePlan(executor, queryInsert(tables.apiKey.table, mutationValues(tables.apiKey.insert(row)))).pipe(Effect.mapError((cause) => apiKeyStoreError("insert", cause)), Effect.asVoid),
1396
+ findById: (id) => findApiKeyById(executor, tables, id),
1397
+ findByPrefix: (prefix) => findApiKeyByPrefix(executor, tables, prefix),
1398
+ listByUser: (input) => {
1399
+ const base = Query.select(selectApiKeyColumns(tables)).pipe(Query.from(tables.apiKey.table), Query.where(queryEq(tables.apiKey.select.userId, input.userId)));
1400
+ const query = input.includeRevoked === true
1401
+ ? base
1402
+ : base.pipe(Query.where(queryIsNull(tables.apiKey.select.revokedAt)));
1403
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => apiKeyStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeApiKeyRow(tables, row))));
1404
+ },
1405
+ markUsed: (input) => executePlan(executor, queryUpdate(tables.apiKey.table, mutationValues(tables.apiKey.markUsed(input))).pipe(Query.where(queryEq(tables.apiKey.select.id, input.id)), Query.where(queryIsNull(tables.apiKey.select.revokedAt)), Query.returning(selectApiKeyColumns(tables)))).pipe(Effect.mapError((cause) => apiKeyStoreError("mark-used", cause)), Effect.map((rows) => rows[0] === undefined
1406
+ ? Option.none()
1407
+ : Option.some(decodeApiKeyRow(tables, rows[0])))),
1408
+ revoke: (input) => findApiKeyById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
1409
+ if (Option.isNone(row)) {
1410
+ return Effect.void;
1411
+ }
1412
+ return executePlan(executor, queryUpdate(tables.apiKey.table, mutationValues(tables.apiKey.revoke(input, row.value))).pipe(Query.where(queryEq(tables.apiKey.select.id, input.id)))).pipe(Effect.mapError((cause) => apiKeyStoreError("revoke", cause)), Effect.asVoid);
1413
+ })),
1414
+ });
1415
+ };
1416
+ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
1417
+ const tables = options.tables ?? authSqliteTables;
1418
+ return RefreshTokenStore.make({
1419
+ insert: (row) => executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(row)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("insert", cause)), Effect.asVoid),
1420
+ findById: (id) => findRefreshTokenById(executor, tables, id),
1421
+ findBySecretHash: (hash) => findRefreshTokenBySecretHash(executor, tables, hash),
1422
+ listByUser: (input) => {
1423
+ const base = Query.select(selectRefreshTokenColumns(tables)).pipe(Query.from(tables.refreshToken.table), Query.where(queryEq(tables.refreshToken.select.userId, input.userId)));
1424
+ const query = input.includeRevoked === true
1425
+ ? base
1426
+ : base.pipe(Query.where(queryIsNull(tables.refreshToken.select.revokedAt)));
1427
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => refreshTokenStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeRefreshTokenRow(tables, row))));
1428
+ },
1429
+ listByFamily: (input) => {
1430
+ const base = Query.select(selectRefreshTokenColumns(tables)).pipe(Query.from(tables.refreshToken.table), Query.where(queryEq(tables.refreshToken.select.familyId, input.familyId)));
1431
+ const query = input.includeRevoked === true
1432
+ ? base
1433
+ : base.pipe(Query.where(queryIsNull(tables.refreshToken.select.revokedAt)));
1434
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => refreshTokenStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeRefreshTokenRow(tables, row))));
1435
+ },
1436
+ rotate: (input) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.rotate(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.where(queryIsNull(tables.refreshToken.select.rotatedAt)), Query.where(queryIsNull(tables.refreshToken.select.revokedAt)), Query.returning({ id: tables.refreshToken.select.id }))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.flatMap((rows) => {
1437
+ const [updated] = rows;
1438
+ if (updated === undefined) {
1439
+ return Effect.succeed(Option.none());
1440
+ }
1441
+ return findRefreshTokenById(executor, tables, RefreshTokenId(updated.id), "rotate").pipe(Effect.flatMap((previous) => {
1442
+ if (Option.isNone(previous)) {
1443
+ return Effect.succeed(Option.none());
1444
+ }
1445
+ return executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(input.replacement)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.as(Option.some({
1446
+ previous: previous.value,
1447
+ replacement: input.replacement,
1448
+ })));
1449
+ }));
1450
+ })),
1451
+ markReuseDetected: (input) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.markReuseDetected(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.returning(selectRefreshTokenColumns(tables)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("reuse", cause)), Effect.map((rows) => rows[0] === undefined
1452
+ ? Option.none()
1453
+ : Option.some(decodeRefreshTokenRow(tables, rows[0])))),
1454
+ revoke: (input) => findRefreshTokenById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
1455
+ if (Option.isNone(row)) {
1456
+ return Effect.void;
1457
+ }
1458
+ return executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.revoke(input, row.value))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("revoke", cause)), Effect.asVoid);
1459
+ })),
1460
+ revokeFamily: (input) => executePlan(executor, Query.select(selectRefreshTokenColumns(tables)).pipe(Query.from(tables.refreshToken.table), Query.where(queryEq(tables.refreshToken.select.familyId, input.familyId)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("revoke", cause)), Effect.flatMap((rows) => Effect.forEach(rows.map((row) => decodeRefreshTokenRow(tables, row)), (row) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.revokeFamily(input, row))).pipe(Query.where(queryEq(tables.refreshToken.select.id, row.id)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("revoke", cause)), Effect.asVoid), { discard: true }))),
1461
+ });
1462
+ };
708
1463
  export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) => {
709
1464
  const tables = options.tables ?? authSqliteTables;
710
1465
  return JwtRevocationStore.make({
@@ -719,6 +1474,57 @@ export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) =>
719
1474
  find: (jwtId) => findJwtRevocation(executor, tables, jwtId),
720
1475
  });
721
1476
  };
1477
+ export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
1478
+ const tables = options.tables ?? authSqliteTables;
1479
+ return OAuthAccountStore.make({
1480
+ insert: (row) => executePlan(executor, queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(row)))).pipe(Effect.mapError((cause) => oauthAccountStoreError("insert", cause)), Effect.asVoid),
1481
+ findById: (id) => findOAuthAccountById(executor, tables, id),
1482
+ findByProviderAccount: (input) => findOAuthAccountByProviderAccount(executor, tables, input),
1483
+ listByUser: (input) => {
1484
+ const base = Query.select(selectOAuthAccountColumns(tables)).pipe(Query.from(tables.oauthAccount.table), Query.where(queryEq(tables.oauthAccount.select.userId, input.userId)));
1485
+ const query = input.includeUnlinked === true
1486
+ ? base
1487
+ : base.pipe(Query.where(queryIsNull(tables.oauthAccount.select.unlinkedAt)));
1488
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => oauthAccountStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeOAuthAccountRow(tables, row))));
1489
+ },
1490
+ unlink: (input) => findOAuthAccountByProviderAccount(executor, tables, {
1491
+ providerId: input.providerId,
1492
+ providerAccountId: input.providerAccountId,
1493
+ }, "unlink").pipe(Effect.flatMap((row) => {
1494
+ if (Option.isNone(row)) {
1495
+ return Effect.succeed(Option.none());
1496
+ }
1497
+ return executePlan(executor, queryUpdate(tables.oauthAccount.table, mutationValues(tables.oauthAccount.unlink(input, row.value))).pipe(Query.where(queryEq(tables.oauthAccount.select.id, row.value.id)), Query.returning(selectOAuthAccountColumns(tables)))).pipe(Effect.mapError((cause) => oauthAccountStoreError("unlink", cause)), Effect.map((rows) => rows[0] === undefined
1498
+ ? Option.none()
1499
+ : Option.some(decodeOAuthAccountRow(tables, rows[0]))));
1500
+ })),
1501
+ });
1502
+ };
1503
+ export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}) => {
1504
+ const tables = options.tables ?? authSqliteTables;
1505
+ return OAuthProviderTokenVault.make({
1506
+ upsert: (record) => findOAuthProviderTokenVaultRecordByAccountId(executor, tables, { accountId: record.accountId }, "upsert", true).pipe(Effect.flatMap((existing) => Option.isNone(existing)
1507
+ ? executePlan(executor, queryInsert(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.insert(record))))
1508
+ : executePlan(executor, queryUpdate(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.update(record))).pipe(Query.where(queryEq(tables.oauthProviderTokenVault.select.accountId, record.accountId))))), Effect.mapError((cause) => cause instanceof OAuthProviderTokenVaultError
1509
+ ? cause
1510
+ : oauthProviderTokenVaultError("upsert", cause)), Effect.asVoid),
1511
+ refreshTokenForAccount: (input) => findOAuthProviderTokenVaultRecordByAccountId(executor, tables, input, "refresh-token").pipe(Effect.map((row) => Option.isNone(row) || row.value.refreshTokenCiphertext === undefined
1512
+ ? Option.none()
1513
+ : Option.some(row.value.refreshTokenCiphertext))),
1514
+ tokensForAccount: (input) => findOAuthProviderTokenVaultRecordByAccountId(executor, tables, input, "tokens").pipe(Effect.map((row) => Option.isNone(row)
1515
+ ? Option.none()
1516
+ : Option.some(providerTokenVaultTokens(row.value)))),
1517
+ markRevoked: (input) => executePlan(executor, queryUpdate(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.markRevoked(input))).pipe(Query.where(queryEq(tables.oauthProviderTokenVault.select.accountId, input.accountId)), Query.returning({
1518
+ accountId: tables.oauthProviderTokenVault.select.accountId,
1519
+ }))).pipe(Effect.mapError((cause) => oauthProviderTokenVaultError("mark-revoked", cause)), Effect.flatMap((rows) => {
1520
+ const [updated] = rows;
1521
+ if (updated === undefined) {
1522
+ return Effect.succeed(Option.none());
1523
+ }
1524
+ return findOAuthProviderTokenVaultRecordByAccountId(executor, tables, { accountId: OAuthAccountId(updated.accountId) }, "mark-revoked", true);
1525
+ })),
1526
+ });
1527
+ };
722
1528
  export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
723
1529
  userStore: makeEffectQbSqliteUserStore(executor, options),
724
1530
  credentialStore: makeEffectQbSqliteCredentialStore(executor, options),
@@ -726,9 +1532,16 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
726
1532
  verificationStore: makeEffectQbSqliteVerificationStore(executor, options),
727
1533
  loginApprovalReviewStore: makeEffectQbSqliteLoginApprovalReviewStore(executor, options),
728
1534
  trustedDeviceStore: makeEffectQbSqliteTrustedDeviceStore(executor, options),
1535
+ passkeyCredentialStore: makeEffectQbSqlitePasskeyCredentialStore(executor, options),
1536
+ totpFactorStore: makeEffectQbSqliteTotpFactorStore(executor, options),
1537
+ recoveryCodeStore: makeEffectQbSqliteRecoveryCodeStore(executor, options),
1538
+ apiKeyStore: makeEffectQbSqliteApiKeyStore(executor, options),
1539
+ refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, options),
729
1540
  jwtRevocationStore: makeEffectQbSqliteJwtRevocationStore(executor, options),
1541
+ oauthAccountStore: makeEffectQbSqliteOAuthAccountStore(executor, options),
1542
+ oauthProviderTokenVault: makeEffectQbSqliteOAuthProviderTokenVault(executor, options),
730
1543
  });
731
- export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)));
1544
+ export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(PasskeyCredentialStore)(makeEffectQbSqlitePasskeyCredentialStore(executor, options)), Layer.succeed(TotpFactorStore)(makeEffectQbSqliteTotpFactorStore(executor, options)), Layer.succeed(RecoveryCodeStore)(makeEffectQbSqliteRecoveryCodeStore(executor, options)), Layer.succeed(ApiKeyStore)(makeEffectQbSqliteApiKeyStore(executor, options)), Layer.succeed(RefreshTokenStore)(makeEffectQbSqliteRefreshTokenStore(executor, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)), Layer.succeed(OAuthAccountStore)(makeEffectQbSqliteOAuthAccountStore(executor, options)), Layer.succeed(OAuthProviderTokenVault)(makeEffectQbSqliteOAuthProviderTokenVault(executor, options)));
732
1545
  export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
733
1546
  driver: Executor.driver((query) => Effect.tryPromise({
734
1547
  try: () => database