@yielded/auth-persistence-drizzle 0.1.0-beta.25 → 0.1.0-beta.27

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 (383) hide show
  1. package/README.md +4 -2
  2. package/dist/D1.d.mts +495 -1151
  3. package/dist/D1.mjs +41 -19
  4. package/dist/D1.mjs.map +1 -1
  5. package/dist/Libsql.d.mts +385 -1996
  6. package/dist/Libsql.mjs +43 -47
  7. package/dist/Libsql.mjs.map +1 -1
  8. package/dist/Mysql2.d.mts +397 -1743
  9. package/dist/Mysql2.mjs +35 -41
  10. package/dist/Mysql2.mjs.map +1 -1
  11. package/dist/Pglite.d.mts +397 -1743
  12. package/dist/Pglite.mjs +42 -31
  13. package/dist/Pglite.mjs.map +1 -1
  14. package/dist/Postgres.d.mts +397 -1743
  15. package/dist/Postgres.mjs +42 -31
  16. package/dist/Postgres.mjs.map +1 -1
  17. package/dist/SqliteBun.d.mts +385 -1992
  18. package/dist/SqliteBun.mjs +43 -43
  19. package/dist/SqliteBun.mjs.map +1 -1
  20. package/dist/SqliteDo.d.mts +385 -1991
  21. package/dist/SqliteDo.mjs +42 -41
  22. package/dist/SqliteDo.mjs.map +1 -1
  23. package/dist/SqliteNode.d.mts +385 -1992
  24. package/dist/SqliteNode.mjs +43 -43
  25. package/dist/SqliteNode.mjs.map +1 -1
  26. package/dist/SqliteWasm.d.mts +385 -1992
  27. package/dist/SqliteWasm.mjs +43 -43
  28. package/dist/SqliteWasm.mjs.map +1 -1
  29. package/dist/drizzle/d1-identity.mjs +2 -2
  30. package/dist/drizzle/d1-identity.mjs.map +1 -1
  31. package/dist/drizzle/driver-types.d.mts +15 -0
  32. package/dist/drizzle/email-drivers.mjs +32 -0
  33. package/dist/drizzle/email-drivers.mjs.map +1 -0
  34. package/dist/drizzle/email-model.d.mts +9 -13
  35. package/dist/drizzle/email-target.d.mts +10 -11
  36. package/dist/drizzle/email-target.mjs +33 -70
  37. package/dist/drizzle/email-target.mjs.map +1 -1
  38. package/dist/drizzle/model.d.mts +1 -10
  39. package/dist/drizzle/model.mjs +1 -2
  40. package/dist/drizzle/model.mjs.map +1 -1
  41. package/dist/drizzle/mysql-identity.mjs +3 -6
  42. package/dist/drizzle/mysql-identity.mjs.map +1 -1
  43. package/dist/drizzle/native-clock.d.mts +9 -0
  44. package/dist/drizzle/native-clock.mjs +18 -0
  45. package/dist/drizzle/native-clock.mjs.map +1 -0
  46. package/dist/drizzle/native-database.d.mts +46 -0
  47. package/dist/drizzle/native-database.mjs +6 -5
  48. package/dist/drizzle/native-database.mjs.map +1 -1
  49. package/dist/drizzle/native-sql-table.mjs +13 -5
  50. package/dist/drizzle/native-sql-table.mjs.map +1 -1
  51. package/dist/drizzle/native-target.d.mts +6 -0
  52. package/dist/drizzle/native-target.mjs +44 -0
  53. package/dist/drizzle/native-target.mjs.map +1 -0
  54. package/dist/drizzle/oauth-connected-drivers.mjs +4 -56
  55. package/dist/drizzle/oauth-connected-drivers.mjs.map +1 -1
  56. package/dist/drizzle/oauth-connected-model.d.mts +5 -22
  57. package/dist/drizzle/oauth-connected-model.mjs +1 -7
  58. package/dist/drizzle/oauth-connected-reference.d.mts +5 -7
  59. package/dist/drizzle/oauth-connected-reference.mjs +11 -3
  60. package/dist/drizzle/oauth-connected-reference.mjs.map +1 -1
  61. package/dist/drizzle/oauth-drivers.mjs +1 -2
  62. package/dist/drizzle/oauth-drivers.mjs.map +1 -1
  63. package/dist/drizzle/oauth-model.d.mts +11 -37
  64. package/dist/drizzle/oauth-model.mjs +3 -4
  65. package/dist/drizzle/oauth-model.mjs.map +1 -1
  66. package/dist/drizzle/oauth-proxy.d.mts +1 -1
  67. package/dist/drizzle/oauth-proxy.mjs +1 -1
  68. package/dist/drizzle/oauth-target.d.mts +9 -6
  69. package/dist/drizzle/oauth-target.mjs +47 -15
  70. package/dist/drizzle/oauth-target.mjs.map +1 -1
  71. package/dist/drizzle/passkey/target.d.mts +14 -0
  72. package/dist/drizzle/passkey/target.mjs +52 -0
  73. package/dist/drizzle/passkey/target.mjs.map +1 -0
  74. package/dist/drizzle/passkey/write-target.d.mts +11 -0
  75. package/dist/drizzle/passkey/write-target.mjs +28 -0
  76. package/dist/drizzle/passkey/write-target.mjs.map +1 -0
  77. package/dist/drizzle/passkey-drivers.mjs +2 -4
  78. package/dist/drizzle/passkey-drivers.mjs.map +1 -1
  79. package/dist/drizzle/passkey-model.d.mts +5 -26
  80. package/dist/drizzle/passkey-model.mjs +2 -8
  81. package/dist/drizzle/passkey-registration-ceremony-model.d.mts +3 -6
  82. package/dist/drizzle/passkey-write-drivers.mjs +2 -3
  83. package/dist/drizzle/passkey-write-drivers.mjs.map +1 -1
  84. package/dist/drizzle/passkey-write-model.d.mts +7 -9
  85. package/dist/drizzle/passkey-write-model.mjs +1 -3
  86. package/dist/drizzle/passkey-write-model.mjs.map +1 -1
  87. package/dist/drizzle/password-drivers.mjs +31 -0
  88. package/dist/drizzle/password-drivers.mjs.map +1 -0
  89. package/dist/drizzle/password-model.d.mts +7 -10
  90. package/dist/drizzle/password-target.d.mts +7 -8
  91. package/dist/drizzle/password-target.mjs +23 -68
  92. package/dist/drizzle/password-target.mjs.map +1 -1
  93. package/dist/drizzle/pg-identity.mjs +3 -6
  94. package/dist/drizzle/pg-identity.mjs.map +1 -1
  95. package/dist/drizzle/phone-drivers.mjs +24 -0
  96. package/dist/drizzle/phone-drivers.mjs.map +1 -0
  97. package/dist/drizzle/phone-model.d.mts +13 -104
  98. package/dist/drizzle/phone-model.mjs +5 -33
  99. package/dist/drizzle/phone-model.mjs.map +1 -1
  100. package/dist/drizzle/phone-target.mjs +22 -171
  101. package/dist/drizzle/phone-target.mjs.map +1 -1
  102. package/dist/drizzle/proof-drivers.mjs +24 -0
  103. package/dist/drizzle/proof-drivers.mjs.map +1 -0
  104. package/dist/drizzle/proof-model.d.mts +7 -15
  105. package/dist/drizzle/proof-target.d.mts +5 -5
  106. package/dist/drizzle/proof-target.mjs +14 -28
  107. package/dist/drizzle/proof-target.mjs.map +1 -1
  108. package/dist/drizzle/session-drivers.mjs +97 -0
  109. package/dist/drizzle/session-drivers.mjs.map +1 -0
  110. package/dist/drizzle/session-model.d.mts +11 -22
  111. package/dist/drizzle/sqlite-do-database.d.mts +3 -3
  112. package/dist/drizzle/sqlite-do-database.mjs +6 -1
  113. package/dist/drizzle/sqlite-do-database.mjs.map +1 -1
  114. package/dist/drizzle/sqlite-identity.mjs +3 -5
  115. package/dist/drizzle/sqlite-identity.mjs.map +1 -1
  116. package/dist/drizzle/step-up-model.d.mts +2 -5
  117. package/dist/drizzle/storage-validation.mjs +2 -1
  118. package/dist/drizzle/storage-validation.mjs.map +1 -1
  119. package/dist/drizzle/totp-model.d.mts +7 -74
  120. package/dist/drizzle/totp-model.mjs +1 -5
  121. package/dist/drizzle/totp-model.mjs.map +1 -1
  122. package/dist/drizzle/totp-target.mjs +39 -75
  123. package/dist/drizzle/totp-target.mjs.map +1 -1
  124. package/dist/drizzle/transaction-handle.mjs +13 -0
  125. package/dist/drizzle/transaction-handle.mjs.map +1 -0
  126. package/dist/drizzle/transaction-mysql.mjs +43 -12
  127. package/dist/drizzle/transaction-mysql.mjs.map +1 -1
  128. package/dist/index.d.mts +17 -22
  129. package/dist/index.mjs +10 -16
  130. package/dist/internal/drizzle-postgres.mjs +4 -6
  131. package/dist/internal/drizzle-postgres.mjs.map +1 -1
  132. package/dist/internal/drizzle-sqlite.mjs +4 -6
  133. package/dist/internal/drizzle-sqlite.mjs.map +1 -1
  134. package/package.json +1 -1
  135. package/src/D1.ts +77 -74
  136. package/src/Libsql.ts +65 -105
  137. package/src/Mysql2.ts +52 -87
  138. package/src/Pglite.ts +74 -81
  139. package/src/Postgres.ts +74 -81
  140. package/src/SqliteBun.ts +65 -113
  141. package/src/SqliteDo.ts +58 -106
  142. package/src/SqliteNode.ts +65 -113
  143. package/src/SqliteWasm.ts +65 -113
  144. package/src/drizzle/d1-identity.ts +2 -2
  145. package/src/drizzle/driver-types.ts +26 -0
  146. package/src/drizzle/email-drivers.ts +320 -0
  147. package/src/drizzle/email-model.ts +24 -78
  148. package/src/drizzle/email-target.ts +118 -211
  149. package/src/drizzle/model.ts +0 -2
  150. package/src/drizzle/mysql-identity.ts +4 -24
  151. package/src/drizzle/native-clock.ts +21 -0
  152. package/src/drizzle/native-database.ts +59 -18
  153. package/src/drizzle/native-sql-table.ts +104 -87
  154. package/src/drizzle/native-target.ts +120 -0
  155. package/src/drizzle/oauth-connected-drivers.ts +24 -233
  156. package/src/drizzle/oauth-connected-model.ts +3 -93
  157. package/src/drizzle/oauth-connected-reference.ts +16 -25
  158. package/src/drizzle/oauth-drivers.ts +58 -99
  159. package/src/drizzle/oauth-model.ts +27 -132
  160. package/src/drizzle/oauth-target.ts +250 -33
  161. package/src/drizzle/passkey/target.ts +205 -0
  162. package/src/drizzle/passkey/write-target.ts +91 -0
  163. package/src/drizzle/passkey-drivers.ts +35 -133
  164. package/src/drizzle/passkey-model.ts +3 -81
  165. package/src/drizzle/passkey-registration-ceremony-model.ts +5 -25
  166. package/src/drizzle/passkey-write-drivers.ts +32 -102
  167. package/src/drizzle/passkey-write-model.ts +10 -44
  168. package/src/drizzle/password-drivers.ts +286 -0
  169. package/src/drizzle/password-model.ts +10 -44
  170. package/src/drizzle/password-target.ts +85 -239
  171. package/src/drizzle/pg-identity.ts +4 -19
  172. package/src/drizzle/phone-drivers.ts +142 -0
  173. package/src/drizzle/phone-model.ts +22 -162
  174. package/src/drizzle/phone-target.ts +51 -493
  175. package/src/drizzle/proof-drivers.ts +123 -0
  176. package/src/drizzle/proof-model.ts +12 -123
  177. package/src/drizzle/proof-target.ts +38 -102
  178. package/src/drizzle/session-drivers.ts +696 -0
  179. package/src/drizzle/session-model.ts +54 -118
  180. package/src/drizzle/sqlite-do-database.ts +8 -0
  181. package/src/drizzle/sqlite-identity.ts +4 -22
  182. package/src/drizzle/step-up-model.ts +13 -37
  183. package/src/drizzle/storage-validation.ts +2 -1
  184. package/src/drizzle/totp-model.ts +20 -77
  185. package/src/drizzle/totp-target.ts +121 -261
  186. package/src/drizzle/transaction-handle.ts +41 -0
  187. package/src/drizzle/transaction-mysql.ts +91 -26
  188. package/src/index.ts +7 -122
  189. package/src/internal/drizzle-postgres.ts +4 -7
  190. package/src/internal/drizzle-sqlite.ts +4 -7
  191. package/dist/drizzle/crypto.mjs +0 -11
  192. package/dist/drizzle/crypto.mjs.map +0 -1
  193. package/dist/drizzle/d1-emails.d.mts +0 -62
  194. package/dist/drizzle/d1-emails.mjs +0 -626
  195. package/dist/drizzle/d1-emails.mjs.map +0 -1
  196. package/dist/drizzle/d1-generated-statement.mjs +0 -79
  197. package/dist/drizzle/d1-generated-statement.mjs.map +0 -1
  198. package/dist/drizzle/d1-oauth.d.mts +0 -331
  199. package/dist/drizzle/d1-oauth.mjs +0 -79
  200. package/dist/drizzle/d1-oauth.mjs.map +0 -1
  201. package/dist/drizzle/d1-passkey.d.mts +0 -37
  202. package/dist/drizzle/d1-passkey.mjs +0 -98
  203. package/dist/drizzle/d1-passkey.mjs.map +0 -1
  204. package/dist/drizzle/d1-password-prepared.d.mts +0 -25
  205. package/dist/drizzle/d1-password-prepared.mjs +0 -473
  206. package/dist/drizzle/d1-password-prepared.mjs.map +0 -1
  207. package/dist/drizzle/d1-passwords.d.mts +0 -70
  208. package/dist/drizzle/d1-passwords.mjs +0 -808
  209. package/dist/drizzle/d1-passwords.mjs.map +0 -1
  210. package/dist/drizzle/d1-phone.d.mts +0 -23
  211. package/dist/drizzle/d1-phone.mjs +0 -33
  212. package/dist/drizzle/d1-phone.mjs.map +0 -1
  213. package/dist/drizzle/d1-planning.d.mts +0 -11
  214. package/dist/drizzle/d1-planning.mjs +0 -43
  215. package/dist/drizzle/d1-planning.mjs.map +0 -1
  216. package/dist/drizzle/d1-proofs.d.mts +0 -80
  217. package/dist/drizzle/d1-proofs.mjs +0 -842
  218. package/dist/drizzle/d1-proofs.mjs.map +0 -1
  219. package/dist/drizzle/d1-sessions.d.mts +0 -68
  220. package/dist/drizzle/d1-sessions.mjs +0 -987
  221. package/dist/drizzle/d1-sessions.mjs.map +0 -1
  222. package/dist/drizzle/d1-totp.d.mts +0 -23
  223. package/dist/drizzle/d1-totp.mjs +0 -30
  224. package/dist/drizzle/d1-totp.mjs.map +0 -1
  225. package/dist/drizzle/email-registration.d.mts +0 -33
  226. package/dist/drizzle/email-registration.mjs +0 -281
  227. package/dist/drizzle/email-registration.mjs.map +0 -1
  228. package/dist/drizzle/email-sql.d.mts +0 -1
  229. package/dist/drizzle/email-sql.mjs +0 -9
  230. package/dist/drizzle/email-sql.mjs.map +0 -1
  231. package/dist/drizzle/mysql-emails.d.mts +0 -71
  232. package/dist/drizzle/mysql-emails.mjs +0 -43
  233. package/dist/drizzle/mysql-emails.mjs.map +0 -1
  234. package/dist/drizzle/mysql-password-prepared.d.mts +0 -30
  235. package/dist/drizzle/mysql-password-prepared.mjs +0 -21
  236. package/dist/drizzle/mysql-password-prepared.mjs.map +0 -1
  237. package/dist/drizzle/mysql-passwords.d.mts +0 -71
  238. package/dist/drizzle/mysql-passwords.mjs +0 -43
  239. package/dist/drizzle/mysql-passwords.mjs.map +0 -1
  240. package/dist/drizzle/mysql-proofs.d.mts +0 -75
  241. package/dist/drizzle/mysql-proofs.mjs +0 -28
  242. package/dist/drizzle/mysql-proofs.mjs.map +0 -1
  243. package/dist/drizzle/mysql-sessions.d.mts +0 -92
  244. package/dist/drizzle/mysql-sessions.mjs +0 -58
  245. package/dist/drizzle/mysql-sessions.mjs.map +0 -1
  246. package/dist/drizzle/oauth-connected-model.mjs.map +0 -1
  247. package/dist/drizzle/oauth-connected-target.d.mts +0 -6
  248. package/dist/drizzle/oauth-connected-target.mjs +0 -12
  249. package/dist/drizzle/oauth-connected-target.mjs.map +0 -1
  250. package/dist/drizzle/oauth-kernel.mjs +0 -11
  251. package/dist/drizzle/oauth-kernel.mjs.map +0 -1
  252. package/dist/drizzle/oauth-mysql.mjs +0 -8
  253. package/dist/drizzle/oauth-mysql.mjs.map +0 -1
  254. package/dist/drizzle/passkey-kernel.mjs +0 -8
  255. package/dist/drizzle/passkey-kernel.mjs.map +0 -1
  256. package/dist/drizzle/passkey-model.mjs.map +0 -1
  257. package/dist/drizzle/passkey-registration-ceremony-model.mjs +0 -2
  258. package/dist/drizzle/passkey-state.mjs +0 -7
  259. package/dist/drizzle/passkey-state.mjs.map +0 -1
  260. package/dist/drizzle/passkey-target.d.mts +0 -2
  261. package/dist/drizzle/passkey-target.mjs +0 -7
  262. package/dist/drizzle/passkey-target.mjs.map +0 -1
  263. package/dist/drizzle/passkey-write-target.d.mts +0 -5
  264. package/dist/drizzle/passkey-write-target.mjs +0 -7
  265. package/dist/drizzle/passkey-write-target.mjs.map +0 -1
  266. package/dist/drizzle/password-prepared-model.d.mts +0 -92
  267. package/dist/drizzle/password-prepared-model.mjs +0 -11
  268. package/dist/drizzle/password-prepared-model.mjs.map +0 -1
  269. package/dist/drizzle/password-prepared-sql.mjs +0 -395
  270. package/dist/drizzle/password-prepared-sql.mjs.map +0 -1
  271. package/dist/drizzle/password-prepared-state.mjs +0 -104
  272. package/dist/drizzle/password-prepared-state.mjs.map +0 -1
  273. package/dist/drizzle/password-prepared-target.d.mts +0 -13
  274. package/dist/drizzle/password-prepared-target.mjs +0 -62
  275. package/dist/drizzle/password-prepared-target.mjs.map +0 -1
  276. package/dist/drizzle/password-registration.d.mts +0 -7
  277. package/dist/drizzle/password-registration.mjs +0 -137
  278. package/dist/drizzle/password-registration.mjs.map +0 -1
  279. package/dist/drizzle/password-sql.d.mts +0 -1
  280. package/dist/drizzle/password-sql.mjs +0 -10
  281. package/dist/drizzle/password-sql.mjs.map +0 -1
  282. package/dist/drizzle/pg-emails.d.mts +0 -72
  283. package/dist/drizzle/pg-emails.mjs +0 -43
  284. package/dist/drizzle/pg-emails.mjs.map +0 -1
  285. package/dist/drizzle/pg-password-prepared.d.mts +0 -31
  286. package/dist/drizzle/pg-password-prepared.mjs +0 -21
  287. package/dist/drizzle/pg-password-prepared.mjs.map +0 -1
  288. package/dist/drizzle/pg-passwords.d.mts +0 -72
  289. package/dist/drizzle/pg-passwords.mjs +0 -43
  290. package/dist/drizzle/pg-passwords.mjs.map +0 -1
  291. package/dist/drizzle/pg-proofs.d.mts +0 -76
  292. package/dist/drizzle/pg-proofs.mjs +0 -27
  293. package/dist/drizzle/pg-proofs.mjs.map +0 -1
  294. package/dist/drizzle/pg-sessions.d.mts +0 -93
  295. package/dist/drizzle/pg-sessions.mjs +0 -58
  296. package/dist/drizzle/pg-sessions.mjs.map +0 -1
  297. package/dist/drizzle/phone-state.mjs +0 -14
  298. package/dist/drizzle/phone-state.mjs.map +0 -1
  299. package/dist/drizzle/phone-target.d.mts +0 -15
  300. package/dist/drizzle/proof-sql.d.mts +0 -1
  301. package/dist/drizzle/proof-sql.mjs +0 -8
  302. package/dist/drizzle/proof-sql.mjs.map +0 -1
  303. package/dist/drizzle/query-operations.mjs +0 -31
  304. package/dist/drizzle/query-operations.mjs.map +0 -1
  305. package/dist/drizzle/session-sql.d.mts +0 -1
  306. package/dist/drizzle/session-sql.mjs +0 -9
  307. package/dist/drizzle/session-sql.mjs.map +0 -1
  308. package/dist/drizzle/session-target.d.mts +0 -30
  309. package/dist/drizzle/session-target.mjs +0 -49
  310. package/dist/drizzle/session-target.mjs.map +0 -1
  311. package/dist/drizzle/sqlite-emails.mjs +0 -50
  312. package/dist/drizzle/sqlite-emails.mjs.map +0 -1
  313. package/dist/drizzle/sqlite-password-prepared.mjs +0 -26
  314. package/dist/drizzle/sqlite-password-prepared.mjs.map +0 -1
  315. package/dist/drizzle/sqlite-passwords.mjs +0 -54
  316. package/dist/drizzle/sqlite-passwords.mjs.map +0 -1
  317. package/dist/drizzle/sqlite-proofs.mjs +0 -34
  318. package/dist/drizzle/sqlite-proofs.mjs.map +0 -1
  319. package/dist/drizzle/sqlite-sessions.mjs +0 -79
  320. package/dist/drizzle/sqlite-sessions.mjs.map +0 -1
  321. package/dist/drizzle/standalone-guard.d.mts +0 -2
  322. package/dist/drizzle/standalone-guard.mjs +0 -21
  323. package/dist/drizzle/standalone-guard.mjs.map +0 -1
  324. package/dist/drizzle/totp-state.d.mts +0 -37
  325. package/dist/drizzle/totp-state.mjs +0 -287
  326. package/dist/drizzle/totp-state.mjs.map +0 -1
  327. package/dist/drizzle/totp-target.d.mts +0 -15
  328. package/dist/drizzle/transaction-execution.d.mts +0 -2
  329. package/dist/drizzle/transaction-execution.mjs +0 -11
  330. package/dist/drizzle/transaction-execution.mjs.map +0 -1
  331. package/dist/drizzle/transaction-owner.d.mts +0 -1
  332. package/dist/drizzle/transaction-owner.mjs +0 -8
  333. package/dist/drizzle/transaction-owner.mjs.map +0 -1
  334. package/src/drizzle/crypto.ts +0 -14
  335. package/src/drizzle/d1-emails.ts +0 -1945
  336. package/src/drizzle/d1-generated-statement.ts +0 -129
  337. package/src/drizzle/d1-oauth.ts +0 -360
  338. package/src/drizzle/d1-passkey.ts +0 -383
  339. package/src/drizzle/d1-password-prepared.ts +0 -1266
  340. package/src/drizzle/d1-passwords.ts +0 -2061
  341. package/src/drizzle/d1-phone.ts +0 -103
  342. package/src/drizzle/d1-planning.ts +0 -104
  343. package/src/drizzle/d1-proofs.ts +0 -2354
  344. package/src/drizzle/d1-sessions.ts +0 -2940
  345. package/src/drizzle/d1-totp.ts +0 -88
  346. package/src/drizzle/email-registration.ts +0 -850
  347. package/src/drizzle/email-sql.ts +0 -25
  348. package/src/drizzle/mysql-emails.ts +0 -343
  349. package/src/drizzle/mysql-password-prepared.ts +0 -198
  350. package/src/drizzle/mysql-passwords.ts +0 -343
  351. package/src/drizzle/mysql-proofs.ts +0 -196
  352. package/src/drizzle/mysql-sessions.ts +0 -780
  353. package/src/drizzle/oauth-connected-target.ts +0 -21
  354. package/src/drizzle/oauth-kernel.ts +0 -10
  355. package/src/drizzle/oauth-mysql.ts +0 -10
  356. package/src/drizzle/passkey-kernel.ts +0 -4
  357. package/src/drizzle/passkey-state.ts +0 -3
  358. package/src/drizzle/passkey-target.ts +0 -23
  359. package/src/drizzle/passkey-write-target.ts +0 -9
  360. package/src/drizzle/password-prepared-model.ts +0 -241
  361. package/src/drizzle/password-prepared-sql.ts +0 -1202
  362. package/src/drizzle/password-prepared-state.ts +0 -231
  363. package/src/drizzle/password-prepared-target.ts +0 -169
  364. package/src/drizzle/password-registration.ts +0 -369
  365. package/src/drizzle/password-sql.ts +0 -16
  366. package/src/drizzle/pg-emails.ts +0 -343
  367. package/src/drizzle/pg-password-prepared.ts +0 -199
  368. package/src/drizzle/pg-passwords.ts +0 -342
  369. package/src/drizzle/pg-proofs.ts +0 -206
  370. package/src/drizzle/pg-sessions.ts +0 -789
  371. package/src/drizzle/phone-state.ts +0 -21
  372. package/src/drizzle/proof-sql.ts +0 -10
  373. package/src/drizzle/query-operations.ts +0 -49
  374. package/src/drizzle/session-sql.ts +0 -15
  375. package/src/drizzle/session-target.ts +0 -337
  376. package/src/drizzle/sqlite-emails.ts +0 -393
  377. package/src/drizzle/sqlite-password-prepared.ts +0 -230
  378. package/src/drizzle/sqlite-passwords.ts +0 -497
  379. package/src/drizzle/sqlite-proofs.ts +0 -229
  380. package/src/drizzle/sqlite-sessions.ts +0 -1013
  381. package/src/drizzle/totp-state.ts +0 -525
  382. package/src/drizzle/transaction-execution.ts +0 -13
  383. package/src/drizzle/transaction-owner.ts +0 -11
