@yielded/auth-persistence-drizzle 0.1.0-beta.14 → 0.1.0-beta.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/D1.d.mts +123 -9
  2. package/dist/Libsql.d.mts +141 -27
  3. package/dist/Mysql2.d.mts +124 -10
  4. package/dist/Pglite.d.mts +124 -10
  5. package/dist/Postgres.d.mts +129 -15
  6. package/dist/SqliteBun.d.mts +135 -21
  7. package/dist/SqliteDo.d.mts +135 -21
  8. package/dist/SqliteNode.d.mts +135 -21
  9. package/dist/SqliteWasm.d.mts +135 -21
  10. package/dist/drizzle/d1-emails.d.mts +3 -3
  11. package/dist/drizzle/d1-emails.mjs +56 -11
  12. package/dist/drizzle/d1-emails.mjs.map +1 -1
  13. package/dist/drizzle/d1-identity.mjs +9 -2
  14. package/dist/drizzle/d1-identity.mjs.map +1 -1
  15. package/dist/drizzle/d1-oauth.d.mts +6 -6
  16. package/dist/drizzle/d1-oauth.mjs +8 -8
  17. package/dist/drizzle/d1-oauth.mjs.map +1 -1
  18. package/dist/drizzle/d1-passkey.d.mts +2 -1
  19. package/dist/drizzle/d1-passkey.mjs +10 -8
  20. package/dist/drizzle/d1-passkey.mjs.map +1 -1
  21. package/dist/drizzle/d1-password-prepared.d.mts +1 -1
  22. package/dist/drizzle/d1-password-prepared.mjs +16 -5
  23. package/dist/drizzle/d1-password-prepared.mjs.map +1 -1
  24. package/dist/drizzle/d1-passwords.d.mts +2 -2
  25. package/dist/drizzle/d1-passwords.mjs +19 -6
  26. package/dist/drizzle/d1-passwords.mjs.map +1 -1
  27. package/dist/drizzle/d1-phone.d.mts +2 -1
  28. package/dist/drizzle/d1-phone.mjs +4 -2
  29. package/dist/drizzle/d1-phone.mjs.map +1 -1
  30. package/dist/drizzle/d1-proofs.d.mts +1 -1
  31. package/dist/drizzle/d1-proofs.mjs +18 -7
  32. package/dist/drizzle/d1-proofs.mjs.map +1 -1
  33. package/dist/drizzle/d1-sessions.d.mts +6 -5
  34. package/dist/drizzle/d1-sessions.mjs +29 -19
  35. package/dist/drizzle/d1-sessions.mjs.map +1 -1
  36. package/dist/drizzle/d1-totp.d.mts +2 -1
  37. package/dist/drizzle/d1-totp.mjs +4 -2
  38. package/dist/drizzle/d1-totp.mjs.map +1 -1
  39. package/dist/drizzle/email-registration.d.mts +5 -1
  40. package/dist/drizzle/email-registration.mjs +48 -4
  41. package/dist/drizzle/email-registration.mjs.map +1 -1
  42. package/dist/drizzle/email-target.d.mts +1 -0
  43. package/dist/drizzle/email-target.mjs +9 -0
  44. package/dist/drizzle/email-target.mjs.map +1 -1
  45. package/dist/drizzle/mysql-emails.d.mts +4 -4
  46. package/dist/drizzle/mysql-emails.mjs +8 -7
  47. package/dist/drizzle/mysql-emails.mjs.map +1 -1
  48. package/dist/drizzle/mysql-identity.mjs +11 -4
  49. package/dist/drizzle/mysql-identity.mjs.map +1 -1
  50. package/dist/drizzle/mysql-password-prepared.d.mts +2 -2
  51. package/dist/drizzle/mysql-password-prepared.mjs +4 -3
  52. package/dist/drizzle/mysql-password-prepared.mjs.map +1 -1
  53. package/dist/drizzle/mysql-passwords.d.mts +3 -3
  54. package/dist/drizzle/mysql-passwords.mjs +7 -6
  55. package/dist/drizzle/mysql-passwords.mjs.map +1 -1
  56. package/dist/drizzle/mysql-proofs.d.mts +2 -2
  57. package/dist/drizzle/mysql-proofs.mjs +4 -3
  58. package/dist/drizzle/mysql-proofs.mjs.map +1 -1
  59. package/dist/drizzle/mysql-sessions.d.mts +6 -6
  60. package/dist/drizzle/mysql-sessions.mjs +16 -15
  61. package/dist/drizzle/mysql-sessions.mjs.map +1 -1
  62. package/dist/drizzle/native-database.mjs +1 -1
  63. package/dist/drizzle/native-database.mjs.map +1 -1
  64. package/dist/drizzle/oauth-connected-drivers.mjs +10 -8
  65. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -1
  66. package/dist/drizzle/oauth-connected-flow.mjs +62 -13
  67. package/dist/drizzle/oauth-connected-flow.mjs.map +1 -1
  68. package/dist/drizzle/oauth-connected-input.mjs +6 -0
  69. package/dist/drizzle/oauth-connected-input.mjs.map +1 -1
  70. package/dist/drizzle/oauth-connected-maintenance.mjs +3 -3
  71. package/dist/drizzle/oauth-connected-maintenance.mjs.map +1 -1
  72. package/dist/drizzle/oauth-connected-target.d.mts +116 -2
  73. package/dist/drizzle/oauth-connected-target.mjs +5 -3
  74. package/dist/drizzle/oauth-connected-target.mjs.map +1 -1
  75. package/dist/drizzle/oauth-drivers.mjs +8 -8
  76. package/dist/drizzle/oauth-drivers.mjs.map +1 -1
  77. package/dist/drizzle/oauth-target.d.mts +3 -3
  78. package/dist/drizzle/oauth-target.mjs +8 -6
  79. package/dist/drizzle/oauth-target.mjs.map +1 -1
  80. package/dist/drizzle/passkey-drivers.mjs +4 -4
  81. package/dist/drizzle/passkey-drivers.mjs.map +1 -1
  82. package/dist/drizzle/passkey-write-drivers.mjs +4 -4
  83. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -1
  84. package/dist/drizzle/passkey-write-target.d.mts +1 -1
  85. package/dist/drizzle/password-prepared-sql.mjs +5 -0
  86. package/dist/drizzle/password-prepared-sql.mjs.map +1 -1
  87. package/dist/drizzle/password-prepared-target.d.mts +2 -1
  88. package/dist/drizzle/password-prepared-target.mjs +5 -0
  89. package/dist/drizzle/password-prepared-target.mjs.map +1 -1
  90. package/dist/drizzle/password-registration.d.mts +1 -1
  91. package/dist/drizzle/password-registration.mjs +2 -0
  92. package/dist/drizzle/password-registration.mjs.map +1 -1
  93. package/dist/drizzle/password-target.d.mts +1 -0
  94. package/dist/drizzle/password-target.mjs +6 -0
  95. package/dist/drizzle/password-target.mjs.map +1 -1
  96. package/dist/drizzle/pg-emails.d.mts +4 -4
  97. package/dist/drizzle/pg-emails.mjs +8 -7
  98. package/dist/drizzle/pg-emails.mjs.map +1 -1
  99. package/dist/drizzle/pg-identity.mjs +12 -5
  100. package/dist/drizzle/pg-identity.mjs.map +1 -1
  101. package/dist/drizzle/pg-password-prepared.d.mts +2 -2
  102. package/dist/drizzle/pg-password-prepared.mjs +4 -3
  103. package/dist/drizzle/pg-password-prepared.mjs.map +1 -1
  104. package/dist/drizzle/pg-passwords.d.mts +3 -3
  105. package/dist/drizzle/pg-passwords.mjs +7 -6
  106. package/dist/drizzle/pg-passwords.mjs.map +1 -1
  107. package/dist/drizzle/pg-proofs.d.mts +2 -2
  108. package/dist/drizzle/pg-proofs.mjs +4 -3
  109. package/dist/drizzle/pg-proofs.mjs.map +1 -1
  110. package/dist/drizzle/pg-sessions.d.mts +6 -6
  111. package/dist/drizzle/pg-sessions.mjs +16 -15
  112. package/dist/drizzle/pg-sessions.mjs.map +1 -1
  113. package/dist/drizzle/phone-target.mjs +11 -2
  114. package/dist/drizzle/phone-target.mjs.map +1 -1
  115. package/dist/drizzle/proof-target.d.mts +1 -0
  116. package/dist/drizzle/proof-target.mjs +2 -0
  117. package/dist/drizzle/proof-target.mjs.map +1 -1
  118. package/dist/drizzle/query-operations.mjs +2 -0
  119. package/dist/drizzle/query-operations.mjs.map +1 -1
  120. package/dist/drizzle/session-target.d.mts +1 -0
  121. package/dist/drizzle/session-target.mjs +9 -6
  122. package/dist/drizzle/session-target.mjs.map +1 -1
  123. package/dist/drizzle/sqlite-emails.mjs +8 -7
  124. package/dist/drizzle/sqlite-emails.mjs.map +1 -1
  125. package/dist/drizzle/sqlite-identity.mjs +7 -2
  126. package/dist/drizzle/sqlite-identity.mjs.map +1 -1
  127. package/dist/drizzle/sqlite-password-prepared.mjs +4 -3
  128. package/dist/drizzle/sqlite-password-prepared.mjs.map +1 -1
  129. package/dist/drizzle/sqlite-passwords.mjs +7 -6
  130. package/dist/drizzle/sqlite-passwords.mjs.map +1 -1
  131. package/dist/drizzle/sqlite-proofs.mjs +4 -3
  132. package/dist/drizzle/sqlite-proofs.mjs.map +1 -1
  133. package/dist/drizzle/sqlite-sessions.mjs +16 -15
  134. package/dist/drizzle/sqlite-sessions.mjs.map +1 -1
  135. package/dist/drizzle/storage-validation.mjs +234 -0
  136. package/dist/drizzle/storage-validation.mjs.map +1 -0
  137. package/dist/drizzle/totp-target.mjs +5 -2
  138. package/dist/drizzle/totp-target.mjs.map +1 -1
  139. package/dist/drizzle/transaction-mysql.mjs +1 -1
  140. package/package.json +1 -1
  141. package/src/drizzle/d1-emails.ts +158 -40
  142. package/src/drizzle/d1-identity.ts +13 -0
  143. package/src/drizzle/d1-oauth.ts +8 -8
  144. package/src/drizzle/d1-passkey.ts +10 -8
  145. package/src/drizzle/d1-password-prepared.ts +14 -2
  146. package/src/drizzle/d1-passwords.ts +22 -4
  147. package/src/drizzle/d1-phone.ts +4 -2
  148. package/src/drizzle/d1-proofs.ts +55 -3
  149. package/src/drizzle/d1-sessions.ts +67 -18
  150. package/src/drizzle/d1-totp.ts +4 -2
  151. package/src/drizzle/email-registration.ts +160 -12
  152. package/src/drizzle/email-target.ts +22 -4
  153. package/src/drizzle/mysql-emails.ts +17 -7
  154. package/src/drizzle/mysql-identity.ts +13 -0
  155. package/src/drizzle/mysql-password-prepared.ts +7 -3
  156. package/src/drizzle/mysql-passwords.ts +13 -6
  157. package/src/drizzle/mysql-proofs.ts +7 -3
  158. package/src/drizzle/mysql-sessions.ts +31 -15
  159. package/src/drizzle/native-database.ts +5 -4
  160. package/src/drizzle/oauth-connected-drivers.ts +10 -8
  161. package/src/drizzle/oauth-connected-flow.ts +98 -10
  162. package/src/drizzle/oauth-connected-input.ts +7 -0
  163. package/src/drizzle/oauth-connected-maintenance.ts +3 -2
  164. package/src/drizzle/oauth-connected-target.ts +4 -2
  165. package/src/drizzle/oauth-drivers.ts +8 -8
  166. package/src/drizzle/oauth-target.ts +13 -7
  167. package/src/drizzle/passkey-drivers.ts +4 -4
  168. package/src/drizzle/passkey-write-drivers.ts +8 -5
  169. package/src/drizzle/password-prepared-sql.ts +13 -1
  170. package/src/drizzle/password-prepared-target.ts +7 -1
  171. package/src/drizzle/password-registration.ts +10 -3
  172. package/src/drizzle/password-target.ts +22 -4
  173. package/src/drizzle/pg-emails.ts +17 -7
  174. package/src/drizzle/pg-identity.ts +13 -0
  175. package/src/drizzle/pg-password-prepared.ts +7 -3
  176. package/src/drizzle/pg-passwords.ts +13 -6
  177. package/src/drizzle/pg-proofs.ts +7 -3
  178. package/src/drizzle/pg-sessions.ts +31 -15
  179. package/src/drizzle/phone-target.ts +30 -5
  180. package/src/drizzle/proof-target.ts +10 -2
  181. package/src/drizzle/query-operations.ts +2 -0
  182. package/src/drizzle/session-target.ts +20 -3
  183. package/src/drizzle/sqlite-emails.ts +17 -7
  184. package/src/drizzle/sqlite-identity.ts +5 -0
  185. package/src/drizzle/sqlite-password-prepared.ts +7 -3
  186. package/src/drizzle/sqlite-passwords.ts +13 -6
  187. package/src/drizzle/sqlite-proofs.ts +7 -3
  188. package/src/drizzle/sqlite-sessions.ts +31 -15
  189. package/src/drizzle/storage-validation.ts +285 -0
  190. package/src/drizzle/totp-target.ts +16 -4