@@ -1,2061 +0,0 @@
1
- /* oxlint-disable no-explicit-any -- D1 plans bridge consumer-owned Drizzle rows and SQL statements. */
2
- import type { D1Client } from "@effect/sql-d1/D1Client";
3
- import {
4
- NativeDatabase,
5
- requireStandalone,
6
- samePasswordCredentialSnapshot as sameSnapshot,
7
- } from "@yielded/auth-persistence/Adapter";
8
- import {
9
- coordinateCommit,
10
- CurrentCommitJournal,
11
- hasCommitScope,
12
- type CommitJournal,
13
- type PreparedCommit,
14
- LifecycleHooks,
15
- HookConfigurationError,
16
- } from "@yielded/auth/Hooks";
17
- import type { LoginIdentifier } from "@yielded/auth/Identity";
18
- import {
19
- EncodedPasswordHash,
20
- PasswordUnavailable,
21
- PasswordPersistence,
22
- type PasswordMutationInput,
23
- type PreparePasswordCommit,
24
- snapshotPasswordCredential,
25
- snapshotPasswordRequirement,
26
- snapshotPasswordRevision,
27
- type PasswordAction,
28
- type PasswordMutationDecision,
29
- } from "@yielded/auth/Password";
30
- import { reportPersistenceFailure } from "@yielded/auth/Persistence";
31
- import {
32
- type ProofCompletionPlan,
33
- type ProofCompletionDecision,
34
- type ProofCompletionInput,
35
- ProofUnavailable,
36
- } from "@yielded/auth/Proofs";
37
- import {
38
- snapshotAuthenticationEvidence,
39
- AuthenticationEvidence,
40
- AuthenticationRequirement,
41
- type SecurityRevision,
42
- } from "@yielded/auth/Sessions";
43
- import {
44
- and,
45
- eq,
46
- getTableColumns,
47
- inArray,
48
- sql,
49
- type AnyRelations,
50
- type SQL,
51
- type Table,
52
- } from "drizzle-orm";
53
- import type { EffectSQLiteD1Database } from "drizzle-orm/effect-d1";
54
- import type { AnySQLiteTable } from "drizzle-orm/sqlite-core";
55
- import { Cause, DateTime, Effect, Option, Predicate, Redacted, Schema, Context } from "effect";
56
- import type { Statement } from "effect/sql/Statement";
57
-
58
- import { Database as DatabaseService } from "./d1-database";
59
- import { balancedD1And } from "./d1-generated-statement";
60
- import { CurrentD1PlanningDatabase, makeD1Owner } from "./d1-planning";
61
- import { compileD1ProofCompletionPlan, type D1ProtectedProofMutation } from "./d1-proofs";
62
- import { D1BatchStatements } from "./D1BatchStatements";
63
- import { column, isMappedConstraintConflict, PersistenceMappingError, updateValues } from "./model";
64
- import { nativeDatabase } from "./native-database";
65
- import {
66
- type AnyPasswordRegistrationMapping,
67
- type D1PasswordPersistenceMapping,
68
- type PasswordRegistrationMapping,
69
- requiredPasswordConstraints,
70
- requiredPasswordRegistrationConstraints,
71
- } from "./password-model";
72
- import type { PasswordRegistrationAuthority } from "./password-registration";
73
- import type { D1ProofPersistenceMapping } from "./proof-model";
74
- import { validateDrizzleStorage } from "./storage-validation";
75
- import type { SuppliedService } from "./SuppliedService";
76
-
77
- type PlanPrepare<Method extends (...args: any[]) => any, A> = (
78
- value: Parameters<Parameters<Method>[1]>[0],
79
- journal: CommitJournal,
80
- ) => PreparedCommit<A>;
81
-
82
- type Database = EffectSQLiteD1Database<AnyRelations> & { readonly $client: D1Client };
83
- type Mapping = D1PasswordPersistenceMapping<Table, Table, Table, Table, Table, unknown>;
84
- type RegistrationMapping<Registration> = AnyPasswordRegistrationMapping<Registration>;
85
-
86
- interface Planned<A> {
87
- readonly receipt: A;
88
- readonly statements: ReadonlyArray<Statement<any>>;
89
- /** Final-state guards without `changes()`, repeated after coordinated statements. */
90
- readonly postconditions?: ReadonlyArray<Statement<any>>;
91
- readonly retryable?: (cause: unknown) => boolean;
92
- readonly journalGuard?: PreparedCommit<void>;
93
- }
94
-
95
- const unavailable = () => PasswordUnavailable.make({});
96
-
97
- const everyFailureMatches = (
98
- cause: Cause.Cause<unknown>,
99
- classify: ((cause: unknown) => boolean) | undefined,
100
- ): boolean =>
101
- classify !== undefined &&
102
- cause.reasons.length > 0 &&
103
- cause.reasons.every((reason) => Cause.isFailReason(reason) && classify(Cause.fail(reason.error)));
104
-
105
- const validConstraints = (mapping: Mapping) =>
106
- Object.entries(requiredPasswordConstraints).every(
107
- ([key, value]) =>
108
- mapping.constraints[key as keyof typeof requiredPasswordConstraints] === value,
109
- );
110
-
111
- const validRegistrationConstraints = <Registration>(mapping: RegistrationMapping<Registration>) =>
112
- Object.entries(requiredPasswordRegistrationConstraints).every(
113
- ([key, value]) =>
114
- mapping.constraints[key as keyof typeof requiredPasswordRegistrationConstraints] === value,
115
- );
116
-
117
- const containsFailure = (failure: unknown, predicate: (value: unknown) => boolean): boolean => {
118
- const seen = new Set<unknown>();
119
- const pending: Array<unknown> = [failure];
120
-
121
- for (let inspected = 0; inspected < 32 && pending.length > 0; inspected++) {
122
- const current = pending.shift();
123
-
124
- if (current === undefined || current === null || seen.has(current)) continue;
125
- seen.add(current);
126
- if (predicate(current)) return true;
127
- if (Cause.isCause(current)) {
128
- for (const reason of current.reasons) {
129
- if (Cause.isFailReason(reason)) pending.push(reason.error);
130
- else if (Cause.isDieReason(reason)) pending.push(reason.defect);
131
- }
132
- continue;
133
- }
134
- if (typeof current !== "object") continue;
135
-
136
- const wrapped = current as {
137
- readonly cause?: unknown;
138
- readonly reason?: unknown;
139
- readonly message?: unknown;
140
- };
141
-
142
- if (wrapped.cause !== undefined) pending.push(wrapped.cause);
143
- if (wrapped.reason !== undefined) pending.push(wrapped.reason);
144
- if (wrapped.message !== undefined) pending.push(wrapped.message);
145
- }
146
-
147
- return false;
148
- };
149
-
150
- // Match only workerd's native SQLite diagnostic. A wrapper that merely echoes
151
- // the bound marker is not evidence that the guard ran and rolled back the batch.
152
- const isGuardFailure = (cause: unknown, marker: string) => {
153
- const path = `$[${marker.replaceAll("'", "''")}]`;
154
- const native = `bad JSON path: '${path}': SQLITE_ERROR`;
155
-
156
- return containsFailure(cause, (value) => value === native || value === `D1_ERROR: ${native}`);
157
- };
158
-
159
- const statement = Effect.fn(function* (query: {
160
- readonly toSQL: () => { readonly sql: string; readonly params: unknown[] };
161
- }) {
162
- const database = yield* CurrentD1PlanningDatabase;
163
- const rendered = query.toSQL();
164
-
165
- return database.$client.unsafe(rendered.sql, rendered.params);
166
- });
167
-
168
- const assertion = Effect.fn(function* (condition: SQL, marker: string) {
169
- const database = yield* CurrentD1PlanningDatabase;
170
-
171
- return yield* statement(
172
- database
173
- .select({
174
- ok: sql`case when ${condition} then 1 else json_extract('[]', ${`$[${marker}]`}) end`.as(
175
- "ok",
176
- ),
177
- })
178
- .from(sql`(select 1)`),
179
- );
180
- });
181
-
182
- const readEngineNowMillis = Effect.fn("Drizzle.readEngineNowMillis")(function* (mapping: Mapping) {
183
- const database = yield* CurrentD1PlanningDatabase;
184
-
185
- return yield* database
186
- .select({ value: mapping.d1.engineNowMillis.as("engine_now_millis") })
187
- .from(sql`(select 1)`)
188
- .pipe(
189
- Effect.flatMap((rows) => {
190
- const value = Number(rows[0]?.value);
191
-
192
- return Number.isFinite(value) ? Effect.succeed(value) : Effect.fail(unavailable());
193
- }),
194
- );
195
- });
196
-
197
- const allocate = <A>(
198
- asyncValue: Effect.Effect<A, PersistenceMappingError> | undefined,
199
- syncValue: (() => A) | undefined,
200
- ) =>
201
- asyncValue !== undefined
202
- ? asyncValue
203
- : syncValue === undefined
204
- ? Effect.fail(unavailable())
205
- : Effect.try({
206
- try: syncValue,
207
- catch: (cause) => new PersistenceMappingError({ operation: "mapping", cause }),
208
- });
209
-
210
- const allocateNextSecurityRevision = (mapping: Mapping, current: SecurityRevision) =>
211
- allocate(
212
- mapping.subject.nextSecurityRevision?.(current) ?? mapping.allocateRevision,
213
- mapping.subject.nextSecurityRevisionSync === undefined
214
- ? mapping.allocateRevisionSync
215
- : () => mapping.subject.nextSecurityRevisionSync!(current),
216
- );
217
-
218
- const subjectColumns = (mapping: Mapping) => ({
219
- id: column(mapping.subject.table, mapping.subject.id),
220
- status: column(mapping.subject.table, mapping.subject.status),
221
- securityRevision: column(mapping.subject.table, mapping.subject.securityRevision),
222
- });
223
-
224
- const identifierColumns = (mapping: Mapping) => ({
225
- namespace: column(mapping.identifier.table, mapping.identifier.namespace),
226
- value: column(mapping.identifier.table, mapping.identifier.value),
227
- subjectId: column(mapping.identifier.table, mapping.identifier.subjectId),
228
- verifiedAt: column(mapping.identifier.table, mapping.identifier.verifiedAt),
229
- bindingRevision: column(mapping.identifier.table, mapping.identifier.bindingRevision),
230
- });
231
-
232
- const credentialColumns = (mapping: Mapping) => ({
233
- moduleId: column(mapping.credential.table, mapping.credential.moduleId),
234
- subjectId: column(mapping.credential.table, mapping.credential.subjectId),
235
- credentialId: column(mapping.credential.table, mapping.credential.credentialId),
236
- credentialRevision: column(mapping.credential.table, mapping.credential.credentialRevision),
237
- verifierVersion: column(mapping.credential.table, mapping.credential.verifierVersion),
238
- verifier: column(mapping.credential.table, mapping.credential.verifier),
239
- normalization: column(mapping.credential.table, mapping.credential.normalization),
240
- });
241
-
242
- const authorityColumns = (mapping: Mapping) => ({
243
- subjectId: column(mapping.authorityCredential.table, mapping.authorityCredential.subjectId),
244
- credentialId: column(mapping.authorityCredential.table, mapping.authorityCredential.credentialId),
245
- revision: column(mapping.authorityCredential.table, mapping.authorityCredential.revision),
246
- status:
247
- mapping.authorityCredential.status === undefined
248
- ? undefined
249
- : column(mapping.authorityCredential.table, mapping.authorityCredential.status),
250
- });
251
-
252
- const commandColumns = (mapping: Mapping) => ({
253
- moduleId: column(mapping.command.table, mapping.command.moduleId),
254
- commandId: column(mapping.command.table, mapping.command.commandId),
255
- action: column(mapping.command.table, mapping.command.action),
256
- bindingDigest: column(mapping.command.table, mapping.command.bindingDigest),
257
- decision: column(mapping.command.table, mapping.command.decision),
258
- retentionUntil: column(mapping.command.table, mapping.command.retentionUntil),
259
- });
260
-
261
- const readIdentifier = Effect.fn("Drizzle.readIdentifier")(function* (
262
- mapping: Mapping,
263
- identifier: LoginIdentifier,
264
- ) {
265
- const database = yield* CurrentD1PlanningDatabase;
266
-
267
- const c = identifierColumns(mapping);
268
-
269
- return yield* database
270
- .select()
271
- .from(mapping.identifier.table)
272
- .where(and(eq(c.namespace, identifier.namespace), eq(c.value, identifier.value)))
273
- .limit(1);
274
- });
275
-
276
- const readSubjectNative = Effect.fn("Drizzle.readSubjectNative")(function* (
277
- mapping: Mapping,
278
- nativeSubjectId: unknown,
279
- ) {
280
- const database = yield* CurrentD1PlanningDatabase;
281
-
282
- return yield* database
283
- .select()
284
- .from(mapping.subject.table)
285
- .where(eq(subjectColumns(mapping).id, nativeSubjectId))
286
- .limit(1);
287
- });
288
-
289
- const readCredentialNative = Effect.fn("Drizzle.readCredentialNative")(function* (
290
- mapping: Mapping,
291
- moduleId: string,
292
- nativeSubjectId: unknown,
293
- ) {
294
- const database = yield* CurrentD1PlanningDatabase;
295
-
296
- const c = credentialColumns(mapping);
297
-
298
- return yield* database
299
- .select()
300
- .from(mapping.credential.table)
301
- .where(and(eq(c.moduleId, moduleId), eq(c.subjectId, nativeSubjectId)))
302
- .limit(1);
303
- });
304
-
305
- const resolveCredential = Effect.fn("DrizzleD1Password.resolveCredential")(function* (
306
- mapping: Mapping,
307
- moduleId: string,
308
- identifier: LoginIdentifier,
309
- requestedSubjectId?: string,
310
- ) {
311
- const identifierRow = (yield* readIdentifier(mapping, identifier))[0];
312
-
313
- if (identifierRow === undefined || !mapping.identifier.isCurrent(identifierRow)) return undefined;
314
- const nativeSubjectId = identifierRow[mapping.identifier.subjectId];
315
- const subject = (yield* readSubjectNative(mapping, nativeSubjectId))[0];
316
-
317
- if (subject === undefined || !mapping.subject.isActiveStatus(subject[mapping.subject.status]))
318
- return undefined;
319
- const subjectId = yield* mapping.subjectId.toSubject(nativeSubjectId);
320
-
321
- if (requestedSubjectId !== undefined && subjectId !== requestedSubjectId) return undefined;
322
- const credential = (yield* readCredentialNative(mapping, moduleId, nativeSubjectId))[0];
323
-
324
- if (credential === undefined) return { nativeSubjectId, subject, identifier: identifierRow };
325
-
326
- const snapshot = yield* mapping.credential
327
- .decode({ moduleId, subject, identifier: identifierRow, credential })
328
- .pipe(Effect.flatMap(snapshotPasswordCredential));
329
-
330
- return { nativeSubjectId, subject, identifier: identifierRow, credential, snapshot };
331
- });
332
-
333
- const evidenceProofsJson = Schema.encodeSync(
334
- Schema.fromJsonString(AuthenticationEvidence.fields.proofs),
335
- );
336
-
337
- const alternativesJson = Schema.encodeSync(
338
- Schema.fromJsonString(AuthenticationRequirement.fields.alternatives),
339
- );
340
-
341
- const assuranceCondition = (
342
- mapping: Mapping,
343
- evidence: PasswordMutationInput["authorization"]["evidence"],
344
- requirement: PasswordMutationInput["authorization"]["requirement"],
345
- ) => {
346
- const proofs = evidenceProofsJson(evidence.proofs);
347
- const fresh = sql`json_extract(proof.value, '$.verifiedAt') <= ${mapping.d1.engineNowMillis} and ${mapping.d1.engineNowMillis} - json_extract(proof.value, '$.verifiedAt') < ${requirement.maximumAgeMillis}`;
348
-
349
- return sql`exists(
350
- select 1 from json_each(${alternativesJson(requirement.alternatives)}) as alternative
351
- where not exists(select 1 from json_each(${proofs}) as proof where json_extract(proof.value, '$.verifiedAt') > ${mapping.d1.engineNowMillis})
352
- and not exists(select 1 from json_each(alternative.value, '$.factors') as factor where not exists(select 1 from json_each(${proofs}) as proof, json_each(proof.value, '$.factors') as held where ${fresh} and held.value = factor.value))
353
- and (select count(distinct json_extract(proof.value, '$.credentialId')) from json_each(${proofs}) as proof where ${fresh}) >= json_extract(alternative.value, '$.minimumCredentials')
354
- and exists(select 1 from json_each(${proofs}) as proof where ${fresh} and (json_extract(alternative.value, '$.userVerified') = 0 or json_extract(proof.value, '$.userVerified') = 1) and (json_extract(alternative.value, '$.phishingResistant') = 0 or json_extract(proof.value, '$.phishingResistant') = 1))
355
- )`;
356
- };
357
-
358
- const sameIdentifier = (left: LoginIdentifier, right: LoginIdentifier) =>
359
- left.namespace === right.namespace && left.value === right.value;
360
-
361
- const evidenceSatisfiedAt = (
362
- evidence: PasswordMutationInput["authorization"]["evidence"],
363
- requirement: PasswordMutationInput["authorization"]["requirement"],
364
- now: number,
365
- ) => {
366
- if (evidence.proofs.length > 64 || evidence.revision.credentials.length > 64) return false;
367
- const revisions = new Map<string, string>();
368
-
369
- for (const revision of evidence.revision.credentials) {
370
- if (revisions.has(revision.credentialId)) return false;
371
- revisions.set(revision.credentialId, revision.revision);
372
- }
373
- if (evidence.proofs.some((proof) => !revisions.has(proof.credentialId))) return false;
374
- if (evidence.proofs.some((proof) => now < DateTime.toEpochMillis(proof.verifiedAt))) return false;
375
-
376
- const fresh = evidence.proofs.filter(
377
- (proof) => now - DateTime.toEpochMillis(proof.verifiedAt) < requirement.maximumAgeMillis,
378
- );
379
-
380
- const factors = new Set(fresh.flatMap((proof) => proof.factors));
381
- const credentials = new Set(fresh.map((proof) => proof.credentialId));
382
-
383
- return requirement.alternatives.some(
384
- (alternative) =>
385
- alternative.factors.every((factor) => factors.has(factor)) &&
386
- credentials.size >= alternative.minimumCredentials &&
387
- fresh.some(
388
- (proof) =>
389
- (!alternative.userVerified || proof.userVerified) &&
390
- (!alternative.phishingResistant || proof.phishingResistant),
391
- ),
392
- );
393
- };
394
-
395
- const snapshotMutationInput = Effect.fn("DrizzleD1Password.snapshotMutationInput")(function* (
396
- input: PasswordMutationInput,
397
- ) {
398
- const expectedRevision = snapshotPasswordRevision(input.expectedRevision);
399
-
400
- const evidence = yield* snapshotAuthenticationEvidence(input.authorization.evidence).pipe(
401
- Effect.mapError(unavailable),
402
- );
403
-
404
- const requirement = yield* snapshotPasswordRequirement(input.authorization.requirement);
405
-
406
- const credential =
407
- input.credential === undefined
408
- ? undefined
409
- : yield* snapshotPasswordCredential(input.credential);
410
-
411
- return Object.freeze({
412
- ...input,
413
- expectedRevision,
414
- replacement: Object.freeze({
415
- verifier: Redacted.make(Redacted.value(input.replacement.verifier)),
416
- normalization: input.replacement.normalization,
417
- }),
418
- invalidation: Object.freeze({ ...input.invalidation }),
419
- ...(credential === undefined ? {} : { credential }),
420
- authorization: Object.freeze({
421
- challenge: Object.freeze({
422
- ...input.authorization.challenge,
423
- revision: snapshotPasswordRevision(input.authorization.challenge.revision),
424
- }),
425
- evidence,
426
- requirement,
427
- }),
428
- });
429
- });
430
-
431
- const completionMatchesPassword = (
432
- input: PasswordMutationInput & { readonly completion: ProofCompletionPlan },
433
- ) => {
434
- const completion = input.completion.input;
435
- const binding = completion.binding;
436
-
437
- if (
438
- completion.moduleId !== `${input.moduleId}/reset` ||
439
- completion.purpose !== "password-reset" ||
440
- binding._tag !== "Subject" ||
441
- input.credential === undefined ||
442
- binding.revision.subjectId !== input.expectedRevision.subjectId ||
443
- binding.revision.securityRevision !== input.expectedRevision.securityRevision ||
444
- !sameIdentifier(binding.identifier, input.credential.identifier)
445
- )
446
- return false;
447
-
448
- const expected = [...input.expectedRevision.credentials].sort((left, right) =>
449
- left.credentialId.localeCompare(right.credentialId),
450
- );
451
-
452
- const actual = [...binding.revision.credentials].sort((left, right) =>
453
- left.credentialId.localeCompare(right.credentialId),
454
- );
455
-
456
- return (
457
- expected.length === actual.length &&
458
- expected.every(
459
- (item, index) =>
460
- item.credentialId === actual[index]!.credentialId &&
461
- item.revision === actual[index]!.revision,
462
- )
463
- );
464
- };
465
-
466
- const probeProofCompletion = Effect.fn("DrizzleD1.probeProofCompletion")(function* (
467
- mapping: D1ProofPersistenceMapping<any, any, any, any, any, any, any, any, any, any, any, any>,
468
- input: ProofCompletionInput,
469
- ) {
470
- const database = yield* CurrentD1PlanningDatabase;
471
-
472
- const plan: ProofCompletionPlan = {
473
- input,
474
- prepare: (decision, journal, project) => journal.prepare(project(decision)),
475
- };
476
-
477
- const marker = `effect-auth-proof-guard:protected:${input.continuationId}`;
478
-
479
- const once = coordinateCommit(
480
- () =>
481
- Effect.gen(function* () {
482
- const compiled = yield* compileD1ProofCompletionPlan(
483
- mapping,
484
- plan,
485
- { statements: [], appliedCondition: sql`false` },
486
- () => false,
487
- );
488
-
489
- if (compiled.statements.length === 0) return false;
490
- yield* database.$client.batch(compiled.statements);
491
-
492
- return false;
493
- }),
494
- { mode: "batch" },
495
- );
496
-
497
- return yield* once.pipe(
498
- Effect.as(false),
499
- Effect.catchCause((cause) =>
500
- isGuardFailure(cause, marker) ? Effect.succeed(true) : Effect.failCause(cause),
501
- ),
502
- );
503
- });
504
-
505
- const identifierCurrentCondition = (
506
- mapping: Mapping,
507
- input: PasswordMutationInput,
508
- nativeSubjectId: unknown,
509
- nativeVerifiedAt: unknown,
510
- ) => {
511
- if (input.credential === undefined) return sql`true`;
512
-
513
- const ic = identifierColumns(mapping),
514
- snapshot = input.credential;
515
-
516
- return and(
517
- mapping.identifier.d1CurrentCondition({ identifier: snapshot.identifier, nativeSubjectId }),
518
- sql`exists(select 1 from ${mapping.identifier.table} where ${ic.namespace} = ${sql.param(snapshot.identifier.namespace, ic.namespace)} and ${ic.value} = ${sql.param(snapshot.identifier.value, ic.value)} and ${ic.subjectId} = ${sql.param(nativeSubjectId, ic.subjectId)} and ${ic.bindingRevision} = ${sql.param(snapshot.identifierBindingRevision, ic.bindingRevision)} and ${Predicate.isNullish(nativeVerifiedAt) ? sql`${ic.verifiedAt} is null` : sql`${ic.verifiedAt} = ${sql.param(nativeVerifiedAt, ic.verifiedAt)}`})`,
519
- )!;
520
- };
521
-
522
- const readAuthority = Effect.fn("DrizzleD1Password.readAuthority")(function* (
523
- mapping: Mapping,
524
- input: PasswordMutationInput,
525
- action: PasswordAction,
526
- ) {
527
- const database = yield* CurrentD1PlanningDatabase;
528
-
529
- if (
530
- input.authorization.evidence.proofs.length > 64 ||
531
- input.authorization.evidence.revision.credentials.length > 64
532
- )
533
- return undefined;
534
-
535
- const revisionIds = new Set(
536
- input.authorization.evidence.revision.credentials.map((item) => item.credentialId),
537
- );
538
-
539
- if (
540
- revisionIds.size !== input.authorization.evidence.revision.credentials.length ||
541
- input.authorization.evidence.proofs.some((proof) => !revisionIds.has(proof.credentialId))
542
- )
543
- return undefined;
544
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.expectedRevision.subjectId);
545
- const subject = (yield* readSubjectNative(mapping, nativeSubjectId))[0];
546
-
547
- if (
548
- subject === undefined ||
549
- !mapping.subject.isActiveStatus(subject[mapping.subject.status]) ||
550
- subject[mapping.subject.securityRevision] !== input.expectedRevision.securityRevision ||
551
- input.authorization.challenge.moduleId !== input.moduleId ||
552
- input.authorization.challenge.action !== action ||
553
- input.authorization.challenge.commandId !== input.commandId ||
554
- input.authorization.challenge.revision.subjectId !== input.expectedRevision.subjectId ||
555
- input.authorization.challenge.revision.securityRevision !==
556
- input.expectedRevision.securityRevision ||
557
- input.authorization.evidence.revision.subjectId !== input.expectedRevision.subjectId ||
558
- input.authorization.evidence.revision.securityRevision !==
559
- input.expectedRevision.securityRevision ||
560
- (input.authorization.evidence.flowId as string) !== (input.commandId as string) ||
561
- input.authorization.evidence.bindingDigest !== input.authorization.challenge.bindingDigest ||
562
- (action === "add-password" &&
563
- (input.credential !== undefined ||
564
- input.authorization.challenge.targetCredentialId !== undefined)) ||
565
- input.expectedRevision.credentials.some(
566
- (expected) =>
567
- !input.authorization.evidence.revision.credentials.some(
568
- (actual) =>
569
- actual.credentialId === expected.credentialId && actual.revision === expected.revision,
570
- ),
571
- )
572
- )
573
- return undefined;
574
- let identifierCondition: SQL = sql`true`;
575
-
576
- if (input.credential !== undefined) {
577
- const identifier = (yield* readIdentifier(mapping, input.credential.identifier))[0];
578
-
579
- if (
580
- identifier === undefined ||
581
- input.authorization.challenge.targetCredentialId !== input.credential.credentialId ||
582
- !mapping.identifier.isCurrent(identifier) ||
583
- !mapping.subjectId.equals(identifier[mapping.identifier.subjectId], nativeSubjectId) ||
584
- identifier[mapping.identifier.bindingRevision] !== input.credential.identifierBindingRevision
585
- )
586
- return undefined;
587
- const credential = (yield* readCredentialNative(mapping, input.moduleId, nativeSubjectId))[0];
588
-
589
- if (credential === undefined) return undefined;
590
-
591
- const snapshot = yield* mapping.credential.decode({
592
- moduleId: input.moduleId,
593
- subject,
594
- identifier,
595
- credential,
596
- });
597
-
598
- if (!sameSnapshot(snapshot, input.credential)) return undefined;
599
- identifierCondition = identifierCurrentCondition(
600
- mapping,
601
- input,
602
- nativeSubjectId,
603
- identifier[mapping.identifier.verifiedAt],
604
- );
605
- const ic = identifierColumns(mapping);
606
-
607
- identifierCondition = and(
608
- identifierCondition,
609
- sql`exists(select 1 from ${mapping.identifier.table} where ${ic.namespace} = ${sql.param(input.credential.identifier.namespace, ic.namespace)} and ${ic.value} = ${sql.param(input.credential.identifier.value, ic.value)} and ${ic.subjectId} = ${sql.param(nativeSubjectId, ic.subjectId)} and ${ic.bindingRevision} = ${sql.param(input.credential.identifierBindingRevision, ic.bindingRevision)})`,
610
- )!;
611
- }
612
-
613
- const expected = [
614
- ...new Map(
615
- [
616
- ...input.expectedRevision.credentials,
617
- ...input.authorization.evidence.revision.credentials,
618
- ].map((item) => [item.credentialId, item]),
619
- ).values(),
620
- ].sort((left, right) => left.credentialId.localeCompare(right.credentialId));
621
-
622
- const ac = authorityColumns(mapping);
623
-
624
- const rows =
625
- expected.length === 0
626
- ? []
627
- : yield* database
628
- .select()
629
- .from(mapping.authorityCredential.table)
630
- .where(
631
- and(
632
- eq(ac.subjectId, nativeSubjectId),
633
- inArray(
634
- ac.credentialId,
635
- expected.map((item) => item.credentialId),
636
- ),
637
- ),
638
- );
639
-
640
- const sortedRows = [...rows].sort((left: any, right: any) =>
641
- String(left[mapping.authorityCredential.credentialId]).localeCompare(
642
- String(right[mapping.authorityCredential.credentialId]),
643
- ),
644
- );
645
-
646
- if (
647
- sortedRows.length !== expected.length ||
648
- sortedRows.some(
649
- (row: any, index) =>
650
- row[mapping.authorityCredential.credentialId] !== expected[index]!.credentialId ||
651
- row[mapping.authorityCredential.revision] !== expected[index]!.revision ||
652
- (mapping.authorityCredential.status !== undefined &&
653
- mapping.authorityCredential.isActiveStatus?.(row[mapping.authorityCredential.status]) !==
654
- true),
655
- )
656
- )
657
- return undefined;
658
- const requirement = yield* mapping.subject.decodeActionRequirement(subject, action);
659
- const engineNow = yield* readEngineNowMillis(mapping);
660
-
661
- if (
662
- !evidenceSatisfiedAt(
663
- input.authorization.evidence,
664
- input.authorization.requirement,
665
- engineNow,
666
- ) ||
667
- !evidenceSatisfiedAt(input.authorization.evidence, requirement, engineNow)
668
- )
669
- return undefined;
670
- if (
671
- (input.invalidation.existingSessions === "immediate" ||
672
- input.invalidation.existingSessions === "cache-expiry") &&
673
- mapping.sessionInvalidation !== "same-authority-immediate"
674
- )
675
- return undefined;
676
- const s = subjectColumns(mapping);
677
-
678
- const credentialConditions = expected.map(
679
- (item) =>
680
- sql`exists(select 1 from ${mapping.authorityCredential.table} where ${ac.subjectId} = ${sql.param(nativeSubjectId, ac.subjectId)} and ${ac.credentialId} = ${sql.param(item.credentialId, ac.credentialId)} and ${ac.revision} = ${sql.param(item.revision, ac.revision)}${ac.status === undefined ? sql`` : sql` and ${ac.status} = ${sql.param(mapping.authorityCredential.d1ActiveStatusValue, ac.status)}`})`,
681
- );
682
-
683
- return {
684
- nativeSubjectId,
685
- subject,
686
- requirement,
687
- identifierCondition,
688
- condition: balancedD1And(
689
- sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(nativeSubjectId, s.id)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)} and ${s.securityRevision} = ${sql.param(input.expectedRevision.securityRevision, s.securityRevision)})`,
690
- identifierCondition,
691
- ...credentialConditions,
692
- assuranceCondition(mapping, input.authorization.evidence, input.authorization.requirement),
693
- assuranceCondition(mapping, input.authorization.evidence, requirement),
694
- )!,
695
- };
696
- });
697
-
698
- const commandAbsentCondition = (mapping: Mapping, moduleId: string, commandId: string) => {
699
- const c = commandColumns(mapping);
700
-
701
- return sql`not exists(select 1 from ${mapping.command.table} where ${c.moduleId} = ${sql.param(moduleId, c.moduleId)} and ${c.commandId} = ${sql.param(commandId, c.commandId)})`;
702
- };
703
-
704
- const credentialCurrentCondition = (
705
- mapping: Mapping,
706
- input: PasswordMutationInput,
707
- nativeSubjectId: unknown,
708
- ) => {
709
- const c = credentialColumns(mapping);
710
-
711
- return input.credential === undefined
712
- ? sql`false`
713
- : sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(input.credential.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(input.credential.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(input.credential.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.credential.verifier), c.verifier)} and ${c.normalization} = ${sql.param(input.credential.normalization, c.normalization)})`;
714
- };
715
-
716
- const mutationStatements = Effect.fn(function* (
717
- mapping: Mapping,
718
- input: PasswordMutationInput,
719
- nativeSubjectId: unknown,
720
- revisions: {
721
- readonly credentialRevision: SecurityRevision;
722
- readonly verifierVersion: SecurityRevision;
723
- readonly nextSecurityRevision: SecurityRevision;
724
- },
725
- now: number,
726
- ) {
727
- const database = yield* CurrentD1PlanningDatabase;
728
- const c = credentialColumns(mapping);
729
- const s = subjectColumns(mapping);
730
- const ac = authorityColumns(mapping);
731
-
732
- return [
733
- yield* statement(
734
- database
735
- .update(mapping.credential.table)
736
- .set(
737
- mapping.credential.encodeReplacement({
738
- replacement: input.replacement,
739
- credentialRevision: revisions.credentialRevision,
740
- verifierVersion: revisions.verifierVersion,
741
- }),
742
- )
743
- .where(
744
- and(
745
- eq(c.moduleId, input.moduleId),
746
- eq(c.subjectId, nativeSubjectId),
747
- eq(c.credentialId, input.credential!.credentialId),
748
- eq(c.credentialRevision, input.credential!.credentialRevision),
749
- eq(c.verifierVersion, input.credential!.verifierVersion),
750
- eq(c.verifier, Redacted.value(input.credential!.verifier)),
751
- eq(c.normalization, input.credential!.normalization),
752
- ),
753
- ),
754
- ),
755
- yield* statement(
756
- database
757
- .update(mapping.subject.table)
758
- .set(updateValues([[mapping.subject.securityRevision, revisions.nextSecurityRevision]]))
759
- .where(
760
- and(
761
- eq(s.id, nativeSubjectId),
762
- eq(s.securityRevision, input.expectedRevision.securityRevision),
763
- ),
764
- ),
765
- ),
766
- yield* statement(
767
- database
768
- .update(mapping.authorityCredential.table)
769
- .set(mapping.authorityCredential.encodeRevision(revisions.credentialRevision))
770
- .where(
771
- and(
772
- eq(ac.subjectId, nativeSubjectId),
773
- eq(ac.credentialId, input.credential!.credentialId),
774
- eq(ac.revision, input.credential!.credentialRevision),
775
- ),
776
- ),
777
- ),
778
- yield* statement(
779
- database.insert(mapping.command.table).values(
780
- mapping.command.encodeInsert({
781
- moduleId: input.moduleId,
782
- commandId: input.commandId,
783
- action: input.authorization.challenge.action,
784
- bindingDigest: input.authorization.challenge.bindingDigest,
785
- decision: "changed",
786
- retentionUntilMillis: now + mapping.commandRetentionMillis,
787
- }),
788
- ),
789
- ),
790
- ];
791
- });
792
-
793
- const replacementApplied = (
794
- mapping: Mapping,
795
- input: PasswordMutationInput,
796
- nativeSubjectId: unknown,
797
- revisions: {
798
- readonly credentialRevision: SecurityRevision;
799
- readonly verifierVersion: SecurityRevision;
800
- readonly nextSecurityRevision: SecurityRevision;
801
- },
802
- now: number,
803
- identifierCondition: SQL,
804
- ) => {
805
- const c = credentialColumns(mapping);
806
- const s = subjectColumns(mapping);
807
- const ac = authorityColumns(mapping);
808
- const mc = commandColumns(mapping);
809
-
810
- return and(
811
- sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(input.credential!.credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(revisions.credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(revisions.verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.replacement.verifier), c.verifier)} and ${c.normalization} = ${sql.param(input.replacement.normalization, c.normalization)})`,
812
- sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(nativeSubjectId, s.id)} and ${s.securityRevision} = ${sql.param(revisions.nextSecurityRevision, s.securityRevision)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)})`,
813
- sql`exists(select 1 from ${mapping.authorityCredential.table} where ${ac.subjectId} = ${sql.param(nativeSubjectId, ac.subjectId)} and ${ac.credentialId} = ${sql.param(input.credential!.credentialId, ac.credentialId)} and ${ac.revision} = ${sql.param(revisions.credentialRevision, ac.revision)}${ac.status === undefined ? sql`` : sql` and ${ac.status} = ${sql.param(mapping.authorityCredential.d1ActiveStatusValue, ac.status)}`})`,
814
- sql`exists(select 1 from ${mapping.command.table} where ${mc.moduleId} = ${sql.param(input.moduleId, mc.moduleId)} and ${mc.commandId} = ${sql.param(input.commandId, mc.commandId)} and ${mc.bindingDigest} = ${sql.param(input.authorization.challenge.bindingDigest, mc.bindingDigest)} and ${mc.action} = ${sql.param(input.authorization.challenge.action, mc.action)} and ${mc.decision} = ${sql.param("changed", mc.decision)} and ${mc.retentionUntil} = ${sql.param(mapping.encodeInstant(now + mapping.commandRetentionMillis), mc.retentionUntil)})`,
815
- identifierCondition,
816
- sql`true`,
817
- )!;
818
- };
819
-
820
- const compileAddMutation = Effect.fn("DrizzleD1Password.compileAddMutation")(function* (
821
- mapping: Mapping,
822
- input: PasswordMutationInput,
823
- revisions: {
824
- readonly credentialId: string;
825
- readonly credentialRevision: SecurityRevision;
826
- readonly verifierVersion: SecurityRevision;
827
- readonly nextSecurityRevision: SecurityRevision;
828
- },
829
- ) {
830
- const database = yield* CurrentD1PlanningDatabase;
831
-
832
- const { credentialId, credentialRevision, verifierVersion, nextSecurityRevision } = revisions;
833
-
834
- const authority = yield* readAuthority(mapping, input, "add-password");
835
-
836
- if (authority === undefined) return undefined;
837
- const now = yield* readEngineNowMillis(mapping);
838
- const c = credentialColumns(mapping);
839
- const s = subjectColumns(mapping);
840
- const ac = authorityColumns(mapping);
841
- const mc = commandColumns(mapping);
842
- const marker = `effect-auth-password-guard:add:${input.commandId}`;
843
- const absent = sql`not exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(authority.nativeSubjectId, c.subjectId)})`;
844
-
845
- const guard = and(
846
- authority.condition,
847
- absent,
848
- commandAbsentCondition(mapping, input.moduleId, input.commandId),
849
- )!;
850
-
851
- const appliedCondition = and(
852
- sql`exists(select 1 from ${mapping.credential.table} where ${c.moduleId} = ${sql.param(input.moduleId, c.moduleId)} and ${c.subjectId} = ${sql.param(authority.nativeSubjectId, c.subjectId)} and ${c.credentialId} = ${sql.param(credentialId, c.credentialId)} and ${c.credentialRevision} = ${sql.param(credentialRevision, c.credentialRevision)} and ${c.verifierVersion} = ${sql.param(verifierVersion, c.verifierVersion)} and ${c.verifier} = ${sql.param(Redacted.value(input.replacement.verifier), c.verifier)} and ${c.normalization} = ${sql.param(input.replacement.normalization, c.normalization)})`,
853
- sql`exists(select 1 from ${mapping.authorityCredential.table} where ${ac.subjectId} = ${sql.param(authority.nativeSubjectId, ac.subjectId)} and ${ac.credentialId} = ${sql.param(credentialId, ac.credentialId)} and ${ac.revision} = ${sql.param(credentialRevision, ac.revision)}${ac.status === undefined ? sql`` : sql` and ${ac.status} = ${sql.param(mapping.authorityCredential.d1ActiveStatusValue, ac.status)}`})`,
854
- sql`exists(select 1 from ${mapping.subject.table} where ${s.id} = ${sql.param(authority.nativeSubjectId, s.id)} and ${s.securityRevision} = ${sql.param(nextSecurityRevision, s.securityRevision)} and ${s.status} = ${sql.param(mapping.subject.d1ActiveStatusValue, s.status)})`,
855
- sql`exists(select 1 from ${mapping.command.table} where ${mc.moduleId} = ${sql.param(input.moduleId, mc.moduleId)} and ${mc.commandId} = ${sql.param(input.commandId, mc.commandId)} and ${mc.bindingDigest} = ${sql.param(input.authorization.challenge.bindingDigest, mc.bindingDigest)} and ${mc.action} = ${sql.param("add-password", mc.action)} and ${mc.decision} = ${sql.param("changed", mc.decision)} and ${mc.retentionUntil} = ${sql.param(mapping.encodeInstant(now + mapping.commandRetentionMillis), mc.retentionUntil)})`,
856
- )!;
857
-
858
- const statements: Statement<any>[] = [
859
- yield* assertion(guard, marker),
860
- yield* statement(
861
- database.insert(mapping.credential.table).values(
862
- mapping.credential.encodeInsert({
863
- moduleId: input.moduleId,
864
- subjectId: authority.nativeSubjectId,
865
- credentialId,
866
- credentialRevision,
867
- verifierVersion,
868
- replacement: input.replacement,
869
- }),
870
- ),
871
- ),
872
- yield* statement(
873
- database.insert(mapping.authorityCredential.table).values(
874
- mapping.authorityCredential.encodeInsert({
875
- subjectId: authority.nativeSubjectId,
876
- credentialId,
877
- revision: credentialRevision,
878
- }),
879
- ),
880
- ),
881
- yield* statement(
882
- database
883
- .update(mapping.subject.table)
884
- .set(updateValues([[mapping.subject.securityRevision, nextSecurityRevision]]))
885
- .where(
886
- and(
887
- eq(s.id, authority.nativeSubjectId),
888
- eq(s.securityRevision, input.expectedRevision.securityRevision),
889
- ),
890
- ),
891
- ),
892
- yield* statement(
893
- database.insert(mapping.command.table).values(
894
- mapping.command.encodeInsert({
895
- moduleId: input.moduleId,
896
- commandId: input.commandId,
897
- action: "add-password",
898
- bindingDigest: input.authorization.challenge.bindingDigest,
899
- decision: "changed",
900
- retentionUntilMillis: now + mapping.commandRetentionMillis,
901
- }),
902
- ),
903
- ),
904
- yield* assertion(appliedCondition, marker),
905
- ];
906
-
907
- return {
908
- statements,
909
- appliedCondition,
910
- marker,
911
- retryable: (cause: unknown) => isGuardFailure(cause, marker),
912
- };
913
- });
914
-
915
- const makePasswordPlans = (
916
- mapping: Mapping,
917
- proofMapping?: D1ProofPersistenceMapping<
918
- any,
919
- any,
920
- any,
921
- any,
922
- any,
923
- any,
924
- any,
925
- any,
926
- any,
927
- any,
928
- any,
929
- any
930
- >,
931
- ) => {
932
- const run = <A, E, R>(plan: Effect.Effect<Planned<A>, E, R>) =>
933
- Effect.gen(function* () {
934
- if (!validConstraints(mapping)) return yield* unavailable();
935
-
936
- return yield* plan;
937
- });
938
-
939
- return {
940
- findCredential: (uncaptured: Parameters<PasswordPersistence["Service"]["findCredential"]>[0]) =>
941
- Effect.gen(function* () {
942
- if (!validConstraints(mapping)) return yield* unavailable();
943
- const input = { ...uncaptured, identifier: { ...uncaptured.identifier } };
944
- const database = yield* CurrentD1PlanningDatabase;
945
- const identifierRow = (yield* readIdentifier(mapping, input.identifier))[0];
946
-
947
- if (identifierRow === undefined || !mapping.identifier.isCurrent(identifierRow))
948
- return Option.none();
949
- const nativeId = identifierRow[mapping.identifier.subjectId];
950
-
951
- const sc = subjectColumns(mapping),
952
- ic = identifierColumns(mapping),
953
- cc = credentialColumns(mapping);
954
-
955
- // Bind the same native ID through each column's codec, then inspect one coherent snapshot.
956
- const rows = yield* database
957
- .select({
958
- subject: getTableColumns(mapping.subject.table),
959
- identifier: getTableColumns(mapping.identifier.table),
960
- credential: getTableColumns(mapping.credential.table),
961
- })
962
- .from(mapping.subject.table)
963
- .innerJoin(
964
- mapping.identifier.table,
965
- and(
966
- eq(ic.namespace, input.identifier.namespace),
967
- eq(ic.value, input.identifier.value),
968
- eq(ic.subjectId, nativeId),
969
- ),
970
- )
971
- .innerJoin(
972
- mapping.credential.table,
973
- and(eq(cc.moduleId, input.moduleId), eq(cc.subjectId, nativeId)),
974
- )
975
- .where(eq(sc.id, nativeId));
976
-
977
- if (rows.length !== 1) return Option.none();
978
- const row = rows[0]!;
979
-
980
- if (
981
- !mapping.subject.isActiveStatus(row.subject[mapping.subject.status]) ||
982
- !mapping.identifier.isCurrent(row.identifier) ||
983
- !mapping.subjectId.equals(nativeId, row.subject[mapping.subject.id]) ||
984
- !mapping.subjectId.equals(nativeId, row.identifier[mapping.identifier.subjectId]) ||
985
- !mapping.subjectId.equals(nativeId, row.credential[mapping.credential.subjectId])
986
- )
987
- return Option.none();
988
-
989
- const credential = yield* mapping.credential
990
- .decode({ moduleId: input.moduleId, ...row })
991
- .pipe(Effect.flatMap(snapshotPasswordCredential));
992
-
993
- return input.subjectId !== undefined && credential.revision.subjectId !== input.subjectId
994
- ? Option.none()
995
- : Option.some(credential);
996
- }),
997
- rehashIfCurrent: (input: Parameters<PasswordPersistence["Service"]["rehashIfCurrent"]>[0]) =>
998
- run(
999
- Effect.gen(function* () {
1000
- const nextVerifier = Redacted.make(
1001
- yield* Schema.decodeEffect(EncodedPasswordHash)(Redacted.value(input.nextVerifier)),
1002
- );
1003
-
1004
- const credential = yield* snapshotPasswordCredential(input.credential);
1005
- const database = yield* CurrentD1PlanningDatabase;
1006
- const nativeId = yield* mapping.subjectId.toNative(credential.revision.subjectId);
1007
- const version = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
1008
-
1009
- if (version === credential.verifierVersion) return yield* unavailable();
1010
- const encoded = mapping.credential.encodeVerifier(nextVerifier, version);
1011
-
1012
- const values = {
1013
- [mapping.credential.verifier]: encoded[mapping.credential.verifier],
1014
- [mapping.credential.verifierVersion]: encoded[mapping.credential.verifierVersion],
1015
- };
1016
-
1017
- if (Object.values(values).some((value) => value === undefined))
1018
- return yield* unavailable();
1019
- const c = credentialColumns(mapping);
1020
-
1021
- return {
1022
- receipt: undefined,
1023
- statements: [
1024
- yield* statement(
1025
- database
1026
- .update(mapping.credential.table)
1027
- .set(values)
1028
- .where(
1029
- and(
1030
- eq(c.moduleId, credential.moduleId),
1031
- eq(c.subjectId, nativeId),
1032
- eq(c.credentialId, credential.credentialId),
1033
- ...(
1034
- [
1035
- [c.moduleId, credential.moduleId],
1036
- [c.subjectId, nativeId],
1037
- [c.credentialId, credential.credentialId],
1038
- [c.credentialRevision, credential.credentialRevision],
1039
- [c.verifierVersion, credential.verifierVersion],
1040
- [c.verifier, Redacted.value(credential.verifier)],
1041
- [c.normalization, credential.normalization],
1042
- ] as const
1043
- ).map(([column, value]) =>
1044
- typeof value === "string"
1045
- ? sql`cast(${column} as blob) = cast(${sql.param(value, column)} as blob)`
1046
- : eq(column, value),
1047
- ),
1048
- ),
1049
- ),
1050
- ),
1051
- ],
1052
- };
1053
- }),
1054
- ),
1055
- readForSubject: (input: Parameters<PasswordPersistence["Service"]["readForSubject"]>[0]) =>
1056
- Effect.gen(function* () {
1057
- const database = yield* CurrentD1PlanningDatabase;
1058
-
1059
- if (yield* hasCommitScope) return yield* unavailable();
1060
- const nativeSubjectId = yield* mapping.subjectId.toNative(input.subjectId);
1061
- const subject = (yield* readSubjectNative(mapping, nativeSubjectId))[0];
1062
-
1063
- if (
1064
- subject === undefined ||
1065
- !mapping.subject.isActiveStatus(subject[mapping.subject.status])
1066
- )
1067
- return Option.none();
1068
-
1069
- const credential = (yield* readCredentialNative(
1070
- mapping,
1071
- input.moduleId,
1072
- nativeSubjectId,
1073
- ))[0];
1074
-
1075
- if (credential === undefined) return Option.none();
1076
- const ic = identifierColumns(mapping);
1077
-
1078
- const identifiers = yield* database
1079
- .select()
1080
- .from(mapping.identifier.table)
1081
- .where(eq(ic.subjectId, nativeSubjectId));
1082
-
1083
- const identifier = identifiers.find((row: any) => mapping.identifier.isCurrent(row));
1084
-
1085
- if (identifier === undefined) return Option.none();
1086
-
1087
- return Option.some(
1088
- yield* mapping.credential
1089
- .decode({ moduleId: input.moduleId, subject, identifier, credential })
1090
- .pipe(Effect.flatMap(snapshotPasswordCredential)),
1091
- );
1092
- }),
1093
- recoveryTarget: (input: Parameters<PasswordPersistence["Service"]["recoveryTarget"]>[0]) =>
1094
- Effect.gen(function* () {
1095
- if (yield* hasCommitScope) return yield* unavailable();
1096
- const resolved = yield* resolveCredential(mapping, input.moduleId, input.identifier);
1097
-
1098
- return resolved?.snapshot === undefined ||
1099
- resolved.snapshot.identifierVerifiedAtMillis === undefined
1100
- ? Option.none()
1101
- : Option.some(resolved.snapshot);
1102
- }),
1103
- addIfAbsent: <A>(
1104
- uncaptured: Parameters<PasswordPersistence["Service"]["addIfAbsent"]>[0],
1105
- prepare: PlanPrepare<PasswordPersistence["Service"]["addIfAbsent"], A>,
1106
- ) =>
1107
- Effect.gen(function* () {
1108
- const input = yield* snapshotMutationInput(uncaptured);
1109
-
1110
- const credentialId = yield* allocate(
1111
- mapping.allocateCredentialId,
1112
- mapping.allocateCredentialIdSync,
1113
- );
1114
-
1115
- const credentialRevision = yield* allocate(
1116
- mapping.allocateRevision,
1117
- mapping.allocateRevisionSync,
1118
- );
1119
-
1120
- const verifierVersion = yield* allocate(
1121
- mapping.allocateRevision,
1122
- mapping.allocateRevisionSync,
1123
- );
1124
-
1125
- const nextSecurityRevision = yield* allocateNextSecurityRevision(
1126
- mapping,
1127
- input.expectedRevision.securityRevision,
1128
- );
1129
-
1130
- if (nextSecurityRevision === input.expectedRevision.securityRevision)
1131
- return yield* unavailable();
1132
-
1133
- return yield* run(
1134
- Effect.gen(function* () {
1135
- const journal = yield* CurrentCommitJournal;
1136
-
1137
- const compiled = yield* compileAddMutation(mapping, input, {
1138
- credentialId,
1139
- credentialRevision,
1140
- verifierVersion,
1141
- nextSecurityRevision,
1142
- });
1143
-
1144
- if (compiled === undefined)
1145
- return { receipt: prepare("rejected", journal), statements: [] };
1146
-
1147
- return {
1148
- receipt: prepare("changed", journal),
1149
- statements: compiled.statements,
1150
- postconditions: [yield* assertion(compiled.appliedCondition, compiled.marker)],
1151
- retryable: compiled.retryable,
1152
- };
1153
- }),
1154
- );
1155
- }),
1156
- replaceIfCurrent: <A>(
1157
- uncaptured: Parameters<PasswordPersistence["Service"]["replaceIfCurrent"]>[0],
1158
- prepare: PlanPrepare<PasswordPersistence["Service"]["replaceIfCurrent"], A>,
1159
- ) =>
1160
- Effect.flatMap(snapshotMutationInput(uncaptured), (input) =>
1161
- replacementPlan(mapping, proofMapping, input, prepare, "change-password"),
1162
- ),
1163
- checkReset: (input: Parameters<PasswordPersistence["Service"]["checkReset"]>[0]) =>
1164
- proofMapping === undefined
1165
- ? Effect.fail(unavailable())
1166
- : Effect.gen(function* () {
1167
- if (yield* hasCommitScope) return yield* unavailable();
1168
- if (
1169
- input.moduleId.length <= "/reset".length ||
1170
- !input.moduleId.endsWith("/reset") ||
1171
- input.purpose !== "password-reset" ||
1172
- input.binding._tag !== "Subject"
1173
- )
1174
- return false;
1175
- const binding = input.binding;
1176
-
1177
- const resolved = yield* resolveCredential(
1178
- mapping,
1179
- input.moduleId.slice(0, -"/reset".length),
1180
- binding.identifier,
1181
- binding.revision.subjectId,
1182
- );
1183
-
1184
- const current =
1185
- resolved?.snapshot !== undefined &&
1186
- resolved.snapshot.revision.securityRevision === binding.revision.securityRevision &&
1187
- binding.revision.credentials.every((item) =>
1188
- resolved.snapshot!.revision.credentials.some(
1189
- (current) =>
1190
- current.credentialId === item.credentialId &&
1191
- current.revision === item.revision,
1192
- ),
1193
- );
1194
-
1195
- return current ? yield* probeProofCompletion(proofMapping, input) : false;
1196
- }),
1197
- resetWithProof: <A>(
1198
- uncaptured: Parameters<PasswordPersistence["Service"]["resetWithProof"]>[0],
1199
- prepare: PlanPrepare<PasswordPersistence["Service"]["resetWithProof"], A>,
1200
- ) =>
1201
- proofMapping === undefined
1202
- ? Effect.fail(unavailable())
1203
- : Effect.flatMap(snapshotMutationInput(uncaptured), (captured) =>
1204
- replacementPlan(
1205
- mapping,
1206
- proofMapping,
1207
- Object.freeze({ ...captured, completion: uncaptured.completion }),
1208
- prepare,
1209
- "reset-password",
1210
- ),
1211
- ),
1212
- };
1213
- };
1214
-
1215
- const compileReplacementMutation = Effect.fn("DrizzleD1Password.compileReplacementMutation")(
1216
- function* (
1217
- mapping: Mapping,
1218
- input: PasswordMutationInput,
1219
- action: "change-password" | "reset-password",
1220
- revisions: {
1221
- readonly credentialRevision: SecurityRevision;
1222
- readonly verifierVersion: SecurityRevision;
1223
- readonly nextSecurityRevision: SecurityRevision;
1224
- },
1225
- ) {
1226
- const { credentialRevision, verifierVersion, nextSecurityRevision } = revisions;
1227
- const authority = yield* readAuthority(mapping, input, action);
1228
-
1229
- const current =
1230
- authority === undefined || input.credential === undefined
1231
- ? undefined
1232
- : (yield* readCredentialNative(mapping, input.moduleId, authority.nativeSubjectId))[0];
1233
-
1234
- const currentMatches =
1235
- current !== undefined &&
1236
- current[mapping.credential.credentialId] === input.credential!.credentialId &&
1237
- current[mapping.credential.credentialRevision] === input.credential!.credentialRevision &&
1238
- current[mapping.credential.verifierVersion] === input.credential!.verifierVersion &&
1239
- current[mapping.credential.verifier] === Redacted.value(input.credential!.verifier) &&
1240
- current[mapping.credential.normalization] === input.credential!.normalization;
1241
-
1242
- if (authority === undefined || !currentMatches) return undefined;
1243
- const now = yield* readEngineNowMillis(mapping);
1244
-
1245
- const guard = and(
1246
- authority.condition,
1247
- credentialCurrentCondition(mapping, input, authority.nativeSubjectId),
1248
- commandAbsentCondition(mapping, input.moduleId, input.commandId),
1249
- )!;
1250
-
1251
- const marker = `effect-auth-password-guard:${action}:${input.commandId}`;
1252
-
1253
- const protectedMutation: D1ProtectedProofMutation = {
1254
- statements: [
1255
- yield* assertion(guard, marker),
1256
- ...(yield* mutationStatements(
1257
- mapping,
1258
- input,
1259
- authority.nativeSubjectId,
1260
- { credentialRevision, verifierVersion, nextSecurityRevision },
1261
- now,
1262
- )),
1263
- ],
1264
- appliedCondition: replacementApplied(
1265
- mapping,
1266
- input,
1267
- authority.nativeSubjectId,
1268
- {
1269
- credentialRevision,
1270
- verifierVersion,
1271
- nextSecurityRevision,
1272
- },
1273
- now,
1274
- authority.identifierCondition,
1275
- ),
1276
- };
1277
-
1278
- return { protectedMutation, marker };
1279
- },
1280
- );
1281
-
1282
- const replacementPlan = Effect.fn("Drizzle.replacementPlan")(function* <A>(
1283
- mapping: Mapping,
1284
- proofMapping:
1285
- | D1ProofPersistenceMapping<any, any, any, any, any, any, any, any, any, any, any, any>
1286
- | undefined,
1287
- input: PasswordMutationInput & { readonly completion?: any },
1288
- prepare: PreparePasswordCommit<PasswordMutationDecision, A>,
1289
- action: "change-password" | "reset-password",
1290
- ) {
1291
- const credentialRevision = yield* allocate(
1292
- mapping.allocateRevision,
1293
- mapping.allocateRevisionSync,
1294
- );
1295
-
1296
- const verifierVersion = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
1297
-
1298
- const nextSecurityRevision = yield* allocateNextSecurityRevision(
1299
- mapping,
1300
- input.expectedRevision.securityRevision,
1301
- );
1302
-
1303
- if (
1304
- nextSecurityRevision === input.expectedRevision.securityRevision ||
1305
- (input.credential !== undefined && credentialRevision === input.credential.credentialRevision)
1306
- )
1307
- return yield* unavailable();
1308
-
1309
- const journal = yield* CurrentCommitJournal;
1310
-
1311
- const compiledMutation = yield* compileReplacementMutation(mapping, input, action, {
1312
- credentialRevision,
1313
- verifierVersion,
1314
- nextSecurityRevision,
1315
- });
1316
-
1317
- if (compiledMutation === undefined)
1318
- return { receipt: prepare("rejected", journal), statements: [] };
1319
- const { protectedMutation, marker } = compiledMutation;
1320
-
1321
- if (action === "reset-password") {
1322
- if (
1323
- proofMapping === undefined ||
1324
- input.completion === undefined ||
1325
- !completionMatchesPassword(
1326
- input as PasswordMutationInput & { readonly completion: ProofCompletionPlan },
1327
- )
1328
- )
1329
- return { receipt: prepare("rejected", journal), statements: [] };
1330
- let passwordReceipt: PreparedCommit<A> | undefined;
1331
-
1332
- const compiled = yield* compileD1ProofCompletionPlan(
1333
- proofMapping,
1334
- input.completion,
1335
- protectedMutation,
1336
- (decision: ProofCompletionDecision) => {
1337
- passwordReceipt = prepare(decision === "completed" ? "changed" : "rejected", journal);
1338
-
1339
- return decision;
1340
- },
1341
- );
1342
-
1343
- return {
1344
- receipt: passwordReceipt ?? prepare("rejected", journal),
1345
- statements: compiled.statements,
1346
- ...(compiled.postconditions === undefined ? {} : { postconditions: compiled.postconditions }),
1347
- retryable: (cause: unknown) =>
1348
- isGuardFailure(cause, marker) ||
1349
- isGuardFailure(
1350
- cause,
1351
- `effect-auth-proof-guard:complete:${input.completion.input.continuationId}`,
1352
- ) ||
1353
- isGuardFailure(
1354
- cause,
1355
- `effect-auth-proof-guard:protected:${input.completion.input.continuationId}`,
1356
- ),
1357
- };
1358
- }
1359
-
1360
- const applied = yield* assertion(protectedMutation.appliedCondition, marker);
1361
-
1362
- return {
1363
- receipt: prepare("changed", journal),
1364
- statements: [...protectedMutation.statements, applied],
1365
- postconditions: [applied],
1366
- retryable: (cause: unknown) => isGuardFailure(cause, marker),
1367
- };
1368
- });
1369
-
1370
- export const makeD1PasswordPersistenceServices = Effect.fnUntraced(function* <
1371
- S extends AnySQLiteTable,
1372
- I extends AnySQLiteTable,
1373
- C extends AnySQLiteTable,
1374
- AC extends AnySQLiteTable,
1375
- M extends AnySQLiteTable,
1376
- NativeId,
1377
- >(
1378
- mapping: D1PasswordPersistenceMapping<S, I, C, AC, M, NativeId>,
1379
- proofMapping?: D1ProofPersistenceMapping<
1380
- any,
1381
- any,
1382
- any,
1383
- any,
1384
- any,
1385
- any,
1386
- any,
1387
- any,
1388
- any,
1389
- any,
1390
- any,
1391
- any
1392
- >,
1393
- ) {
1394
- const database = yield* DatabaseService;
1395
-
1396
- yield* validateDrizzleStorage({ ...mapping, proof: proofMapping }).pipe(
1397
- Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1398
- Effect.mapError(unavailable),
1399
- );
1400
-
1401
- const hooks = yield* LifecycleHooks;
1402
- const plans = makePasswordPlans(mapping as unknown as Mapping, proofMapping);
1403
-
1404
- const standalone = Effect.gen(function* () {
1405
- if (yield* hasCommitScope) return yield* unavailable();
1406
- yield* requireStandalone(unavailable, database.$client.transactionService);
1407
- });
1408
-
1409
- const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
1410
- Effect.gen(function* () {
1411
- yield* standalone;
1412
-
1413
- return yield* executeStandalone(plan, 2);
1414
- }).pipe(
1415
- Effect.provideService(CurrentD1PlanningDatabase, database),
1416
- Effect.provideService(LifecycleHooks, hooks),
1417
- );
1418
-
1419
- const service: PasswordPersistence["Service"] = {
1420
- findCredential: (input) =>
1421
- standalone.pipe(
1422
- Effect.andThen(plans.findCredential(input)),
1423
- Effect.provideService(CurrentD1PlanningDatabase, database),
1424
- translateFailure,
1425
- ),
1426
- rehashIfCurrent: (input) => run(plans.rehashIfCurrent(input)).pipe(translateFailure),
1427
- readForSubject: (input) =>
1428
- plans
1429
- .readForSubject(input)
1430
- .pipe(
1431
- Effect.provideService(CurrentD1PlanningDatabase, database),
1432
- Effect.provideService(LifecycleHooks, hooks),
1433
- translateFailure,
1434
- ),
1435
- recoveryTarget: (input) =>
1436
- plans
1437
- .recoveryTarget(input)
1438
- .pipe(
1439
- Effect.provideService(CurrentD1PlanningDatabase, database),
1440
- Effect.provideService(LifecycleHooks, hooks),
1441
- translateFailure,
1442
- ),
1443
- addIfAbsent: (input, prepare) => run(plans.addIfAbsent(input, prepare)).pipe(translateFailure),
1444
- replaceIfCurrent: (input, prepare) =>
1445
- run(plans.replaceIfCurrent(input, prepare)).pipe(translateFailure),
1446
- checkReset: (input) =>
1447
- plans
1448
- .checkReset(input)
1449
- .pipe(
1450
- Effect.provideService(CurrentD1PlanningDatabase, database),
1451
- Effect.provideService(LifecycleHooks, hooks),
1452
- translateFailure,
1453
- ),
1454
- resetWithProof: (input, prepare) =>
1455
- run(plans.resetWithProof(input, prepare)).pipe(translateFailure),
1456
- };
1457
-
1458
- return { passwordPersistence: service };
1459
- });
1460
-
1461
- type CoordinatorError<E> = E | PasswordUnavailable | HookConfigurationError;
1462
-
1463
- /**
1464
- * Owns one password transition plus application statements in one D1 batch.
1465
- * The owner Effect is invoked once and is never replayed after an ambiguous or
1466
- * guarded batch failure. Standalone adapter-owned commands may replan boundedly.
1467
- */
1468
- export function coordinateD1PasswordPersistence<
1469
- S extends AnySQLiteTable,
1470
- I extends AnySQLiteTable,
1471
- C extends AnySQLiteTable,
1472
- AC extends AnySQLiteTable,
1473
- M extends AnySQLiteTable,
1474
- NativeId,
1475
- A,
1476
- E,
1477
- R,
1478
- DatabaseError,
1479
- DatabaseRequirements,
1480
- >(
1481
- acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
1482
- options: {
1483
- readonly mapping: D1PasswordPersistenceMapping<S, I, C, AC, M, NativeId>;
1484
- readonly proofMapping?:
1485
- | D1ProofPersistenceMapping<any, any, any, any, any, any, any, any, any, any, any, any>
1486
- | undefined;
1487
- },
1488
- body: Effect.Effect<A, E, R>,
1489
- ): Effect.Effect<
1490
- A,
1491
- CoordinatorError<E> | DatabaseError,
1492
- Exclude<R, PasswordPersistence | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
1493
- > {
1494
- return Effect.flatMap(nativeDatabase(acquire), (database) =>
1495
- Effect.gen(function* () {
1496
- yield* validateDrizzleStorage({
1497
- ...options.mapping,
1498
- proof: options.proofMapping,
1499
- }).pipe(Effect.mapError(unavailable));
1500
- const hooks = yield* LifecycleHooks;
1501
-
1502
- if (yield* hasCommitScope) return yield* unavailable();
1503
-
1504
- const result = yield* coordinateCommit(
1505
- () =>
1506
- Effect.gen(function* () {
1507
- const statements: Statement<any>[] = [];
1508
- let mutation: Planned<any> | undefined;
1509
-
1510
- const nativeCollector = D1BatchStatements.of({
1511
- append: (statement) => Effect.sync(() => statements.push(statement)),
1512
- });
1513
-
1514
- const owner = yield* makeD1Owner(unavailable()).pipe(
1515
- Effect.provideService(D1BatchStatements, nativeCollector),
1516
- );
1517
-
1518
- const plans = makePasswordPlans(
1519
- options.mapping as unknown as Mapping,
1520
- options.proofMapping,
1521
- );
1522
-
1523
- const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
1524
- coordinateCommit(
1525
- () =>
1526
- Effect.gen(function* () {
1527
- const journal = yield* CurrentCommitJournal;
1528
- const planned = yield* plan;
1529
-
1530
- yield* owner.check;
1531
- if (mutation !== undefined) return yield* unavailable();
1532
- const guarded = { ...planned, journalGuard: journal.prepare(undefined) };
1533
-
1534
- mutation = guarded;
1535
- statements.push(...guarded.statements);
1536
-
1537
- return guarded.receipt;
1538
- }),
1539
- { mode: "batch" },
1540
- ).pipe(
1541
- Effect.map((result) => result.value),
1542
- Effect.provideService(CurrentD1PlanningDatabase, database),
1543
- Effect.provideService(LifecycleHooks, hooks),
1544
- );
1545
-
1546
- const service: PasswordPersistence["Service"] = {
1547
- findCredential: (input) =>
1548
- owner.run(
1549
- plans
1550
- .findCredential(input)
1551
- .pipe(
1552
- Effect.provideService(CurrentD1PlanningDatabase, database),
1553
- translateFailure,
1554
- ),
1555
- ),
1556
- rehashIfCurrent: (input) =>
1557
- owner.run(run(plans.rehashIfCurrent(input)).pipe(translateFailure)),
1558
- readForSubject: (input) =>
1559
- owner.run(
1560
- plans
1561
- .readForSubject(input)
1562
- .pipe(
1563
- Effect.provideService(CurrentD1PlanningDatabase, database),
1564
- Effect.provideService(LifecycleHooks, hooks),
1565
- translateFailure,
1566
- ),
1567
- ),
1568
- recoveryTarget: (input) =>
1569
- owner.run(
1570
- plans
1571
- .recoveryTarget(input)
1572
- .pipe(
1573
- Effect.provideService(CurrentD1PlanningDatabase, database),
1574
- Effect.provideService(LifecycleHooks, hooks),
1575
- translateFailure,
1576
- ),
1577
- ),
1578
- addIfAbsent: (input, prepare) =>
1579
- owner.run(run(plans.addIfAbsent(input, prepare)).pipe(translateFailure)),
1580
- replaceIfCurrent: (input, prepare) =>
1581
- owner.run(run(plans.replaceIfCurrent(input, prepare)).pipe(translateFailure)),
1582
- checkReset: (input) =>
1583
- owner.run(
1584
- plans
1585
- .checkReset(input)
1586
- .pipe(
1587
- Effect.provideService(CurrentD1PlanningDatabase, database),
1588
- Effect.provideService(LifecycleHooks, hooks),
1589
- translateFailure,
1590
- ),
1591
- ),
1592
- resetWithProof: (input, prepare) =>
1593
- owner.run(run(plans.resetWithProof(input, prepare)).pipe(translateFailure)),
1594
- };
1595
-
1596
- const provided = Context.make(PasswordPersistence, service).pipe(
1597
- Context.add(D1BatchStatements, owner.collector),
1598
- );
1599
-
1600
- const value = yield* owner.close(Effect.provideContext(body, provided));
1601
-
1602
- if (mutation?.journalGuard !== undefined) {
1603
- const status = yield* Effect.result(mutation.journalGuard.read);
1604
-
1605
- if (status._tag === "Success" || status.failure._tag !== "CommitPending")
1606
- return yield* unavailable();
1607
- }
1608
- // Re-assert the password's final state after application statements.
1609
- yield* database.$client
1610
- .batch([...statements, ...(mutation?.postconditions ?? [])])
1611
- .pipe(
1612
- Effect.catchCause((cause) =>
1613
- Effect.failCause(
1614
- Cause.map(cause, (error) =>
1615
- everyFailureMatches(cause, mutation?.retryable) ? unavailable() : error,
1616
- ),
1617
- ),
1618
- ),
1619
- translateFailure,
1620
- );
1621
-
1622
- return value;
1623
- }),
1624
- { mode: "batch" },
1625
- ).pipe(Effect.provideService(LifecycleHooks, hooks));
1626
-
1627
- return result.value;
1628
- }).pipe(Effect.provideService(NativeDatabase, database)),
1629
- );
1630
- }
1631
-
1632
- const registrationRows = Effect.fn("Drizzle.registrationRows")(function* <Registration>(
1633
- mapping: RegistrationMapping<Registration>,
1634
- moduleId: string,
1635
- requestId: string,
1636
- ) {
1637
- const database = yield* CurrentD1PlanningDatabase;
1638
-
1639
- const module = column(mapping.registration.table, mapping.registration.moduleId);
1640
- const request = column(mapping.registration.table, mapping.registration.requestId);
1641
-
1642
- return yield* database
1643
- .select()
1644
- .from(mapping.registration.table)
1645
- .where(and(eq(module, moduleId), eq(request, requestId)))
1646
- .limit(1);
1647
- });
1648
-
1649
- const registrationIdentifierRows = Effect.fn("Drizzle.registrationIdentifierRows")(function* <
1650
- Registration,
1651
- >(mapping: RegistrationMapping<Registration>, identifier: LoginIdentifier) {
1652
- const database = yield* CurrentD1PlanningDatabase;
1653
-
1654
- const namespace = column(mapping.identifier.table, mapping.identifier.namespace);
1655
- const value = column(mapping.identifier.table, mapping.identifier.value);
1656
-
1657
- return yield* database
1658
- .select()
1659
- .from(mapping.identifier.table)
1660
- .where(and(eq(namespace, identifier.namespace), eq(value, identifier.value)))
1661
- .limit(1);
1662
- });
1663
-
1664
- const planRegistration = Effect.fn("Drizzle.planRegistration")(function* <Registration, A>(
1665
- mapping: RegistrationMapping<Registration>,
1666
- input: {
1667
- readonly moduleId: string;
1668
- readonly requestId: string;
1669
- readonly identifier: LoginIdentifier;
1670
- readonly registration: Registration;
1671
- readonly replacement: PasswordMutationInput["replacement"];
1672
- },
1673
- allocated: {
1674
- readonly credentialId: string;
1675
- readonly securityRevision: SecurityRevision;
1676
- readonly identifierRevision: SecurityRevision;
1677
- readonly credentialRevision: SecurityRevision;
1678
- readonly verifierVersion: SecurityRevision;
1679
- readonly nativeSubjectId?: unknown;
1680
- readonly recoveryReference?: any;
1681
- },
1682
- prepare: PreparePasswordCommit<import("@yielded/auth/Password").PasswordRegistrationDecision, A>,
1683
- ) {
1684
- const database = yield* CurrentD1PlanningDatabase;
1685
- const journal = yield* CurrentCommitJournal;
1686
-
1687
- const existing = (yield* registrationRows(mapping, input.moduleId, input.requestId))[0];
1688
-
1689
- if (existing !== undefined)
1690
- return {
1691
- receipt: prepare(yield* mapping.registration.decodeReplay(existing), journal),
1692
- statements: [],
1693
- };
1694
-
1695
- const intent = {
1696
- moduleId: input.moduleId,
1697
- requestId: input.requestId,
1698
- identifier: input.identifier,
1699
- registration: input.registration,
1700
- replacement: input.replacement,
1701
- };
1702
-
1703
- if (mapping.mode === "pending") {
1704
- if (allocated.recoveryReference === undefined) return yield* unavailable();
1705
-
1706
- return {
1707
- receipt: prepare({ _tag: "Pending", reference: allocated.recoveryReference }, journal),
1708
- statements: [
1709
- yield* statement(
1710
- database.insert(mapping.registration.table).values(
1711
- mapping.registration.encodeInsert(intent, {
1712
- state: "pending",
1713
- recoveryReference: allocated.recoveryReference,
1714
- }),
1715
- ),
1716
- ),
1717
- ],
1718
- retryable: (cause: unknown) => isMappedConstraintConflict(mapping.isRequestConflict, cause),
1719
- };
1720
- }
1721
- if (mapping.provisioning.idMode === "generated" || allocated.nativeSubjectId === undefined)
1722
- return yield* unavailable();
1723
- if ((yield* registrationIdentifierRows(mapping, input.identifier)).length > 0)
1724
- return { receipt: prepare({ _tag: "Suppressed" }, journal), statements: [] };
1725
- const nativeSubjectId = allocated.nativeSubjectId;
1726
- const subjectId = yield* mapping.subjectId.toSubject(nativeSubjectId);
1727
-
1728
- return {
1729
- receipt: prepare({ _tag: "Created", subjectId }, journal),
1730
- statements: [
1731
- yield* statement(
1732
- database.insert(mapping.subject.table).values(
1733
- mapping.provisioning.encodeSubjectInsert(intent, {
1734
- nativeSubjectId,
1735
- securityRevision: allocated.securityRevision,
1736
- }),
1737
- ),
1738
- ),
1739
- yield* statement(
1740
- database
1741
- .insert(mapping.identifier.table)
1742
- .values(
1743
- mapping.identifier.encodeInitialInsert(
1744
- input.identifier,
1745
- nativeSubjectId,
1746
- allocated.identifierRevision,
1747
- ),
1748
- ),
1749
- ),
1750
- yield* statement(
1751
- database.insert(mapping.credential.table).values(
1752
- mapping.credential.encodeInsert({
1753
- moduleId: input.moduleId,
1754
- subjectId: nativeSubjectId,
1755
- credentialId: allocated.credentialId,
1756
- credentialRevision: allocated.credentialRevision,
1757
- verifierVersion: allocated.verifierVersion,
1758
- replacement: input.replacement,
1759
- }),
1760
- ),
1761
- ),
1762
- yield* statement(
1763
- database.insert(mapping.authorityCredential.table).values(
1764
- mapping.authorityCredential.encodeInsert({
1765
- subjectId: nativeSubjectId,
1766
- credentialId: allocated.credentialId,
1767
- revision: allocated.credentialRevision,
1768
- }),
1769
- ),
1770
- ),
1771
- yield* statement(
1772
- database.insert(mapping.registration.table).values(
1773
- mapping.registration.encodeInsert(intent, {
1774
- state: "created",
1775
- nativeSubjectId,
1776
- }),
1777
- ),
1778
- ),
1779
- ],
1780
- retryable: (cause: unknown) =>
1781
- isMappedConstraintConflict(mapping.isRequestConflict, cause) ||
1782
- isMappedConstraintConflict(mapping.isIdentifierConflict, cause),
1783
- };
1784
- });
1785
-
1786
- const allocateRegistration = <Registration>(mapping: RegistrationMapping<Registration>) =>
1787
- Effect.gen(function* () {
1788
- const credentialId = yield* allocate(
1789
- mapping.allocateCredentialId,
1790
- mapping.allocateCredentialIdSync,
1791
- );
1792
-
1793
- const securityRevision = yield* allocate(
1794
- mapping.allocateRevision,
1795
- mapping.allocateRevisionSync,
1796
- );
1797
-
1798
- const identifierRevision = yield* allocate(
1799
- mapping.allocateRevision,
1800
- mapping.allocateRevisionSync,
1801
- );
1802
-
1803
- const credentialRevision = yield* allocate(
1804
- mapping.allocateRevision,
1805
- mapping.allocateRevisionSync,
1806
- );
1807
-
1808
- const verifierVersion = yield* allocate(mapping.allocateRevision, mapping.allocateRevisionSync);
1809
-
1810
- const nativeSubjectId =
1811
- mapping.mode !== "atomic" || mapping.provisioning.idMode === "generated"
1812
- ? undefined
1813
- : yield* allocate(
1814
- mapping.provisioning.allocateSubjectId,
1815
- mapping.provisioning.allocateSubjectIdSync,
1816
- );
1817
-
1818
- const recoveryReference =
1819
- mapping.mode === "pending"
1820
- ? yield* allocate(mapping.allocateRecoveryReference, mapping.allocateRecoveryReferenceSync)
1821
- : undefined;
1822
-
1823
- return {
1824
- credentialId,
1825
- securityRevision,
1826
- identifierRevision,
1827
- credentialRevision,
1828
- verifierVersion,
1829
- ...(nativeSubjectId === undefined ? {} : { nativeSubjectId }),
1830
- ...(recoveryReference === undefined ? {} : { recoveryReference }),
1831
- };
1832
- });
1833
-
1834
- const makeRegistrationPlans = <Registration>(mapping: RegistrationMapping<Registration>) => ({
1835
- register: <A>(
1836
- input: Parameters<PasswordRegistrationAuthority<Registration>["register"]>[0],
1837
- prepare: PlanPrepare<PasswordRegistrationAuthority<Registration>["register"], A>,
1838
- ) =>
1839
- Effect.gen(function* () {
1840
- if (!validRegistrationConstraints(mapping)) return yield* unavailable();
1841
- if (mapping.mode === "atomic" && mapping.provisioning.idMode === "generated")
1842
- return yield* unavailable();
1843
- const allocated = yield* allocateRegistration(mapping);
1844
-
1845
- return yield* planRegistration(mapping, input, allocated, prepare);
1846
- }),
1847
- });
1848
-
1849
- export const makeD1PasswordRegistrationServices = Effect.fnUntraced(function* <
1850
- Registration,
1851
- S extends AnySQLiteTable,
1852
- I extends AnySQLiteTable,
1853
- C extends AnySQLiteTable,
1854
- AC extends AnySQLiteTable,
1855
- Rq extends AnySQLiteTable,
1856
- NativeId,
1857
- >(mapping: PasswordRegistrationMapping<Registration, S, I, C, AC, Rq, NativeId>) {
1858
- const database = yield* DatabaseService;
1859
-
1860
- yield* validateDrizzleStorage(mapping).pipe(
1861
- Effect.provideServiceEffect(NativeDatabase, nativeDatabase(DatabaseService)),
1862
- Effect.mapError(unavailable),
1863
- );
1864
-
1865
- const hooks = yield* LifecycleHooks;
1866
- const plans = makeRegistrationPlans(mapping as unknown as RegistrationMapping<Registration>);
1867
-
1868
- const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
1869
- Effect.gen(function* () {
1870
- if (yield* hasCommitScope) return yield* unavailable();
1871
-
1872
- return yield* executeStandalone(plan, 2);
1873
- }).pipe(
1874
- Effect.provideService(CurrentD1PlanningDatabase, database),
1875
- Effect.provideService(LifecycleHooks, hooks),
1876
- );
1877
-
1878
- const service: PasswordRegistrationAuthority<Registration> = {
1879
- register: (input, prepare) => run(plans.register(input, prepare)).pipe(translateFailure),
1880
- };
1881
-
1882
- return { registrationAuthority: service };
1883
- });
1884
-
1885
- export function coordinateD1PasswordRegistration<
1886
- TargetId,
1887
- Registration,
1888
- S extends AnySQLiteTable,
1889
- I extends AnySQLiteTable,
1890
- C extends AnySQLiteTable,
1891
- AC extends AnySQLiteTable,
1892
- Rq extends AnySQLiteTable,
1893
- NativeId,
1894
- A,
1895
- E,
1896
- R,
1897
- DatabaseError,
1898
- DatabaseRequirements,
1899
- >(
1900
- acquire: Effect.Effect<Database, DatabaseError, DatabaseRequirements>,
1901
- options: {
1902
- readonly mapping: PasswordRegistrationMapping<NoInfer<Registration>, S, I, C, AC, Rq, NativeId>;
1903
- readonly target: SuppliedService<TargetId, PasswordRegistrationAuthority<Registration>>;
1904
- },
1905
- body: Effect.Effect<A, E, R>,
1906
- ): Effect.Effect<
1907
- A,
1908
- CoordinatorError<E> | DatabaseError,
1909
- Exclude<R, TargetId | D1BatchStatements> | LifecycleHooks | DatabaseRequirements
1910
- > {
1911
- return Effect.flatMap(nativeDatabase(acquire), (database) =>
1912
- Effect.gen(function* () {
1913
- yield* validateDrizzleStorage(options.mapping).pipe(Effect.mapError(unavailable));
1914
- const hooks = yield* LifecycleHooks;
1915
-
1916
- if (yield* hasCommitScope) return yield* unavailable();
1917
-
1918
- const result = yield* coordinateCommit(
1919
- () =>
1920
- Effect.gen(function* () {
1921
- const statements: Statement<any>[] = [];
1922
- let mutation: Planned<any> | undefined;
1923
-
1924
- const nativeCollector = D1BatchStatements.of({
1925
- append: (statement) => Effect.sync(() => statements.push(statement)),
1926
- });
1927
-
1928
- const owner = yield* makeD1Owner(unavailable()).pipe(
1929
- Effect.provideService(D1BatchStatements, nativeCollector),
1930
- );
1931
-
1932
- const plans = makeRegistrationPlans(
1933
- options.mapping as unknown as RegistrationMapping<Registration>,
1934
- );
1935
-
1936
- const run = <Out, Err, Env>(plan: Effect.Effect<Planned<Out>, Err, Env>) =>
1937
- coordinateCommit(
1938
- () =>
1939
- Effect.gen(function* () {
1940
- const journal = yield* CurrentCommitJournal;
1941
- const planned = yield* plan;
1942
-
1943
- yield* owner.check;
1944
- if (mutation !== undefined) return yield* unavailable();
1945
- const guarded = { ...planned, journalGuard: journal.prepare(undefined) };
1946
-
1947
- mutation = guarded;
1948
- statements.push(...guarded.statements);
1949
-
1950
- return guarded.receipt;
1951
- }),
1952
- { mode: "batch" },
1953
- ).pipe(
1954
- Effect.map((result) => result.value),
1955
- Effect.provideService(CurrentD1PlanningDatabase, database),
1956
- Effect.provideService(LifecycleHooks, hooks),
1957
- );
1958
-
1959
- const service: PasswordRegistrationAuthority<Registration> = {
1960
- register: (input, prepare) =>
1961
- owner.run(run(plans.register(input, prepare)).pipe(translateFailure)),
1962
- };
1963
-
1964
- const provided = Context.make(options.target, service).pipe(
1965
- Context.add(D1BatchStatements, owner.collector),
1966
- );
1967
-
1968
- const value = yield* owner.close(Effect.provideContext(body, provided));
1969
-
1970
- if (mutation?.journalGuard !== undefined) {
1971
- const status = yield* Effect.result(mutation.journalGuard.read);
1972
-
1973
- if (status._tag === "Success" || status.failure._tag !== "CommitPending")
1974
- return yield* unavailable();
1975
- }
1976
- yield* database.$client.batch(statements).pipe(
1977
- Effect.catchCause((cause) =>
1978
- Effect.failCause(
1979
- Cause.map(cause, (error) =>
1980
- everyFailureMatches(cause, mutation?.retryable) ? unavailable() : error,
1981
- ),
1982
- ),
1983
- ),
1984
- translateFailure,
1985
- );
1986
-
1987
- return value;
1988
- }),
1989
- { mode: "batch" },
1990
- ).pipe(Effect.provideService(LifecycleHooks, hooks));
1991
-
1992
- return result.value;
1993
- }).pipe(Effect.provideService(NativeDatabase, database)),
1994
- );
1995
- }
1996
-
1997
- /** Private prepared compiler seam: it never replays a caller or prepared callback. */
1998
- export const passwordD1Kernel = {
1999
- statement,
2000
- assertion,
2001
- readEngineNowMillis,
2002
- allocate,
2003
- allocateNextSecurityRevision,
2004
- subjectColumns,
2005
- identifierColumns,
2006
- credentialColumns,
2007
- authorityColumns,
2008
- readAuthority,
2009
- assuranceCondition,
2010
- credentialCurrentCondition,
2011
- commandAbsentCondition,
2012
- compileAddMutation,
2013
- compileReplacementMutation,
2014
- completionMatchesPassword,
2015
- isGuardFailure,
2016
- };
2017
-
2018
- const translateFailure = <A, E, R>(
2019
- effect: Effect.Effect<A, E, R>,
2020
- ): Effect.Effect<A, PasswordUnavailable, R> =>
2021
- reportPersistenceFailure(
2022
- effect,
2023
- (error) =>
2024
- Schema.is(PasswordUnavailable)(error) ||
2025
- Schema.is(ProofUnavailable)(error) ||
2026
- Schema.is(HookConfigurationError)(error),
2027
- ).pipe(Effect.catchCause((cause) => Effect.failCause(Cause.map(cause, () => unavailable()))));
2028
-
2029
- const executeStandalone = <A, E, R>(
2030
- plan: Effect.Effect<Planned<A>, E, R>,
2031
- retries = 2,
2032
- ): Effect.Effect<
2033
- A,
2034
- E | import("@yielded/auth/Hooks").HookConfigurationError | import("effect/sql/SqlError").SqlError,
2035
- Exclude<R, CurrentCommitJournal> | CurrentD1PlanningDatabase | LifecycleHooks
2036
- > =>
2037
- Effect.suspend(() => {
2038
- let retryable: ((cause: unknown) => boolean) | undefined;
2039
-
2040
- const once = coordinateCommit(
2041
- () =>
2042
- Effect.gen(function* () {
2043
- const database = yield* CurrentD1PlanningDatabase;
2044
- const planned = yield* plan;
2045
-
2046
- retryable = planned.retryable;
2047
- yield* database.$client.batch(planned.statements);
2048
-
2049
- return planned.receipt;
2050
- }),
2051
- { mode: "batch" },
2052
- ).pipe(Effect.map((result) => result.value));
2053
-
2054
- return once.pipe(
2055
- Effect.catchCause((cause) =>
2056
- retries > 0 && everyFailureMatches(cause, retryable)
2057
- ? executeStandalone(plan, retries - 1)
2058
- : Effect.failCause(cause),
2059
- ),
2060
- );
2061
- });