@@ -1,3 +1,4 @@
1
+ import type { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
2
  import {
2
3
  type PrepareEmailCommit,
3
4
  type EmailCommandId,
@@ -16,7 +17,7 @@ import type { LoginIdentifier } from "@yielded/auth/Identity";
16
17
  import { reportPersistenceFailure } from "@yielded/auth/Persistence";
17
18
  import { type ProofCompletionPlan, ProofUnavailable } from "@yielded/auth/Proofs";
18
19
  import type { TokenDigest } from "@yielded/auth/Schema";
19
- import type { SecurityRevision } from "@yielded/auth/Sessions";
20
+ import { SecurityRevision } from "@yielded/auth/Sessions";
20
21
  /* oxlint-disable no-explicit-any -- registration plans bridge consumer-owned Drizzle rows. */
21
22
  import { and, eq, isNull } from "drizzle-orm";
22
23
  import { Cause, DateTime, Effect, Schema } from "effect";
@@ -26,13 +27,14 @@ import {
26
27
  requiredEmailRegistrationConstraints,
27
28
  } from "./email-model";
28
29
  import { CurrentEmailSql, type EmailSqlDatabase, type EmailSqlQuery } from "./email-sql";
29
- import { column, isMappedConstraintConflict, PersistenceMappingError } from "./model";
30
+ import { column, isMappedConstraintConflict, PersistenceMappingError, updateValues } from "./model";
30
31
  import {
31
32
  CurrentProofSql,
32
33
  completeProofPlanIn,
33
34
  type ProofSqlConfiguration,
34
35
  type ProofSqlDatabase,
35
36
  } from "./proof-sql";
37
+ import { validateDrizzleStorage } from "./storage-validation";
36
38
 
37
39
  type Mapping<Registration> = AnyEmailRegistrationMapping<Registration>;
38
40
  const unavailable = () => EmailUnavailable.make({});
@@ -146,6 +148,69 @@ const registrationRows = Effect.fn("Drizzle.registrationRows")(function* <Regist
146
148
  );
147
149
  });
148
150
 
151
+ /** Discover before locking, then lock subject before identifier like the other
152
+ * identity owners. A changed discovery is rejected rather than locking a second
153
+ * subject in the opposite order. Proof authority separately enforces the host's
154
+ * active/unverified eligibility policy in the same physical owner. */
155
+ export const readEmailRegistrationTarget = Effect.fnUntraced(function* <Registration>(
156
+ mapping: Extract<Mapping<Registration>, { readonly mode: "atomic" }>,
157
+ identifier: LoginIdentifier,
158
+ locking: boolean,
159
+ ) {
160
+ const database = yield* CurrentEmailSql;
161
+ const i = mapping.identifier;
162
+ const s = mapping.subject;
163
+
164
+ const lookup = () =>
165
+ database
166
+ .select()
167
+ .from(i.table)
168
+ .where(
169
+ and(
170
+ eq(column(i.table, i.namespace), identifier.namespace),
171
+ eq(column(i.table, i.value), identifier.value),
172
+ ),
173
+ )
174
+ .limit(1);
175
+
176
+ const discovered = (yield* lookup())[0];
177
+
178
+ if (discovered === undefined) return { _tag: "Absent" } as const;
179
+ if (discovered[i.verifiedAt] !== null) return { _tag: "Rejected" } as const;
180
+ const nativeSubjectId = discovered[i.subjectId];
181
+
182
+ const subject = (yield* selectRows(
183
+ database
184
+ .select()
185
+ .from(s.table)
186
+ .where(eq(column(s.table, s.id), nativeSubjectId))
187
+ .limit(1),
188
+ locking,
189
+ ))[0];
190
+
191
+ const current = (yield* selectRows(lookup(), locking))[0];
192
+
193
+ if (
194
+ subject === undefined ||
195
+ current === undefined ||
196
+ current[i.verifiedAt] !== null ||
197
+ !mapping.subjectId.equals(current[i.subjectId], nativeSubjectId) ||
198
+ current[i.bindingRevision] !== discovered[i.bindingRevision]
199
+ )
200
+ return { _tag: "Rejected" } as const;
201
+
202
+ return {
203
+ _tag: "Reserved" as const,
204
+ nativeSubjectId,
205
+ bindingRevision: yield* Schema.decodeUnknownEffect(SecurityRevision)(
206
+ current[i.bindingRevision],
207
+ ),
208
+ securityRevision: yield* Schema.decodeUnknownEffect(SecurityRevision)(
209
+ subject[s.securityRevision],
210
+ ),
211
+ };
212
+ });
213
+
149
214
  const owned = <A, E, R>(
150
215
  database: EmailSqlDatabase,
151
216
  configuration: EmailRegistrationConfiguration,
@@ -223,6 +288,7 @@ const registerIn = Effect.fn("DrizzleEmailRegistration.registerIn")(function* <R
223
288
  allocated: {
224
289
  readonly credentialId?: string;
225
290
  readonly securityRevision?: SecurityRevision;
291
+ readonly displacedSecurityRevision?: SecurityRevision;
226
292
  readonly identifierRevision?: SecurityRevision;
227
293
  readonly credentialRevision?: SecurityRevision;
228
294
  readonly nativeSubjectId?: unknown;
@@ -244,6 +310,20 @@ const registerIn = Effect.fn("DrizzleEmailRegistration.registerIn")(function* <R
244
310
  if (!completionMatches(mapping, input)) return prepare({ _tag: "Rejected" }, journal);
245
311
  if (!inspected.eligible || inspected.fingerprint !== input.fingerprint)
246
312
  return prepare({ _tag: "Rejected" }, journal);
313
+
314
+ const target =
315
+ mapping.mode === "atomic"
316
+ ? yield* readEmailRegistrationTarget(mapping, input.identifier, configuration.locking)
317
+ : undefined;
318
+
319
+ if (target?._tag === "Rejected") return prepare({ _tag: "Rejected" }, journal);
320
+ if (
321
+ target?._tag === "Reserved" &&
322
+ (allocated.displacedSecurityRevision === undefined ||
323
+ allocated.displacedSecurityRevision === target.securityRevision ||
324
+ allocated.identifierRevision === target.bindingRevision)
325
+ )
326
+ return yield* unavailable();
247
327
  const now = yield* nowMillis;
248
328
 
249
329
  const intent = {
@@ -298,14 +378,49 @@ const registerIn = Effect.fn("DrizzleEmailRegistration.registerIn")(function* <R
298
378
  nativeSubjectId = yield* mapping.provisioning.decodeGeneratedId(rows);
299
379
  } else yield* subjectInsert;
300
380
  if (nativeSubjectId === undefined) return false;
301
- yield* transaction.insert(mapping.identifier.table).values(
302
- mapping.identifier.encodeVerifiedInsert({
303
- identifier: input.identifier,
304
- subjectId: nativeSubjectId,
305
- verifiedAtMillis: now,
306
- bindingRevision: allocated.identifierRevision,
307
- }),
308
- );
381
+ if (target?._tag === "Reserved") {
382
+ const i = mapping.identifier;
383
+ const s = mapping.subject;
384
+
385
+ // Change only mapped ownership columns. Never delete a consumer-owned
386
+ // identifier row or copy insertion defaults over unrelated columns.
387
+ yield* transaction
388
+ .update(i.table)
389
+ .set(
390
+ updateValues([
391
+ [i.subjectId, nativeSubjectId],
392
+ [i.verifiedAt, mapping.encodeInstant(now)],
393
+ [i.bindingRevision, allocated.identifierRevision],
394
+ ]),
395
+ )
396
+ .where(
397
+ and(
398
+ eq(column(i.table, i.namespace), input.identifier.namespace),
399
+ eq(column(i.table, i.value), input.identifier.value),
400
+ eq(column(i.table, i.subjectId), target.nativeSubjectId),
401
+ eq(column(i.table, i.bindingRevision), target.bindingRevision),
402
+ isNull(column(i.table, i.verifiedAt)),
403
+ ),
404
+ );
405
+ yield* transaction
406
+ .update(s.table)
407
+ .set(updateValues([[s.securityRevision, allocated.displacedSecurityRevision]]))
408
+ .where(
409
+ and(
410
+ eq(column(s.table, s.id), target.nativeSubjectId),
411
+ eq(column(s.table, s.securityRevision), target.securityRevision),
412
+ ),
413
+ );
414
+ } else {
415
+ yield* transaction.insert(mapping.identifier.table).values(
416
+ mapping.identifier.encodeVerifiedInsert({
417
+ identifier: input.identifier,
418
+ subjectId: nativeSubjectId,
419
+ verifiedAtMillis: now,
420
+ bindingRevision: allocated.identifierRevision,
421
+ }),
422
+ );
423
+ }
309
424
  yield* transaction.insert(mapping.credential.table).values(
310
425
  mapping.credential.encodeVerifiedInsert({
311
426
  moduleId: input.moduleId,
@@ -456,7 +571,25 @@ const registerIn = Effect.fn("DrizzleEmailRegistration.registerIn")(function* <R
456
571
  false,
457
572
  ))[0];
458
573
 
574
+ const displaced =
575
+ target?._tag === "Reserved"
576
+ ? (yield* transaction
577
+ .select()
578
+ .from(mapping.subject.table)
579
+ .where(
580
+ and(
581
+ eq(subjectId, target.nativeSubjectId),
582
+ eq(
583
+ column(mapping.subject.table, mapping.subject.securityRevision),
584
+ allocated.displacedSecurityRevision,
585
+ ),
586
+ ),
587
+ )
588
+ .limit(1))[0]
589
+ : undefined;
590
+
459
591
  if (
592
+ (target?._tag === "Reserved" && displaced === undefined) ||
460
593
  subject === undefined ||
461
594
  !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
462
595
  subject[mapping.subject.securityRevision] !== allocated.securityRevision ||
@@ -544,10 +677,16 @@ export const makeSqlEmailRegistrationAuthority = Effect.fn("makeSqlEmailRegistra
544
677
  configuration: EmailRegistrationConfiguration,
545
678
  ): Effect.fn.Return<
546
679
  EmailRegistrationAuthority<Registration>,
547
- never,
548
- LifecycleHooks | CurrentEmailSql
680
+ EmailUnavailable,
681
+ LifecycleHooks | CurrentEmailSql | NativeDatabase
549
682
  > {
550
683
  const database = yield* CurrentEmailSql;
684
+
685
+ if (!configuration.coordinated)
686
+ yield* validateDrizzleStorage({
687
+ ...mapping,
688
+ proof: configuration.proof.mapping,
689
+ }).pipe(Effect.mapError(unavailable));
551
690
  const hooks = yield* LifecycleHooks;
552
691
 
553
692
  return {
@@ -618,6 +757,14 @@ export const makeSqlEmailRegistrationAuthority = Effect.fn("makeSqlEmailRegistra
618
757
  )
619
758
  : undefined;
620
759
 
760
+ const displacedSecurityRevision = atomic
761
+ ? yield* allocate(
762
+ configuration.mode,
763
+ mapping.allocateRevision,
764
+ mapping.allocateRevisionSync,
765
+ )
766
+ : undefined;
767
+
621
768
  const identifierRevision = atomic
622
769
  ? yield* allocate(
623
770
  configuration.mode,
@@ -664,6 +811,7 @@ export const makeSqlEmailRegistrationAuthority = Effect.fn("makeSqlEmailRegistra
664
811
  {
665
812
  ...(credentialId === undefined ? {} : { credentialId }),
666
813
  ...(securityRevision === undefined ? {} : { securityRevision }),
814
+ ...(displacedSecurityRevision === undefined ? {} : { displacedSecurityRevision }),
667
815
  ...(identifierRevision === undefined ? {} : { identifierRevision }),
668
816
  ...(credentialRevision === undefined ? {} : { credentialRevision }),
669
817
  ...(nativeSubjectId === undefined ? {} : { nativeSubjectId }),
@@ -1,3 +1,4 @@
1
+ import type { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
2
  import { EmailAddressPersistence, EmailSignInTargets, EmailUnavailable } from "@yielded/auth/Email";
2
3
  import {
3
4
  coordinateCommit,
@@ -24,6 +25,7 @@ import {
24
25
  } from "./email-sql";
25
26
  import type { ProofTargetConfiguration } from "./proof-target";
26
27
  import { sqlClientStandaloneGuard, type TransactionService } from "./standalone-guard";
28
+ import { validateDrizzleStorage } from "./storage-validation";
27
29
 
28
30
  export interface EmailTargetConfiguration {
29
31
  readonly mode: "interactive" | "synchronous";
@@ -144,13 +146,21 @@ export const coordinateTargetEmailAddress = <Transaction, A, E, R>(
144
146
  transaction: Transaction,
145
147
  services: { readonly emailAddressPersistence: EmailAddressPersistence["Service"] },
146
148
  ) => Effect.Effect<A, E, R>,
147
- ): Effect.Effect<A, EmailCoordinatorError<E>, R | LifecycleHooks> =>
148
- Effect.gen(function* (): Effect.fn.Return<A, EmailCoordinatorError<E>, R | LifecycleHooks> {
149
+ ): Effect.Effect<A, EmailCoordinatorError<E>, R | LifecycleHooks | NativeDatabase> =>
150
+ Effect.gen(function* (): Effect.fn.Return<
151
+ A,
152
+ EmailCoordinatorError<E>,
153
+ R | LifecycleHooks | NativeDatabase
154
+ > {
149
155
  const hooks = yield* LifecycleHooks;
150
156
 
151
157
  if (yield* hasCommitScope) return yield* EmailUnavailable.make({});
152
158
  yield* configuration.coordinatorGuard ?? configuration.standaloneGuard;
153
159
 
160
+ yield* validateDrizzleStorage({ ...mapping, proof: proofMapping }).pipe(
161
+ Effect.mapError(() => EmailUnavailable.make({})),
162
+ );
163
+
154
164
  const result = yield* coordinateCommit(
155
165
  () =>
156
166
  database.transaction((transaction) =>
@@ -180,13 +190,21 @@ export const coordinateTargetEmailRegistration = <Registration, Transaction, A,
180
190
  transaction: Transaction,
181
191
  services: { readonly registrationAuthority: EmailRegistrationAuthority<Registration> },
182
192
  ) => Effect.Effect<A, E, R>,
183
- ): Effect.Effect<A, EmailCoordinatorError<E>, R | LifecycleHooks> =>
184
- Effect.gen(function* (): Effect.fn.Return<A, EmailCoordinatorError<E>, R | LifecycleHooks> {
193
+ ): Effect.Effect<A, EmailCoordinatorError<E>, R | LifecycleHooks | NativeDatabase> =>
194
+ Effect.gen(function* (): Effect.fn.Return<
195
+ A,
196
+ EmailCoordinatorError<E>,
197
+ R | LifecycleHooks | NativeDatabase
198
+ > {
185
199
  const hooks = yield* LifecycleHooks;
186
200
 
187
201
  if (yield* hasCommitScope) return yield* EmailUnavailable.make({});
188
202
  yield* configuration.coordinatorGuard ?? configuration.standaloneGuard;
189
203
 
204
+ yield* validateDrizzleStorage({ ...mapping, proof: proofMapping }).pipe(
205
+ Effect.mapError(() => EmailUnavailable.make({})),
206
+ );
207
+
190
208
  const result = yield* coordinateCommit(
191
209
  () =>
192
210
  database.transaction((transaction) =>
@@ -1,3 +1,4 @@
1
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
2
  import { EmailAddressPersistence, type EmailUnavailable } from "@yielded/auth/Email";
2
3
  import type { LifecycleHooks, HookConfigurationError } from "@yielded/auth/Hooks";
3
4
  /* oxlint-disable no-explicit-any -- proof mapping tables are independently typed by the proof owner; this driver forwards them unchanged to the shared target. */
@@ -77,7 +78,10 @@ export const makeMysqlEmailSignInServices = <
77
78
  Effect.flatMap(
78
79
  Effect.map(acquireTransactionService(DatabaseService), configuration),
79
80
  (configuration) => makeTargetEmailSignInServices(mapping, configuration),
80
- ).pipe(Effect.provideServiceEffect(CurrentEmailSql, nativeDatabase(DatabaseService)));
81
+ ).pipe(
82
+ Effect.provideServiceEffect(CurrentEmailSql, nativeDatabase(DatabaseService)),
83
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
84
+ );
81
85
 
82
86
  export const makeMysqlEmailAddressServices = <
83
87
  S extends AnyMySqlTable,
@@ -93,7 +97,10 @@ export const makeMysqlEmailAddressServices = <
93
97
  Effect.flatMap(
94
98
  Effect.map(acquireTransactionService(DatabaseService), configuration),
95
99
  (configuration) => makeTargetEmailAddressServices(mapping, proofMapping, configuration),
96
- ).pipe(Effect.provideServiceEffect(CurrentEmailSql, nativeDatabase(DatabaseService)));
100
+ ).pipe(
101
+ Effect.provideServiceEffect(CurrentEmailSql, nativeDatabase(DatabaseService)),
102
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
103
+ );
97
104
 
98
105
  export function coordinateMysqlEmailAddress<
99
106
  D extends Database,
@@ -179,7 +186,7 @@ export function coordinateMysqlEmailAddress<
179
186
  E | EmailUnavailable | HookConfigurationError | SqlError | DatabaseError,
180
187
  Exclude<R, EmailAddressPersistence> | LifecycleHooks | DatabaseRequirements
181
188
  > {
182
- return Effect.flatMap(acquire, (database) =>
189
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
183
190
  coordinateTargetEmailAddress<TransactionOf<D>, Out, E, Exclude<R, EmailAddressPersistence>>(
184
191
  database,
185
192
  options.mapping,
@@ -196,7 +203,7 @@ export function coordinateMysqlEmailAddress<
196
203
  ? work
197
204
  : Effect.provideService(work, options.transaction, options.transaction.of(transaction));
198
205
  },
199
- ),
206
+ ).pipe(Effect.provideService(NativeDatabase, database)),
200
207
  );
201
208
  }
202
209
 
@@ -216,7 +223,10 @@ export const makeMysqlEmailRegistrationServices = <
216
223
  Effect.map(acquireTransactionService(DatabaseService), configuration),
217
224
  (configuration) =>
218
225
  makeTargetEmailRegistrationServices<Registration>(mapping, proofMapping, configuration),
219
- ).pipe(Effect.provideServiceEffect(CurrentEmailSql, nativeDatabase(DatabaseService)));
226
+ ).pipe(
227
+ Effect.provideServiceEffect(CurrentEmailSql, nativeDatabase(DatabaseService)),
228
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
229
+ );
220
230
 
221
231
  export function coordinateMysqlEmailRegistration<
222
232
  TargetId,
@@ -311,7 +321,7 @@ export function coordinateMysqlEmailRegistration<
311
321
  E | EmailUnavailable | HookConfigurationError | SqlError | DatabaseError,
312
322
  Exclude<R, TargetId> | LifecycleHooks | DatabaseRequirements
313
323
  > {
314
- return Effect.flatMap(acquire, (database) =>
324
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
315
325
  coordinateTargetEmailRegistration<Registration, TransactionOf<D>, Out, E, Exclude<R, TargetId>>(
316
326
  database,
317
327
  options.mapping,
@@ -328,6 +338,6 @@ export function coordinateMysqlEmailRegistration<
328
338
  ? work
329
339
  : Effect.provideService(work, options.transaction, options.transaction.of(transaction));
330
340
  },
331
- ),
341
+ ).pipe(Effect.provideService(NativeDatabase, database)),
332
342
  );
333
343
  }
@@ -1,3 +1,4 @@
1
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
2
  import {
2
3
  ExternalIdentityMutation,
3
4
  type ExternalIdentity,
@@ -25,6 +26,8 @@ import {
25
26
  type SubjectProvisioningTables,
26
27
  } from "./model";
27
28
  import { Database as DatabaseService } from "./mysql-database";
29
+ import { nativeDatabase } from "./native-database";
30
+ import { validateDrizzleStorage } from "./storage-validation";
28
31
 
29
32
  type RuntimeDatabase = EffectMysql2Database<any>;
30
33
  const unavailable = () => IdentityUnavailable.make();
@@ -47,6 +50,11 @@ export const makeMysqlSubjectProvisioningServices = Effect.fnUntraced(function*
47
50
  >(mapping: SubjectProvisioningTables<Subject, Identifier, Request, NativeId>) {
48
51
  const database = yield* DatabaseService;
49
52
 
53
+ yield* validateDrizzleStorage(mapping).pipe(
54
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
55
+ Effect.mapError(() => IdentityUnavailable.make()),
56
+ );
57
+
50
58
  const db = database as RuntimeDatabase;
51
59
  const requestTable = mapping.provisioningRequest.table;
52
60
 
@@ -198,6 +206,11 @@ export const makeMysqlExternalIdentityServices = Effect.fnUntraced(function* <
198
206
  >(mapping: ExternalIdentityTables<Subject, External, NativeId>) {
199
207
  const database = yield* DatabaseService;
200
208
 
209
+ yield* validateDrizzleStorage(mapping).pipe(
210
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
211
+ Effect.mapError(() => IdentityUnavailable.make()),
212
+ );
213
+
201
214
  const db = database as RuntimeDatabase;
202
215
  const externalTable = mapping.externalIdentity.table;
203
216
 
@@ -1,3 +1,4 @@
1
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
2
  import type { LifecycleHooks, HookConfigurationError } from "@yielded/auth/Hooks";
2
3
  import type { PasswordUnavailable, PasswordPreparedPersistence } from "@yielded/auth/Password";
3
4
  import type { AnyRelations } from "drizzle-orm";
@@ -50,7 +51,10 @@ export const makeMySqlPasswordPreparedPersistenceServices = <
50
51
  Effect.map(acquireTransactionService(DatabaseService), configuration),
51
52
  (configuration) =>
52
53
  makeTargetPasswordPreparedPersistenceServices(mapping, configuration, proofMapping),
53
- ).pipe(Effect.provideServiceEffect(CurrentPasswordSql, nativeDatabase(DatabaseService)));
54
+ ).pipe(
55
+ Effect.provideServiceEffect(CurrentPasswordSql, nativeDatabase(DatabaseService)),
56
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
57
+ );
54
58
 
55
59
  export function coordinateMySqlPasswordPreparedPersistence<
56
60
  TargetId,
@@ -184,7 +188,7 @@ export function coordinateMySqlPasswordPreparedPersistence<
184
188
  E | PasswordUnavailable | HookConfigurationError | SqlError | DatabaseError,
185
189
  Exclude<R, TargetId> | LifecycleHooks | DatabaseRequirements
186
190
  > {
187
- return Effect.flatMap(acquire, (database) =>
191
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
188
192
  coordinateTargetPasswordPreparedPersistence<TransactionOf<D>, Out, E, Exclude<R, TargetId>>(
189
193
  database,
190
194
  options.mapping,
@@ -201,6 +205,6 @@ export function coordinateMySqlPasswordPreparedPersistence<
201
205
  ? work
202
206
  : Effect.provideService(work, options.transaction, options.transaction.of(transaction));
203
207
  },
204
- ),
208
+ ).pipe(Effect.provideService(NativeDatabase, database)),
205
209
  );
206
210
  }
@@ -1,3 +1,4 @@
1
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
2
  import type { LifecycleHooks, HookConfigurationError } from "@yielded/auth/Hooks";
2
3
  import { type PasswordUnavailable, PasswordPersistence } from "@yielded/auth/Password";
3
4
  /* oxlint-disable no-explicit-any -- proof mapping tables are independently typed by the proof owner; this driver forwards them unchanged to the shared target. */
@@ -91,7 +92,10 @@ export const makeMysqlPasswordPersistenceServices = <
91
92
  Effect.flatMap(
92
93
  Effect.map(acquireTransactionService(DatabaseService), configuration),
93
94
  (configuration) => makeTargetPasswordPersistenceServices(mapping, configuration, proofMapping),
94
- ).pipe(Effect.provideServiceEffect(CurrentPasswordSql, nativeDatabase(DatabaseService)));
95
+ ).pipe(
96
+ Effect.provideServiceEffect(CurrentPasswordSql, nativeDatabase(DatabaseService)),
97
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
98
+ );
95
99
 
96
100
  export function coordinateMysqlPasswordPersistence<
97
101
  D extends Database,
@@ -195,7 +199,7 @@ export function coordinateMysqlPasswordPersistence<
195
199
  E | PasswordUnavailable | HookConfigurationError | SqlError | DatabaseError,
196
200
  Exclude<R, PasswordPersistence> | LifecycleHooks | DatabaseRequirements
197
201
  > {
198
- return Effect.flatMap(acquire, (database) =>
202
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
199
203
  coordinateTargetPasswordPersistence<TransactionOf<D>, Out, E, Exclude<R, PasswordPersistence>>(
200
204
  database,
201
205
  options.mapping,
@@ -212,7 +216,7 @@ export function coordinateMysqlPasswordPersistence<
212
216
  ? work
213
217
  : Effect.provideService(work, options.transaction, options.transaction.of(transaction));
214
218
  },
215
- ),
219
+ ).pipe(Effect.provideService(NativeDatabase, database)),
216
220
  );
217
221
  }
218
222
 
@@ -230,7 +234,10 @@ export const makeMysqlPasswordRegistrationServices = <
230
234
  Effect.flatMap(
231
235
  Effect.map(acquireTransactionService(DatabaseService), configuration),
232
236
  (configuration) => makeTargetPasswordRegistrationServices<Registration>(mapping, configuration),
233
- ).pipe(Effect.provideServiceEffect(CurrentPasswordSql, nativeDatabase(DatabaseService)));
237
+ ).pipe(
238
+ Effect.provideServiceEffect(CurrentPasswordSql, nativeDatabase(DatabaseService)),
239
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
240
+ );
234
241
 
235
242
  export function coordinateMysqlPasswordRegistration<
236
243
  TargetId,
@@ -322,7 +329,7 @@ export function coordinateMysqlPasswordRegistration<
322
329
  E | PasswordUnavailable | HookConfigurationError | SqlError | DatabaseError,
323
330
  Exclude<R, TargetId> | LifecycleHooks | DatabaseRequirements
324
331
  > {
325
- return Effect.flatMap(acquire, (database) =>
332
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
326
333
  coordinateTargetPasswordRegistration<
327
334
  Registration,
328
335
  TransactionOf<D>,
@@ -344,7 +351,7 @@ export function coordinateMysqlPasswordRegistration<
344
351
  ? work
345
352
  : Effect.provideService(work, options.transaction, options.transaction.of(transaction));
346
353
  },
347
- ),
354
+ ).pipe(Effect.provideService(NativeDatabase, database)),
348
355
  );
349
356
  }
350
357
 
@@ -1,3 +1,4 @@
1
+ import { NativeDatabase } from "@yielded/auth-persistence/Adapter";
1
2
  import type { LifecycleHooks, HookConfigurationError } from "@yielded/auth/Hooks";
2
3
  import { type ProofUnavailable, ProofPersistence } from "@yielded/auth/Proofs";
3
4
  import type { AnyRelations } from "drizzle-orm";
@@ -70,7 +71,10 @@ export const makeMysqlProofPersistenceServices = <
70
71
  Effect.flatMap(
71
72
  Effect.map(acquireTransactionService(DatabaseService), configuration),
72
73
  (configuration) => makeTargetProofPersistenceServices(mapping, configuration),
73
- ).pipe(Effect.provideServiceEffect(CurrentProofSql, nativeDatabase(DatabaseService)));
74
+ ).pipe(
75
+ Effect.provideServiceEffect(CurrentProofSql, nativeDatabase(DatabaseService)),
76
+ Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
77
+ );
74
78
 
75
79
  export function coordinateMysqlProofPersistence<
76
80
  D extends Database,
@@ -171,7 +175,7 @@ export function coordinateMysqlProofPersistence<
171
175
  E | ProofUnavailable | HookConfigurationError | SqlError | DatabaseError,
172
176
  Exclude<R, ProofPersistence> | LifecycleHooks | DatabaseRequirements
173
177
  > {
174
- return Effect.flatMap(acquire, (database) =>
178
+ return Effect.flatMap(nativeDatabase(acquire), (database) =>
175
179
  coordinateTargetProofPersistence<Transaction, A, E, Exclude<R, ProofPersistence>>(
176
180
  database,
177
181
  options.mapping,
@@ -187,6 +191,6 @@ export function coordinateMysqlProofPersistence<
187
191
  ? work
188
192
  : Effect.provideService(work, options.transaction, options.transaction.of(transaction));
189
193
  },
190
- ),
194
+ ).pipe(Effect.provideService(NativeDatabase, database)),
191
195
  );
192
196
  }