@effect-auth/core 0.1.0-alpha.16 → 0.1.0-alpha.19

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 (637) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +307 -133
  3. package/dist/AbuseProtection.d.ts +100 -0
  4. package/dist/AbuseProtection.d.ts.map +1 -0
  5. package/dist/AbuseProtection.js +71 -0
  6. package/dist/AbuseProtection.js.map +1 -0
  7. package/dist/AccessGrant.d.ts +344 -0
  8. package/dist/AccessGrant.d.ts.map +1 -0
  9. package/dist/AccessGrant.js +687 -0
  10. package/dist/AccessGrant.js.map +1 -0
  11. package/dist/ApiKey.d.ts +0 -12
  12. package/dist/ApiKey.d.ts.map +1 -1
  13. package/dist/ApiKey.js +36 -27
  14. package/dist/ApiKey.js.map +1 -1
  15. package/dist/Assurance.d.ts +415 -0
  16. package/dist/Assurance.d.ts.map +1 -0
  17. package/dist/Assurance.js +764 -0
  18. package/dist/Assurance.js.map +1 -0
  19. package/dist/AtomicPlanExecutor.d.ts +28 -0
  20. package/dist/AtomicPlanExecutor.d.ts.map +1 -0
  21. package/dist/AtomicPlanExecutor.js +5 -0
  22. package/dist/AtomicPlanExecutor.js.map +1 -0
  23. package/dist/AuditLog.d.ts +2314 -52
  24. package/dist/AuditLog.d.ts.map +1 -1
  25. package/dist/AuditLog.js +511 -33
  26. package/dist/AuditLog.js.map +1 -1
  27. package/dist/AuthFlow.d.ts +128 -38
  28. package/dist/AuthFlow.d.ts.map +1 -1
  29. package/dist/AuthFlow.js +611 -263
  30. package/dist/AuthFlow.js.map +1 -1
  31. package/dist/AuthRateLimit.d.ts +96 -0
  32. package/dist/AuthRateLimit.d.ts.map +1 -0
  33. package/dist/AuthRateLimit.js +212 -0
  34. package/dist/AuthRateLimit.js.map +1 -0
  35. package/dist/Challenge.d.ts +0 -3
  36. package/dist/Challenge.d.ts.map +1 -1
  37. package/dist/Challenge.js +1 -4
  38. package/dist/Challenge.js.map +1 -1
  39. package/dist/Client.d.ts +205 -10
  40. package/dist/Client.d.ts.map +1 -1
  41. package/dist/Client.js +397 -16
  42. package/dist/Client.js.map +1 -1
  43. package/dist/CloudflareTurnstile.d.ts +28 -0
  44. package/dist/CloudflareTurnstile.d.ts.map +1 -0
  45. package/dist/CloudflareTurnstile.js +205 -0
  46. package/dist/CloudflareTurnstile.js.map +1 -0
  47. package/dist/Crypto.d.ts +0 -3
  48. package/dist/Crypto.d.ts.map +1 -1
  49. package/dist/Crypto.js +1 -4
  50. package/dist/Crypto.js.map +1 -1
  51. package/dist/DelegatedAccessStorageMigrations.d.ts +7 -0
  52. package/dist/DelegatedAccessStorageMigrations.d.ts.map +1 -0
  53. package/dist/DelegatedAccessStorageMigrations.js +159 -0
  54. package/dist/DelegatedAccessStorageMigrations.js.map +1 -0
  55. package/dist/DevEmail.d.ts +44 -0
  56. package/dist/DevEmail.d.ts.map +1 -0
  57. package/dist/DevEmail.js +115 -0
  58. package/dist/DevEmail.js.map +1 -0
  59. package/dist/DevelopmentSeed.d.ts +47 -0
  60. package/dist/DevelopmentSeed.d.ts.map +1 -0
  61. package/dist/DevelopmentSeed.js +337 -0
  62. package/dist/DevelopmentSeed.js.map +1 -0
  63. package/dist/DisposableEmailDomains.d.ts +19 -0
  64. package/dist/DisposableEmailDomains.d.ts.map +1 -0
  65. package/dist/DisposableEmailDomains.js +85 -0
  66. package/dist/DisposableEmailDomains.js.map +1 -0
  67. package/dist/DomainVerification.d.ts +0 -9
  68. package/dist/DomainVerification.d.ts.map +1 -1
  69. package/dist/DomainVerification.js +36 -22
  70. package/dist/DomainVerification.js.map +1 -1
  71. package/dist/DrizzleBunSqliteStorage.d.ts +18 -0
  72. package/dist/DrizzleBunSqliteStorage.d.ts.map +1 -0
  73. package/dist/DrizzleBunSqliteStorage.js +11 -0
  74. package/dist/DrizzleBunSqliteStorage.js.map +1 -0
  75. package/dist/DrizzleD1SqliteStorage.d.ts +4 -4
  76. package/dist/DrizzleD1SqliteStorage.d.ts.map +1 -1
  77. package/dist/DrizzleD1SqliteStorage.js +5 -5
  78. package/dist/DrizzleD1SqliteStorage.js.map +1 -1
  79. package/dist/DrizzleEffectSqliteStorage.d.ts +13 -4
  80. package/dist/DrizzleEffectSqliteStorage.d.ts.map +1 -1
  81. package/dist/DrizzleEffectSqliteStorage.js +71 -4
  82. package/dist/DrizzleEffectSqliteStorage.js.map +1 -1
  83. package/dist/DrizzleNodeSqliteStorage.d.ts +18 -0
  84. package/dist/DrizzleNodeSqliteStorage.d.ts.map +1 -0
  85. package/dist/DrizzleNodeSqliteStorage.js +11 -0
  86. package/dist/DrizzleNodeSqliteStorage.js.map +1 -0
  87. package/dist/DrizzlePostgresStorage.d.ts +26 -0
  88. package/dist/DrizzlePostgresStorage.d.ts.map +1 -0
  89. package/dist/DrizzlePostgresStorage.js +14 -0
  90. package/dist/DrizzlePostgresStorage.js.map +1 -0
  91. package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts +34 -0
  92. package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts.map +1 -0
  93. package/dist/EffectQbPostgresDelegatedAccessStorage.js +499 -0
  94. package/dist/EffectQbPostgresDelegatedAccessStorage.js.map +1 -0
  95. package/dist/EffectQbPostgresStorage.d.ts +178 -0
  96. package/dist/EffectQbPostgresStorage.d.ts.map +1 -0
  97. package/dist/EffectQbPostgresStorage.js +4637 -0
  98. package/dist/EffectQbPostgresStorage.js.map +1 -0
  99. package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts +112 -0
  100. package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts.map +1 -0
  101. package/dist/EffectQbSqliteDelegatedAccessStorage.js +602 -0
  102. package/dist/EffectQbSqliteDelegatedAccessStorage.js.map +1 -0
  103. package/dist/EffectQbSqliteStorage.d.ts +221 -23
  104. package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
  105. package/dist/EffectQbSqliteStorage.js +2064 -135
  106. package/dist/EffectQbSqliteStorage.js.map +1 -1
  107. package/dist/EmailAuth.d.ts +65 -0
  108. package/dist/EmailAuth.d.ts.map +1 -0
  109. package/dist/EmailAuth.js +171 -0
  110. package/dist/EmailAuth.js.map +1 -0
  111. package/dist/EmailIdentityAuth.d.ts +30 -0
  112. package/dist/EmailIdentityAuth.d.ts.map +1 -0
  113. package/dist/EmailIdentityAuth.js +142 -0
  114. package/dist/EmailIdentityAuth.js.map +1 -0
  115. package/dist/EmailOtp.d.ts +10 -11
  116. package/dist/EmailOtp.d.ts.map +1 -1
  117. package/dist/EmailOtp.js +44 -81
  118. package/dist/EmailOtp.js.map +1 -1
  119. package/dist/EmailRisk.d.ts +48 -0
  120. package/dist/EmailRisk.d.ts.map +1 -0
  121. package/dist/EmailRisk.js +33 -0
  122. package/dist/EmailRisk.js.map +1 -0
  123. package/dist/EmailVerification.d.ts +10 -16
  124. package/dist/EmailVerification.d.ts.map +1 -1
  125. package/dist/EmailVerification.js +85 -42
  126. package/dist/EmailVerification.js.map +1 -1
  127. package/dist/Guard.d.ts +11 -0
  128. package/dist/Guard.d.ts.map +1 -0
  129. package/dist/Guard.js +11 -0
  130. package/dist/Guard.js.map +1 -0
  131. package/dist/HibpPwnedPasswords.d.ts +56 -0
  132. package/dist/HibpPwnedPasswords.d.ts.map +1 -0
  133. package/dist/HibpPwnedPasswords.js +232 -0
  134. package/dist/HibpPwnedPasswords.js.map +1 -0
  135. package/dist/HttpApi/AdminPermissionDefinition.d.ts +4 -0
  136. package/dist/HttpApi/AdminPermissionDefinition.d.ts.map +1 -0
  137. package/dist/HttpApi/AdminPermissionDefinition.js +4 -0
  138. package/dist/HttpApi/AdminPermissionDefinition.js.map +1 -0
  139. package/dist/HttpApi/AdminSecurityTimeline.d.ts +4 -0
  140. package/dist/HttpApi/AdminSecurityTimeline.d.ts.map +1 -0
  141. package/dist/HttpApi/AdminSecurityTimeline.js +4 -0
  142. package/dist/HttpApi/AdminSecurityTimeline.js.map +1 -0
  143. package/dist/HttpApi/AdminSession.d.ts +4 -0
  144. package/dist/HttpApi/AdminSession.d.ts.map +1 -0
  145. package/dist/HttpApi/AdminSession.js +4 -0
  146. package/dist/HttpApi/AdminSession.js.map +1 -0
  147. package/dist/HttpApi/AdminTrustedDevice.d.ts +4 -0
  148. package/dist/HttpApi/AdminTrustedDevice.d.ts.map +1 -0
  149. package/dist/HttpApi/AdminTrustedDevice.js +4 -0
  150. package/dist/HttpApi/AdminTrustedDevice.js.map +1 -0
  151. package/dist/HttpApi/Api.d.ts +939 -884
  152. package/dist/HttpApi/Api.d.ts.map +1 -1
  153. package/dist/HttpApi/Api.js +2254 -940
  154. package/dist/HttpApi/Api.js.map +1 -1
  155. package/dist/HttpApi/ApiKey.d.ts +4 -0
  156. package/dist/HttpApi/ApiKey.d.ts.map +1 -0
  157. package/dist/HttpApi/ApiKey.js +4 -0
  158. package/dist/HttpApi/ApiKey.js.map +1 -0
  159. package/dist/HttpApi/Audit.d.ts +6 -0
  160. package/dist/HttpApi/Audit.d.ts.map +1 -0
  161. package/dist/HttpApi/Audit.js +10 -0
  162. package/dist/HttpApi/Audit.js.map +1 -0
  163. package/dist/HttpApi/ClientApi.d.ts +917 -0
  164. package/dist/HttpApi/ClientApi.d.ts.map +1 -0
  165. package/dist/HttpApi/ClientApi.js +101 -0
  166. package/dist/HttpApi/ClientApi.js.map +1 -0
  167. package/dist/HttpApi/EmailAuth.d.ts +6 -0
  168. package/dist/HttpApi/EmailAuth.d.ts.map +1 -0
  169. package/dist/HttpApi/EmailAuth.js +6 -0
  170. package/dist/HttpApi/EmailAuth.js.map +1 -0
  171. package/dist/HttpApi/EmailGuards.d.ts +115 -0
  172. package/dist/HttpApi/EmailGuards.d.ts.map +1 -0
  173. package/dist/HttpApi/EmailGuards.js +191 -0
  174. package/dist/HttpApi/EmailGuards.js.map +1 -0
  175. package/dist/HttpApi/EmailOtp.d.ts +6 -0
  176. package/dist/HttpApi/EmailOtp.d.ts.map +1 -0
  177. package/dist/HttpApi/EmailOtp.js +6 -0
  178. package/dist/HttpApi/EmailOtp.js.map +1 -0
  179. package/dist/HttpApi/EmailVerification.d.ts +6 -0
  180. package/dist/HttpApi/EmailVerification.d.ts.map +1 -0
  181. package/dist/HttpApi/EmailVerification.js +6 -0
  182. package/dist/HttpApi/EmailVerification.js.map +1 -0
  183. package/dist/HttpApi/Endpoints.d.ts +661 -301
  184. package/dist/HttpApi/Endpoints.d.ts.map +1 -1
  185. package/dist/HttpApi/Endpoints.js +207 -120
  186. package/dist/HttpApi/Endpoints.js.map +1 -1
  187. package/dist/HttpApi/Errors.d.ts +37 -10
  188. package/dist/HttpApi/Errors.d.ts.map +1 -1
  189. package/dist/HttpApi/Errors.js +42 -4
  190. package/dist/HttpApi/Errors.js.map +1 -1
  191. package/dist/HttpApi/Identity.d.ts +6 -0
  192. package/dist/HttpApi/Identity.d.ts.map +1 -0
  193. package/dist/HttpApi/Identity.js +6 -0
  194. package/dist/HttpApi/Identity.js.map +1 -0
  195. package/dist/HttpApi/IdentityGuards.d.ts +31 -0
  196. package/dist/HttpApi/IdentityGuards.d.ts.map +1 -0
  197. package/dist/HttpApi/IdentityGuards.js +33 -0
  198. package/dist/HttpApi/IdentityGuards.js.map +1 -0
  199. package/dist/HttpApi/Jwt.d.ts +4 -0
  200. package/dist/HttpApi/Jwt.d.ts.map +1 -0
  201. package/dist/HttpApi/Jwt.js +4 -0
  202. package/dist/HttpApi/Jwt.js.map +1 -0
  203. package/dist/HttpApi/JwtDiscovery.d.ts +4 -0
  204. package/dist/HttpApi/JwtDiscovery.d.ts.map +1 -0
  205. package/dist/HttpApi/JwtDiscovery.js +4 -0
  206. package/dist/HttpApi/JwtDiscovery.js.map +1 -0
  207. package/dist/HttpApi/LoginApproval.d.ts +5 -0
  208. package/dist/HttpApi/LoginApproval.d.ts.map +1 -0
  209. package/dist/HttpApi/LoginApproval.js +5 -0
  210. package/dist/HttpApi/LoginApproval.js.map +1 -0
  211. package/dist/HttpApi/LoginNotification.d.ts +5 -0
  212. package/dist/HttpApi/LoginNotification.d.ts.map +1 -0
  213. package/dist/HttpApi/LoginNotification.js +5 -0
  214. package/dist/HttpApi/LoginNotification.js.map +1 -0
  215. package/dist/HttpApi/MagicLink.d.ts +6 -0
  216. package/dist/HttpApi/MagicLink.d.ts.map +1 -0
  217. package/dist/HttpApi/MagicLink.js +6 -0
  218. package/dist/HttpApi/MagicLink.js.map +1 -0
  219. package/dist/HttpApi/Mfa.d.ts +5 -0
  220. package/dist/HttpApi/Mfa.d.ts.map +1 -0
  221. package/dist/HttpApi/Mfa.js +5 -0
  222. package/dist/HttpApi/Mfa.js.map +1 -0
  223. package/dist/HttpApi/Middleware.d.ts +26 -0
  224. package/dist/HttpApi/Middleware.d.ts.map +1 -0
  225. package/dist/HttpApi/Middleware.js +13 -0
  226. package/dist/HttpApi/Middleware.js.map +1 -0
  227. package/dist/HttpApi/OAuth.d.ts +4 -0
  228. package/dist/HttpApi/OAuth.d.ts.map +1 -0
  229. package/dist/HttpApi/OAuth.js +4 -0
  230. package/dist/HttpApi/OAuth.js.map +1 -0
  231. package/dist/HttpApi/OAuthDeviceAuthorization.d.ts +106 -0
  232. package/dist/HttpApi/OAuthDeviceAuthorization.d.ts.map +1 -0
  233. package/dist/HttpApi/OAuthDeviceAuthorization.js +415 -0
  234. package/dist/HttpApi/OAuthDeviceAuthorization.js.map +1 -0
  235. package/dist/HttpApi/OAuthProviderAuthorization.d.ts +4 -0
  236. package/dist/HttpApi/OAuthProviderAuthorization.d.ts.map +1 -0
  237. package/dist/HttpApi/OAuthProviderAuthorization.js +4 -0
  238. package/dist/HttpApi/OAuthProviderAuthorization.js.map +1 -0
  239. package/dist/HttpApi/OAuthToken.d.ts +4 -0
  240. package/dist/HttpApi/OAuthToken.d.ts.map +1 -0
  241. package/dist/HttpApi/OAuthToken.js +4 -0
  242. package/dist/HttpApi/OAuthToken.js.map +1 -0
  243. package/dist/HttpApi/OidcDiscovery.d.ts +4 -0
  244. package/dist/HttpApi/OidcDiscovery.d.ts.map +1 -0
  245. package/dist/HttpApi/OidcDiscovery.js +4 -0
  246. package/dist/HttpApi/OidcDiscovery.js.map +1 -0
  247. package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts +28 -0
  248. package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts.map +1 -0
  249. package/dist/HttpApi/Operations/AdminPermissionDefinition.js +5 -0
  250. package/dist/HttpApi/Operations/AdminPermissionDefinition.js.map +1 -0
  251. package/dist/HttpApi/Operations/AdminSession.d.ts +16 -0
  252. package/dist/HttpApi/Operations/AdminSession.d.ts.map +1 -0
  253. package/dist/HttpApi/Operations/AdminSession.js +5 -0
  254. package/dist/HttpApi/Operations/AdminSession.js.map +1 -0
  255. package/dist/HttpApi/Operations/ApiKey.d.ts +16 -0
  256. package/dist/HttpApi/Operations/ApiKey.d.ts.map +1 -0
  257. package/dist/HttpApi/Operations/ApiKey.js +5 -0
  258. package/dist/HttpApi/Operations/ApiKey.js.map +1 -0
  259. package/dist/HttpApi/Operations/Discovery.d.ts +19 -0
  260. package/dist/HttpApi/Operations/Discovery.d.ts.map +1 -0
  261. package/dist/HttpApi/Operations/Discovery.js +7 -0
  262. package/dist/HttpApi/Operations/Discovery.js.map +1 -0
  263. package/dist/HttpApi/Operations/EmailAuth.d.ts +12 -0
  264. package/dist/HttpApi/Operations/EmailAuth.d.ts.map +1 -0
  265. package/dist/HttpApi/Operations/EmailAuth.js +5 -0
  266. package/dist/HttpApi/Operations/EmailAuth.js.map +1 -0
  267. package/dist/HttpApi/Operations/EmailOtp.d.ts +14 -0
  268. package/dist/HttpApi/Operations/EmailOtp.d.ts.map +1 -0
  269. package/dist/HttpApi/Operations/EmailOtp.js +5 -0
  270. package/dist/HttpApi/Operations/EmailOtp.js.map +1 -0
  271. package/dist/HttpApi/Operations/EmailVerification.d.ts +14 -0
  272. package/dist/HttpApi/Operations/EmailVerification.d.ts.map +1 -0
  273. package/dist/HttpApi/Operations/EmailVerification.js +5 -0
  274. package/dist/HttpApi/Operations/EmailVerification.js.map +1 -0
  275. package/dist/HttpApi/Operations/HttpOperation.d.ts +4 -0
  276. package/dist/HttpApi/Operations/HttpOperation.d.ts.map +1 -0
  277. package/dist/HttpApi/Operations/HttpOperation.js +2 -0
  278. package/dist/HttpApi/Operations/HttpOperation.js.map +1 -0
  279. package/dist/HttpApi/Operations/Identity.d.ts +22 -0
  280. package/dist/HttpApi/Operations/Identity.d.ts.map +1 -0
  281. package/dist/HttpApi/Operations/Identity.js +5 -0
  282. package/dist/HttpApi/Operations/Identity.js.map +1 -0
  283. package/dist/HttpApi/Operations/Jwt.d.ts +14 -0
  284. package/dist/HttpApi/Operations/Jwt.d.ts.map +1 -0
  285. package/dist/HttpApi/Operations/Jwt.js +5 -0
  286. package/dist/HttpApi/Operations/Jwt.js.map +1 -0
  287. package/dist/HttpApi/Operations/LoginApproval.d.ts +16 -0
  288. package/dist/HttpApi/Operations/LoginApproval.d.ts.map +1 -0
  289. package/dist/HttpApi/Operations/LoginApproval.js +5 -0
  290. package/dist/HttpApi/Operations/LoginApproval.js.map +1 -0
  291. package/dist/HttpApi/Operations/MagicLink.d.ts +14 -0
  292. package/dist/HttpApi/Operations/MagicLink.d.ts.map +1 -0
  293. package/dist/HttpApi/Operations/MagicLink.js +5 -0
  294. package/dist/HttpApi/Operations/MagicLink.js.map +1 -0
  295. package/dist/HttpApi/Operations/Mfa.d.ts +24 -0
  296. package/dist/HttpApi/Operations/Mfa.d.ts.map +1 -0
  297. package/dist/HttpApi/Operations/Mfa.js +5 -0
  298. package/dist/HttpApi/Operations/Mfa.js.map +1 -0
  299. package/dist/HttpApi/Operations/OAuth.d.ts +20 -0
  300. package/dist/HttpApi/Operations/OAuth.d.ts.map +1 -0
  301. package/dist/HttpApi/Operations/OAuth.js +5 -0
  302. package/dist/HttpApi/Operations/OAuth.js.map +1 -0
  303. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.d.ts +23 -0
  304. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.d.ts.map +1 -0
  305. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.js +7 -0
  306. package/dist/HttpApi/Operations/OAuthDeviceAuthorization.js.map +1 -0
  307. package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts +12 -0
  308. package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts.map +1 -0
  309. package/dist/HttpApi/Operations/OAuthProviderAuthorization.js +5 -0
  310. package/dist/HttpApi/Operations/OAuthProviderAuthorization.js.map +1 -0
  311. package/dist/HttpApi/Operations/OAuthToken.d.ts +26 -0
  312. package/dist/HttpApi/Operations/OAuthToken.d.ts.map +1 -0
  313. package/dist/HttpApi/Operations/OAuthToken.js +9 -0
  314. package/dist/HttpApi/Operations/OAuthToken.js.map +1 -0
  315. package/dist/HttpApi/Operations/Passkey.d.ts +22 -0
  316. package/dist/HttpApi/Operations/Passkey.d.ts.map +1 -0
  317. package/dist/HttpApi/Operations/Passkey.js +5 -0
  318. package/dist/HttpApi/Operations/Passkey.js.map +1 -0
  319. package/dist/HttpApi/Operations/Password.d.ts +22 -0
  320. package/dist/HttpApi/Operations/Password.d.ts.map +1 -0
  321. package/dist/HttpApi/Operations/Password.js +5 -0
  322. package/dist/HttpApi/Operations/Password.js.map +1 -0
  323. package/dist/HttpApi/Operations/RecoveryCodes.d.ts +20 -0
  324. package/dist/HttpApi/Operations/RecoveryCodes.d.ts.map +1 -0
  325. package/dist/HttpApi/Operations/RecoveryCodes.js +5 -0
  326. package/dist/HttpApi/Operations/RecoveryCodes.js.map +1 -0
  327. package/dist/HttpApi/Operations/RefreshToken.d.ts +12 -0
  328. package/dist/HttpApi/Operations/RefreshToken.d.ts.map +1 -0
  329. package/dist/HttpApi/Operations/RefreshToken.js +5 -0
  330. package/dist/HttpApi/Operations/RefreshToken.js.map +1 -0
  331. package/dist/HttpApi/Operations/Security.d.ts +12 -0
  332. package/dist/HttpApi/Operations/Security.d.ts.map +1 -0
  333. package/dist/HttpApi/Operations/Security.js +5 -0
  334. package/dist/HttpApi/Operations/Security.js.map +1 -0
  335. package/dist/HttpApi/Operations/SecurityTimeline.d.ts +19 -0
  336. package/dist/HttpApi/Operations/SecurityTimeline.d.ts.map +1 -0
  337. package/dist/HttpApi/Operations/SecurityTimeline.js +7 -0
  338. package/dist/HttpApi/Operations/SecurityTimeline.js.map +1 -0
  339. package/dist/HttpApi/Operations/Session.d.ts +22 -0
  340. package/dist/HttpApi/Operations/Session.d.ts.map +1 -0
  341. package/dist/HttpApi/Operations/Session.js +5 -0
  342. package/dist/HttpApi/Operations/Session.js.map +1 -0
  343. package/dist/HttpApi/Operations/StepUp.d.ts +22 -0
  344. package/dist/HttpApi/Operations/StepUp.d.ts.map +1 -0
  345. package/dist/HttpApi/Operations/StepUp.js +5 -0
  346. package/dist/HttpApi/Operations/StepUp.js.map +1 -0
  347. package/dist/HttpApi/Operations/Totp.d.ts +20 -0
  348. package/dist/HttpApi/Operations/Totp.d.ts.map +1 -0
  349. package/dist/HttpApi/Operations/Totp.js +5 -0
  350. package/dist/HttpApi/Operations/Totp.js.map +1 -0
  351. package/dist/HttpApi/Operations/TrustedDevice.d.ts +23 -0
  352. package/dist/HttpApi/Operations/TrustedDevice.d.ts.map +1 -0
  353. package/dist/HttpApi/Operations/TrustedDevice.js +7 -0
  354. package/dist/HttpApi/Operations/TrustedDevice.js.map +1 -0
  355. package/dist/HttpApi/Operations/index.d.ts +28 -0
  356. package/dist/HttpApi/Operations/index.d.ts.map +1 -0
  357. package/dist/HttpApi/Operations/index.js +28 -0
  358. package/dist/HttpApi/Operations/index.js.map +1 -0
  359. package/dist/HttpApi/Passkey.d.ts +5 -0
  360. package/dist/HttpApi/Passkey.d.ts.map +1 -0
  361. package/dist/HttpApi/Passkey.js +5 -0
  362. package/dist/HttpApi/Passkey.js.map +1 -0
  363. package/dist/HttpApi/Password.d.ts +220 -0
  364. package/dist/HttpApi/Password.d.ts.map +1 -0
  365. package/dist/HttpApi/Password.js +237 -0
  366. package/dist/HttpApi/Password.js.map +1 -0
  367. package/dist/HttpApi/PasswordContract.d.ts +307 -0
  368. package/dist/HttpApi/PasswordContract.d.ts.map +1 -0
  369. package/dist/HttpApi/PasswordContract.js +63 -0
  370. package/dist/HttpApi/PasswordContract.js.map +1 -0
  371. package/dist/HttpApi/PasswordGuards.d.ts +82 -0
  372. package/dist/HttpApi/PasswordGuards.d.ts.map +1 -0
  373. package/dist/HttpApi/PasswordGuards.js +179 -0
  374. package/dist/HttpApi/PasswordGuards.js.map +1 -0
  375. package/dist/HttpApi/RecoveryCodes.d.ts +5 -0
  376. package/dist/HttpApi/RecoveryCodes.d.ts.map +1 -0
  377. package/dist/HttpApi/RecoveryCodes.js +5 -0
  378. package/dist/HttpApi/RecoveryCodes.js.map +1 -0
  379. package/dist/HttpApi/RefreshToken.d.ts +4 -0
  380. package/dist/HttpApi/RefreshToken.d.ts.map +1 -0
  381. package/dist/HttpApi/RefreshToken.js +4 -0
  382. package/dist/HttpApi/RefreshToken.js.map +1 -0
  383. package/dist/HttpApi/RequestGuards.d.ts +42 -0
  384. package/dist/HttpApi/RequestGuards.d.ts.map +1 -0
  385. package/dist/HttpApi/RequestGuards.js +194 -0
  386. package/dist/HttpApi/RequestGuards.js.map +1 -0
  387. package/dist/HttpApi/Schemas.d.ts +399 -84
  388. package/dist/HttpApi/Schemas.d.ts.map +1 -1
  389. package/dist/HttpApi/Schemas.js +225 -27
  390. package/dist/HttpApi/Schemas.js.map +1 -1
  391. package/dist/HttpApi/Security.d.ts +12 -93
  392. package/dist/HttpApi/Security.d.ts.map +1 -1
  393. package/dist/HttpApi/Security.js +65 -356
  394. package/dist/HttpApi/Security.js.map +1 -1
  395. package/dist/HttpApi/SecurityTimeline.d.ts +4 -0
  396. package/dist/HttpApi/SecurityTimeline.d.ts.map +1 -0
  397. package/dist/HttpApi/SecurityTimeline.js +4 -0
  398. package/dist/HttpApi/SecurityTimeline.js.map +1 -0
  399. package/dist/HttpApi/Session.d.ts +5 -0
  400. package/dist/HttpApi/Session.d.ts.map +1 -0
  401. package/dist/HttpApi/Session.js +5 -0
  402. package/dist/HttpApi/Session.js.map +1 -0
  403. package/dist/HttpApi/SessionGuards.d.ts +15 -0
  404. package/dist/HttpApi/SessionGuards.d.ts.map +1 -0
  405. package/dist/HttpApi/SessionGuards.js +37 -0
  406. package/dist/HttpApi/SessionGuards.js.map +1 -0
  407. package/dist/HttpApi/StepUp.d.ts +5 -0
  408. package/dist/HttpApi/StepUp.d.ts.map +1 -0
  409. package/dist/HttpApi/StepUp.js +5 -0
  410. package/dist/HttpApi/StepUp.js.map +1 -0
  411. package/dist/HttpApi/Totp.d.ts +5 -0
  412. package/dist/HttpApi/Totp.d.ts.map +1 -0
  413. package/dist/HttpApi/Totp.js +5 -0
  414. package/dist/HttpApi/Totp.js.map +1 -0
  415. package/dist/HttpApi/Transport.d.ts +4 -6
  416. package/dist/HttpApi/Transport.d.ts.map +1 -1
  417. package/dist/HttpApi/Transport.js +62 -36
  418. package/dist/HttpApi/Transport.js.map +1 -1
  419. package/dist/HttpApi/TrustedDevice.d.ts +4 -0
  420. package/dist/HttpApi/TrustedDevice.d.ts.map +1 -0
  421. package/dist/HttpApi/TrustedDevice.js +4 -0
  422. package/dist/HttpApi/TrustedDevice.js.map +1 -0
  423. package/dist/HttpApi/index.d.ts +11 -3
  424. package/dist/HttpApi/index.d.ts.map +1 -1
  425. package/dist/HttpApi/index.js +10 -2
  426. package/dist/HttpApi/index.js.map +1 -1
  427. package/dist/Identity.d.ts +138 -0
  428. package/dist/Identity.d.ts.map +1 -0
  429. package/dist/Identity.js +105 -0
  430. package/dist/Identity.js.map +1 -0
  431. package/dist/IdentityManagement.d.ts +189 -0
  432. package/dist/IdentityManagement.d.ts.map +1 -0
  433. package/dist/IdentityManagement.js +317 -0
  434. package/dist/IdentityManagement.js.map +1 -0
  435. package/dist/IncidentAction.d.ts +1 -4
  436. package/dist/IncidentAction.d.ts.map +1 -1
  437. package/dist/IncidentAction.js +15 -9
  438. package/dist/IncidentAction.js.map +1 -1
  439. package/dist/Internal.d.ts +2 -0
  440. package/dist/Internal.d.ts.map +1 -1
  441. package/dist/Internal.js +30 -0
  442. package/dist/Internal.js.map +1 -1
  443. package/dist/Invitation.d.ts +362 -0
  444. package/dist/Invitation.d.ts.map +1 -0
  445. package/dist/Invitation.js +633 -0
  446. package/dist/Invitation.js.map +1 -0
  447. package/dist/IpQualityScore.d.ts +38 -0
  448. package/dist/IpQualityScore.d.ts.map +1 -0
  449. package/dist/IpQualityScore.js +215 -0
  450. package/dist/IpQualityScore.js.map +1 -0
  451. package/dist/Jwt.d.ts +8 -29
  452. package/dist/Jwt.d.ts.map +1 -1
  453. package/dist/Jwt.js +67 -56
  454. package/dist/Jwt.js.map +1 -1
  455. package/dist/LoginApproval.d.ts +15 -30
  456. package/dist/LoginApproval.d.ts.map +1 -1
  457. package/dist/LoginApproval.js +47 -41
  458. package/dist/LoginApproval.js.map +1 -1
  459. package/dist/LoginNotification.d.ts +7 -11
  460. package/dist/LoginNotification.d.ts.map +1 -1
  461. package/dist/LoginNotification.js +30 -17
  462. package/dist/LoginNotification.js.map +1 -1
  463. package/dist/LoginRisk.d.ts +11 -13
  464. package/dist/LoginRisk.d.ts.map +1 -1
  465. package/dist/LoginRisk.js +66 -26
  466. package/dist/LoginRisk.js.map +1 -1
  467. package/dist/LoginRiskEnrichment.d.ts +145 -0
  468. package/dist/LoginRiskEnrichment.d.ts.map +1 -0
  469. package/dist/LoginRiskEnrichment.js +220 -0
  470. package/dist/LoginRiskEnrichment.js.map +1 -0
  471. package/dist/MagicLink.d.ts +9 -8
  472. package/dist/MagicLink.d.ts.map +1 -1
  473. package/dist/MagicLink.js +40 -73
  474. package/dist/MagicLink.js.map +1 -1
  475. package/dist/Mailer.d.ts +19 -12
  476. package/dist/Mailer.d.ts.map +1 -1
  477. package/dist/Mailer.js +10 -12
  478. package/dist/Mailer.js.map +1 -1
  479. package/dist/OAuth.d.ts +138 -96
  480. package/dist/OAuth.d.ts.map +1 -1
  481. package/dist/OAuth.js +522 -221
  482. package/dist/OAuth.js.map +1 -1
  483. package/dist/OAuthDeviceAuthorization.d.ts +378 -0
  484. package/dist/OAuthDeviceAuthorization.d.ts.map +1 -0
  485. package/dist/OAuthDeviceAuthorization.js +860 -0
  486. package/dist/OAuthDeviceAuthorization.js.map +1 -0
  487. package/dist/OAuthDeviceClient.d.ts +76 -0
  488. package/dist/OAuthDeviceClient.d.ts.map +1 -0
  489. package/dist/OAuthDeviceClient.js +362 -0
  490. package/dist/OAuthDeviceClient.js.map +1 -0
  491. package/dist/Passkey.d.ts +15 -17
  492. package/dist/Passkey.d.ts.map +1 -1
  493. package/dist/Passkey.js +86 -39
  494. package/dist/Passkey.js.map +1 -1
  495. package/dist/PasskeyBrowser.d.ts +39 -0
  496. package/dist/PasskeyBrowser.d.ts.map +1 -0
  497. package/dist/PasskeyBrowser.js +206 -0
  498. package/dist/PasskeyBrowser.js.map +1 -0
  499. package/dist/PasskeySimpleWebAuthn.d.ts.map +1 -1
  500. package/dist/PasskeySimpleWebAuthn.js +19 -7
  501. package/dist/PasskeySimpleWebAuthn.js.map +1 -1
  502. package/dist/Password.d.ts +71 -37
  503. package/dist/Password.d.ts.map +1 -1
  504. package/dist/Password.js +295 -81
  505. package/dist/Password.js.map +1 -1
  506. package/dist/PasswordRisk.d.ts +63 -0
  507. package/dist/PasswordRisk.d.ts.map +1 -0
  508. package/dist/PasswordRisk.js +37 -0
  509. package/dist/PasswordRisk.js.map +1 -0
  510. package/dist/Permission.d.ts +308 -0
  511. package/dist/Permission.d.ts.map +1 -0
  512. package/dist/Permission.js +827 -0
  513. package/dist/Permission.js.map +1 -0
  514. package/dist/Policy.d.ts +31 -18
  515. package/dist/Policy.d.ts.map +1 -1
  516. package/dist/Policy.js +58 -14
  517. package/dist/Policy.js.map +1 -1
  518. package/dist/PostgresDelegatedAccessStorageMigrations.d.ts +7 -0
  519. package/dist/PostgresDelegatedAccessStorageMigrations.d.ts.map +1 -0
  520. package/dist/PostgresDelegatedAccessStorageMigrations.js +119 -0
  521. package/dist/PostgresDelegatedAccessStorageMigrations.js.map +1 -0
  522. package/dist/PostgresStorageMigrations.d.ts +16 -0
  523. package/dist/PostgresStorageMigrations.d.ts.map +1 -0
  524. package/dist/PostgresStorageMigrations.js +674 -0
  525. package/dist/PostgresStorageMigrations.js.map +1 -0
  526. package/dist/Privacy.d.ts +0 -3
  527. package/dist/Privacy.d.ts.map +1 -1
  528. package/dist/Privacy.js +2 -5
  529. package/dist/Privacy.js.map +1 -1
  530. package/dist/RateLimiter.d.ts +0 -3
  531. package/dist/RateLimiter.d.ts.map +1 -1
  532. package/dist/RateLimiter.js +1 -4
  533. package/dist/RateLimiter.js.map +1 -1
  534. package/dist/RecoveryCode.d.ts +83 -11
  535. package/dist/RecoveryCode.d.ts.map +1 -1
  536. package/dist/RecoveryCode.js +273 -75
  537. package/dist/RecoveryCode.js.map +1 -1
  538. package/dist/RecoveryPolicy.d.ts +81 -0
  539. package/dist/RecoveryPolicy.d.ts.map +1 -0
  540. package/dist/RecoveryPolicy.js +119 -0
  541. package/dist/RecoveryPolicy.js.map +1 -0
  542. package/dist/RefreshToken.d.ts +0 -9
  543. package/dist/RefreshToken.d.ts.map +1 -1
  544. package/dist/RefreshToken.js +31 -22
  545. package/dist/RefreshToken.js.map +1 -1
  546. package/dist/Retention.d.ts +6 -1
  547. package/dist/Retention.d.ts.map +1 -1
  548. package/dist/Retention.js +49 -12
  549. package/dist/Retention.js.map +1 -1
  550. package/dist/SecurityTimeline.d.ts +1 -4
  551. package/dist/SecurityTimeline.d.ts.map +1 -1
  552. package/dist/SecurityTimeline.js +110 -17
  553. package/dist/SecurityTimeline.js.map +1 -1
  554. package/dist/Sessions.d.ts +124 -29
  555. package/dist/Sessions.d.ts.map +1 -1
  556. package/dist/Sessions.js +626 -94
  557. package/dist/Sessions.js.map +1 -1
  558. package/dist/SqliteStorageSchemaData.d.ts +2 -0
  559. package/dist/SqliteStorageSchemaData.d.ts.map +1 -0
  560. package/dist/SqliteStorageSchemaData.js +508 -0
  561. package/dist/SqliteStorageSchemaData.js.map +1 -0
  562. package/dist/StepUp.d.ts +132 -11
  563. package/dist/StepUp.d.ts.map +1 -1
  564. package/dist/StepUp.js +387 -19
  565. package/dist/StepUp.js.map +1 -1
  566. package/dist/Storage.d.ts +89 -23
  567. package/dist/Storage.d.ts.map +1 -1
  568. package/dist/Storage.js +10 -12
  569. package/dist/Storage.js.map +1 -1
  570. package/dist/StorageMigrations.d.ts +7 -1
  571. package/dist/StorageMigrations.d.ts.map +1 -1
  572. package/dist/StorageMigrations.js +256 -0
  573. package/dist/StorageMigrations.js.map +1 -1
  574. package/dist/StorageSchemaGenerator.d.ts +544 -0
  575. package/dist/StorageSchemaGenerator.d.ts.map +1 -0
  576. package/dist/StorageSchemaGenerator.js +1418 -0
  577. package/dist/StorageSchemaGenerator.js.map +1 -0
  578. package/dist/StrongFactor.d.ts +64 -0
  579. package/dist/StrongFactor.d.ts.map +1 -0
  580. package/dist/StrongFactor.js +100 -0
  581. package/dist/StrongFactor.js.map +1 -0
  582. package/dist/Testing.js +2 -2
  583. package/dist/Testing.js.map +1 -1
  584. package/dist/Totp.d.ts +88 -13
  585. package/dist/Totp.d.ts.map +1 -1
  586. package/dist/Totp.js +192 -34
  587. package/dist/Totp.js.map +1 -1
  588. package/dist/TrustedDevice.d.ts +0 -9
  589. package/dist/TrustedDevice.d.ts.map +1 -1
  590. package/dist/TrustedDevice.js +15 -17
  591. package/dist/TrustedDevice.js.map +1 -1
  592. package/dist/WaitUntil.d.ts +0 -3
  593. package/dist/WaitUntil.d.ts.map +1 -1
  594. package/dist/WaitUntil.js +1 -4
  595. package/dist/WaitUntil.js.map +1 -1
  596. package/dist/Webhook.d.ts +42 -12
  597. package/dist/Webhook.d.ts.map +1 -1
  598. package/dist/Webhook.js +165 -26
  599. package/dist/Webhook.js.map +1 -1
  600. package/dist/alchemy/cloudflare/Email.d.ts.map +1 -1
  601. package/dist/alchemy/cloudflare/Email.js +9 -4
  602. package/dist/alchemy/cloudflare/Email.js.map +1 -1
  603. package/dist/cloudflare/Email.js +1 -1
  604. package/dist/cloudflare/Email.js.map +1 -1
  605. package/dist/cloudflare/RequestMetadata.d.ts +49 -0
  606. package/dist/cloudflare/RequestMetadata.d.ts.map +1 -0
  607. package/dist/cloudflare/RequestMetadata.js +71 -0
  608. package/dist/cloudflare/RequestMetadata.js.map +1 -0
  609. package/dist/index.d.ts +0 -35
  610. package/dist/index.d.ts.map +1 -1
  611. package/dist/index.js +0 -35
  612. package/dist/index.js.map +1 -1
  613. package/dist/internal/AuthFlowFinalizer.d.ts +31 -0
  614. package/dist/internal/AuthFlowFinalizer.d.ts.map +1 -0
  615. package/dist/internal/AuthFlowFinalizer.js +182 -0
  616. package/dist/internal/AuthFlowFinalizer.js.map +1 -0
  617. package/migrations/0001_auth_storage.sql +1 -0
  618. package/migrations/0006_auth_totp_factor.sql +1 -0
  619. package/migrations/0024_auth_permission.sql +39 -0
  620. package/migrations/0025_auth_permission_definition.sql +19 -0
  621. package/migrations/0026_auth_user_identity.sql +59 -0
  622. package/migrations/0027_auth_audit_log_storage_order.sql +46 -0
  623. package/migrations/0028_auth_oauth_client_secret_key.sql +39 -0
  624. package/migrations/0029_auth_oauth_device_authorization.sql +28 -0
  625. package/migrations/delegated-access/0001_auth_delegated_access.sql +152 -0
  626. package/migrations/postgres/0001_auth_core.sql +77 -0
  627. package/migrations/postgres/0002_auth_security_oauth.sql +65 -0
  628. package/migrations/postgres/0003_auth_credentials_tokens.sql +43 -0
  629. package/migrations/postgres/0004_auth_security_history.sql +103 -0
  630. package/migrations/postgres/0005_auth_webhooks.sql +29 -0
  631. package/migrations/postgres/0006_auth_domain_timeline.sql +50 -0
  632. package/migrations/postgres/0007_auth_oauth_clients.sql +85 -0
  633. package/migrations/postgres/0008_auth_oauth_tokens.sql +63 -0
  634. package/migrations/postgres/0009_auth_permissions.sql +66 -0
  635. package/migrations/postgres/0010_auth_oauth_device_authorization.sql +50 -0
  636. package/migrations/postgres/delegated-access/0001_auth_delegated_access.sql +112 -0
  637. package/package.json +309 -2
@@ -1,34 +1,60 @@
1
1
  import { Data, Effect, Layer, Option, Redacted } from "effect";
2
- import { Column, Query, Scalar, Table } from "effect-qb";
3
- import { Executor, Query as SqliteQuery } from "effect-qb/sqlite";
4
- import { AuditLogStore, AuditLogStoreError, auditEventActorUserId, auditEventUserId, filterAuditLogEvents, } from "./AuditLog.js";
2
+ import { Column, Executor as EffectQbExecutor, Query, Scalar, Table, } from "effect-qb";
3
+ import { Executor, Query as SqliteQuery, Renderer } from "effect-qb/sqlite";
5
4
  import { ApiKeyId, ApiKeyPrefix, ApiKeySecretHash, ApiKeyStore, ApiKeyStoreError, } from "./ApiKey.js";
5
+ import { deriveAssurance, deriveMfaVerifiedAt, parseAuthenticationEvents, } from "./Assurance.js";
6
+ import { AtomicPlanError } from "./AtomicPlanExecutor.js";
7
+ import { AuditLogStore, AuditLogStoreError, auditEventActorUserId, auditEventUserId, decodeAuditEvent, filterAuditLogEvents, } from "./AuditLog.js";
6
8
  import { DomainName, DomainVerificationId, DomainVerificationProofToken, DomainVerificationStore, DomainVerificationStoreError, } from "./DomainVerification.js";
7
- import { AuthFlowId, ChallengeId, CredentialId, Email, OAuthAccountId, OAuthClientId, OAuthProviderId, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
9
+ import { AuthFlowId, ChallengeId, CredentialId, Email, IdentityId, OAuthAccountId, OAuthClientId, OAuthProviderId, PermissionId, RoleId, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
10
+ import { isIdentityLoginEligible } from "./Identity.js";
8
11
  import { currentUnixMillis } from "./Internal.js";
9
12
  import { JwtError, JwtRevocationStore } from "./Jwt.js";
10
13
  import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
11
14
  import { LoginRiskContext, LoginRiskHistory, LoginRiskHistoryError, LoginRiskSignal, maxLoginRiskLevel, } from "./LoginRisk.js";
12
- import { OAuthAccountStore, OAuthAccountStoreError, OAuthAuthorizationCodeStore, OAuthAuthorizationCodeStoreError, OAuthClientStore, OAuthClientStoreError, OAuthConsentStore, OAuthConsentStoreError, OAuthClientSecretHash, OAuthClientSecretPrefix, OAuthClientSecretStore, OAuthClientSecretStoreError, OAuthCodeChallenge, OAuthProviderModeTokenStore, OAuthProviderModeTokenStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
15
+ import { OAuthAccountStore, OAuthAccountStoreError, OAuthIdentityBridgeConflictError, OAuthIdentityBridgeStore, OAuthIdentityBridgeStoreError, OAuthAuthorizationCodeStore, OAuthAuthorizationCodeStoreError, OAuthClientStore, OAuthClientStoreError, OAuthConsentStore, OAuthConsentStoreError, OAuthClientSecretHash, OAuthClientSecretPrefix, OAuthClientSecretStore, OAuthClientSecretStoreError, OAuthCodeChallenge, OAuthProviderModeTokenStore, OAuthProviderModeTokenStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
16
+ import { OAuthDeviceAuthorizationId, OAuthDeviceAuthorizationStore, OAuthDeviceAuthorizationStoreError, OAuthDeviceCodeTokenCommitStore, OAuthDeviceCodeTokenCommitStoreError, } from "./OAuthDeviceAuthorization.js";
13
17
  import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
14
- import { RecoveryCodeHash, RecoveryCodeStore, RecoveryCodeStoreError, } from "./RecoveryCode.js";
18
+ import { canonicalPermissionScope, normalizePermissionDefinitionListLimit, PermissionStore, PermissionStoreError, } from "./Permission.js";
19
+ import { RecoveryCodeHash, RecoveryCodeLoginCommitStore, RecoveryCodeLoginCommitStoreError, RecoveryCodeStore, RecoveryCodeStoreError, RecoveryCodeSessionRotateStore, RecoveryCodeSessionRotateStoreError, } from "./RecoveryCode.js";
15
20
  import { RefreshTokenFamilyId, RefreshTokenId, RefreshTokenSecretHash, RefreshTokenStore, RefreshTokenStoreError, } from "./RefreshToken.js";
16
21
  import { filterSecurityTimelineEvents, SecurityTimelineEventId, SecurityTimelineStore, SecurityTimelineStoreError, } from "./SecurityTimeline.js";
17
- import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
18
- import { TotpFactorStore, TotpFactorStoreError, TotpSecret, } from "./Totp.js";
22
+ import { CredentialStore, IdentityConflictError, IdentityStore, RegistrationConflictError, RegistrationFailureError, RegistrationStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
23
+ import { TotpFactorStore, TotpFactorStoreError, TotpLoginCommitStore, TotpLoginCommitStoreError, TotpSessionRotateStore, TotpSessionRotateStoreError, TotpSecret, } from "./Totp.js";
19
24
  import { filterTrustedDevicesForUser, TrustedDeviceError, TrustedDeviceStore, } from "./TrustedDevice.js";
20
25
  import { filterDueWebhookOutboxRecords, WebhookOutboxItemId, WebhookOutboxStore, WebhookOutboxStoreError, WebhookReplayStore, WebhookReplayStoreError, } from "./Webhook.js";
26
+ const boundColumnName = (expression) => {
27
+ const state = expression[Symbol.for("effect-qb/BoundColumn")];
28
+ if (typeof state?.columnName !== "string") {
29
+ throw new TypeError("Atomic SQLite storage requires bound table columns");
30
+ }
31
+ return state.columnName;
32
+ };
21
33
  export class D1EffectQbError extends Data.TaggedError("D1EffectQbError") {
22
34
  }
23
35
  const makeAuthUserTable = (name) => Table.make(name, {
24
36
  id: Column.text().pipe(Column.primaryKey),
25
- email: Column.text(),
26
- email_verified: Column.int(),
27
37
  created_at: Column.int(),
28
38
  updated_at: Column.int(),
29
39
  disabled_at: Column.int().pipe(Column.nullable),
30
40
  metadata: Column.text().pipe(Column.nullable),
31
41
  });
42
+ const makeAuthUserIdentityTable = (name) => Table.make(name, {
43
+ id: Column.text().pipe(Column.primaryKey),
44
+ user_id: Column.text(),
45
+ scope_type: Column.text(),
46
+ scope_id: Column.text(),
47
+ kind: Column.text(),
48
+ value: Column.text(),
49
+ normalized_value: Column.text(),
50
+ verified_at: Column.int().pipe(Column.nullable),
51
+ is_primary_login: Column.int(),
52
+ created_at: Column.int(),
53
+ updated_at: Column.int(),
54
+ revoked_at: Column.int().pipe(Column.nullable),
55
+ replaced_by_id: Column.text().pipe(Column.nullable),
56
+ metadata: Column.text().pipe(Column.nullable),
57
+ });
32
58
  const makeAuthCredentialTable = (name) => Table.make(name, {
33
59
  id: Column.text().pipe(Column.primaryKey),
34
60
  user_id: Column.text(),
@@ -46,6 +72,7 @@ const makeAuthSessionTable = (name) => Table.make(name, {
46
72
  created_at: Column.int(),
47
73
  expires_at: Column.int(),
48
74
  auth_time: Column.int(),
75
+ authentication_events: Column.text(),
49
76
  aal: Column.text(),
50
77
  amr: Column.text(),
51
78
  mfa_verified_at: Column.int().pipe(Column.nullable),
@@ -65,6 +92,7 @@ const makeAuthVerificationTable = (name) => Table.make(name, {
65
92
  consumed_at: Column.int().pipe(Column.nullable),
66
93
  });
67
94
  const makeAuthAuditLogTable = (name) => Table.make(name, {
95
+ storage_id: Column.int().pipe(Column.primaryKey),
68
96
  id: Column.text().pipe(Column.nullable),
69
97
  type: Column.text(),
70
98
  user_id: Column.text().pipe(Column.nullable),
@@ -142,6 +170,7 @@ const makeAuthTotpFactorTable = (name) => Table.make(name, {
142
170
  created_at: Column.int(),
143
171
  confirmed_at: Column.int().pipe(Column.nullable),
144
172
  last_used_at: Column.int().pipe(Column.nullable),
173
+ last_accepted_counter: Column.int().pipe(Column.nullable),
145
174
  revoked_at: Column.int().pipe(Column.nullable),
146
175
  metadata: Column.text().pipe(Column.nullable),
147
176
  });
@@ -249,8 +278,27 @@ const makeAuthOAuthAuthorizationCodeTable = (name) => Table.make(name, {
249
278
  consumed_at: Column.int().pipe(Column.nullable),
250
279
  metadata: Column.text().pipe(Column.nullable),
251
280
  });
281
+ const makeAuthOAuthDeviceAuthorizationTable = (name) => Table.make(name, {
282
+ id: Column.text().pipe(Column.primaryKey),
283
+ device_code_hash: Column.text().pipe(Column.unique),
284
+ user_code_hash: Column.text().pipe(Column.unique),
285
+ client_id: Column.text(),
286
+ requested_scopes: Column.text(),
287
+ granted_scopes: Column.text().pipe(Column.nullable),
288
+ subject: Column.text().pipe(Column.nullable),
289
+ status: Column.text(),
290
+ issued_at: Column.int(),
291
+ expires_at: Column.int(),
292
+ poll_interval_seconds: Column.int(),
293
+ next_poll_at: Column.int(),
294
+ last_polled_at: Column.int().pipe(Column.nullable),
295
+ approved_at: Column.int().pipe(Column.nullable),
296
+ denied_at: Column.int().pipe(Column.nullable),
297
+ consumed_at: Column.int().pipe(Column.nullable),
298
+ metadata: Column.text().pipe(Column.nullable),
299
+ });
252
300
  const makeAuthOAuthClientSecretTable = (name) => Table.make(name, {
253
- prefix: Column.text().pipe(Column.primaryKey),
301
+ prefix: Column.text(),
254
302
  client_id: Column.text(),
255
303
  secret_hash: Column.text(),
256
304
  authentication_methods: Column.text(),
@@ -320,6 +368,52 @@ const makeAuthWebhookReplayTable = (name) => Table.make(name, {
320
368
  expires_at: Column.int(),
321
369
  created_at: Column.int(),
322
370
  });
371
+ const makeAuthPermissionDefinitionTable = (name) => Table.make(name, {
372
+ id: Column.text().pipe(Column.primaryKey),
373
+ description: Column.text().pipe(Column.nullable),
374
+ scope_type_present: Column.int(),
375
+ scope_type: Column.text(),
376
+ created_at: Column.int(),
377
+ updated_at: Column.int(),
378
+ disabled_at: Column.int().pipe(Column.nullable),
379
+ deleted_at: Column.int().pipe(Column.nullable),
380
+ });
381
+ const makeAuthRoleDefinitionTable = (name) => Table.make(name, {
382
+ id: Column.text().pipe(Column.primaryKey),
383
+ description: Column.text().pipe(Column.nullable),
384
+ created_at: Column.int(),
385
+ updated_at: Column.int(),
386
+ disabled_at: Column.int().pipe(Column.nullable),
387
+ deleted_at: Column.int().pipe(Column.nullable),
388
+ });
389
+ const makeAuthPermissionGrantTable = (name) => Table.make(name, {
390
+ subject_type: Column.text(),
391
+ subject_id: Column.text(),
392
+ permission_id: Column.text(),
393
+ scope_type: Column.text(),
394
+ scope_id_present: Column.int(),
395
+ scope_id: Column.text(),
396
+ expires_at: Column.int().pipe(Column.nullable),
397
+ metadata: Column.text().pipe(Column.nullable),
398
+ revoked_at: Column.int().pipe(Column.nullable),
399
+ });
400
+ const makeAuthRoleGrantTable = (name) => Table.make(name, {
401
+ subject_type: Column.text(),
402
+ subject_id: Column.text(),
403
+ role_id: Column.text(),
404
+ scope_type: Column.text(),
405
+ scope_id_present: Column.int(),
406
+ scope_id: Column.text(),
407
+ expires_at: Column.int().pipe(Column.nullable),
408
+ metadata: Column.text().pipe(Column.nullable),
409
+ revoked_at: Column.int().pipe(Column.nullable),
410
+ });
411
+ const makeAuthRolePermissionTable = (name) => Table.make(name, {
412
+ role_id: Column.text(),
413
+ permission_id: Column.text(),
414
+ scope_type_present: Column.int(),
415
+ scope_type: Column.text(),
416
+ });
323
417
  const authSqliteTablesTypeId = Symbol.for("@effect-auth/core/EffectQbSqliteStorage/AuthSqliteTables");
324
418
  const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
325
419
  const jsonDecode = (value) => {
@@ -346,14 +440,60 @@ const decodeBoolean = (value) => {
346
440
  };
347
441
  const mutationValues = (value) => value;
348
442
  const queryInsert = (table, values) => Query.insert(table, values);
443
+ const queryInsertMany = (table, values) => Query.from(Query.values(values))(Query.insert(table));
349
444
  const queryUpdate = (table, values) => Query.update(table, values);
350
445
  const queryEq = (left, right) => Query.eq(left, right);
446
+ const queryNeq = (left, right) => Query.neq(left, right);
351
447
  const queryGt = (left, right) => Query.gt(left, right);
352
448
  const queryLt = (left, right) => Query.lt(left, right);
353
449
  const queryLte = (left, right) => Query.lte(left, right);
354
450
  const queryIsNull = (value) => Query.isNull(value);
451
+ const queryIsNotNull = (value) => Query.isNotNull(value);
452
+ const queryIsNotDistinctFrom = (left, right) => Query.isNotDistinctFrom(left, right);
453
+ const queryAnd = (...values) => Query.and(...values);
454
+ const queryOr = (...values) => Query.or(...values);
455
+ const queryNot = (value) => Query.not(value);
456
+ const queryExists = (plan) => Query.exists(plan);
457
+ const queryScalar = (plan) => Query.scalar(plan);
458
+ const queryChanges = () => Query.call("changes");
459
+ const queryChangedOne = () => queryEq(queryChanges(), 1);
460
+ const queryWithoutNullRefinement = (value) => Query.cast(value, value[Scalar.TypeId].dbType);
355
461
  const queryIn = (value, first, ...rest) => Query.in(value, first, ...rest);
462
+ const sqliteCastLike = (value, column) => Query.cast(value, column[Scalar.TypeId].dbType);
463
+ const sqliteIncrementByFive = (value) => Query.call("length", Query.concat(Query.call("printf", "%.*c", value, "x"), "xxxxx"));
464
+ const sqliteUnixMillisAfterSeconds = (now, seconds, textColumn, millisColumn) => {
465
+ const secondsText = sqliteCastLike(seconds, textColumn);
466
+ const modifier = Query.concat("+", secondsText, " seconds");
467
+ const epochSeconds = Query.call("strftime", "%s", new Date(Number(now)).toISOString(), modifier);
468
+ const fractionalSeconds = Query.call("strftime", "%f", new Date(Number(now)).toISOString(), modifier);
469
+ const milliseconds = Query.call("substr", fractionalSeconds, -3);
470
+ return sqliteCastLike(Query.concat(epochSeconds, milliseconds), millisColumn);
471
+ };
472
+ const aliasTable = (table, alias) => Table.alias(table, alias);
473
+ const aliasColumn = (table, column) => table[boundColumnName(column)];
474
+ const queryAstTypeId = Symbol.for("effect-qb/QueryAst");
475
+ const targetMutationExpression = (table, columnName, value) => {
476
+ if (value === null) {
477
+ return Query.literal(null);
478
+ }
479
+ const plan = queryUpdate(table, { [columnName]: value });
480
+ const ast = plan[queryAstTypeId];
481
+ const assignment = ast?.set?.find((candidate) => candidate.columnName === columnName);
482
+ if (assignment === undefined) {
483
+ throw new TypeError(`Invalid SQLite mutation column: ${columnName}`);
484
+ }
485
+ return assignment.value;
486
+ };
487
+ const queryInsertWhere = (table, values, predicate) => {
488
+ const selection = Object.fromEntries(Object.entries(values).map(([key, value]) => [
489
+ key,
490
+ targetMutationExpression(table, key, value),
491
+ ]));
492
+ const select = Query.where(predicate)(Query.select(selection));
493
+ return Query.from(select)(Query.insert(table));
494
+ };
356
495
  const executePlan = (executor, plan) => executor.execute(plan);
496
+ const executeAtomicPlans = (atomic, plans) => atomic.execute(plans);
357
497
  const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
358
498
  const sessionMetadataEnvelopeKey = "__effectAuthSession";
359
499
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -426,13 +566,31 @@ const encodeSessionMetadata = (row) => {
426
566
  };
427
567
  const decodeDefaultUserRow = (row) => omitUndefined({
428
568
  id: UserId(row.id),
429
- email: Email(row.email),
430
- emailVerified: decodeBoolean(row.emailVerified),
431
569
  createdAt: UnixMillis(Number(row.createdAt)),
432
570
  updatedAt: UnixMillis(Number(row.updatedAt)),
433
571
  disabledAt: optionalUnixMillis(row.disabledAt),
434
572
  metadata: jsonDecode(row.metadata),
435
573
  });
574
+ const decodeDefaultUserIdentityRow = (row) => omitUndefined({
575
+ id: IdentityId(row.id),
576
+ userId: UserId(row.userId),
577
+ scope: row.scopeType === "global"
578
+ ? { type: "global" }
579
+ : { type: "tenant", tenantId: row.scopeId },
580
+ kind: row.kind,
581
+ value: row.value,
582
+ normalizedValue: row.normalizedValue,
583
+ verifiedAt: optionalUnixMillis(row.verifiedAt),
584
+ isPrimaryLogin: decodeBoolean(row.isPrimaryLogin),
585
+ createdAt: UnixMillis(Number(row.createdAt)),
586
+ updatedAt: UnixMillis(Number(row.updatedAt)),
587
+ revokedAt: optionalUnixMillis(row.revokedAt),
588
+ replacedById: row.replacedById === null ? undefined : IdentityId(row.replacedById),
589
+ metadata: jsonDecode(row.metadata),
590
+ });
591
+ const decodeUserIdentityRow = (tables, row) => tables.identity.decode === undefined
592
+ ? decodeDefaultUserIdentityRow(row)
593
+ : tables.identity.decode(row);
436
594
  const decodeUserRow = (tables, row) => tables.user.decode === undefined
437
595
  ? decodeDefaultUserRow(row)
438
596
  : tables.user.decode(row);
@@ -450,6 +608,34 @@ const decodePasswordCredentialRow = (tables, row) => tables.credential.decodePas
450
608
  : tables.credential.decodePassword(row);
451
609
  const decodeDefaultSessionRow = (row) => {
452
610
  const sessionMetadata = decodeSessionMetadata(row.metadata);
611
+ const authenticationEvents = parseAuthenticationEvents(jsonDecode(row.authenticationEvents));
612
+ if (authenticationEvents.some((event) => event.type === "custom")) {
613
+ return omitUndefined({
614
+ id: SessionId(row.id),
615
+ userId: UserId(row.userId),
616
+ secretHash: row.secretHash,
617
+ createdAt: UnixMillis(Number(row.createdAt)),
618
+ expiresAt: UnixMillis(Number(row.expiresAt)),
619
+ authTime: UnixMillis(Number(row.authTime)),
620
+ authenticationEvents,
621
+ aal: row.aal,
622
+ amr: decodeAmr(row.amr),
623
+ mfaVerifiedAt: optionalUnixMillis(row.mfaVerifiedAt),
624
+ claims: sessionMetadata.claims,
625
+ metadata: sessionMetadata.metadata,
626
+ revokedAt: optionalUnixMillis(row.revokedAt),
627
+ lastSeenAt: optionalUnixMillis(row.lastSeenAt),
628
+ rotatedAt: optionalUnixMillis(row.rotatedAt),
629
+ });
630
+ }
631
+ const assurance = deriveAssurance(authenticationEvents);
632
+ const mfaVerifiedAt = deriveMfaVerifiedAt(authenticationEvents);
633
+ if (row.aal !== assurance.level ||
634
+ JSON.stringify(decodeAmr(row.amr)) !== JSON.stringify(assurance.amr) ||
635
+ optionalUnixMillis(row.mfaVerifiedAt) !== mfaVerifiedAt ||
636
+ Number(row.authTime) !== Number(assurance.authenticatedAt)) {
637
+ throw new Error("Session assurance summary does not match authentication events");
638
+ }
453
639
  return omitUndefined({
454
640
  id: SessionId(row.id),
455
641
  userId: UserId(row.userId),
@@ -457,6 +643,7 @@ const decodeDefaultSessionRow = (row) => {
457
643
  createdAt: UnixMillis(Number(row.createdAt)),
458
644
  expiresAt: UnixMillis(Number(row.expiresAt)),
459
645
  authTime: UnixMillis(Number(row.authTime)),
646
+ authenticationEvents,
460
647
  aal: row.aal,
461
648
  amr: decodeAmr(row.amr),
462
649
  mfaVerifiedAt: optionalUnixMillis(row.mfaVerifiedAt),
@@ -467,9 +654,13 @@ const decodeDefaultSessionRow = (row) => {
467
654
  rotatedAt: optionalUnixMillis(row.rotatedAt),
468
655
  });
469
656
  };
470
- const decodeSessionRow = (tables, row) => tables.session.decode === undefined
471
- ? decodeDefaultSessionRow(row)
472
- : tables.session.decode(row);
657
+ const decodeSessionRow = (tables, row) => {
658
+ const decoded = tables.session.decode === undefined
659
+ ? decodeDefaultSessionRow(row)
660
+ : tables.session.decode(row);
661
+ assertSessionAssurance(decoded);
662
+ return decoded;
663
+ };
473
664
  const decodeDefaultVerificationRow = (row) => omitUndefined({
474
665
  id: ChallengeId(row.id),
475
666
  type: row.type,
@@ -483,7 +674,7 @@ const decodeDefaultVerificationRow = (row) => omitUndefined({
483
674
  const decodeVerificationRow = (tables, row) => tables.verification.decode === undefined
484
675
  ? decodeDefaultVerificationRow(row)
485
676
  : tables.verification.decode(row);
486
- const decodeDefaultAuditLogEvent = (row) => jsonDecode(row.event);
677
+ const decodeDefaultAuditLogEvent = (row) => decodeAuditEvent(jsonDecode(row.event));
487
678
  const decodeAuditLogEvent = (tables, row) => tables.auditLog.decode === undefined
488
679
  ? decodeDefaultAuditLogEvent(row)
489
680
  : tables.auditLog.decode(row);
@@ -523,7 +714,9 @@ const decodeDefaultLoginRiskHistoryRecord = (row) => omitUndefined({
523
714
  locationKey: row.locationKey ?? undefined,
524
715
  country: row.country ?? undefined,
525
716
  region: row.region ?? undefined,
526
- latitude: row.latitudeMicro === null ? undefined : Number(row.latitudeMicro) / 1_000_000,
717
+ latitude: row.latitudeMicro === null
718
+ ? undefined
719
+ : Number(row.latitudeMicro) / 1_000_000,
527
720
  longitude: row.longitudeMicro === null
528
721
  ? undefined
529
722
  : Number(row.longitudeMicro) / 1_000_000,
@@ -546,7 +739,9 @@ const decodeTrustedDeviceRow = (tables, row) => tables.trustedDevice.decode ===
546
739
  : tables.trustedDevice.decode(row);
547
740
  const decodePasskeyTransports = (value) => {
548
741
  const decoded = jsonDecode(value);
549
- return Array.isArray(decoded) ? decoded : undefined;
742
+ return Array.isArray(decoded)
743
+ ? decoded
744
+ : undefined;
550
745
  };
551
746
  const decodeDefaultPasskeyCredentialRow = (row) => omitUndefined({
552
747
  id: CredentialId(row.id),
@@ -576,6 +771,9 @@ const decodeDefaultTotpFactorRow = (row) => omitUndefined({
576
771
  createdAt: UnixMillis(Number(row.createdAt)),
577
772
  confirmedAt: optionalUnixMillis(row.confirmedAt),
578
773
  lastUsedAt: optionalUnixMillis(row.lastUsedAt),
774
+ lastAcceptedCounter: row.lastAcceptedCounter === null
775
+ ? undefined
776
+ : Number(row.lastAcceptedCounter),
579
777
  revokedAt: optionalUnixMillis(row.revokedAt),
580
778
  metadata: jsonDecode(row.metadata),
581
779
  });
@@ -712,7 +910,8 @@ const decodeOAuthConsentRow = (tables, row) => tables.oauthConsent.decode === un
712
910
  : tables.oauthConsent.decode(row);
713
911
  const oauthConsentCoversScopes = (record, scopes = []) => scopes.every((scope) => record.scopes.includes(scope));
714
912
  const isOAuthConsentActive = (record, input) => record.revokedAt === undefined &&
715
- (record.expiresAt === undefined || Number(record.expiresAt) > Number(input.now)) &&
913
+ (record.expiresAt === undefined ||
914
+ Number(record.expiresAt) > Number(input.now)) &&
716
915
  oauthConsentCoversScopes(record, input.scopes);
717
916
  const oauthClientRedirectUriValidationResult = (client, input) => {
718
917
  if (client === undefined) {
@@ -740,7 +939,9 @@ const decodeDefaultOAuthAuthorizationCodeRow = (row) => omitUndefined({
740
939
  subject: row.subject,
741
940
  redirectUri: row.redirectUri,
742
941
  scopes: decodeStringArray(row.scopes),
743
- codeChallenge: row.codeChallenge === null ? undefined : OAuthCodeChallenge(row.codeChallenge),
942
+ codeChallenge: row.codeChallenge === null
943
+ ? undefined
944
+ : OAuthCodeChallenge(row.codeChallenge),
744
945
  codeChallengeMethod: row.codeChallengeMethod === null
745
946
  ? undefined
746
947
  : row.codeChallengeMethod,
@@ -752,6 +953,42 @@ const decodeDefaultOAuthAuthorizationCodeRow = (row) => omitUndefined({
752
953
  const decodeOAuthAuthorizationCodeRow = (tables, row) => tables.oauthAuthorizationCode.decode === undefined
753
954
  ? decodeDefaultOAuthAuthorizationCodeRow(row)
754
955
  : tables.oauthAuthorizationCode.decode(row);
956
+ const decodeDefaultOAuthDeviceAuthorizationRow = (row) => {
957
+ const base = omitUndefined({
958
+ id: OAuthDeviceAuthorizationId(row.id),
959
+ deviceCodeHash: row.deviceCodeHash,
960
+ userCodeHash: row.userCodeHash,
961
+ clientId: OAuthClientId(row.clientId),
962
+ requestedScopes: decodeStringArray(row.requestedScopes),
963
+ issuedAt: UnixMillis(Number(row.issuedAt)),
964
+ expiresAt: UnixMillis(Number(row.expiresAt)),
965
+ pollIntervalSeconds: Number(row.pollIntervalSeconds),
966
+ nextPollAt: UnixMillis(Number(row.nextPollAt)),
967
+ lastPolledAt: optionalUnixMillis(row.lastPolledAt),
968
+ metadata: jsonDecode(row.metadata),
969
+ });
970
+ if (row.status === "approved") {
971
+ return {
972
+ ...base,
973
+ status: "approved",
974
+ subject: row.subject ?? "",
975
+ grantedScopes: decodeStringArray(row.grantedScopes),
976
+ approvedAt: UnixMillis(Number(row.approvedAt)),
977
+ ...omitUndefined({ consumedAt: optionalUnixMillis(row.consumedAt) }),
978
+ };
979
+ }
980
+ if (row.status === "denied") {
981
+ return {
982
+ ...base,
983
+ status: "denied",
984
+ deniedAt: UnixMillis(Number(row.deniedAt)),
985
+ };
986
+ }
987
+ return { ...base, status: "pending" };
988
+ };
989
+ const decodeOAuthDeviceAuthorizationRow = (table, row) => table.decode === undefined
990
+ ? decodeDefaultOAuthDeviceAuthorizationRow(row)
991
+ : table.decode(row);
755
992
  const decodeDefaultOAuthClientSecretRow = (row) => omitUndefined({
756
993
  prefix: OAuthClientSecretPrefix(row.prefix),
757
994
  clientId: OAuthClientId(row.clientId),
@@ -840,6 +1077,82 @@ const decodeDefaultWebhookOutboxRecord = (row) => omitUndefined({
840
1077
  const decodeWebhookOutboxRecord = (tables, row) => tables.webhookOutbox.decode === undefined
841
1078
  ? decodeDefaultWebhookOutboxRecord(row)
842
1079
  : tables.webhookOutbox.decode(row);
1080
+ const decodeDefaultPermissionDefinition = (row) => omitUndefined({
1081
+ id: PermissionId(row.id),
1082
+ description: row.description ?? undefined,
1083
+ scopeType: decodeBoolean(row.scopeTypePresent) ? row.scopeType : undefined,
1084
+ createdAt: UnixMillis(Number(row.createdAt)),
1085
+ updatedAt: UnixMillis(Number(row.updatedAt)),
1086
+ disabledAt: optionalUnixMillis(row.disabledAt),
1087
+ deletedAt: optionalUnixMillis(row.deletedAt),
1088
+ });
1089
+ const decodePermissionDefinition = (tables, row) => tables.permissionDefinition.decode === undefined
1090
+ ? decodeDefaultPermissionDefinition(row)
1091
+ : tables.permissionDefinition.decode(row);
1092
+ const decodeDefaultRoleDefinition = (row) => omitUndefined({
1093
+ id: RoleId(row.id),
1094
+ description: row.description ?? undefined,
1095
+ createdAt: UnixMillis(Number(row.createdAt)),
1096
+ updatedAt: UnixMillis(Number(row.updatedAt)),
1097
+ disabledAt: optionalUnixMillis(row.disabledAt),
1098
+ deletedAt: optionalUnixMillis(row.deletedAt),
1099
+ });
1100
+ const decodeRoleDefinition = (tables, row) => tables.roleDefinition.decode === undefined
1101
+ ? decodeDefaultRoleDefinition(row)
1102
+ : tables.roleDefinition.decode(row);
1103
+ const decodePermissionScope = (scopeType, scopeIdPresent, scopeId) => {
1104
+ const hasId = decodeBoolean(scopeIdPresent);
1105
+ if (scopeType === "global" && !hasId) {
1106
+ return undefined;
1107
+ }
1108
+ return hasId ? { type: scopeType, id: scopeId } : { type: scopeType };
1109
+ };
1110
+ const decodeDefaultPermissionGrant = (row) => omitUndefined({
1111
+ subject: { type: row.subjectType, id: row.subjectId },
1112
+ permission: PermissionId(row.permissionId),
1113
+ scope: decodePermissionScope(row.scopeType, row.scopeIdPresent, row.scopeId),
1114
+ expiresAt: optionalUnixMillis(row.expiresAt),
1115
+ metadata: jsonDecode(row.metadata),
1116
+ revokedAt: optionalUnixMillis(row.revokedAt),
1117
+ });
1118
+ const decodePermissionGrant = (tables, row) => {
1119
+ const decoded = tables.permissionGrant.decode === undefined
1120
+ ? decodeDefaultPermissionGrant(row)
1121
+ : tables.permissionGrant.decode(row);
1122
+ const { scope: _scope, ...withoutScope } = decoded;
1123
+ const scope = canonicalPermissionScope(decoded.scope);
1124
+ return {
1125
+ ...withoutScope,
1126
+ ...(scope === undefined ? {} : { scope }),
1127
+ };
1128
+ };
1129
+ const decodeDefaultRoleGrant = (row) => omitUndefined({
1130
+ subject: { type: row.subjectType, id: row.subjectId },
1131
+ role: RoleId(row.roleId),
1132
+ scope: decodePermissionScope(row.scopeType, row.scopeIdPresent, row.scopeId),
1133
+ expiresAt: optionalUnixMillis(row.expiresAt),
1134
+ metadata: jsonDecode(row.metadata),
1135
+ revokedAt: optionalUnixMillis(row.revokedAt),
1136
+ });
1137
+ const decodeRoleGrant = (tables, row) => {
1138
+ const decoded = tables.roleGrant.decode === undefined
1139
+ ? decodeDefaultRoleGrant(row)
1140
+ : tables.roleGrant.decode(row);
1141
+ const { scope: _scope, ...withoutScope } = decoded;
1142
+ const scope = canonicalPermissionScope(decoded.scope);
1143
+ return {
1144
+ ...withoutScope,
1145
+ ...(scope === undefined ? {} : { scope }),
1146
+ };
1147
+ };
1148
+ const decodeDefaultRolePermission = (row) => omitUndefined({
1149
+ role: RoleId(row.roleId),
1150
+ permission: PermissionId(row.permissionId),
1151
+ scopeType: decodeBoolean(row.scopeTypePresent) ? row.scopeType : undefined,
1152
+ });
1153
+ const decodeRolePermission = (tables, row) => tables.rolePermission.decode === undefined
1154
+ ? decodeDefaultRolePermission(row)
1155
+ : tables.rolePermission.decode(row);
843
1156
  const providerTokenVaultTokens = (record) => omitUndefined({
844
1157
  accessTokenCiphertext: record.accessTokenCiphertext,
845
1158
  refreshTokenCiphertext: record.refreshTokenCiphertext,
@@ -850,13 +1163,50 @@ const providerTokenVaultTokens = (record) => omitUndefined({
850
1163
  });
851
1164
  const userInsert = (row) => ({
852
1165
  id: row.id,
853
- email: row.email,
854
- email_verified: row.emailVerified ? 1 : 0,
855
1166
  created_at: Number(row.createdAt),
856
1167
  updated_at: Number(row.updatedAt),
857
1168
  disabled_at: row.disabledAt === undefined ? null : Number(row.disabledAt),
858
1169
  metadata: jsonEncode(row.metadata),
859
1170
  });
1171
+ const identityScopeId = (scope) => scope.type === "global" ? "global" : scope.tenantId;
1172
+ const userIdentityInsert = (row) => ({
1173
+ id: row.id,
1174
+ user_id: row.userId,
1175
+ scope_type: row.scope.type,
1176
+ scope_id: identityScopeId(row.scope),
1177
+ kind: row.kind,
1178
+ value: row.value,
1179
+ normalized_value: row.normalizedValue,
1180
+ verified_at: row.verifiedAt === undefined ? null : Number(row.verifiedAt),
1181
+ is_primary_login: row.isPrimaryLogin === true ? 1 : 0,
1182
+ created_at: Number(row.createdAt),
1183
+ updated_at: Number(row.createdAt),
1184
+ revoked_at: null,
1185
+ replaced_by_id: null,
1186
+ metadata: jsonEncode(row.metadata),
1187
+ });
1188
+ const userIdentityVerify = (input) => ({
1189
+ verified_at: Number(input.verifiedAt),
1190
+ updated_at: Number(input.verifiedAt),
1191
+ });
1192
+ const userIdentitySetPrimary = (input, primary) => ({
1193
+ is_primary_login: primary ? 1 : 0,
1194
+ updated_at: Number(input.updatedAt),
1195
+ });
1196
+ const userIdentityReplace = (input) => ({
1197
+ is_primary_login: 0,
1198
+ revoked_at: Number(input.replacedAt),
1199
+ replaced_by_id: input.replacement.id,
1200
+ updated_at: Number(input.replacedAt),
1201
+ });
1202
+ const userIdentityRevoke = (input, _row, metadata) => ({
1203
+ is_primary_login: 0,
1204
+ revoked_at: Number(input.revokedAt),
1205
+ updated_at: Number(input.revokedAt),
1206
+ metadata: input.reason === undefined
1207
+ ? metadata
1208
+ : Query.call("json_set", Query.coalesce(metadata, "{}"), "$.revokeReason", input.reason),
1209
+ });
860
1210
  const passwordCredentialInsert = (row) => ({
861
1211
  id: row.id,
862
1212
  user_id: row.userId,
@@ -871,21 +1221,40 @@ const passwordCredentialUpdatePassword = (input) => ({
871
1221
  password_hash: input.passwordHash,
872
1222
  updated_at: Number(input.updatedAt),
873
1223
  });
874
- const sessionInsert = (row) => ({
875
- id: row.id,
876
- user_id: row.userId,
877
- secret_hash: row.secretHash,
878
- created_at: Number(row.createdAt),
879
- expires_at: Number(row.expiresAt),
880
- auth_time: Number(row.authTime),
881
- aal: row.aal,
882
- amr: JSON.stringify(row.amr),
883
- mfa_verified_at: row.mfaVerifiedAt === undefined ? null : Number(row.mfaVerifiedAt),
884
- metadata: encodeSessionMetadata(row),
885
- revoked_at: null,
886
- last_seen_at: null,
887
- rotated_at: null,
888
- });
1224
+ const assertSessionAssurance = (row) => {
1225
+ const authenticationEvents = parseAuthenticationEvents(row.authenticationEvents);
1226
+ // Sessions owns registry-aware validation for custom evidence.
1227
+ if (authenticationEvents.some((event) => event.type === "custom")) {
1228
+ return;
1229
+ }
1230
+ const assurance = deriveAssurance(authenticationEvents);
1231
+ if (assurance.authenticatedAt === undefined ||
1232
+ row.aal !== assurance.level ||
1233
+ JSON.stringify(row.amr) !== JSON.stringify(assurance.amr) ||
1234
+ row.mfaVerifiedAt !== deriveMfaVerifiedAt(authenticationEvents) ||
1235
+ Number(row.authTime) !== Number(assurance.authenticatedAt)) {
1236
+ throw new Error("Session assurance summary does not match authentication events");
1237
+ }
1238
+ };
1239
+ const sessionInsert = (row) => {
1240
+ assertSessionAssurance(row);
1241
+ return {
1242
+ id: row.id,
1243
+ user_id: row.userId,
1244
+ secret_hash: row.secretHash,
1245
+ created_at: Number(row.createdAt),
1246
+ expires_at: Number(row.expiresAt),
1247
+ auth_time: Number(row.authTime),
1248
+ authentication_events: JSON.stringify(row.authenticationEvents),
1249
+ aal: row.aal,
1250
+ amr: JSON.stringify(row.amr),
1251
+ mfa_verified_at: row.mfaVerifiedAt === undefined ? null : Number(row.mfaVerifiedAt),
1252
+ metadata: encodeSessionMetadata(row),
1253
+ revoked_at: null,
1254
+ last_seen_at: null,
1255
+ rotated_at: null,
1256
+ };
1257
+ };
889
1258
  const verificationInsert = (row) => ({
890
1259
  id: row.id,
891
1260
  type: row.type,
@@ -946,10 +1315,6 @@ const loginRiskHistoryInsert = (row) => ({
946
1315
  risk_level: row.riskLevel ?? null,
947
1316
  created_at: Number(row.createdAt),
948
1317
  });
949
- const userMarkEmailVerifiedUpdate = (input) => ({
950
- email_verified: 1,
951
- updated_at: Number(input.updatedAt),
952
- });
953
1318
  const sessionRefreshUpdate = (input) => omitUndefined({
954
1319
  expires_at: Number(input.expiresAt),
955
1320
  last_seen_at: input.lastSeenAt === undefined ? undefined : Number(input.lastSeenAt),
@@ -960,14 +1325,28 @@ const sessionRotateUpdate = (input) => omitUndefined({
960
1325
  expires_at: input.expiresAt === undefined ? undefined : Number(input.expiresAt),
961
1326
  });
962
1327
  const sessionUpdateClaimsUpdate = (row, input) => omitUndefined({
963
- aal: input.aal,
964
- amr: input.amr === undefined ? undefined : JSON.stringify(input.amr),
965
- mfa_verified_at: input.mfaVerifiedAt === undefined ? undefined : Number(input.mfaVerifiedAt),
966
1328
  metadata: encodeSessionMetadata({
967
1329
  ...row,
968
1330
  claims: input.claims ?? row.claims,
969
1331
  }),
970
1332
  });
1333
+ const sessionAppendEvidenceUpdate = (input) => {
1334
+ assertSessionAssurance(input);
1335
+ return {
1336
+ authentication_events: JSON.stringify(input.authenticationEvents),
1337
+ auth_time: Number(input.authTime),
1338
+ aal: input.aal,
1339
+ amr: JSON.stringify(input.amr),
1340
+ mfa_verified_at: input.mfaVerifiedAt === undefined ? null : Number(input.mfaVerifiedAt),
1341
+ };
1342
+ };
1343
+ const sessionAssureAndRotateUpdate = (row, input) => ({
1344
+ ...sessionAppendEvidenceUpdate(input),
1345
+ ...sessionRotateUpdate(input),
1346
+ ...(input.claims === undefined
1347
+ ? {}
1348
+ : { metadata: encodeSessionMetadata({ ...row, claims: input.claims }) }),
1349
+ });
971
1350
  const sessionRevokeUpdate = (input) => ({
972
1351
  revoked_at: Number(input.revokedAt),
973
1352
  });
@@ -1032,6 +1411,7 @@ const totpFactorInsert = (row) => ({
1032
1411
  created_at: Number(row.createdAt),
1033
1412
  confirmed_at: row.confirmedAt === undefined ? null : Number(row.confirmedAt),
1034
1413
  last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
1414
+ last_accepted_counter: row.lastAcceptedCounter ?? null,
1035
1415
  revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
1036
1416
  metadata: jsonEncode(row.metadata),
1037
1417
  });
@@ -1039,8 +1419,9 @@ const totpFactorConfirm = (input) => omitUndefined({
1039
1419
  confirmed_at: Number(input.confirmedAt),
1040
1420
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1041
1421
  });
1042
- const totpFactorMarkUsed = (input) => omitUndefined({
1422
+ const totpFactorAcceptCounter = (input) => omitUndefined({
1043
1423
  last_used_at: Number(input.lastUsedAt),
1424
+ last_accepted_counter: input.counter,
1044
1425
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1045
1426
  });
1046
1427
  const totpFactorRevoke = (input, row) => ({
@@ -1060,6 +1441,14 @@ const recoveryCodeMarkUsed = (input) => omitUndefined({
1060
1441
  used_at: Number(input.usedAt),
1061
1442
  metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1062
1443
  });
1444
+ const recoveryCodeReplaceActive = (input, metadata) => ({
1445
+ revoked_at: Number(input.revokedAt),
1446
+ ...(input.revokeReason === undefined
1447
+ ? {}
1448
+ : {
1449
+ metadata: Query.call("json_set", Query.coalesce(metadata, "{}"), "$.revokeReason", input.revokeReason),
1450
+ }),
1451
+ });
1063
1452
  const recoveryCodeRevoke = (input, row) => ({
1064
1453
  revoked_at: Number(input.revokedAt),
1065
1454
  metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
@@ -1145,7 +1534,9 @@ const oauthProviderTokenVaultValues = (row) => ({
1145
1534
  refresh_token_ciphertext: row.refreshTokenCiphertext === undefined
1146
1535
  ? null
1147
1536
  : Redacted.value(row.refreshTokenCiphertext),
1148
- id_token_ciphertext: row.idTokenCiphertext === undefined ? null : Redacted.value(row.idTokenCiphertext),
1537
+ id_token_ciphertext: row.idTokenCiphertext === undefined
1538
+ ? null
1539
+ : Redacted.value(row.idTokenCiphertext),
1149
1540
  token_type: row.tokenType,
1150
1541
  scopes: jsonEncode(row.scopes),
1151
1542
  expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
@@ -1201,6 +1592,37 @@ const oauthAuthorizationCodeValues = (record) => ({
1201
1592
  const oauthAuthorizationCodeMarkConsumed = (input) => ({
1202
1593
  consumed_at: Number(input.consumedAt),
1203
1594
  });
1595
+ const oauthDeviceAuthorizationValues = (record) => ({
1596
+ id: record.id,
1597
+ device_code_hash: record.deviceCodeHash,
1598
+ user_code_hash: record.userCodeHash,
1599
+ client_id: record.clientId,
1600
+ requested_scopes: JSON.stringify(record.requestedScopes),
1601
+ granted_scopes: null,
1602
+ subject: null,
1603
+ status: "pending",
1604
+ issued_at: Number(record.issuedAt),
1605
+ expires_at: Number(record.expiresAt),
1606
+ poll_interval_seconds: record.pollIntervalSeconds,
1607
+ next_poll_at: Number(record.nextPollAt),
1608
+ last_polled_at: record.lastPolledAt === undefined ? null : Number(record.lastPolledAt),
1609
+ approved_at: null,
1610
+ denied_at: null,
1611
+ consumed_at: null,
1612
+ metadata: jsonEncode(record.metadata),
1613
+ });
1614
+ const oauthDeviceAuthorizationApprove = (input) => omitUndefined({
1615
+ status: "approved",
1616
+ subject: input.subject,
1617
+ granted_scopes: JSON.stringify(input.grantedScopes),
1618
+ approved_at: Number(input.decidedAt),
1619
+ metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1620
+ });
1621
+ const oauthDeviceAuthorizationDeny = (input) => omitUndefined({
1622
+ status: "denied",
1623
+ denied_at: Number(input.decidedAt),
1624
+ metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
1625
+ });
1204
1626
  const oauthClientSecretValues = (record) => ({
1205
1627
  prefix: record.prefix,
1206
1628
  client_id: record.clientId,
@@ -1321,6 +1743,101 @@ const webhookReplayUpdate = (input) => ({
1321
1743
  expires_at: Number(input.expiresAt),
1322
1744
  created_at: Number(input.now),
1323
1745
  });
1746
+ const permissionScopeValues = (scope) => ({
1747
+ scope_type: scope?.type ?? "global",
1748
+ scope_id_present: scope?.id === undefined ? 0 : 1,
1749
+ scope_id: scope?.id ?? "",
1750
+ });
1751
+ const permissionGrantValues = (input) => ({
1752
+ subject_type: input.subject.type,
1753
+ subject_id: input.subject.id,
1754
+ permission_id: input.permission,
1755
+ ...permissionScopeValues(input.scope),
1756
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1757
+ metadata: jsonEncode(input.metadata),
1758
+ revoked_at: null,
1759
+ });
1760
+ const permissionGrantUpdate = (input) => ({
1761
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1762
+ metadata: jsonEncode(input.metadata),
1763
+ revoked_at: null,
1764
+ });
1765
+ const permissionGrantRevoke = (_input, revokedAt) => ({ revoked_at: Number(revokedAt) });
1766
+ const roleGrantValues = (input) => ({
1767
+ subject_type: input.subject.type,
1768
+ subject_id: input.subject.id,
1769
+ role_id: input.role,
1770
+ ...permissionScopeValues(input.scope),
1771
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1772
+ metadata: jsonEncode(input.metadata),
1773
+ revoked_at: null,
1774
+ });
1775
+ const roleGrantUpdate = (input) => ({
1776
+ expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
1777
+ metadata: jsonEncode(input.metadata),
1778
+ revoked_at: null,
1779
+ });
1780
+ const roleGrantRevoke = (_input, revokedAt) => ({
1781
+ revoked_at: Number(revokedAt),
1782
+ });
1783
+ const rolePermissionValues = (input) => ({
1784
+ role_id: input.role,
1785
+ permission_id: input.permission,
1786
+ scope_type_present: input.scopeType === undefined ? 0 : 1,
1787
+ scope_type: input.scopeType ?? "",
1788
+ });
1789
+ const permissionDefinitionValues = (definition) => ({
1790
+ id: definition.id,
1791
+ description: definition.description ?? null,
1792
+ scope_type_present: definition.scopeType === undefined ? 0 : 1,
1793
+ scope_type: definition.scopeType ?? "",
1794
+ created_at: Number(definition.createdAt),
1795
+ updated_at: Number(definition.updatedAt),
1796
+ disabled_at: definition.disabledAt === undefined ? null : Number(definition.disabledAt),
1797
+ deleted_at: definition.deletedAt === undefined ? null : Number(definition.deletedAt),
1798
+ });
1799
+ const permissionDefinitionUpdate = (input) => ({
1800
+ ...(input.description === undefined
1801
+ ? {}
1802
+ : { description: input.description }),
1803
+ ...(input.scopeType === undefined
1804
+ ? {}
1805
+ : {
1806
+ scope_type_present: input.scopeType === null ? 0 : 1,
1807
+ scope_type: input.scopeType ?? "",
1808
+ }),
1809
+ updated_at: Number(input.updatedAt),
1810
+ });
1811
+ const permissionDefinitionSetDisabled = (input) => ({
1812
+ disabled_at: input.disabledAt === undefined ? null : Number(input.disabledAt),
1813
+ updated_at: Number(input.updatedAt),
1814
+ });
1815
+ const permissionDefinitionDelete = (input) => ({
1816
+ deleted_at: Number(input.deletedAt),
1817
+ updated_at: Number(input.updatedAt),
1818
+ });
1819
+ const roleDefinitionValues = (definition) => ({
1820
+ id: definition.id,
1821
+ description: definition.description ?? null,
1822
+ created_at: Number(definition.createdAt),
1823
+ updated_at: Number(definition.updatedAt),
1824
+ disabled_at: definition.disabledAt === undefined ? null : Number(definition.disabledAt),
1825
+ deleted_at: definition.deletedAt === undefined ? null : Number(definition.deletedAt),
1826
+ });
1827
+ const roleDefinitionUpdate = (input) => ({
1828
+ ...(input.description === undefined
1829
+ ? {}
1830
+ : { description: input.description }),
1831
+ updated_at: Number(input.updatedAt),
1832
+ });
1833
+ const roleDefinitionSetDisabled = (input) => ({
1834
+ disabled_at: input.disabledAt === undefined ? null : Number(input.disabledAt),
1835
+ updated_at: Number(input.updatedAt),
1836
+ });
1837
+ const roleDefinitionDelete = (input) => ({
1838
+ deleted_at: Number(input.deletedAt),
1839
+ updated_at: Number(input.updatedAt),
1840
+ });
1324
1841
  const jwtRevocationUpdate = (record) => ({
1325
1842
  revoked_at: Number(record.revokedAt),
1326
1843
  expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
@@ -1329,6 +1846,7 @@ const jwtRevocationUpdate = (record) => ({
1329
1846
  const authSqliteTableName = (name, defaultName) => name ?? defaultName;
1330
1847
  const makeInternalAuthSqliteTables = (names = {}) => {
1331
1848
  const user = makeAuthUserTable(names.user ?? "auth_user");
1849
+ const identity = makeAuthUserIdentityTable(names.identity ?? "auth_user_identity");
1332
1850
  const credential = makeAuthCredentialTable(names.credential ?? "auth_credential");
1333
1851
  const session = makeAuthSessionTable(names.session ?? "auth_session");
1334
1852
  const verification = makeAuthVerificationTable(names.verification ?? "auth_verification");
@@ -1346,6 +1864,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1346
1864
  const oauthClient = makeAuthOAuthClientTable(authSqliteTableName(names.oauthClient, "auth_oauth_client"));
1347
1865
  const oauthConsent = makeAuthOAuthConsentTable(authSqliteTableName(names.oauthConsent, "auth_oauth_consent"));
1348
1866
  const oauthAuthorizationCode = makeAuthOAuthAuthorizationCodeTable(authSqliteTableName(names.oauthAuthorizationCode, "auth_oauth_authorization_code"));
1867
+ const oauthDeviceAuthorization = makeAuthOAuthDeviceAuthorizationTable(authSqliteTableName(names.oauthDeviceAuthorization, "auth_oauth_device_authorization"));
1349
1868
  const oauthClientSecret = makeAuthOAuthClientSecretTable(authSqliteTableName(names.oauthClientSecret, "auth_oauth_client_secret"));
1350
1869
  const oauthProviderModeToken = makeAuthOAuthProviderModeTokenTable(authSqliteTableName(names.oauthProviderModeToken, "auth_oauth_provider_mode_token"));
1351
1870
  const oauthProviderTokenVault = makeAuthOAuthProviderTokenVaultTable(names.oauthProviderTokenVault ?? "auth_oauth_provider_token_vault");
@@ -1353,20 +1872,46 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1353
1872
  const securityTimeline = makeAuthSecurityTimelineTable(names.securityTimeline ?? "auth_security_timeline");
1354
1873
  const webhookOutbox = makeAuthWebhookOutboxTable(names.webhookOutbox ?? "auth_webhook_outbox");
1355
1874
  const webhookReplay = makeAuthWebhookReplayTable(names.webhookReplay ?? "auth_webhook_replay");
1875
+ const permissionDefinition = makeAuthPermissionDefinitionTable(names.permissionDefinition ?? "auth_permission_definition");
1876
+ const roleDefinition = makeAuthRoleDefinitionTable(names.roleDefinition ?? "auth_role_definition");
1877
+ const permissionGrant = makeAuthPermissionGrantTable(names.permissionGrant ?? "auth_permission_grant");
1878
+ const roleGrant = makeAuthRoleGrantTable(names.roleGrant ?? "auth_role_grant");
1879
+ const rolePermission = makeAuthRolePermissionTable(names.rolePermission ?? "auth_role_permission");
1356
1880
  return {
1357
1881
  user: {
1358
1882
  table: user,
1359
1883
  select: {
1360
1884
  id: user.id,
1361
- email: user.email,
1362
- emailVerified: user.email_verified,
1363
1885
  createdAt: user.created_at,
1364
1886
  updatedAt: user.updated_at,
1365
1887
  disabledAt: user.disabled_at,
1366
1888
  metadata: user.metadata,
1367
1889
  },
1368
1890
  insert: userInsert,
1369
- markEmailVerified: userMarkEmailVerifiedUpdate,
1891
+ },
1892
+ identity: {
1893
+ table: identity,
1894
+ select: {
1895
+ id: identity.id,
1896
+ userId: identity.user_id,
1897
+ scopeType: identity.scope_type,
1898
+ scopeId: identity.scope_id,
1899
+ kind: identity.kind,
1900
+ value: identity.value,
1901
+ normalizedValue: identity.normalized_value,
1902
+ verifiedAt: identity.verified_at,
1903
+ isPrimaryLogin: identity.is_primary_login,
1904
+ createdAt: identity.created_at,
1905
+ updatedAt: identity.updated_at,
1906
+ revokedAt: identity.revoked_at,
1907
+ replacedById: identity.replaced_by_id,
1908
+ metadata: identity.metadata,
1909
+ },
1910
+ insert: userIdentityInsert,
1911
+ verify: userIdentityVerify,
1912
+ setPrimary: userIdentitySetPrimary,
1913
+ replace: userIdentityReplace,
1914
+ revoke: userIdentityRevoke,
1370
1915
  },
1371
1916
  credential: {
1372
1917
  table: credential,
@@ -1392,6 +1937,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1392
1937
  createdAt: session.created_at,
1393
1938
  expiresAt: session.expires_at,
1394
1939
  authTime: session.auth_time,
1940
+ authenticationEvents: session.authentication_events,
1395
1941
  aal: session.aal,
1396
1942
  amr: session.amr,
1397
1943
  mfaVerifiedAt: session.mfa_verified_at,
@@ -1404,6 +1950,8 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1404
1950
  refresh: sessionRefreshUpdate,
1405
1951
  rotate: sessionRotateUpdate,
1406
1952
  updateClaims: sessionUpdateClaimsUpdate,
1953
+ appendEvidence: sessionAppendEvidenceUpdate,
1954
+ assureAndRotate: sessionAssureAndRotateUpdate,
1407
1955
  revoke: sessionRevokeUpdate,
1408
1956
  },
1409
1957
  verification: {
@@ -1424,6 +1972,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1424
1972
  auditLog: {
1425
1973
  table: auditLog,
1426
1974
  select: {
1975
+ storageOrder: auditLog.storage_id,
1427
1976
  id: auditLog.id,
1428
1977
  type: auditLog.type,
1429
1978
  userId: auditLog.user_id,
@@ -1526,12 +2075,13 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1526
2075
  createdAt: totpFactor.created_at,
1527
2076
  confirmedAt: totpFactor.confirmed_at,
1528
2077
  lastUsedAt: totpFactor.last_used_at,
2078
+ lastAcceptedCounter: totpFactor.last_accepted_counter,
1529
2079
  revokedAt: totpFactor.revoked_at,
1530
2080
  metadata: totpFactor.metadata,
1531
2081
  },
1532
2082
  insert: totpFactorInsert,
1533
2083
  confirm: totpFactorConfirm,
1534
- markUsed: totpFactorMarkUsed,
2084
+ acceptCounter: totpFactorAcceptCounter,
1535
2085
  revoke: totpFactorRevoke,
1536
2086
  },
1537
2087
  recoveryCode: {
@@ -1547,6 +2097,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1547
2097
  },
1548
2098
  insert: recoveryCodeInsert,
1549
2099
  markUsed: recoveryCodeMarkUsed,
2100
+ replaceActive: recoveryCodeReplaceActive,
1550
2101
  revoke: recoveryCodeRevoke,
1551
2102
  },
1552
2103
  apiKey: {
@@ -1670,6 +2221,31 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1670
2221
  update: oauthAuthorizationCodeValues,
1671
2222
  markConsumed: oauthAuthorizationCodeMarkConsumed,
1672
2223
  },
2224
+ oauthDeviceAuthorization: {
2225
+ table: oauthDeviceAuthorization,
2226
+ select: {
2227
+ id: oauthDeviceAuthorization.id,
2228
+ deviceCodeHash: oauthDeviceAuthorization.device_code_hash,
2229
+ userCodeHash: oauthDeviceAuthorization.user_code_hash,
2230
+ clientId: oauthDeviceAuthorization.client_id,
2231
+ requestedScopes: oauthDeviceAuthorization.requested_scopes,
2232
+ grantedScopes: oauthDeviceAuthorization.granted_scopes,
2233
+ subject: oauthDeviceAuthorization.subject,
2234
+ status: oauthDeviceAuthorization.status,
2235
+ issuedAt: oauthDeviceAuthorization.issued_at,
2236
+ expiresAt: oauthDeviceAuthorization.expires_at,
2237
+ pollIntervalSeconds: oauthDeviceAuthorization.poll_interval_seconds,
2238
+ nextPollAt: oauthDeviceAuthorization.next_poll_at,
2239
+ lastPolledAt: oauthDeviceAuthorization.last_polled_at,
2240
+ approvedAt: oauthDeviceAuthorization.approved_at,
2241
+ deniedAt: oauthDeviceAuthorization.denied_at,
2242
+ consumedAt: oauthDeviceAuthorization.consumed_at,
2243
+ metadata: oauthDeviceAuthorization.metadata,
2244
+ },
2245
+ insert: oauthDeviceAuthorizationValues,
2246
+ approve: oauthDeviceAuthorizationApprove,
2247
+ deny: oauthDeviceAuthorizationDeny,
2248
+ },
1673
2249
  oauthClientSecret: {
1674
2250
  table: oauthClientSecret,
1675
2251
  select: {
@@ -1799,6 +2375,84 @@ const makeInternalAuthSqliteTables = (names = {}) => {
1799
2375
  insert: webhookReplayInsert,
1800
2376
  update: webhookReplayUpdate,
1801
2377
  },
2378
+ permissionDefinition: {
2379
+ table: permissionDefinition,
2380
+ conflictTarget: "id",
2381
+ select: {
2382
+ id: permissionDefinition.id,
2383
+ description: permissionDefinition.description,
2384
+ scopeTypePresent: permissionDefinition.scope_type_present,
2385
+ scopeType: permissionDefinition.scope_type,
2386
+ createdAt: permissionDefinition.created_at,
2387
+ updatedAt: permissionDefinition.updated_at,
2388
+ disabledAt: permissionDefinition.disabled_at,
2389
+ deletedAt: permissionDefinition.deleted_at,
2390
+ },
2391
+ insert: permissionDefinitionValues,
2392
+ update: permissionDefinitionUpdate,
2393
+ setDisabled: permissionDefinitionSetDisabled,
2394
+ delete: permissionDefinitionDelete,
2395
+ },
2396
+ roleDefinition: {
2397
+ table: roleDefinition,
2398
+ conflictTarget: "id",
2399
+ select: {
2400
+ id: roleDefinition.id,
2401
+ description: roleDefinition.description,
2402
+ createdAt: roleDefinition.created_at,
2403
+ updatedAt: roleDefinition.updated_at,
2404
+ disabledAt: roleDefinition.disabled_at,
2405
+ deletedAt: roleDefinition.deleted_at,
2406
+ },
2407
+ insert: roleDefinitionValues,
2408
+ update: roleDefinitionUpdate,
2409
+ setDisabled: roleDefinitionSetDisabled,
2410
+ delete: roleDefinitionDelete,
2411
+ },
2412
+ permissionGrant: {
2413
+ table: permissionGrant,
2414
+ select: {
2415
+ subjectType: permissionGrant.subject_type,
2416
+ subjectId: permissionGrant.subject_id,
2417
+ permissionId: permissionGrant.permission_id,
2418
+ scopeType: permissionGrant.scope_type,
2419
+ scopeIdPresent: permissionGrant.scope_id_present,
2420
+ scopeId: permissionGrant.scope_id,
2421
+ expiresAt: permissionGrant.expires_at,
2422
+ metadata: permissionGrant.metadata,
2423
+ revokedAt: permissionGrant.revoked_at,
2424
+ },
2425
+ insert: permissionGrantValues,
2426
+ update: permissionGrantUpdate,
2427
+ revoke: permissionGrantRevoke,
2428
+ },
2429
+ roleGrant: {
2430
+ table: roleGrant,
2431
+ select: {
2432
+ subjectType: roleGrant.subject_type,
2433
+ subjectId: roleGrant.subject_id,
2434
+ roleId: roleGrant.role_id,
2435
+ scopeType: roleGrant.scope_type,
2436
+ scopeIdPresent: roleGrant.scope_id_present,
2437
+ scopeId: roleGrant.scope_id,
2438
+ expiresAt: roleGrant.expires_at,
2439
+ metadata: roleGrant.metadata,
2440
+ revokedAt: roleGrant.revoked_at,
2441
+ },
2442
+ insert: roleGrantValues,
2443
+ update: roleGrantUpdate,
2444
+ revoke: roleGrantRevoke,
2445
+ },
2446
+ rolePermission: {
2447
+ table: rolePermission,
2448
+ select: {
2449
+ roleId: rolePermission.role_id,
2450
+ permissionId: rolePermission.permission_id,
2451
+ scopeTypePresent: rolePermission.scope_type_present,
2452
+ scopeType: rolePermission.scope_type,
2453
+ },
2454
+ insert: rolePermissionValues,
2455
+ },
1802
2456
  };
1803
2457
  };
1804
2458
  const asAuthSqliteTables = (tables) => tables;
@@ -1806,6 +2460,7 @@ export const defineEffectQbAuthTables = (tables) => asAuthSqliteTables(tables);
1806
2460
  export const makeAuthSqliteTables = (names = {}) => asAuthSqliteTables(makeInternalAuthSqliteTables(names));
1807
2461
  export const authSqliteTables = makeAuthSqliteTables();
1808
2462
  const selectUserColumns = (tables) => tables.user.select;
2463
+ const selectIdentityColumns = (tables) => tables.identity.select;
1809
2464
  const selectCredentialColumns = (tables) => tables.credential.select;
1810
2465
  const selectSessionColumns = (tables) => tables.session.select;
1811
2466
  const selectVerificationColumns = (tables) => tables.verification.select;
@@ -1823,6 +2478,8 @@ const selectOAuthAccountColumns = (tables) => tables.oauthAccount.select;
1823
2478
  const selectOAuthClientColumns = (tables) => tables.oauthClient.select;
1824
2479
  const selectOAuthConsentColumns = (tables) => tables.oauthConsent.select;
1825
2480
  const selectOAuthAuthorizationCodeColumns = (tables) => tables.oauthAuthorizationCode.select;
2481
+ const oauthDeviceAuthorizationTable = (tables) => tables.oauthDeviceAuthorization ?? authSqliteTables.oauthDeviceAuthorization;
2482
+ const selectOAuthDeviceAuthorizationColumns = (tables) => oauthDeviceAuthorizationTable(tables).select;
1826
2483
  const selectOAuthClientSecretColumns = (tables) => tables.oauthClientSecret.select;
1827
2484
  const selectOAuthProviderModeTokenColumns = (tables) => tables.oauthProviderModeToken.select;
1828
2485
  const selectOAuthProviderTokenVaultColumns = (tables) => tables.oauthProviderTokenVault.select;
@@ -1830,7 +2487,16 @@ const selectDomainVerificationColumns = (tables) => tables.domainVerification.se
1830
2487
  const selectSecurityTimelineColumns = (tables) => tables.securityTimeline.select;
1831
2488
  const selectWebhookOutboxColumns = (tables) => tables.webhookOutbox.select;
1832
2489
  const selectWebhookReplayColumns = (tables) => tables.webhookReplay.select;
2490
+ const selectPermissionDefinitionColumns = (tables) => tables.permissionDefinition.select;
2491
+ const selectRoleDefinitionColumns = (tables) => tables.roleDefinition.select;
2492
+ const selectPermissionGrantColumns = (tables) => tables.permissionGrant.select;
2493
+ const selectRoleGrantColumns = (tables) => tables.roleGrant.select;
2494
+ const selectRolePermissionColumns = (tables) => tables.rolePermission.select;
1833
2495
  const storageError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
2496
+ const decodeSessionResult = (operation, decode) => Effect.try({
2497
+ try: decode,
2498
+ catch: (cause) => storageError("session", operation, cause),
2499
+ });
1834
2500
  const auditLogStoreError = (operation, cause) => new AuditLogStoreError({
1835
2501
  operation,
1836
2502
  message: `Audit log ${operation} failed`,
@@ -1900,6 +2566,11 @@ const oauthAuthorizationCodeStoreError = (operation, cause) => new OAuthAuthoriz
1900
2566
  message: `OAuth authorization code ${operation} failed`,
1901
2567
  cause,
1902
2568
  });
2569
+ const oauthDeviceAuthorizationStoreError = (operation, cause) => new OAuthDeviceAuthorizationStoreError({
2570
+ operation,
2571
+ message: `OAuth device authorization ${operation} failed`,
2572
+ cause,
2573
+ });
1903
2574
  const oauthClientSecretStoreError = (operation, cause) => new OAuthClientSecretStoreError({
1904
2575
  operation,
1905
2576
  message: `OAuth client secret ${operation} failed`,
@@ -1935,22 +2606,36 @@ const webhookReplayStoreError = (operation, cause) => new WebhookReplayStoreErro
1935
2606
  message: `Webhook replay ${operation} failed`,
1936
2607
  cause,
1937
2608
  });
2609
+ const permissionStoreError = (operation, cause) => new PermissionStoreError({
2610
+ operation,
2611
+ message: `Permission store ${operation} failed`,
2612
+ cause,
2613
+ });
1938
2614
  const findUserById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.id, id)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
1939
2615
  ? Option.none()
1940
2616
  : Option.some(decodeUserRow(tables, rows[0]))));
1941
- const findUserByEmail = (executor, tables, email, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.email, email)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
2617
+ const findIdentity = (executor, tables, where, operation = "find") => executePlan(executor, where.reduce((query, [column, value]) => query.pipe(Query.where(queryEq(column, value))), Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table)))).pipe(Effect.mapError((cause) => storageError("identity", operation, cause)), Effect.map((rows) => rows[0] === undefined
1942
2618
  ? Option.none()
1943
- : Option.some(decodeUserRow(tables, rows[0]))));
2619
+ : Option.some(decodeUserIdentityRow(tables, rows[0]))));
1944
2620
  const findPasswordCredentialByUserId = (executor, tables, userId, operation = "find") => executePlan(executor, Query.select(selectCredentialColumns(tables)).pipe(Query.from(tables.credential.table), Query.where(queryEq(tables.credential.select.userId, userId)), Query.where(queryEq(tables.credential.select.type, "password")))).pipe(Effect.mapError((cause) => storageError("credential", operation, cause)), Effect.map((rows) => rows[0] === undefined
1945
2621
  ? Option.none()
1946
2622
  : Option.some(decodePasswordCredentialRow(tables, rows[0]))));
1947
2623
  const findJwtRevocation = (executor, tables, jwtId, operation = "find") => executePlan(executor, Query.select(selectJwtRevocationColumns(tables)).pipe(Query.from(tables.jwtRevocation.table), Query.where(queryEq(tables.jwtRevocation.select.jwtId, jwtId)))).pipe(Effect.mapError((cause) => jwtRevocationStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
1948
2624
  ? Option.none()
1949
2625
  : Option.some(decodeJwtRevocationRow(tables, rows[0]))));
1950
- const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows[0] === undefined
2626
+ const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => rows[0] === undefined
1951
2627
  ? Option.none()
1952
- : Option.some(decodeSessionRow(tables, rows[0]))));
1953
- const listSessionsByUser = (executor, tables, input, operation = "listByUser") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.map((rows) => rows.map((row) => decodeSessionRow(tables, row))));
2628
+ : Option.some(decodeSessionRow(tables, rows[0])))));
2629
+ const findSessionForMutation = (executor, tables, id, operation) => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => {
2630
+ const [selected] = rows;
2631
+ return selected === undefined
2632
+ ? Option.none()
2633
+ : Option.some({
2634
+ row: decodeSessionRow(tables, selected),
2635
+ authenticationEvents: selected.authenticationEvents,
2636
+ });
2637
+ })));
2638
+ const listSessionsByUser = (executor, tables, input, operation = "listByUser") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => rows.map((row) => decodeSessionRow(tables, row)))));
1954
2639
  const findVerificationById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectVerificationColumns(tables)).pipe(Query.from(tables.verification.table), Query.where(queryEq(tables.verification.select.id, id)))).pipe(Effect.mapError((cause) => storageError("verification", operation, cause)), Effect.map((rows) => rows[0] === undefined
1955
2640
  ? Option.none()
1956
2641
  : Option.some(decodeVerificationRow(tables, rows[0]))));
@@ -2022,7 +2707,9 @@ const findOAuthClientSecretByClientIdAndPrefix = (executor, tables, input, opera
2022
2707
  const findOAuthProviderModeTokenByHash = (executor, tables, input, operation = "find") => executePlan(executor, Query.select(selectOAuthProviderModeTokenColumns(tables)).pipe(Query.from(tables.oauthProviderModeToken.table), Query.where(queryEq(tables.oauthProviderModeToken.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => oauthProviderModeTokenStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
2023
2708
  ? Option.none()
2024
2709
  : Option.some(decodeOAuthProviderModeTokenRow(tables, rows[0]))));
2025
- const deleteOAuthProviderModeTokenRowsBefore = (executor, tables, column, before) => executePlan(executor, Query.delete(tables.oauthProviderModeToken.table).pipe(Query.where(queryLt(column, Number(before))), Query.returning({ tokenHash: tables.oauthProviderModeToken.select.tokenHash }))).pipe(Effect.map((rows) => rows.length));
2710
+ const deleteOAuthProviderModeTokenRowsBefore = (executor, tables, column, before) => executePlan(executor, Query.delete(tables.oauthProviderModeToken.table).pipe(Query.where(queryLt(column, Number(before))), Query.returning({
2711
+ tokenHash: tables.oauthProviderModeToken.select.tokenHash,
2712
+ }))).pipe(Effect.map((rows) => rows.length));
2026
2713
  const deleteOAuthProviderModeRetiredTokens = (executor, tables, input) => Effect.gen(function* () {
2027
2714
  const expiredDeleted = input.beforeExpiresAt === undefined
2028
2715
  ? 0
@@ -2096,7 +2783,14 @@ const listAuditLogRecords = (executor, tables, input = {}) => {
2096
2783
  const query = input.types === undefined
2097
2784
  ? byOccurredAt
2098
2785
  : byOccurredAt.pipe(Query.where(queryIn(tables.auditLog.select.type, input.types[0], ...input.types.slice(1))));
2099
- return executePlan(executor, query).pipe(Effect.mapError((cause) => auditLogStoreError("list", cause)), Effect.map((rows) => filterAuditLogEvents(rows.map((row) => decodeAuditLogEvent(tables, row)), input)));
2786
+ let ordered = query.pipe(Query.orderBy(tables.auditLog.select.occurredAt, "desc"), Query.orderBy(Query.coalesce(tables.auditLog.select.id, Query.literal("")), "desc"));
2787
+ if (tables.auditLog.select.storageOrder !== undefined) {
2788
+ ordered = ordered.pipe(Query.orderBy(tables.auditLog.select.storageOrder, "asc"));
2789
+ }
2790
+ return executePlan(executor, ordered).pipe(Effect.mapError((cause) => auditLogStoreError("list", cause)), Effect.flatMap((rows) => Effect.try({
2791
+ try: () => filterAuditLogEvents(rows.map((row) => decodeAuditLogEvent(tables, row)), input),
2792
+ catch: (cause) => auditLogStoreError("list", cause),
2793
+ })));
2100
2794
  };
2101
2795
  const deleteAuditLogRecordsBefore = (executor, tables, input) => {
2102
2796
  const base = Query.delete(tables.auditLog.table).pipe(Query.where(queryLt(tables.auditLog.select.occurredAt, Number(input.beforeOccurredAt))));
@@ -2132,7 +2826,9 @@ const distanceKm = (first, second) => {
2132
2826
  Math.cos(firstLatitude) *
2133
2827
  Math.cos(secondLatitude) *
2134
2828
  Math.sin(longitudeDelta / 2) ** 2;
2135
- return (2 * earthRadiusKm * Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)));
2829
+ return (2 *
2830
+ earthRadiusKm *
2831
+ Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)));
2136
2832
  };
2137
2833
  const hasCoordinates = (value) => value.latitude !== undefined &&
2138
2834
  Number.isFinite(value.latitude) &&
@@ -2215,18 +2911,287 @@ const deleteLoginRiskHistoryRecordsBefore = (executor, tables, input) => {
2215
2911
  };
2216
2912
  export const makeEffectQbSqliteUserStore = (executor, options = {}) => {
2217
2913
  const tables = options.tables ?? authSqliteTables;
2218
- return UserStore.make({
2914
+ return UserStore.of({
2219
2915
  findById: (id) => findUserById(executor, tables, id),
2220
- findByEmail: (email) => findUserByEmail(executor, tables, email),
2221
2916
  insert: (row) => executePlan(executor, queryInsert(tables.user.table, mutationValues(tables.user.insert(row)))).pipe(Effect.mapError((cause) => storageError("user", "insert", cause)), Effect.asVoid),
2222
- markEmailVerified: (input) => executePlan(executor, queryUpdate(tables.user.table, mutationValues(tables.user.markEmailVerified(input))).pipe(Query.where(queryEq(tables.user.select.id, input.userId)), Query.returning(selectUserColumns(tables)))).pipe(Effect.mapError((cause) => storageError("user", "markEmailVerified", cause)), Effect.map((rows) => rows[0] === undefined
2917
+ });
2918
+ };
2919
+ const identityConflict = (row) => new IdentityConflictError({
2920
+ scope: row.scope,
2921
+ kind: row.kind,
2922
+ normalizedValue: row.normalizedValue,
2923
+ });
2924
+ const sqliteConstraintDiscriminator = (cause) => {
2925
+ const pending = [cause];
2926
+ const seen = new Set();
2927
+ while (pending.length > 0) {
2928
+ const current = pending.pop();
2929
+ if (current === undefined || current === null || seen.has(current)) {
2930
+ continue;
2931
+ }
2932
+ seen.add(current);
2933
+ if (typeof current === "string") {
2934
+ if (current === "UniqueViolation" ||
2935
+ /\bUNIQUE constraint failed\b/i.test(current) ||
2936
+ /\bPRIMARY KEY constraint failed\b/i.test(current)) {
2937
+ return true;
2938
+ }
2939
+ continue;
2940
+ }
2941
+ if (typeof current !== "object") {
2942
+ continue;
2943
+ }
2944
+ const record = current;
2945
+ const { code, errno, message, name, _tag } = record;
2946
+ if (_tag === "UniqueViolation" || name === "UniqueViolation") {
2947
+ return true;
2948
+ }
2949
+ if (code === "SQLITE_CONSTRAINT_UNIQUE" ||
2950
+ code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
2951
+ return true;
2952
+ }
2953
+ if (errno === 2067 || errno === 1555) {
2954
+ return true;
2955
+ }
2956
+ if (typeof message === "string" &&
2957
+ (/\bUNIQUE constraint failed\b/i.test(message) ||
2958
+ /\bPRIMARY KEY constraint failed\b/i.test(message))) {
2959
+ return true;
2960
+ }
2961
+ pending.push(record.cause, record.reason, record.error, record.raw);
2962
+ if (Array.isArray(record.failures)) {
2963
+ pending.push(...record.failures);
2964
+ }
2965
+ if (Array.isArray(record.reasons)) {
2966
+ pending.push(...record.reasons);
2967
+ }
2968
+ }
2969
+ return false;
2970
+ };
2971
+ const isAtomicExecuteConflict = (cause, statement) => cause instanceof AtomicPlanError &&
2972
+ cause.phase === "execute" &&
2973
+ cause.statement === statement &&
2974
+ sqliteConstraintDiscriminator(cause.cause) === true;
2975
+ export const makeEffectQbSqliteIdentityStore = (executor, atomic, options = {}) => {
2976
+ const tables = options.tables ?? authSqliteTables;
2977
+ const byId = (id, operation = "find") => findIdentity(executor, tables, [[tables.identity.select.id, id]], operation);
2978
+ const insert = (row) => executePlan(executor, queryInsert(tables.identity.table, mutationValues(tables.identity.insert(row)))).pipe(Effect.mapError(() => identityConflict(row)), Effect.asVoid);
2979
+ return IdentityStore.of({
2980
+ findById: byId,
2981
+ findByKindAndNormalizedValue: (input) => executePlan(executor, Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table), Query.where(queryEq(tables.identity.select.scopeType, input.scope.type)), Query.where(queryEq(tables.identity.select.scopeId, identityScopeId(input.scope))), Query.where(queryEq(tables.identity.select.kind, input.kind)), Query.where(queryEq(tables.identity.select.normalizedValue, input.normalizedValue)), Query.where(queryIsNull(tables.identity.select.revokedAt)))).pipe(Effect.mapError((cause) => storageError("identity", "find", cause)), Effect.map((rows) => rows[0] === undefined
2223
2982
  ? Option.none()
2224
- : Option.some(decodeUserRow(tables, rows[0])))),
2983
+ : Option.some(decodeUserIdentityRow(tables, rows[0])))),
2984
+ listByUser: (input) => {
2985
+ let query = Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table), Query.where(queryEq(tables.identity.select.userId, input.userId)));
2986
+ if (input.includeRevoked !== true) {
2987
+ query = query.pipe(Query.where(queryIsNull(tables.identity.select.revokedAt)));
2988
+ }
2989
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("identity", "find", cause)), Effect.map((rows) => rows.map((row) => decodeUserIdentityRow(tables, row))));
2990
+ },
2991
+ insert,
2992
+ markVerified: (input) => executePlan(executor, queryUpdate(tables.identity.table, mutationValues(tables.identity.verify(input))).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.normalizedValue, input.expectedNormalizedValue)), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.returning(selectIdentityColumns(tables)))).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map((rows) => rows[0]
2993
+ ? Option.some(decodeUserIdentityRow(tables, rows[0]))
2994
+ : Option.none())),
2995
+ setPrimaryLogin: (input) => {
2996
+ const candidate = aliasTable(tables.identity.table, "primary_candidate");
2997
+ const candidateColumn = (column) => aliasColumn(candidate, column);
2998
+ const candidateExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(candidate), Query.where(queryEq(candidateColumn(tables.identity.select.id), input.identityId)), Query.where(queryEq(candidateColumn(tables.identity.select.userId), input.userId)), Query.where(queryEq(candidateColumn(tables.identity.select.updatedAt), Number(input.expectedUpdatedAt))), Query.where(queryIsNull(candidateColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(candidateColumn(tables.identity.select.kind), "email"), queryIsNotNull(candidateColumn(tables.identity.select.verifiedAt))))));
2999
+ const primaryColumnName = boundColumnName(tables.identity.select.isPrimaryLogin);
3000
+ const values = {
3001
+ ...mutationValues(tables.identity.setPrimary(input, false)),
3002
+ [primaryColumnName]: Query.case()
3003
+ .when(queryEq(tables.identity.select.id, input.identityId), targetMutationExpression(tables.identity.table, primaryColumnName, 1))
3004
+ .else(targetMutationExpression(tables.identity.table, primaryColumnName, 0)),
3005
+ };
3006
+ const plan = queryUpdate(tables.identity.table, values).pipe(Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.where(candidateExists), Query.returning(selectIdentityColumns(tables)));
3007
+ return executeAtomicPlans(atomic, [plan]).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map(([rows]) => {
3008
+ const selected = rows.find((row) => row.id === input.identityId);
3009
+ return selected === undefined
3010
+ ? Option.none()
3011
+ : Option.some(decodeUserIdentityRow(tables, selected));
3012
+ }));
3013
+ },
3014
+ replace: (input) => byId(input.id, "update").pipe(Effect.flatMap((found) => {
3015
+ if (Option.isNone(found) ||
3016
+ found.value.userId !== input.userId ||
3017
+ input.replacement.userId !== input.userId) {
3018
+ return Effect.succeed(Option.none());
3019
+ }
3020
+ const row = {
3021
+ ...input.replacement,
3022
+ isPrimaryLogin: input.replacement.isPrimaryLogin ?? found.value.isPrimaryLogin,
3023
+ };
3024
+ const active = aliasTable(tables.identity.table, "active_identity");
3025
+ const activeColumn = (column) => aliasColumn(active, column);
3026
+ const usableCount = queryScalar(Query.select({
3027
+ count: Query.count(activeColumn(tables.identity.select.id)),
3028
+ }).pipe(Query.from(active), Query.where(queryEq(activeColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(activeColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(activeColumn(tables.identity.select.kind), "email"), queryIsNotNull(activeColumn(tables.identity.select.verifiedAt))))));
3029
+ const updatePlanBase = queryUpdate(tables.identity.table, mutationValues(tables.identity.replace(input))).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryEq(tables.identity.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.identity.select.revokedAt)));
3030
+ const updatePlan = (isIdentityLoginEligible(found.value) &&
3031
+ !isIdentityLoginEligible(row)
3032
+ ? updatePlanBase.pipe(Query.where(queryGt(usableCount, 1)))
3033
+ : updatePlanBase).pipe(Query.returning({
3034
+ ...selectIdentityColumns(tables),
3035
+ revokedAt: queryWithoutNullRefinement(tables.identity.select.revokedAt),
3036
+ replacedById: queryWithoutNullRefinement(tables.identity.select.replacedById),
3037
+ }));
3038
+ const insertPlan = queryInsertWhere(tables.identity.table, mutationValues(tables.identity.insert(row)), queryChangedOne()).pipe(Query.returning(selectIdentityColumns(tables)));
3039
+ return executeAtomicPlans(atomic, [
3040
+ updatePlan,
3041
+ insertPlan,
3042
+ ]).pipe(Effect.mapError((cause) => isAtomicExecuteConflict(cause, 1)
3043
+ ? identityConflict(row)
3044
+ : storageError("identity", "update", cause)), Effect.map(([updated, inserted]) => inserted[0] === undefined || updated[0] === undefined
3045
+ ? Option.none()
3046
+ : Option.some({
3047
+ previous: decodeUserIdentityRow(tables, updated[0]),
3048
+ replacement: decodeUserIdentityRow(tables, inserted[0]),
3049
+ })));
3050
+ })),
3051
+ revoke: (input) => byId(input.id, "update").pipe(Effect.flatMap((found) => {
3052
+ if (Option.isNone(found)) {
3053
+ return Effect.succeed(Option.none());
3054
+ }
3055
+ const active = aliasTable(tables.identity.table, "active_identity");
3056
+ const activeColumn = (column) => aliasColumn(active, column);
3057
+ const usableCount = queryScalar(Query.select({
3058
+ count: Query.count(activeColumn(tables.identity.select.id)),
3059
+ }).pipe(Query.from(active), Query.where(queryEq(activeColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(activeColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(activeColumn(tables.identity.select.kind), "email"), queryIsNotNull(activeColumn(tables.identity.select.verifiedAt))))));
3060
+ const revokeValues = mutationValues(tables.identity.revoke(input, found.value, tables.identity.select.metadata));
3061
+ const revokePlan = queryUpdate(tables.identity.table, revokeValues).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryEq(tables.identity.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.where(queryOr(queryAnd(queryEq(tables.identity.select.kind, "email"), queryIsNull(tables.identity.select.verifiedAt)), queryGt(usableCount, 1))), Query.returning({
3062
+ ...selectIdentityColumns(tables),
3063
+ revokedAt: queryWithoutNullRefinement(tables.identity.select.revokedAt),
3064
+ metadata: queryWithoutNullRefinement(tables.identity.select.metadata),
3065
+ }));
3066
+ const next = aliasTable(tables.identity.table, "next_identity");
3067
+ const nextColumn = (column) => aliasColumn(next, column);
3068
+ const nextId = queryScalar(Query.select({ id: nextColumn(tables.identity.select.id) }).pipe(Query.from(next), Query.where(queryEq(nextColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(nextColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(nextColumn(tables.identity.select.kind), "email"), queryIsNotNull(nextColumn(tables.identity.select.verifiedAt)))), Query.orderBy(nextColumn(tables.identity.select.createdAt), "asc"), Query.orderBy(nextColumn(tables.identity.select.id), "asc"), Query.limit(1)));
3069
+ const primary = aliasTable(tables.identity.table, "existing_primary");
3070
+ const primaryColumn = (column) => aliasColumn(primary, column);
3071
+ const primaryExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(primary), Query.where(queryEq(primaryColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(primaryColumn(tables.identity.select.revokedAt))), Query.where(queryEq(primaryColumn(tables.identity.select.isPrimaryLogin), 1))));
3072
+ const promotePlan = queryUpdate(tables.identity.table, mutationValues(tables.identity.setPrimary({
3073
+ userId: input.userId,
3074
+ identityId: input.id,
3075
+ expectedUpdatedAt: input.expectedUpdatedAt,
3076
+ updatedAt: input.revokedAt,
3077
+ }, true))).pipe(Query.where(queryEq(tables.identity.select.id, nextId)), Query.where(queryNot(primaryExists)), Query.where(queryChangedOne()));
3078
+ return executeAtomicPlans(atomic, [
3079
+ revokePlan,
3080
+ promotePlan,
3081
+ ]).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map(([rows]) => rows[0] === undefined
3082
+ ? Option.none()
3083
+ : Option.some(decodeUserIdentityRow(tables, rows[0]))));
3084
+ })),
3085
+ });
3086
+ };
3087
+ export const makeEffectQbSqliteRegistrationStore = (executor, atomic, options = {}) => {
3088
+ const tables = options.tables ?? authSqliteTables;
3089
+ const identityStore = makeEffectQbSqliteIdentityStore(executor, atomic, options);
3090
+ const credentialStore = makeEffectQbSqliteCredentialStore(executor, options);
3091
+ const classify = (input, cause) => {
3092
+ const failure = () => Effect.fail(new RegistrationFailureError({
3093
+ message: "Atomic registration failed",
3094
+ cause,
3095
+ }));
3096
+ if (cause instanceof AtomicPlanError && cause.phase !== "execute") {
3097
+ return failure();
3098
+ }
3099
+ const statement = cause instanceof AtomicPlanError ? (cause.statement ?? -1) : -1;
3100
+ if (statement >= 0 && !isAtomicExecuteConflict(cause, statement)) {
3101
+ return failure();
3102
+ }
3103
+ if (statement < 0) {
3104
+ return Effect.gen(function* () {
3105
+ if (Option.isSome(yield* findUserById(executor, options.tables ?? authSqliteTables, input.user.id))) {
3106
+ return yield* new RegistrationConflictError({ reason: "user-id" });
3107
+ }
3108
+ if (Option.isSome(yield* identityStore.findById(input.identity.id))) {
3109
+ return yield* new RegistrationConflictError({
3110
+ reason: "identity-id",
3111
+ });
3112
+ }
3113
+ if (Option.isSome(yield* identityStore.findByKindAndNormalizedValue({
3114
+ scope: input.identity.scope,
3115
+ kind: input.identity.kind,
3116
+ normalizedValue: input.identity.normalizedValue,
3117
+ }))) {
3118
+ return yield* new RegistrationConflictError({
3119
+ reason: "identity-value",
3120
+ });
3121
+ }
3122
+ if (input.passwordCredential !== undefined) {
3123
+ const credential = yield* credentialStore.findPasswordByUserId(input.user.id);
3124
+ if (Option.isSome(credential)) {
3125
+ return yield* new RegistrationConflictError({
3126
+ reason: credential.value.id === input.passwordCredential.id
3127
+ ? "credential-id"
3128
+ : "password-credential",
3129
+ });
3130
+ }
3131
+ }
3132
+ return yield* new RegistrationFailureError({
3133
+ message: "Atomic registration failed",
3134
+ cause,
3135
+ });
3136
+ }).pipe(Effect.catch((error) => error instanceof RegistrationConflictError ||
3137
+ error instanceof RegistrationFailureError
3138
+ ? Effect.fail(error)
3139
+ : Effect.fail(new RegistrationFailureError({
3140
+ message: "Registration conflict classification failed",
3141
+ cause: error,
3142
+ }))));
3143
+ }
3144
+ if (statement === 0) {
3145
+ return Effect.fail(new RegistrationConflictError({ reason: "user-id" }));
3146
+ }
3147
+ if (statement === 1) {
3148
+ return identityStore.findById(input.identity.id).pipe(Effect.flatMap((found) => Option.isSome(found)
3149
+ ? Effect.fail(new RegistrationConflictError({ reason: "identity-id" }))
3150
+ : Effect.fail(new RegistrationConflictError({ reason: "identity-value" }))), Effect.catch(() => Effect.fail(new RegistrationConflictError({ reason: "identity-value" }))));
3151
+ }
3152
+ if (statement === 2 && input.passwordCredential !== undefined) {
3153
+ return credentialStore.findPasswordByUserId(input.user.id).pipe(Effect.flatMap((found) => Effect.fail(new RegistrationConflictError({
3154
+ reason: Option.isSome(found) &&
3155
+ found.value.id === input.passwordCredential?.id
3156
+ ? "credential-id"
3157
+ : "password-credential",
3158
+ }))), Effect.catch(() => Effect.fail(new RegistrationConflictError({ reason: "password-credential" }))));
3159
+ }
3160
+ return failure();
3161
+ };
3162
+ return RegistrationStore.of({
3163
+ create: (input) => {
3164
+ if (input.identity.userId !== input.user.id ||
3165
+ (input.passwordCredential !== undefined &&
3166
+ input.passwordCredential.userId !== input.user.id)) {
3167
+ return Effect.fail(new RegistrationConflictError({ reason: "user-id" }));
3168
+ }
3169
+ const userPlan = queryInsert(tables.user.table, mutationValues(tables.user.insert(input.user)));
3170
+ const identityPlan = queryInsert(tables.identity.table, mutationValues(tables.identity.insert(input.identity)));
3171
+ const plans = input.passwordCredential === undefined
3172
+ ? [userPlan, identityPlan]
3173
+ : [
3174
+ userPlan,
3175
+ identityPlan,
3176
+ queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(input.passwordCredential))),
3177
+ ];
3178
+ return atomic.execute(plans).pipe(Effect.map(() => ({
3179
+ user: input.user,
3180
+ identity: {
3181
+ ...input.identity,
3182
+ isPrimaryLogin: input.identity.isPrimaryLogin ?? false,
3183
+ updatedAt: input.identity.createdAt,
3184
+ },
3185
+ ...(input.passwordCredential === undefined
3186
+ ? {}
3187
+ : { passwordCredential: input.passwordCredential }),
3188
+ })), Effect.catch((error) => classify(input, error)));
3189
+ },
2225
3190
  });
2226
3191
  };
2227
3192
  export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
2228
3193
  const tables = options.tables ?? authSqliteTables;
2229
- return CredentialStore.make({
3194
+ return CredentialStore.of({
2230
3195
  findPasswordByUserId: (userId) => findPasswordCredentialByUserId(executor, tables, userId),
2231
3196
  insertPassword: (row) => executePlan(executor, queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(row)))).pipe(Effect.mapError((cause) => storageError("credential", "insert", cause)), Effect.asVoid),
2232
3197
  updatePassword: (input) => executePlan(executor, queryUpdate(tables.credential.table, mutationValues(tables.credential.updatePassword(input))).pipe(Query.where(queryEq(tables.credential.select.userId, input.userId)), Query.where(queryEq(tables.credential.select.type, "password")), Query.where(queryIsNull(tables.credential.select.revokedAt)), Query.returning({ id: tables.credential.select.id }))).pipe(Effect.mapError((cause) => storageError("credential", "updatePassword", cause)), Effect.flatMap((rows) => rows[0] === undefined
@@ -2236,29 +3201,44 @@ export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
2236
3201
  };
2237
3202
  export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
2238
3203
  const tables = options.tables ?? authSqliteTables;
2239
- return SessionStore.make({
3204
+ return SessionStore.of({
2240
3205
  findById: (id) => findSessionById(executor, tables, id),
2241
3206
  listByUser: (input) => listSessionsByUser(executor, tables, input),
2242
- insert: (row) => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row)))).pipe(Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
2243
- refresh: (input) => {
2244
- const query = queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
2245
- return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "refresh", cause)), Effect.map((rows) => rows[0] === undefined
3207
+ insert: (row) => decodeSessionResult("insert", () => parseAuthenticationEvents(row.authenticationEvents)).pipe(Effect.flatMap(() => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row))))), Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
3208
+ refresh: (input) => findSessionForMutation(executor, tables, input.sessionId, "refresh").pipe(Effect.flatMap((existing) => Option.isNone(existing)
3209
+ ? Effect.succeed(Option.none())
3210
+ : executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "refresh", cause)), Effect.flatMap((rows) => decodeSessionResult("refresh", () => rows[0] === undefined
2246
3211
  ? Option.none()
2247
- : Option.some(decodeSessionRow(tables, rows[0]))));
2248
- },
2249
- rotate: (input) => {
2250
- const query = queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)));
2251
- return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.map((rows) => rows[0] === undefined
3212
+ : Option.some(decodeSessionRow(tables, rows[0]))))))),
3213
+ rotate: (input) => findSessionForMutation(executor, tables, input.sessionId, "rotate").pipe(Effect.flatMap((existing) => Option.isNone(existing)
3214
+ ? Effect.succeed(Option.none())
3215
+ : executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.flatMap((rows) => decodeSessionResult("rotate", () => rows[0] === undefined
2252
3216
  ? Option.none()
2253
- : Option.some(decodeSessionRow(tables, rows[0]))));
2254
- },
2255
- updateClaims: (input) => findSessionById(executor, tables, input.sessionId, "updateClaims").pipe(Effect.flatMap((rowOption) => {
3217
+ : Option.some(decodeSessionRow(tables, rows[0]))))))),
3218
+ updateClaims: (input) => findSessionForMutation(executor, tables, input.sessionId, "updateClaims").pipe(Effect.flatMap((selected) => {
3219
+ if (Option.isNone(selected)) {
3220
+ return Effect.succeed(Option.none());
3221
+ }
3222
+ return executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(selected.value.row, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, selected.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "updateClaims", cause)), Effect.flatMap((rows) => decodeSessionResult("updateClaims", () => rows[0] === undefined
3223
+ ? Option.none()
3224
+ : Option.some(decodeSessionRow(tables, rows[0])))));
3225
+ })),
3226
+ appendEvidence: (input) => decodeSessionResult("appendEvidence", () => parseAuthenticationEvents(input.authenticationEvents)).pipe(Effect.flatMap((authenticationEvents) => findSessionForMutation(executor, tables, input.sessionId, "appendEvidence").pipe(Effect.flatMap((existing) => Option.isNone(existing)
3227
+ ? Effect.succeed(Option.none())
3228
+ : executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.appendEvidence({
3229
+ ...input,
3230
+ authenticationEvents,
3231
+ }))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "appendEvidence", cause)), Effect.flatMap((rows) => decodeSessionResult("appendEvidence", () => rows[0] === undefined
3232
+ ? Option.none()
3233
+ : Option.some(decodeSessionRow(tables, rows[0]))))))))),
3234
+ assureAndRotate: (input) => decodeSessionResult("assureAndRotate", () => parseAuthenticationEvents(input.authenticationEvents)).pipe(Effect.flatMap(() => findSessionById(executor, tables, input.sessionId, "assureAndRotate")), Effect.flatMap((rowOption) => {
2256
3235
  if (Option.isNone(rowOption)) {
2257
3236
  return Effect.succeed(Option.none());
2258
3237
  }
2259
- return executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "updateClaims", cause)), Effect.map((rows) => rows[0] === undefined
3238
+ const query = queryUpdate(tables.session.table, mutationValues(tables.session.assureAndRotate(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.returning(selectSessionColumns(tables)));
3239
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "assureAndRotate", cause)), Effect.flatMap((rows) => decodeSessionResult("assureAndRotate", () => rows[0] === undefined
2260
3240
  ? Option.none()
2261
- : Option.some(decodeSessionRow(tables, rows[0]))));
3241
+ : Option.some(decodeSessionRow(tables, rows[0])))));
2262
3242
  })),
2263
3243
  revoke: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)))).pipe(Effect.mapError((cause) => storageError("session", "revoke", cause)), Effect.asVoid),
2264
3244
  revokeAllForUser: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", "revokeAllForUser", cause)), Effect.asVoid),
@@ -2266,7 +3246,7 @@ export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
2266
3246
  };
2267
3247
  export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
2268
3248
  const tables = options.tables ?? authSqliteTables;
2269
- return VerificationStore.make({
3249
+ return VerificationStore.of({
2270
3250
  findById: (id) => findVerificationById(executor, tables, id),
2271
3251
  insert: (row) => executePlan(executor, queryInsert(tables.verification.table, mutationValues(tables.verification.insert(row)))).pipe(Effect.mapError((cause) => storageError("verification", "insert", cause)), Effect.asVoid),
2272
3252
  consume: (input) => {
@@ -2294,10 +3274,13 @@ export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
2294
3274
  };
2295
3275
  export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
2296
3276
  const tables = options.tables ?? authSqliteTables;
2297
- return AuditLogStore.make({
2298
- insert: (event) => Effect.gen(function* () {
3277
+ return AuditLogStore.of({
3278
+ insertMany: (events) => Effect.gen(function* () {
3279
+ if (events.length === 0) {
3280
+ return;
3281
+ }
2299
3282
  const createdAt = yield* currentUnixMillis;
2300
- yield* executePlan(executor, queryInsert(tables.auditLog.table, mutationValues(tables.auditLog.insert(event, createdAt)))).pipe(Effect.mapError((cause) => auditLogStoreError("insert", cause)), Effect.asVoid);
3283
+ yield* executePlan(executor, queryInsertMany(tables.auditLog.table, events.map((event) => mutationValues(tables.auditLog.insert(event, createdAt))))).pipe(Effect.mapError((cause) => auditLogStoreError("insertMany", cause)), Effect.asVoid);
2301
3284
  }),
2302
3285
  list: (input = {}) => listAuditLogRecords(executor, tables, input),
2303
3286
  deleteBefore: (input) => deleteAuditLogRecordsBefore(executor, tables, input),
@@ -2305,7 +3288,7 @@ export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
2305
3288
  };
2306
3289
  export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {}) => {
2307
3290
  const tables = options.tables ?? authSqliteTables;
2308
- return LoginApprovalReviewStore.make({
3291
+ return LoginApprovalReviewStore.of({
2309
3292
  insertPending: (input) => executePlan(executor, queryInsert(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.insertPending(input)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("insert", cause)), Effect.as(input)),
2310
3293
  findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(executor, tables, input),
2311
3294
  approve: (input) => executePlan(executor, queryUpdate(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.approve(input))).pipe(Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)), Query.where(queryEq(tables.loginApprovalReview.select.status, "pending")), Query.returning(selectLoginApprovalReviewColumns(tables)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("approve", cause)), Effect.map((rows) => rows[0] === undefined
@@ -2318,7 +3301,7 @@ export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {
2318
3301
  };
2319
3302
  export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {}) => {
2320
3303
  const tables = options.tables ?? authSqliteTables;
2321
- return LoginRiskHistory.make({
3304
+ return LoginRiskHistory.of({
2322
3305
  lookup: (input) => lookupLoginRiskHistory(executor, tables, input, options.loginRiskHistory),
2323
3306
  record: (input) => Effect.gen(function* () {
2324
3307
  const createdAt = input.createdAt ?? (yield* currentUnixMillis);
@@ -2329,15 +3312,21 @@ export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {})
2329
3312
  };
2330
3313
  export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) => {
2331
3314
  const tables = options.tables ?? authSqliteTables;
2332
- return TrustedDeviceStore.make({
3315
+ const updateExisting = (input, existing) => executePlan(executor, queryUpdate(tables.trustedDevice.table, mutationValues(tables.trustedDevice.update(input, existing))).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)), Query.where(queryLte(tables.trustedDevice.select.lastSeenAt, Number(input.now))))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
3316
+ const updateAfterInsertConflict = (input, originalError) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => Option.isSome(existing)
3317
+ ? updateExisting(input, existing.value)
3318
+ : Effect.fail(trustedDeviceStoreError("trust", originalError))));
3319
+ return TrustedDeviceStore.of({
2333
3320
  find: (input) => findTrustedDevice(executor, tables, input),
2334
3321
  listForUser: (input) => listTrustedDevicesForUser(executor, tables, input),
2335
3322
  revokeForUser: (input) => executePlan(executor, Query.delete(tables.trustedDevice.table).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("revoke", cause)), Effect.asVoid),
2336
3323
  upsert: (input) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => {
2337
3324
  if (Option.isNone(existing)) {
2338
- return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
3325
+ return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.catch((error) => sqliteConstraintDiscriminator(error)
3326
+ ? updateAfterInsertConflict(input, error)
3327
+ : Effect.fail(trustedDeviceStoreError("trust", error))), Effect.asVoid);
2339
3328
  }
2340
- return executePlan(executor, queryUpdate(tables.trustedDevice.table, mutationValues(tables.trustedDevice.update(input, existing.value))).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
3329
+ return updateExisting(input, existing.value);
2341
3330
  }), Effect.flatMap(() => findTrustedDeviceIncludingExpired(executor, tables, input)), Effect.flatMap((stored) => Option.isSome(stored)
2342
3331
  ? Effect.succeed(stored.value)
2343
3332
  : Effect.fail(trustedDeviceStoreError("trust", "missing row")))),
@@ -2345,7 +3334,7 @@ export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) =>
2345
3334
  };
2346
3335
  export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {}) => {
2347
3336
  const tables = options.tables ?? authSqliteTables;
2348
- return PasskeyCredentialStore.make({
3337
+ return PasskeyCredentialStore.of({
2349
3338
  insert: (row) => executePlan(executor, queryInsert(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.insert(row)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("insert", cause)), Effect.asVoid),
2350
3339
  findByCredentialId: (credentialId) => findPasskeyCredentialByCredentialId(executor, tables, credentialId),
2351
3340
  listByUser: (input) => {
@@ -2355,7 +3344,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
2355
3344
  : base.pipe(Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)));
2356
3345
  return executePlan(executor, query).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodePasskeyCredentialRow(tables, row))));
2357
3346
  },
2358
- updateSignCount: (input) => executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.updateSignCount(input))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)), Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)), Query.returning(selectPasskeyCredentialColumns(tables)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("update-sign-count", cause)), Effect.map((rows) => rows[0] === undefined
3347
+ updateSignCount: (input) => executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.updateSignCount(input))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)), Query.where(queryEq(tables.passkeyCredential.select.signCount, input.expectedSignCount)), Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)), Query.returning(selectPasskeyCredentialColumns(tables)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("update-sign-count", cause)), Effect.map((rows) => rows[0] === undefined
2359
3348
  ? Option.none()
2360
3349
  : Option.some(decodePasskeyCredentialRow(tables, rows[0])))),
2361
3350
  revoke: (input) => findPasskeyCredentialByCredentialId(executor, tables, input.credentialId, "revoke").pipe(Effect.flatMap((row) => {
@@ -2368,7 +3357,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
2368
3357
  };
2369
3358
  export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2370
3359
  const tables = options.tables ?? authSqliteTables;
2371
- return TotpFactorStore.make({
3360
+ return TotpFactorStore.of({
2372
3361
  insert: (row) => executePlan(executor, queryInsert(tables.totpFactor.table, mutationValues(tables.totpFactor.insert(row)))).pipe(Effect.mapError((cause) => totpFactorStoreError("insert", cause)), Effect.asVoid),
2373
3362
  findById: (id) => findTotpFactorById(executor, tables, id),
2374
3363
  listByUser: (input) => {
@@ -2384,9 +3373,35 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2384
3373
  confirm: (input) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.confirm(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.returning(selectTotpFactorColumns(tables)))).pipe(Effect.mapError((cause) => totpFactorStoreError("confirm", cause)), Effect.map((rows) => rows[0] === undefined
2385
3374
  ? Option.none()
2386
3375
  : Option.some(decodeTotpFactorRow(tables, rows[0])))),
2387
- markUsed: (input) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.markUsed(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryGt(tables.totpFactor.select.confirmedAt, 0)), Query.returning(selectTotpFactorColumns(tables)))).pipe(Effect.mapError((cause) => totpFactorStoreError("mark-used", cause)), Effect.map((rows) => rows[0] === undefined
2388
- ? Option.none()
2389
- : Option.some(decodeTotpFactorRow(tables, rows[0])))),
3376
+ acceptCounter: (input) => {
3377
+ const attempt = (initial) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryGt(tables.totpFactor.select.confirmedAt, 0)), Query.where(initial
3378
+ ? queryIsNull(tables.totpFactor.select.lastAcceptedCounter)
3379
+ : queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter)), Query.returning({ id: tables.totpFactor.select.id })));
3380
+ return Effect.gen(function* () {
3381
+ let rows = yield* attempt(true).pipe(Effect.mapError((cause) => totpFactorStoreError("accept-counter", cause)));
3382
+ if (rows[0] === undefined) {
3383
+ rows = yield* attempt(false).pipe(Effect.mapError((cause) => totpFactorStoreError("accept-counter", cause)));
3384
+ }
3385
+ if (rows[0] !== undefined) {
3386
+ const factor = yield* findTotpFactorById(executor, tables, input.id, "accept-counter");
3387
+ if (Option.isNone(factor)) {
3388
+ return { _tag: "Unavailable" };
3389
+ }
3390
+ return {
3391
+ _tag: "Accepted",
3392
+ factor: factor.value,
3393
+ };
3394
+ }
3395
+ const factor = yield* findTotpFactorById(executor, tables, input.id, "accept-counter");
3396
+ return Option.isSome(factor) &&
3397
+ factor.value.confirmedAt !== undefined &&
3398
+ factor.value.revokedAt === undefined &&
3399
+ factor.value.lastAcceptedCounter !== undefined &&
3400
+ factor.value.lastAcceptedCounter >= input.counter
3401
+ ? { _tag: "Replay" }
3402
+ : { _tag: "Unavailable" };
3403
+ });
3404
+ },
2390
3405
  revoke: (input) => findTotpFactorById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
2391
3406
  if (Option.isNone(row)) {
2392
3407
  return Effect.void;
@@ -2395,9 +3410,9 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
2395
3410
  })),
2396
3411
  });
2397
3412
  };
2398
- export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
3413
+ export const makeEffectQbSqliteRecoveryCodeStore = (executor, atomic, options = {}) => {
2399
3414
  const tables = options.tables ?? authSqliteTables;
2400
- return RecoveryCodeStore.make({
3415
+ return RecoveryCodeStore.of({
2401
3416
  insertMany: (rows) => Effect.forEach(rows, (row) => executePlan(executor, queryInsert(tables.recoveryCode.table, mutationValues(tables.recoveryCode.insert(row)))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("insert", cause)), Effect.asVoid), { discard: true }),
2402
3417
  findById: (id) => findRecoveryCodeById(executor, tables, id),
2403
3418
  listByUser: (input) => {
@@ -2413,6 +3428,13 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
2413
3428
  markUsed: (input) => executePlan(executor, queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed(input))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.id)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.returning({ id: tables.recoveryCode.select.id }))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("mark-used", cause)), Effect.flatMap((rows) => rows[0] === undefined
2414
3429
  ? Effect.succeed(Option.none())
2415
3430
  : findRecoveryCodeById(executor, tables, input.id, "mark-used"))),
3431
+ replaceActiveForUser: (input) => {
3432
+ const plans = [
3433
+ queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.replaceActive(input, tables.recoveryCode.select.metadata))).pipe(Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt))),
3434
+ ...input.rows.map((row) => queryInsert(tables.recoveryCode.table, mutationValues(tables.recoveryCode.insert(row)))),
3435
+ ];
3436
+ return atomic.execute(plans).pipe(Effect.mapError((cause) => recoveryCodeStoreError("replace-active", cause)), Effect.asVoid);
3437
+ },
2416
3438
  revoke: (input) => findRecoveryCodeById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
2417
3439
  if (Option.isNone(row)) {
2418
3440
  return Effect.void;
@@ -2421,9 +3443,219 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
2421
3443
  })),
2422
3444
  });
2423
3445
  };
3446
+ export const makeEffectQbSqliteRecoveryCodeSessionRotateStore = (executor, atomic, options = {}) => {
3447
+ const tables = options.tables ?? authSqliteTables;
3448
+ return RecoveryCodeSessionRotateStore.of({
3449
+ consumeAndRotate: (input) => findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.flatMap((current) => {
3450
+ if (Option.isNone(current) || current.value.userId !== input.userId) {
3451
+ return Effect.succeed(Option.none());
3452
+ }
3453
+ try {
3454
+ assertSessionAssurance(input);
3455
+ }
3456
+ catch (error) {
3457
+ return Effect.fail(new RecoveryCodeSessionRotateStoreError({
3458
+ message: "Session assurance validation failed",
3459
+ cause: error,
3460
+ }));
3461
+ }
3462
+ const metadata = input.claims === undefined
3463
+ ? undefined
3464
+ : encodeSessionMetadata({
3465
+ ...current.value,
3466
+ claims: input.claims,
3467
+ });
3468
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now)))));
3469
+ const consumePlan = queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed({
3470
+ id: input.codeId,
3471
+ usedAt: input.usedAt,
3472
+ ...(input.codeMetadata === undefined
3473
+ ? {}
3474
+ : { metadata: input.codeMetadata }),
3475
+ }))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.where(sessionExists));
3476
+ const sessionValues = mutationValues(tables.session.assureAndRotate(current.value, input));
3477
+ if (metadata !== undefined) {
3478
+ sessionValues[boundColumnName(tables.session.select.metadata)] =
3479
+ metadata;
3480
+ }
3481
+ const rotatePlan = queryUpdate(tables.session.table, sessionValues).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.where(queryChangedOne()), Query.returning({ id: tables.session.select.id }));
3482
+ return executeAtomicPlans(atomic, [
3483
+ consumePlan,
3484
+ rotatePlan,
3485
+ ]).pipe(Effect.mapError((cause) => new RecoveryCodeSessionRotateStoreError({
3486
+ message: "Atomic recovery session rotation failed",
3487
+ cause,
3488
+ })), Effect.flatMap((results) => results[1].length === 0
3489
+ ? Effect.succeed(Option.none())
3490
+ : findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.mapError((cause) => new RecoveryCodeSessionRotateStoreError({
3491
+ message: "Failed to load rotated session",
3492
+ cause,
3493
+ })))));
3494
+ }), Effect.mapError((cause) => cause instanceof RecoveryCodeSessionRotateStoreError
3495
+ ? cause
3496
+ : new RecoveryCodeSessionRotateStoreError({
3497
+ message: "Failed to load current session",
3498
+ cause,
3499
+ }))),
3500
+ });
3501
+ };
3502
+ export const makeEffectQbSqliteTotpSessionRotateStore = (executor, atomic, options = {}) => {
3503
+ const tables = options.tables ?? authSqliteTables;
3504
+ return TotpSessionRotateStore.of({
3505
+ acceptAndRotate: (input) => findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.flatMap((current) => {
3506
+ if (Option.isNone(current) || current.value.userId !== input.userId) {
3507
+ return Effect.succeed(Option.none());
3508
+ }
3509
+ try {
3510
+ assertSessionAssurance(input);
3511
+ }
3512
+ catch (error) {
3513
+ return Effect.fail(new TotpSessionRotateStoreError({
3514
+ message: "Session assurance validation failed",
3515
+ cause: error,
3516
+ }));
3517
+ }
3518
+ const metadata = input.claims === undefined
3519
+ ? undefined
3520
+ : encodeSessionMetadata({
3521
+ ...current.value,
3522
+ claims: input.claims,
3523
+ });
3524
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now)))));
3525
+ const acceptPlan = queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter({
3526
+ id: input.factorId,
3527
+ counter: input.counter,
3528
+ lastUsedAt: input.lastUsedAt,
3529
+ ...(input.factorMetadata === undefined
3530
+ ? {}
3531
+ : { metadata: input.factorMetadata }),
3532
+ }))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter))), Query.where(sessionExists));
3533
+ const sessionValues = mutationValues(tables.session.assureAndRotate(current.value, input));
3534
+ if (metadata !== undefined) {
3535
+ sessionValues[boundColumnName(tables.session.select.metadata)] =
3536
+ metadata;
3537
+ }
3538
+ const rotatePlan = queryUpdate(tables.session.table, sessionValues).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.where(queryChangedOne()), Query.returning({ id: tables.session.select.id }));
3539
+ return executeAtomicPlans(atomic, [
3540
+ acceptPlan,
3541
+ rotatePlan,
3542
+ ]).pipe(Effect.mapError((cause) => new TotpSessionRotateStoreError({
3543
+ message: "Atomic TOTP session rotation failed",
3544
+ cause,
3545
+ })), Effect.flatMap((results) => results[1].length === 0
3546
+ ? Effect.succeed(Option.none())
3547
+ : findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.mapError((cause) => new TotpSessionRotateStoreError({
3548
+ message: "Failed to load rotated session",
3549
+ cause,
3550
+ })))));
3551
+ }), Effect.mapError((cause) => cause instanceof TotpSessionRotateStoreError
3552
+ ? cause
3553
+ : new TotpSessionRotateStoreError({
3554
+ message: "Failed to load current session",
3555
+ cause,
3556
+ }))),
3557
+ });
3558
+ };
3559
+ export const makeEffectQbSqliteTotpLoginCommitStore = (atomic, options = {}) => {
3560
+ const tables = options.tables ?? authSqliteTables;
3561
+ return TotpLoginCommitStore.of({
3562
+ commit: (input) => {
3563
+ const { verification, session } = input;
3564
+ if (verification.type !== "auth-flow-state" ||
3565
+ verification.subject !== input.userId ||
3566
+ (session === undefined && input.continuation !== "login-approval") ||
3567
+ (session !== undefined && input.continuation !== undefined) ||
3568
+ (session !== undefined && session.userId !== input.userId)) {
3569
+ return Effect.succeed(false);
3570
+ }
3571
+ const factorExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.totpFactor.table), Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter)))));
3572
+ const verificationPlan = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume({
3573
+ id: verification.id,
3574
+ type: verification.type,
3575
+ consumedAt: input.consumedAt,
3576
+ }))).pipe(Query.where(queryEq(tables.verification.select.id, verification.id)), Query.where(queryEq(tables.verification.select.type, verification.type)), Query.where(queryEq(tables.verification.select.subject, verification.subject)), Query.where(queryIsNotDistinctFrom(tables.verification.select.secretHash, verification.secretHash ?? null)), Query.where(queryEq(tables.verification.select.createdAt, Number(verification.createdAt))), Query.where(queryEq(tables.verification.select.expiresAt, Number(verification.expiresAt))), Query.where(queryIsNotDistinctFrom(tables.verification.select.metadata, jsonEncode(verification.metadata))), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))), Query.where(factorExists));
3577
+ const factorPlan = queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter({
3578
+ id: input.factorId,
3579
+ counter: input.counter,
3580
+ lastUsedAt: input.lastUsedAt,
3581
+ ...(input.factorMetadata === undefined
3582
+ ? {}
3583
+ : { metadata: input.factorMetadata }),
3584
+ }))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter))), Query.where(queryChangedOne()), Query.returning({ id: tables.totpFactor.select.id }));
3585
+ if (session === undefined) {
3586
+ return executeAtomicPlans(atomic, [
3587
+ verificationPlan,
3588
+ factorPlan,
3589
+ ]).pipe(Effect.map((results) => results[1].length === 1), Effect.mapError((cause) => new TotpLoginCommitStoreError({
3590
+ message: "Atomic TOTP login commit failed",
3591
+ cause,
3592
+ })));
3593
+ }
3594
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, session.id))));
3595
+ const sessionVerificationPlan = verificationPlan.pipe(Query.where(queryNot(sessionExists)));
3596
+ const sessionPlan = queryInsertWhere(tables.session.table, mutationValues(tables.session.insert(session)), queryChangedOne()).pipe(Query.returning({ id: tables.session.select.id }));
3597
+ return executeAtomicPlans(atomic, [
3598
+ sessionVerificationPlan,
3599
+ factorPlan,
3600
+ sessionPlan,
3601
+ ]).pipe(Effect.map((results) => results[2].length === 1), Effect.mapError((cause) => new TotpLoginCommitStoreError({
3602
+ message: "Atomic TOTP login commit failed",
3603
+ cause,
3604
+ })));
3605
+ },
3606
+ });
3607
+ };
3608
+ export const makeEffectQbSqliteRecoveryCodeLoginCommitStore = (atomic, options = {}) => {
3609
+ const tables = options.tables ?? authSqliteTables;
3610
+ return RecoveryCodeLoginCommitStore.of({
3611
+ commit: (input) => {
3612
+ const { verification, session } = input;
3613
+ if (verification.type !== "auth-flow-state" ||
3614
+ verification.subject !== input.userId ||
3615
+ (session === undefined && input.continuation !== "login-approval") ||
3616
+ (session !== undefined && input.continuation !== undefined) ||
3617
+ (session !== undefined && session.userId !== input.userId)) {
3618
+ return Effect.succeed(false);
3619
+ }
3620
+ const codeExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.recoveryCode.table), Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt))));
3621
+ const verificationPlan = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume({
3622
+ id: verification.id,
3623
+ type: verification.type,
3624
+ consumedAt: input.consumedAt,
3625
+ }))).pipe(Query.where(queryEq(tables.verification.select.id, verification.id)), Query.where(queryEq(tables.verification.select.type, verification.type)), Query.where(queryEq(tables.verification.select.subject, verification.subject)), Query.where(queryIsNotDistinctFrom(tables.verification.select.secretHash, verification.secretHash ?? null)), Query.where(queryEq(tables.verification.select.createdAt, Number(verification.createdAt))), Query.where(queryEq(tables.verification.select.expiresAt, Number(verification.expiresAt))), Query.where(queryIsNotDistinctFrom(tables.verification.select.metadata, jsonEncode(verification.metadata))), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))), Query.where(codeExists));
3626
+ const codePlan = queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed({
3627
+ id: input.codeId,
3628
+ usedAt: input.usedAt,
3629
+ ...(input.codeMetadata === undefined
3630
+ ? {}
3631
+ : { metadata: input.codeMetadata }),
3632
+ }))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.where(queryChangedOne()), Query.returning({ id: tables.recoveryCode.select.id }));
3633
+ if (session === undefined) {
3634
+ return executeAtomicPlans(atomic, [
3635
+ verificationPlan,
3636
+ codePlan,
3637
+ ]).pipe(Effect.map((results) => results[1].length === 1), Effect.mapError((cause) => new RecoveryCodeLoginCommitStoreError({
3638
+ message: "Atomic recovery-code login commit failed",
3639
+ cause,
3640
+ })));
3641
+ }
3642
+ const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, session.id))));
3643
+ const sessionVerificationPlan = verificationPlan.pipe(Query.where(queryNot(sessionExists)));
3644
+ const sessionPlan = queryInsertWhere(tables.session.table, mutationValues(tables.session.insert(session)), queryChangedOne()).pipe(Query.returning({ id: tables.session.select.id }));
3645
+ return executeAtomicPlans(atomic, [
3646
+ sessionVerificationPlan,
3647
+ codePlan,
3648
+ sessionPlan,
3649
+ ]).pipe(Effect.map((results) => results[2].length === 1), Effect.mapError((cause) => new RecoveryCodeLoginCommitStoreError({
3650
+ message: "Atomic recovery-code login commit failed",
3651
+ cause,
3652
+ })));
3653
+ },
3654
+ });
3655
+ };
2424
3656
  export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
2425
3657
  const tables = options.tables ?? authSqliteTables;
2426
- return ApiKeyStore.make({
3658
+ return ApiKeyStore.of({
2427
3659
  insert: (row) => executePlan(executor, queryInsert(tables.apiKey.table, mutationValues(tables.apiKey.insert(row)))).pipe(Effect.mapError((cause) => apiKeyStoreError("insert", cause)), Effect.asVoid),
2428
3660
  findById: (id) => findApiKeyById(executor, tables, id),
2429
3661
  findByPrefix: (prefix) => findApiKeyByPrefix(executor, tables, prefix),
@@ -2445,9 +3677,9 @@ export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
2445
3677
  })),
2446
3678
  });
2447
3679
  };
2448
- export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
3680
+ export const makeEffectQbSqliteRefreshTokenStore = (executor, atomic, options = {}) => {
2449
3681
  const tables = options.tables ?? authSqliteTables;
2450
- return RefreshTokenStore.make({
3682
+ return RefreshTokenStore.of({
2451
3683
  insert: (row) => executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(row)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("insert", cause)), Effect.asVoid),
2452
3684
  findById: (id) => findRefreshTokenById(executor, tables, id),
2453
3685
  findBySecretHash: (hash) => findRefreshTokenBySecretHash(executor, tables, hash),
@@ -2465,21 +3697,28 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
2465
3697
  : base.pipe(Query.where(queryIsNull(tables.refreshToken.select.revokedAt)));
2466
3698
  return executePlan(executor, query).pipe(Effect.mapError((cause) => refreshTokenStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeRefreshTokenRow(tables, row))));
2467
3699
  },
2468
- rotate: (input) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.rotate(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.where(queryIsNull(tables.refreshToken.select.rotatedAt)), Query.where(queryIsNull(tables.refreshToken.select.revokedAt)), Query.returning({ id: tables.refreshToken.select.id }))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.flatMap((rows) => {
2469
- const [updated] = rows;
2470
- if (updated === undefined) {
2471
- return Effect.succeed(Option.none());
2472
- }
2473
- return findRefreshTokenById(executor, tables, RefreshTokenId(updated.id), "rotate").pipe(Effect.flatMap((previous) => {
2474
- if (Option.isNone(previous)) {
2475
- return Effect.succeed(Option.none());
2476
- }
2477
- return executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(input.replacement)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.as(Option.some({
2478
- previous: previous.value,
2479
- replacement: input.replacement,
2480
- })));
3700
+ rotate: (input) => {
3701
+ const { replacement } = input;
3702
+ const rotatePlan = queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.rotate(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.where(queryIsNull(tables.refreshToken.select.rotatedAt)), Query.where(queryIsNull(tables.refreshToken.select.revokedAt)), Query.returning({
3703
+ ...selectRefreshTokenColumns(tables),
3704
+ lastUsedAt: queryWithoutNullRefinement(tables.refreshToken.select.lastUsedAt),
3705
+ rotatedAt: queryWithoutNullRefinement(tables.refreshToken.select.rotatedAt),
3706
+ replacedById: queryWithoutNullRefinement(tables.refreshToken.select.replacedById),
2481
3707
  }));
2482
- })),
3708
+ const replacementPlan = queryInsertWhere(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(replacement)), queryChangedOne()).pipe(Query.returning({ id: tables.refreshToken.select.id }));
3709
+ return executeAtomicPlans(atomic, [
3710
+ rotatePlan,
3711
+ replacementPlan,
3712
+ ]).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.map(([previousRows, replacementRows]) => {
3713
+ const [previous] = previousRows;
3714
+ return previous === undefined || replacementRows.length === 0
3715
+ ? Option.none()
3716
+ : Option.some({
3717
+ previous: decodeRefreshTokenRow(tables, previous),
3718
+ replacement,
3719
+ });
3720
+ }));
3721
+ },
2483
3722
  markReuseDetected: (input) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.markReuseDetected(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.returning(selectRefreshTokenColumns(tables)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("reuse", cause)), Effect.map((rows) => rows[0] === undefined
2484
3723
  ? Option.none()
2485
3724
  : Option.some(decodeRefreshTokenRow(tables, rows[0])))),
@@ -2494,7 +3733,7 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
2494
3733
  };
2495
3734
  export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) => {
2496
3735
  const tables = options.tables ?? authSqliteTables;
2497
- return JwtRevocationStore.make({
3736
+ return JwtRevocationStore.of({
2498
3737
  revoke: (record) => findJwtRevocation(executor, tables, record.jwtId, "revoke").pipe(Effect.flatMap((existing) => {
2499
3738
  if (Option.isNone(existing)) {
2500
3739
  return executePlan(executor, queryInsert(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.insert(record))));
@@ -2508,7 +3747,7 @@ export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) =>
2508
3747
  };
2509
3748
  export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
2510
3749
  const tables = options.tables ?? authSqliteTables;
2511
- return OAuthAccountStore.make({
3750
+ return OAuthAccountStore.of({
2512
3751
  insert: (row) => executePlan(executor, queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(row)))).pipe(Effect.mapError((cause) => oauthAccountStoreError("insert", cause)), Effect.asVoid),
2513
3752
  findById: (id) => findOAuthAccountById(executor, tables, id),
2514
3753
  findByProviderAccount: (input) => findOAuthAccountByProviderAccount(executor, tables, input),
@@ -2532,9 +3771,90 @@ export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
2532
3771
  })),
2533
3772
  });
2534
3773
  };
3774
+ export const makeEffectQbSqliteOAuthIdentityBridgeStore = (executor, atomic, options = {}) => {
3775
+ const tables = options.tables ?? authSqliteTables;
3776
+ const identities = makeEffectQbSqliteIdentityStore(executor, atomic, options);
3777
+ const accounts = makeEffectQbSqliteOAuthAccountStore(executor, options);
3778
+ const classify = (input, cause) => Effect.gen(function* () {
3779
+ if (Option.isSome(yield* findUserById(executor, tables, input.user.id))) {
3780
+ return yield* new OAuthIdentityBridgeConflictError({
3781
+ reason: "user-id",
3782
+ });
3783
+ }
3784
+ if (input.identity !== undefined) {
3785
+ if (Option.isSome(yield* identities.findById(input.identity.id))) {
3786
+ return yield* new OAuthIdentityBridgeConflictError({
3787
+ reason: "identity-id",
3788
+ });
3789
+ }
3790
+ if (Option.isSome(yield* identities.findByKindAndNormalizedValue({
3791
+ scope: input.identity.scope,
3792
+ kind: input.identity.kind,
3793
+ normalizedValue: input.identity.normalizedValue,
3794
+ }))) {
3795
+ return yield* new OAuthIdentityBridgeConflictError({
3796
+ reason: "identity-value",
3797
+ });
3798
+ }
3799
+ }
3800
+ if (Option.isSome(yield* accounts.findById(input.account.id))) {
3801
+ return yield* new OAuthIdentityBridgeConflictError({
3802
+ reason: "oauth-account-id",
3803
+ });
3804
+ }
3805
+ if (Option.isSome(yield* accounts.findByProviderAccount({
3806
+ providerId: input.account.providerId,
3807
+ providerAccountId: input.account.providerAccountId,
3808
+ includeUnlinked: true,
3809
+ }))) {
3810
+ return yield* new OAuthIdentityBridgeConflictError({
3811
+ reason: "provider-account",
3812
+ });
3813
+ }
3814
+ return yield* new OAuthIdentityBridgeStoreError({
3815
+ message: "Atomic OAuth identity creation failed",
3816
+ cause,
3817
+ });
3818
+ }).pipe(Effect.catch((error) => error instanceof OAuthIdentityBridgeConflictError ||
3819
+ error instanceof OAuthIdentityBridgeStoreError
3820
+ ? Effect.fail(error)
3821
+ : Effect.fail(new OAuthIdentityBridgeStoreError({
3822
+ message: "OAuth identity conflict classification failed",
3823
+ cause: error,
3824
+ }))));
3825
+ return OAuthIdentityBridgeStore.of({
3826
+ create: (input) => {
3827
+ if (input.account.userId !== input.user.id ||
3828
+ (input.identity !== undefined &&
3829
+ input.identity.userId !== input.user.id)) {
3830
+ return Effect.fail(new OAuthIdentityBridgeConflictError({ reason: "user-id" }));
3831
+ }
3832
+ const plans = [
3833
+ queryInsert(tables.user.table, mutationValues(tables.user.insert(input.user))),
3834
+ ];
3835
+ if (input.identity !== undefined) {
3836
+ plans.push(queryInsert(tables.identity.table, mutationValues(tables.identity.insert(input.identity))));
3837
+ }
3838
+ plans.push(queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(input.account))));
3839
+ return atomic.execute(plans).pipe(Effect.map(() => ({
3840
+ user: input.user,
3841
+ ...(input.identity === undefined
3842
+ ? {}
3843
+ : {
3844
+ identity: {
3845
+ ...input.identity,
3846
+ isPrimaryLogin: input.identity.isPrimaryLogin ?? false,
3847
+ updatedAt: input.identity.createdAt,
3848
+ },
3849
+ }),
3850
+ account: input.account,
3851
+ })), Effect.catch((error) => classify(input, error)));
3852
+ },
3853
+ });
3854
+ };
2535
3855
  export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
2536
3856
  const tables = options.tables ?? authSqliteTables;
2537
- return OAuthClientStore.make({
3857
+ return OAuthClientStore.of({
2538
3858
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthClient.table, mutationValues(tables.oauthClient.insert(record))).pipe(Query.onConflict("id", {
2539
3859
  update: mutationValues(tables.oauthClient.update(record)),
2540
3860
  }))).pipe(Effect.mapError((cause) => oauthClientStoreError("upsert", cause)), Effect.asVoid),
@@ -2551,7 +3871,7 @@ export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
2551
3871
  };
2552
3872
  export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
2553
3873
  const tables = options.tables ?? authSqliteTables;
2554
- return OAuthConsentStore.make({
3874
+ return OAuthConsentStore.of({
2555
3875
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthConsent.table, mutationValues(tables.oauthConsent.insert(record))).pipe(Query.onConflict("id", {
2556
3876
  update: mutationValues(tables.oauthConsent.update(record)),
2557
3877
  }))).pipe(Effect.mapError((cause) => oauthConsentStoreError("upsert", cause)), Effect.asVoid),
@@ -2575,12 +3895,14 @@ export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
2575
3895
  };
2576
3896
  export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options = {}) => {
2577
3897
  const tables = options.tables ?? authSqliteTables;
2578
- return OAuthAuthorizationCodeStore.make({
3898
+ return OAuthAuthorizationCodeStore.of({
2579
3899
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.insert(record))).pipe(Query.onConflict("code_hash", {
2580
3900
  update: mutationValues(tables.oauthAuthorizationCode.update(record)),
2581
3901
  }))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("upsert", cause)), Effect.asVoid),
2582
3902
  findByHash: (input) => findOAuthAuthorizationCodeByHash(executor, tables, input),
2583
- markConsumed: (input) => executePlan(executor, queryUpdate(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.markConsumed(input))).pipe(Query.where(queryEq(tables.oauthAuthorizationCode.select.codeHash, input.codeHash)), Query.where(queryIsNull(tables.oauthAuthorizationCode.select.consumedAt)), Query.returning({ codeHash: tables.oauthAuthorizationCode.select.codeHash }))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("mark-consumed", cause)), Effect.flatMap((rows) => {
3903
+ markConsumed: (input) => executePlan(executor, queryUpdate(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.markConsumed(input))).pipe(Query.where(queryEq(tables.oauthAuthorizationCode.select.codeHash, input.codeHash)), Query.where(queryIsNull(tables.oauthAuthorizationCode.select.consumedAt)), Query.returning({
3904
+ codeHash: tables.oauthAuthorizationCode.select.codeHash,
3905
+ }))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("mark-consumed", cause)), Effect.flatMap((rows) => {
2584
3906
  const [consumed] = rows;
2585
3907
  if (consumed !== undefined) {
2586
3908
  return findOAuthAuthorizationCodeByHash(executor, tables, { codeHash: consumed.codeHash }, "mark-consumed").pipe(Effect.map((record) => Option.isNone(record)
@@ -2599,10 +3921,214 @@ export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options
2599
3921
  })),
2600
3922
  });
2601
3923
  };
3924
+ export const makeEffectQbSqliteOAuthDeviceAuthorizationStore = (executor, atomic, options = {}) => {
3925
+ const tables = options.tables ?? authSqliteTables;
3926
+ const mapping = oauthDeviceAuthorizationTable(tables);
3927
+ const selection = selectOAuthDeviceAuthorizationColumns(tables);
3928
+ const decode = (row) => decodeOAuthDeviceAuthorizationRow(mapping, row);
3929
+ const find = (column, value, operation) => executePlan(executor, Query.select(selection).pipe(Query.from(mapping.table), Query.where(queryEq(column, value)))).pipe(Effect.mapError((cause) => oauthDeviceAuthorizationStoreError(operation, cause)), Effect.flatMap((rows) => Effect.try({
3930
+ try: () => rows[0] === undefined
3931
+ ? Option.none()
3932
+ : Option.some(decode(rows[0])),
3933
+ catch: (cause) => oauthDeviceAuthorizationStoreError(operation, cause),
3934
+ })));
3935
+ return OAuthDeviceAuthorizationStore.of({
3936
+ insert: (record) => executePlan(executor, queryInsert(mapping.table, mutationValues(mapping.insert(record))).pipe(Query.returning({ id: mapping.select.id }))).pipe(Effect.as(true), Effect.catch((error) => sqliteConstraintDiscriminator(error)
3937
+ ? Effect.succeed(false)
3938
+ : Effect.fail(oauthDeviceAuthorizationStoreError("insert", error)))),
3939
+ findByDeviceCodeHash: (input) => find(mapping.select.deviceCodeHash, input.deviceCodeHash, "find-by-device-code-hash"),
3940
+ findByUserCodeHash: (input) => find(mapping.select.userCodeHash, input.userCodeHash, "find-by-user-code-hash"),
3941
+ findById: (id) => find(mapping.select.id, id, "find-by-id"),
3942
+ decide: (input) => {
3943
+ const updatePlan = queryUpdate(mapping.table, mutationValues(input.decision === "approve"
3944
+ ? mapping.approve(input)
3945
+ : mapping.deny(input))).pipe(Query.where(queryEq(mapping.select.id, input.id)), Query.where(queryEq(mapping.select.status, "pending")), Query.where(queryGt(mapping.select.expiresAt, Number(input.decidedAt))), Query.returning(selection));
3946
+ const classifyPlan = Query.select(selection).pipe(Query.from(mapping.table), Query.where(queryEq(mapping.select.id, input.id)));
3947
+ return executeAtomicPlans(atomic, [
3948
+ updatePlan,
3949
+ classifyPlan,
3950
+ ]).pipe(Effect.mapError((cause) => oauthDeviceAuthorizationStoreError("decide", cause)), Effect.flatMap(([updated, selected]) => Effect.try({
3951
+ try: () => {
3952
+ const changed = updated[0];
3953
+ if (changed !== undefined) {
3954
+ const record = decode(changed);
3955
+ return record.status === "approved"
3956
+ ? { status: "approved", record }
3957
+ : {
3958
+ status: "denied",
3959
+ record: record,
3960
+ };
3961
+ }
3962
+ const row = selected[0];
3963
+ if (row === undefined) {
3964
+ return { status: "missing" };
3965
+ }
3966
+ const record = decode(row);
3967
+ if (Number(input.decidedAt) >= Number(record.expiresAt)) {
3968
+ return { status: "expired", record };
3969
+ }
3970
+ if (record.status === "pending") {
3971
+ return { status: "expired", record };
3972
+ }
3973
+ return { status: "already_decided", record };
3974
+ },
3975
+ catch: (cause) => oauthDeviceAuthorizationStoreError("decide", cause),
3976
+ })));
3977
+ },
3978
+ poll: (input) => {
3979
+ const active = [
3980
+ queryEq(mapping.select.deviceCodeHash, input.deviceCodeHash),
3981
+ queryEq(mapping.select.clientId, input.clientId),
3982
+ queryGt(mapping.select.expiresAt, Number(input.now)),
3983
+ queryIsNull(mapping.select.consumedAt),
3984
+ queryIn(mapping.select.status, "pending", "approved"),
3985
+ ];
3986
+ const intervalColumn = boundColumnName(mapping.select.pollIntervalSeconds);
3987
+ const nextPollColumn = boundColumnName(mapping.select.nextPollAt);
3988
+ const lastPolledColumn = boundColumnName(mapping.select.lastPolledAt);
3989
+ const slowedInterval = sqliteIncrementByFive(mapping.select.pollIntervalSeconds);
3990
+ const slowPlan = queryUpdate(mapping.table, {
3991
+ [intervalColumn]: slowedInterval,
3992
+ [nextPollColumn]: sqliteUnixMillisAfterSeconds(input.now, slowedInterval, mapping.select.status, mapping.select.nextPollAt),
3993
+ [lastPolledColumn]: Number(input.now),
3994
+ }).pipe(Query.where(active[0]), Query.where(active[1]), Query.where(active[2]), Query.where(active[3]), Query.where(active[4]), Query.where(queryEq(mapping.select.status, "pending")), Query.where(queryGt(mapping.select.nextPollAt, Number(input.now))), Query.returning({
3995
+ ...selection,
3996
+ consumedAt: queryWithoutNullRefinement(mapping.select.consumedAt),
3997
+ }));
3998
+ const pendingPlan = queryUpdate(mapping.table, {
3999
+ [nextPollColumn]: sqliteUnixMillisAfterSeconds(input.now, mapping.select.pollIntervalSeconds, mapping.select.status, mapping.select.nextPollAt),
4000
+ [lastPolledColumn]: Number(input.now),
4001
+ }).pipe(Query.where(active[0]), Query.where(active[1]), Query.where(active[2]), Query.where(active[3]), Query.where(active[4]), Query.where(queryEq(mapping.select.status, "pending")), Query.where(queryLte(mapping.select.nextPollAt, Number(input.now))), Query.returning(selection));
4002
+ const classifyPlan = Query.select(selection).pipe(Query.from(mapping.table), Query.where(queryEq(mapping.select.deviceCodeHash, input.deviceCodeHash)), Query.where(queryEq(mapping.select.clientId, input.clientId)));
4003
+ return executeAtomicPlans(atomic, [
4004
+ slowPlan,
4005
+ pendingPlan,
4006
+ classifyPlan,
4007
+ ]).pipe(Effect.mapError((cause) => oauthDeviceAuthorizationStoreError("poll", cause)), Effect.flatMap(([slowed, pending, selected]) => Effect.try({
4008
+ try: () => {
4009
+ const slowRow = slowed[0];
4010
+ if (slowRow !== undefined) {
4011
+ const record = decode(slowRow);
4012
+ return {
4013
+ status: "slow_down",
4014
+ record,
4015
+ interval: record.pollIntervalSeconds,
4016
+ };
4017
+ }
4018
+ const pendingRow = pending[0];
4019
+ if (pendingRow !== undefined) {
4020
+ return {
4021
+ status: "pending",
4022
+ record: decode(pendingRow),
4023
+ };
4024
+ }
4025
+ const row = selected[0];
4026
+ if (row === undefined) {
4027
+ return { status: "missing" };
4028
+ }
4029
+ const record = decode(row);
4030
+ if (record.status === "approved" &&
4031
+ record.consumedAt !== undefined) {
4032
+ return {
4033
+ status: "already_consumed",
4034
+ record: record,
4035
+ };
4036
+ }
4037
+ if (Number(input.now) >= Number(record.expiresAt)) {
4038
+ return { status: "expired", record };
4039
+ }
4040
+ if (record.status === "denied") {
4041
+ return { status: "denied", record };
4042
+ }
4043
+ if (record.status === "approved") {
4044
+ return {
4045
+ status: "approved",
4046
+ record: record,
4047
+ };
4048
+ }
4049
+ return { status: "missing" };
4050
+ },
4051
+ catch: (cause) => oauthDeviceAuthorizationStoreError("poll", cause),
4052
+ })));
4053
+ },
4054
+ deleteExpired: (input) => executePlan(executor, Query.delete(mapping.table).pipe(Query.where(queryLte(mapping.select.expiresAt, Number(input.beforeExpiresAt))), Query.returning({ id: mapping.select.id }))).pipe(Effect.mapError((cause) => oauthDeviceAuthorizationStoreError("delete-expired", cause)), Effect.map((rows) => rows.length)),
4055
+ });
4056
+ };
4057
+ export const makeEffectQbSqliteOAuthDeviceCodeTokenCommitStore = (atomic, options = {}) => {
4058
+ const tables = options.tables ?? authSqliteTables;
4059
+ const authorizationTable = oauthDeviceAuthorizationTable(tables);
4060
+ const authorizationSelection = selectOAuthDeviceAuthorizationColumns(tables);
4061
+ const decodeAuthorization = (row) => decodeOAuthDeviceAuthorizationRow(authorizationTable, row);
4062
+ return OAuthDeviceCodeTokenCommitStore.of({
4063
+ commit: (input) => {
4064
+ const { authorization } = input;
4065
+ const nextPollColumn = boundColumnName(authorizationTable.select.nextPollAt);
4066
+ const lastPolledColumn = boundColumnName(authorizationTable.select.lastPolledAt);
4067
+ const consumedColumn = boundColumnName(authorizationTable.select.consumedAt);
4068
+ const consumePlan = queryUpdate(authorizationTable.table, {
4069
+ [consumedColumn]: Number(input.consumedAt),
4070
+ [lastPolledColumn]: Number(input.consumedAt),
4071
+ [nextPollColumn]: sqliteUnixMillisAfterSeconds(input.consumedAt, authorizationTable.select.pollIntervalSeconds, authorizationTable.select.status, authorizationTable.select.nextPollAt),
4072
+ }).pipe(Query.where(queryEq(authorizationTable.select.id, authorization.id)), Query.where(queryEq(authorizationTable.select.deviceCodeHash, authorization.deviceCodeHash)), Query.where(queryEq(authorizationTable.select.userCodeHash, authorization.userCodeHash)), Query.where(queryEq(authorizationTable.select.clientId, authorization.clientId)), Query.where(queryEq(authorizationTable.select.requestedScopes, JSON.stringify(authorization.requestedScopes))), Query.where(queryEq(authorizationTable.select.status, "approved")), Query.where(queryEq(authorizationTable.select.subject, authorization.subject)), Query.where(queryEq(authorizationTable.select.grantedScopes, JSON.stringify(authorization.grantedScopes))), Query.where(queryEq(authorizationTable.select.issuedAt, Number(authorization.issuedAt))), Query.where(queryEq(authorizationTable.select.expiresAt, Number(authorization.expiresAt))), Query.where(queryEq(authorizationTable.select.pollIntervalSeconds, authorization.pollIntervalSeconds)), Query.where(queryEq(authorizationTable.select.nextPollAt, Number(authorization.nextPollAt))), Query.where(queryIsNotDistinctFrom(authorizationTable.select.lastPolledAt, authorization.lastPolledAt === undefined
4073
+ ? null
4074
+ : Number(authorization.lastPolledAt))), Query.where(queryEq(authorizationTable.select.approvedAt, Number(authorization.approvedAt))), Query.where(queryIsNotDistinctFrom(authorizationTable.select.metadata, jsonEncode(authorization.metadata))), Query.where(queryIsNull(authorizationTable.select.consumedAt)), Query.where(queryGt(authorizationTable.select.expiresAt, Number(input.consumedAt))), Query.returning({
4075
+ ...authorizationSelection,
4076
+ lastPolledAt: queryWithoutNullRefinement(authorizationTable.select.lastPolledAt),
4077
+ consumedAt: queryWithoutNullRefinement(authorizationTable.select.consumedAt),
4078
+ }));
4079
+ const tokenPlans = input.tokenRecords.map((record) => queryInsertWhere(tables.oauthProviderModeToken.table, mutationValues(tables.oauthProviderModeToken.insert(record)), queryChangedOne()).pipe(Query.returning({
4080
+ tokenHash: tables.oauthProviderModeToken.select.tokenHash,
4081
+ })));
4082
+ const classifyPlan = Query.select(authorizationSelection).pipe(Query.from(authorizationTable.table), Query.where(queryEq(authorizationTable.select.id, authorization.id)));
4083
+ const plans = [
4084
+ consumePlan,
4085
+ ...tokenPlans,
4086
+ classifyPlan,
4087
+ ];
4088
+ return executeAtomicPlans(atomic, plans).pipe(Effect.mapError((cause) => new OAuthDeviceCodeTokenCommitStoreError({
4089
+ operation: "commit",
4090
+ message: "Atomic OAuth device token commit failed",
4091
+ cause,
4092
+ })), Effect.flatMap((results) => Effect.try({
4093
+ try: () => {
4094
+ const changed = results[0]?.[0];
4095
+ if (changed !== undefined) {
4096
+ return {
4097
+ status: "committed",
4098
+ record: decodeAuthorization(changed),
4099
+ };
4100
+ }
4101
+ const selected = results.at(-1)?.[0];
4102
+ if (selected === undefined) {
4103
+ return { status: "missing" };
4104
+ }
4105
+ const record = decodeAuthorization(selected);
4106
+ if (record.status === "approved" &&
4107
+ record.consumedAt !== undefined) {
4108
+ return {
4109
+ status: "already_consumed",
4110
+ record: record,
4111
+ };
4112
+ }
4113
+ if (record.status === "approved" &&
4114
+ Number(input.consumedAt) >= Number(record.expiresAt)) {
4115
+ return { status: "expired", record };
4116
+ }
4117
+ return { status: "missing" };
4118
+ },
4119
+ catch: (cause) => new OAuthDeviceCodeTokenCommitStoreError({
4120
+ operation: "commit",
4121
+ message: "Failed to decode OAuth device token commit result",
4122
+ cause,
4123
+ }),
4124
+ })));
4125
+ },
4126
+ });
4127
+ };
2602
4128
  export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {}) => {
2603
4129
  const tables = options.tables ?? authSqliteTables;
2604
- return OAuthClientSecretStore.make({
2605
- upsert: (record) => executePlan(executor, queryInsert(tables.oauthClientSecret.table, mutationValues(tables.oauthClientSecret.insert(record))).pipe(Query.onConflict("prefix", {
4130
+ return OAuthClientSecretStore.of({
4131
+ upsert: (record) => executePlan(executor, queryInsert(tables.oauthClientSecret.table, mutationValues(tables.oauthClientSecret.insert(record))).pipe(Query.onConflict(["client_id", "prefix"], {
2606
4132
  update: mutationValues(tables.oauthClientSecret.update(record)),
2607
4133
  }))).pipe(Effect.mapError((cause) => oauthClientSecretStoreError("upsert", cause)), Effect.asVoid),
2608
4134
  findByClientIdAndPrefix: (input) => findOAuthClientSecretByClientIdAndPrefix(executor, tables, input),
@@ -2621,7 +4147,7 @@ export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {})
2621
4147
  };
2622
4148
  export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options = {}) => {
2623
4149
  const tables = options.tables ?? authSqliteTables;
2624
- return OAuthProviderModeTokenStore.make({
4150
+ return OAuthProviderModeTokenStore.of({
2625
4151
  upsert: (record) => executePlan(executor, queryInsert(tables.oauthProviderModeToken.table, mutationValues(tables.oauthProviderModeToken.insert(record))).pipe(Query.onConflict("token_hash", {
2626
4152
  update: mutationValues(tables.oauthProviderModeToken.update(record)),
2627
4153
  }))).pipe(Effect.mapError((cause) => oauthProviderModeTokenStoreError("upsert", cause)), Effect.asVoid),
@@ -2646,7 +4172,7 @@ export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options
2646
4172
  };
2647
4173
  export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}) => {
2648
4174
  const tables = options.tables ?? authSqliteTables;
2649
- return OAuthProviderTokenVault.make({
4175
+ return OAuthProviderTokenVault.of({
2650
4176
  upsert: (record) => findOAuthProviderTokenVaultRecordByAccountId(executor, tables, { accountId: record.accountId }, "upsert", true).pipe(Effect.flatMap((existing) => Option.isNone(existing)
2651
4177
  ? executePlan(executor, queryInsert(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.insert(record))))
2652
4178
  : executePlan(executor, queryUpdate(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.update(record))).pipe(Query.where(queryEq(tables.oauthProviderTokenVault.select.accountId, record.accountId))))), Effect.mapError((cause) => cause instanceof OAuthProviderTokenVaultError
@@ -2671,7 +4197,7 @@ export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}
2671
4197
  };
2672
4198
  export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}) => {
2673
4199
  const tables = options.tables ?? authSqliteTables;
2674
- return DomainVerificationStore.make({
4200
+ return DomainVerificationStore.of({
2675
4201
  upsert: (record) => findDomainVerificationByOwnerAndDomain(executor, tables, { ownerId: record.ownerId, domain: record.domain }, "upsert").pipe(Effect.flatMap((existing) => Option.isNone(existing)
2676
4202
  ? executePlan(executor, queryInsert(tables.domainVerification.table, mutationValues(tables.domainVerification.insert(record))))
2677
4203
  : executePlan(executor, queryUpdate(tables.domainVerification.table, mutationValues(tables.domainVerification.update(record))).pipe(Query.where(queryEq(tables.domainVerification.select.ownerId, record.ownerId)), Query.where(queryEq(tables.domainVerification.select.domain, record.domain))))), Effect.mapError((cause) => cause instanceof DomainVerificationStoreError
@@ -2683,8 +4209,8 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
2683
4209
  const base = Query.select(selectDomainVerificationColumns(tables)).pipe(Query.from(tables.domainVerification.table), Query.where(queryEq(tables.domainVerification.select.ownerId, input.ownerId)));
2684
4210
  const query = input.includeRevoked === true
2685
4211
  ? base
2686
- : base.pipe(Query.where(queryIsNull(tables.domainVerification.select.revokedAt)));
2687
- return executePlan(executor, query).pipe(Effect.mapError((cause) => domainVerificationStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeDomainVerificationRecord(tables, row))));
4212
+ : base.pipe(Query.where(queryNeq(tables.domainVerification.select.status, "revoked")));
4213
+ return executePlan(executor, query.pipe(Query.orderBy(tables.domainVerification.select.createdAt, "asc"), Query.orderBy(tables.domainVerification.select.id, "asc"))).pipe(Effect.mapError((cause) => domainVerificationStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeDomainVerificationRecord(tables, row))));
2688
4214
  },
2689
4215
  markVerified: (input) => executePlan(executor, queryUpdate(tables.domainVerification.table, mutationValues(tables.domainVerification.markVerified(input))).pipe(Query.where(queryEq(tables.domainVerification.select.id, input.id)), Query.where(queryEq(tables.domainVerification.select.status, "pending")), Query.returning(selectDomainVerificationColumns(tables)))).pipe(Effect.mapError((cause) => domainVerificationStoreError("mark-verified", cause)), Effect.map((rows) => rows[0] === undefined
2690
4216
  ? Option.none()
@@ -2699,7 +4225,7 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
2699
4225
  };
2700
4226
  export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {}) => {
2701
4227
  const tables = options.tables ?? authSqliteTables;
2702
- return SecurityTimelineStore.make({
4228
+ return SecurityTimelineStore.of({
2703
4229
  insert: (event) => executePlan(executor, queryInsert(tables.securityTimeline.table, mutationValues(tables.securityTimeline.insert(event)))).pipe(Effect.mapError((cause) => securityTimelineStoreError("insert", cause)), Effect.asVoid),
2704
4230
  listByUser: (input) => listSecurityTimelineByUser(executor, tables, input),
2705
4231
  deleteBefore: (input) => deleteSecurityTimelineBefore(executor, tables, input),
@@ -2707,7 +4233,7 @@ export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {})
2707
4233
  };
2708
4234
  export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) => {
2709
4235
  const tables = options.tables ?? authSqliteTables;
2710
- return WebhookOutboxStore.make({
4236
+ return WebhookOutboxStore.of({
2711
4237
  enqueue: (input) => Effect.gen(function* () {
2712
4238
  const now = input.now ?? (yield* currentUnixMillis);
2713
4239
  const record = {
@@ -2726,7 +4252,8 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
2726
4252
  claimDue: (input) => claimDueWebhookOutboxRecords(executor, tables, input),
2727
4253
  markDelivered: (input) => Effect.gen(function* () {
2728
4254
  const existing = yield* findWebhookOutboxRecordById(executor, tables, input.id, "markDelivered");
2729
- if (Option.isNone(existing) || existing.value.status === "dead_lettered") {
4255
+ if (Option.isNone(existing) ||
4256
+ existing.value.status === "dead_lettered") {
2730
4257
  return;
2731
4258
  }
2732
4259
  const deliveredAt = input.deliveredAt ?? (yield* currentUnixMillis);
@@ -2764,7 +4291,7 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
2764
4291
  };
2765
4292
  export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) => {
2766
4293
  const tables = options.tables ?? authSqliteTables;
2767
- return WebhookReplayStore.make({
4294
+ return WebhookReplayStore.of({
2768
4295
  claim: (input) => Effect.gen(function* () {
2769
4296
  const now = input.now ?? (yield* currentUnixMillis);
2770
4297
  const claim = { ...input, now };
@@ -2777,8 +4304,297 @@ export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) =>
2777
4304
  deleteExpired: (input = {}) => deleteExpiredWebhookReplayRecords(executor, tables, input),
2778
4305
  });
2779
4306
  };
2780
- export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
4307
+ const permissionScopeKey = (scope) => [
4308
+ scope?.type ?? "global",
4309
+ scope?.id === undefined ? 0 : 1,
4310
+ scope?.id ?? "",
4311
+ ];
4312
+ const comparePermissionText = (left, right) => left === right
4313
+ ? 0
4314
+ : left === undefined
4315
+ ? -1
4316
+ : right === undefined
4317
+ ? 1
4318
+ : left < right
4319
+ ? -1
4320
+ : 1;
4321
+ const comparePermissionScopes = (left, right) => {
4322
+ if (left === undefined || right === undefined) {
4323
+ return left === right ? 0 : left === undefined ? -1 : 1;
4324
+ }
4325
+ return (comparePermissionText(left.type, right.type) ||
4326
+ comparePermissionText(left.id, right.id));
4327
+ };
4328
+ const permissionScopesMatchExactly = (left, right) => {
4329
+ const [leftType, leftIdPresent, leftId] = permissionScopeKey(left);
4330
+ const [rightType, rightIdPresent, rightId] = permissionScopeKey(right);
4331
+ return (leftType === rightType &&
4332
+ leftIdPresent === rightIdPresent &&
4333
+ leftId === rightId);
4334
+ };
4335
+ const permissionGrantIsActive = (grant, at) => grant.revokedAt === undefined &&
4336
+ (grant.expiresAt === undefined || Number(grant.expiresAt) > Number(at));
4337
+ const comparePermissionGrantRecords = (left, right) => comparePermissionText(left.subject.type, right.subject.type) ||
4338
+ comparePermissionText(left.subject.id, right.subject.id) ||
4339
+ comparePermissionText(left.permission, right.permission) ||
4340
+ comparePermissionScopes(left.scope, right.scope);
4341
+ const compareRoleGrantRecords = (left, right) => comparePermissionText(left.subject.type, right.subject.type) ||
4342
+ comparePermissionText(left.subject.id, right.subject.id) ||
4343
+ comparePermissionText(left.role, right.role) ||
4344
+ comparePermissionScopes(left.scope, right.scope);
4345
+ const compareRolePermissionRecords = (left, right) => comparePermissionText(left.role, right.role) ||
4346
+ comparePermissionText(left.permission, right.permission) ||
4347
+ comparePermissionText(left.scopeType, right.scopeType);
4348
+ export const makeEffectQbSqlitePermissionStore = (executor, options = {}) => {
4349
+ const tables = options.tables ?? authSqliteTables;
4350
+ const permissionGrantWhere = (input) => {
4351
+ const { scope: _scope, ...withoutScope } = input;
4352
+ const scope = canonicalPermissionScope(input.scope);
4353
+ const canonicalInput = {
4354
+ ...withoutScope,
4355
+ ...(scope === undefined ? {} : { scope }),
4356
+ };
4357
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(scope);
4358
+ return queryUpdate(tables.permissionGrant.table, mutationValues(tables.permissionGrant.revoke(canonicalInput, input.revokedAt ?? UnixMillis(Date.now())))).pipe(Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)), Query.where(queryEq(tables.permissionGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.permissionGrant.select.scopeId, scopeId)));
4359
+ };
4360
+ const roleGrantWhere = (input) => {
4361
+ const { scope: _scope, ...withoutScope } = input;
4362
+ const scope = canonicalPermissionScope(input.scope);
4363
+ const canonicalInput = {
4364
+ ...withoutScope,
4365
+ ...(scope === undefined ? {} : { scope }),
4366
+ };
4367
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(scope);
4368
+ return queryUpdate(tables.roleGrant.table, mutationValues(tables.roleGrant.revoke(canonicalInput, input.revokedAt ?? UnixMillis(Date.now())))).pipe(Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.roleGrant.select.roleId, input.role)), Query.where(queryEq(tables.roleGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.roleGrant.select.scopeId, scopeId)));
4369
+ };
4370
+ const checkResult = (operation, allowed) => executePlan(executor, Query.select({ allowed: allowed })).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => decodeBoolean(rows[0]?.allowed)));
4371
+ const findPermissionDefinition = (id, operation = "find_permission_definition") => executePlan(executor, Query.select(selectPermissionDefinitionColumns(tables)).pipe(Query.from(tables.permissionDefinition.table), Query.where(queryEq(tables.permissionDefinition.select.id, id)))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4372
+ ? Option.none()
4373
+ : Option.some(decodePermissionDefinition(tables, rows[0]))));
4374
+ const findRoleDefinition = (id, operation = "find_role_definition") => executePlan(executor, Query.select(selectRoleDefinitionColumns(tables)).pipe(Query.from(tables.roleDefinition.table), Query.where(queryEq(tables.roleDefinition.select.id, id)))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4375
+ ? Option.none()
4376
+ : Option.some(decodeRoleDefinition(tables, rows[0]))));
4377
+ const mutatePermissionDefinition = (operation, input, values) => {
4378
+ const returning = operation === "delete_permission_definition"
4379
+ ? {
4380
+ ...selectPermissionDefinitionColumns(tables),
4381
+ deletedAt: queryWithoutNullRefinement(tables.permissionDefinition.select.deletedAt),
4382
+ }
4383
+ : selectPermissionDefinitionColumns(tables);
4384
+ return executePlan(executor, queryUpdate(tables.permissionDefinition.table, mutationValues(values)).pipe(Query.where(queryEq(tables.permissionDefinition.select.id, input.id)), Query.where(queryEq(tables.permissionDefinition.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.permissionDefinition.select.deletedAt)), Query.returning(returning))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4385
+ ? Option.none()
4386
+ : Option.some(decodePermissionDefinition(tables, rows[0]))));
4387
+ };
4388
+ const mutateRoleDefinition = (operation, input, values) => {
4389
+ const returning = operation === "delete_role_definition"
4390
+ ? {
4391
+ ...selectRoleDefinitionColumns(tables),
4392
+ deletedAt: queryWithoutNullRefinement(tables.roleDefinition.select.deletedAt),
4393
+ }
4394
+ : selectRoleDefinitionColumns(tables);
4395
+ return executePlan(executor, queryUpdate(tables.roleDefinition.table, mutationValues(values)).pipe(Query.where(queryEq(tables.roleDefinition.select.id, input.id)), Query.where(queryEq(tables.roleDefinition.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.roleDefinition.select.deletedAt)), Query.returning(returning))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
4396
+ ? Option.none()
4397
+ : Option.some(decodeRoleDefinition(tables, rows[0]))));
4398
+ };
4399
+ return PermissionStore.of({
4400
+ createPermissionDefinition: (definition) => executePlan(executor, queryInsert(tables.permissionDefinition.table, mutationValues(tables.permissionDefinition.insert(definition))).pipe(SqliteQuery.onConflict(tables.permissionDefinition.conflictTarget), Query.returning(selectPermissionDefinitionColumns(tables)))).pipe(Effect.mapError((cause) => permissionStoreError("create_permission_definition", cause)), Effect.map((rows) => rows.length > 0)),
4401
+ findPermissionDefinition: (id) => findPermissionDefinition(id),
4402
+ listPermissionDefinitions: (input = {}) => {
4403
+ const base = Query.select(selectPermissionDefinitionColumns(tables)).pipe(Query.from(tables.permissionDefinition.table));
4404
+ const withoutDisabled = input.includeDisabled === true
4405
+ ? base
4406
+ : base.pipe(Query.where(queryIsNull(tables.permissionDefinition.select.disabledAt)));
4407
+ const withoutDeleted = input.includeDeleted === true
4408
+ ? withoutDisabled
4409
+ : withoutDisabled.pipe(Query.where(queryIsNull(tables.permissionDefinition.select.deletedAt)));
4410
+ const after = input.after === undefined
4411
+ ? withoutDeleted
4412
+ : withoutDeleted.pipe(Query.where(queryGt(tables.permissionDefinition.select.id, input.after)));
4413
+ const query = after.pipe(Query.orderBy(tables.permissionDefinition.select.id, "asc"), Query.limit(normalizePermissionDefinitionListLimit(input.limit)));
4414
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_permission_definitions", cause)), Effect.map((rows) => rows.map((row) => decodePermissionDefinition(tables, row))));
4415
+ },
4416
+ updatePermissionDefinition: (input) => mutatePermissionDefinition("update_permission_definition", input, tables.permissionDefinition.update(input)),
4417
+ setPermissionDefinitionDisabled: (input) => mutatePermissionDefinition("set_permission_definition_disabled", input, tables.permissionDefinition.setDisabled(input)),
4418
+ deletePermissionDefinition: (input) => mutatePermissionDefinition("delete_permission_definition", input, tables.permissionDefinition.delete(input)),
4419
+ createRoleDefinition: (definition) => executePlan(executor, queryInsert(tables.roleDefinition.table, mutationValues(tables.roleDefinition.insert(definition))).pipe(SqliteQuery.onConflict(tables.roleDefinition.conflictTarget), Query.returning(selectRoleDefinitionColumns(tables)))).pipe(Effect.mapError((cause) => permissionStoreError("create_role_definition", cause)), Effect.map((rows) => rows.length > 0)),
4420
+ findRoleDefinition: (id) => findRoleDefinition(id),
4421
+ listRoleDefinitions: (input = {}) => {
4422
+ const base = Query.select(selectRoleDefinitionColumns(tables)).pipe(Query.from(tables.roleDefinition.table));
4423
+ const withoutDisabled = input.includeDisabled === true
4424
+ ? base
4425
+ : base.pipe(Query.where(queryIsNull(tables.roleDefinition.select.disabledAt)));
4426
+ const query = input.includeDeleted === true
4427
+ ? withoutDisabled
4428
+ : withoutDisabled.pipe(Query.where(queryIsNull(tables.roleDefinition.select.deletedAt)));
4429
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_definitions", cause)), Effect.map((rows) => rows
4430
+ .map((row) => decodeRoleDefinition(tables, row))
4431
+ .sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0)));
4432
+ },
4433
+ updateRoleDefinition: (input) => mutateRoleDefinition("update_role_definition", input, tables.roleDefinition.update(input)),
4434
+ setRoleDefinitionDisabled: (input) => mutateRoleDefinition("set_role_definition_disabled", input, tables.roleDefinition.setDisabled(input)),
4435
+ deleteRoleDefinition: (input) => mutateRoleDefinition("delete_role_definition", input, tables.roleDefinition.delete(input)),
4436
+ grantPermission: (input) => Effect.suspend(() => {
4437
+ const { scope: _scope, ...withoutScope } = input;
4438
+ const scope = canonicalPermissionScope(input.scope);
4439
+ const grant = {
4440
+ ...withoutScope,
4441
+ ...(scope === undefined ? {} : { scope }),
4442
+ };
4443
+ return executePlan(executor, queryInsert(tables.permissionGrant.table, mutationValues(tables.permissionGrant.insert(grant))).pipe(SqliteQuery.onConflict([
4444
+ "subject_type",
4445
+ "subject_id",
4446
+ "permission_id",
4447
+ "scope_type",
4448
+ "scope_id_present",
4449
+ "scope_id",
4450
+ ], { update: mutationValues(tables.permissionGrant.update(grant)) }))).pipe(Effect.mapError((cause) => permissionStoreError("grant_permission", cause)), Effect.asVoid);
4451
+ }),
4452
+ revokePermission: (input) => Effect.suspend(() => executePlan(executor, permissionGrantWhere(input)).pipe(Effect.mapError((cause) => permissionStoreError("revoke_permission", cause)), Effect.asVoid)),
4453
+ listPermissionGrants: (input = {}) => Effect.suspend(() => {
4454
+ const at = input.at ?? UnixMillis(Date.now());
4455
+ const activity = input.activity ?? "active";
4456
+ const base = Query.select(selectPermissionGrantColumns(tables)).pipe(Query.from(tables.permissionGrant.table));
4457
+ const bySubjectType = input.subject === undefined
4458
+ ? base
4459
+ : base.pipe(Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)));
4460
+ const bySubject = input.subject === undefined
4461
+ ? bySubjectType
4462
+ : bySubjectType.pipe(Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)));
4463
+ const byPermission = input.permission === undefined
4464
+ ? bySubject
4465
+ : bySubject.pipe(Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)));
4466
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4467
+ const query = input.scope === undefined
4468
+ ? byPermission
4469
+ : byPermission.pipe(Query.where(queryEq(tables.permissionGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.permissionGrant.select.scopeId, scopeId)));
4470
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_permission_grants", cause)), Effect.flatMap((rows) => Effect.try({
4471
+ try: () => rows
4472
+ .map((row) => decodePermissionGrant(tables, row))
4473
+ .filter((grant) => (input.subject === undefined ||
4474
+ (grant.subject.type === input.subject.type &&
4475
+ grant.subject.id === input.subject.id)) &&
4476
+ (input.permission === undefined ||
4477
+ grant.permission === input.permission) &&
4478
+ (input.scope === undefined ||
4479
+ permissionScopesMatchExactly(grant.scope, input.scope)) &&
4480
+ (activity === "all" ||
4481
+ permissionGrantIsActive(grant, at) ===
4482
+ (activity === "active")))
4483
+ .sort(comparePermissionGrantRecords),
4484
+ catch: (cause) => permissionStoreError("list_permission_grants", cause),
4485
+ })));
4486
+ }),
4487
+ grantRole: (input) => Effect.suspend(() => {
4488
+ const { scope: _scope, ...withoutScope } = input;
4489
+ const scope = canonicalPermissionScope(input.scope);
4490
+ const grant = {
4491
+ ...withoutScope,
4492
+ ...(scope === undefined ? {} : { scope }),
4493
+ };
4494
+ return executePlan(executor, queryInsert(tables.roleGrant.table, mutationValues(tables.roleGrant.insert(grant))).pipe(SqliteQuery.onConflict([
4495
+ "subject_type",
4496
+ "subject_id",
4497
+ "role_id",
4498
+ "scope_type",
4499
+ "scope_id_present",
4500
+ "scope_id",
4501
+ ], { update: mutationValues(tables.roleGrant.update(grant)) }))).pipe(Effect.mapError((cause) => permissionStoreError("grant_role", cause)), Effect.asVoid);
4502
+ }),
4503
+ revokeRole: (input) => Effect.suspend(() => executePlan(executor, roleGrantWhere(input)).pipe(Effect.mapError((cause) => permissionStoreError("revoke_role", cause)), Effect.asVoid)),
4504
+ listRoleGrants: (input = {}) => Effect.suspend(() => {
4505
+ const at = input.at ?? UnixMillis(Date.now());
4506
+ const activity = input.activity ?? "active";
4507
+ const base = Query.select(selectRoleGrantColumns(tables)).pipe(Query.from(tables.roleGrant.table));
4508
+ const bySubjectType = input.subject === undefined
4509
+ ? base
4510
+ : base.pipe(Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)));
4511
+ const bySubject = input.subject === undefined
4512
+ ? bySubjectType
4513
+ : bySubjectType.pipe(Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)));
4514
+ const byRole = input.role === undefined
4515
+ ? bySubject
4516
+ : bySubject.pipe(Query.where(queryEq(tables.roleGrant.select.roleId, input.role)));
4517
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4518
+ const query = input.scope === undefined
4519
+ ? byRole
4520
+ : byRole.pipe(Query.where(queryEq(tables.roleGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.roleGrant.select.scopeId, scopeId)));
4521
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_grants", cause)), Effect.flatMap((rows) => Effect.try({
4522
+ try: () => rows
4523
+ .map((row) => decodeRoleGrant(tables, row))
4524
+ .filter((grant) => (input.subject === undefined ||
4525
+ (grant.subject.type === input.subject.type &&
4526
+ grant.subject.id === input.subject.id)) &&
4527
+ (input.role === undefined || grant.role === input.role) &&
4528
+ (input.scope === undefined ||
4529
+ permissionScopesMatchExactly(grant.scope, input.scope)) &&
4530
+ (activity === "all" ||
4531
+ permissionGrantIsActive(grant, at) ===
4532
+ (activity === "active")))
4533
+ .sort(compareRoleGrantRecords),
4534
+ catch: (cause) => permissionStoreError("list_role_grants", cause),
4535
+ })));
4536
+ }),
4537
+ assignRolePermission: (input) => executePlan(executor, queryInsert(tables.rolePermission.table, mutationValues(tables.rolePermission.insert(input))).pipe(SqliteQuery.onConflict([
4538
+ "role_id",
4539
+ "permission_id",
4540
+ "scope_type_present",
4541
+ "scope_type",
4542
+ ]))).pipe(Effect.mapError((cause) => permissionStoreError("assign_role_permission", cause)), Effect.asVoid),
4543
+ removeRolePermission: (input) => executePlan(executor, Query.delete(tables.rolePermission.table).pipe(Query.where(queryEq(tables.rolePermission.select.roleId, input.role)), Query.where(queryEq(tables.rolePermission.select.permissionId, input.permission)), Query.where(queryEq(tables.rolePermission.select.scopeTypePresent, input.scopeType === undefined ? 0 : 1)), Query.where(queryEq(tables.rolePermission.select.scopeType, input.scopeType ?? "")))).pipe(Effect.mapError((cause) => permissionStoreError("remove_role_permission", cause)), Effect.asVoid),
4544
+ listRolePermissions: (input = {}) => {
4545
+ const base = Query.select(selectRolePermissionColumns(tables)).pipe(Query.from(tables.rolePermission.table));
4546
+ const byRole = input.role === undefined
4547
+ ? base
4548
+ : base.pipe(Query.where(queryEq(tables.rolePermission.select.roleId, input.role)));
4549
+ const byPermission = input.permission === undefined
4550
+ ? byRole
4551
+ : byRole.pipe(Query.where(queryEq(tables.rolePermission.select.permissionId, input.permission)));
4552
+ const byScopePresence = input.scopeType === undefined
4553
+ ? byPermission
4554
+ : byPermission.pipe(Query.where(queryEq(tables.rolePermission.select.scopeTypePresent, input.scopeType === null ? 0 : 1)));
4555
+ const query = input.scopeType === undefined || input.scopeType === null
4556
+ ? byScopePresence
4557
+ : byScopePresence.pipe(Query.where(queryEq(tables.rolePermission.select.scopeType, input.scopeType)));
4558
+ return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_permissions", cause)), Effect.flatMap((rows) => Effect.try({
4559
+ try: () => rows
4560
+ .map((row) => decodeRolePermission(tables, row))
4561
+ .filter((rolePermission) => (input.role === undefined ||
4562
+ rolePermission.role === input.role) &&
4563
+ (input.permission === undefined ||
4564
+ rolePermission.permission === input.permission) &&
4565
+ (input.scopeType === undefined ||
4566
+ (input.scopeType === null
4567
+ ? rolePermission.scopeType === undefined
4568
+ : rolePermission.scopeType === input.scopeType)))
4569
+ .sort(compareRolePermissionRecords),
4570
+ catch: (cause) => permissionStoreError("list_role_permissions", cause),
4571
+ })));
4572
+ },
4573
+ hasPermission: (input) => Effect.suspend(() => {
4574
+ const now = Date.now();
4575
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4576
+ const permissionScope = queryOr(queryAnd(queryEq(tables.permissionGrant.select.scopeType, "global"), queryEq(tables.permissionGrant.select.scopeIdPresent, 0)), queryAnd(queryEq(tables.permissionGrant.select.scopeType, scopeType), queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent), queryEq(tables.permissionGrant.select.scopeId, scopeId)));
4577
+ const direct = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.permissionGrant.table), Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)), Query.where(queryIsNull(tables.permissionGrant.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.permissionGrant.select.expiresAt), queryGt(tables.permissionGrant.select.expiresAt, now))), Query.where(permissionScope)));
4578
+ const roleGrant = aliasTable(tables.roleGrant.table, "role_grant");
4579
+ const roleGrantColumn = (column) => aliasColumn(roleGrant, column);
4580
+ const rolePermission = aliasTable(tables.rolePermission.table, "role_permission");
4581
+ const rolePermissionColumn = (column) => aliasColumn(rolePermission, column);
4582
+ const rolePermissionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(rolePermission), Query.where(queryEq(rolePermissionColumn(tables.rolePermission.select.roleId), roleGrantColumn(tables.roleGrant.select.roleId))), Query.where(queryEq(rolePermissionColumn(tables.rolePermission.select.permissionId), input.permission)), Query.where(queryOr(queryEq(rolePermissionColumn(tables.rolePermission.select.scopeTypePresent), 0), queryAnd(queryEq(rolePermissionColumn(tables.rolePermission.select.scopeTypePresent), 1), queryEq(rolePermissionColumn(tables.rolePermission.select.scopeType), scopeType))))));
4583
+ const role = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(roleGrant), Query.where(queryEq(roleGrantColumn(tables.roleGrant.select.subjectType), input.subject.type)), Query.where(queryEq(roleGrantColumn(tables.roleGrant.select.subjectId), input.subject.id)), Query.where(queryIsNull(roleGrantColumn(tables.roleGrant.select.revokedAt))), Query.where(queryOr(queryIsNull(roleGrantColumn(tables.roleGrant.select.expiresAt)), queryGt(roleGrantColumn(tables.roleGrant.select.expiresAt), now))), Query.where(queryOr(queryAnd(queryEq(roleGrantColumn(tables.roleGrant.select.scopeType), "global"), queryEq(roleGrantColumn(tables.roleGrant.select.scopeIdPresent), 0)), queryAnd(queryEq(roleGrantColumn(tables.roleGrant.select.scopeType), scopeType), queryEq(roleGrantColumn(tables.roleGrant.select.scopeIdPresent), scopeIdPresent), queryEq(roleGrantColumn(tables.roleGrant.select.scopeId), scopeId)))), Query.where(rolePermissionExists)));
4584
+ return checkResult("has_permission", queryOr(direct, role));
4585
+ }),
4586
+ hasRole: (input) => Effect.suspend(() => {
4587
+ const now = Date.now();
4588
+ const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
4589
+ const allowed = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.roleGrant.table), Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.roleGrant.select.roleId, input.role)), Query.where(queryIsNull(tables.roleGrant.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.roleGrant.select.expiresAt), queryGt(tables.roleGrant.select.expiresAt, now))), Query.where(queryOr(queryAnd(queryEq(tables.roleGrant.select.scopeType, "global"), queryEq(tables.roleGrant.select.scopeIdPresent, 0)), queryAnd(queryEq(tables.roleGrant.select.scopeType, scopeType), queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent), queryEq(tables.roleGrant.select.scopeId, scopeId))))));
4590
+ return checkResult("has_role", allowed);
4591
+ }),
4592
+ });
4593
+ };
4594
+ export const makeEffectQbSqliteStorage = (executor, atomic, options = {}) => ({
2781
4595
  userStore: makeEffectQbSqliteUserStore(executor, options),
4596
+ identityStore: makeEffectQbSqliteIdentityStore(executor, atomic, options),
4597
+ registrationStore: makeEffectQbSqliteRegistrationStore(executor, atomic, options),
2782
4598
  credentialStore: makeEffectQbSqliteCredentialStore(executor, options),
2783
4599
  sessionStore: makeEffectQbSqliteSessionStore(executor, options),
2784
4600
  verificationStore: makeEffectQbSqliteVerificationStore(executor, options),
@@ -2788,14 +4604,21 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
2788
4604
  trustedDeviceStore: makeEffectQbSqliteTrustedDeviceStore(executor, options),
2789
4605
  passkeyCredentialStore: makeEffectQbSqlitePasskeyCredentialStore(executor, options),
2790
4606
  totpFactorStore: makeEffectQbSqliteTotpFactorStore(executor, options),
2791
- recoveryCodeStore: makeEffectQbSqliteRecoveryCodeStore(executor, options),
4607
+ totpSessionRotateStore: makeEffectQbSqliteTotpSessionRotateStore(executor, atomic, options),
4608
+ totpLoginCommitStore: makeEffectQbSqliteTotpLoginCommitStore(atomic, options),
4609
+ recoveryCodeStore: makeEffectQbSqliteRecoveryCodeStore(executor, atomic, options),
4610
+ recoveryCodeSessionRotateStore: makeEffectQbSqliteRecoveryCodeSessionRotateStore(executor, atomic, options),
4611
+ recoveryCodeLoginCommitStore: makeEffectQbSqliteRecoveryCodeLoginCommitStore(atomic, options),
2792
4612
  apiKeyStore: makeEffectQbSqliteApiKeyStore(executor, options),
2793
- refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, options),
4613
+ refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, atomic, options),
2794
4614
  jwtRevocationStore: makeEffectQbSqliteJwtRevocationStore(executor, options),
2795
4615
  oauthAccountStore: makeEffectQbSqliteOAuthAccountStore(executor, options),
4616
+ oauthIdentityBridgeStore: makeEffectQbSqliteOAuthIdentityBridgeStore(executor, atomic, options),
2796
4617
  oauthClientStore: makeEffectQbSqliteOAuthClientStore(executor, options),
2797
4618
  oauthConsentStore: makeEffectQbSqliteOAuthConsentStore(executor, options),
2798
4619
  oauthAuthorizationCodeStore: makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options),
4620
+ oauthDeviceAuthorizationStore: makeEffectQbSqliteOAuthDeviceAuthorizationStore(executor, atomic, options),
4621
+ oauthDeviceCodeTokenCommitStore: makeEffectQbSqliteOAuthDeviceCodeTokenCommitStore(atomic, options),
2799
4622
  oauthClientSecretStore: makeEffectQbSqliteOAuthClientSecretStore(executor, options),
2800
4623
  oauthProviderModeTokenStore: makeEffectQbSqliteOAuthProviderModeTokenStore(executor, options),
2801
4624
  oauthProviderTokenVault: makeEffectQbSqliteOAuthProviderTokenVault(executor, options),
@@ -2803,8 +4626,9 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
2803
4626
  securityTimelineStore: makeEffectQbSqliteSecurityTimelineStore(executor, options),
2804
4627
  webhookOutboxStore: makeEffectQbSqliteWebhookOutboxStore(executor, options),
2805
4628
  webhookReplayStore: makeEffectQbSqliteWebhookReplayStore(executor, options),
4629
+ permissionStore: makeEffectQbSqlitePermissionStore(executor, options),
2806
4630
  });
2807
- export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(AuditLogStore)(makeEffectQbSqliteAuditLogStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(LoginRiskHistory)(makeEffectQbSqliteLoginRiskHistoryStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(PasskeyCredentialStore)(makeEffectQbSqlitePasskeyCredentialStore(executor, options)), Layer.succeed(TotpFactorStore)(makeEffectQbSqliteTotpFactorStore(executor, options)), Layer.succeed(RecoveryCodeStore)(makeEffectQbSqliteRecoveryCodeStore(executor, options)), Layer.succeed(ApiKeyStore)(makeEffectQbSqliteApiKeyStore(executor, options)), Layer.succeed(RefreshTokenStore)(makeEffectQbSqliteRefreshTokenStore(executor, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)), Layer.succeed(OAuthAccountStore)(makeEffectQbSqliteOAuthAccountStore(executor, options)), Layer.succeed(OAuthClientStore)(makeEffectQbSqliteOAuthClientStore(executor, options)), Layer.succeed(OAuthConsentStore)(makeEffectQbSqliteOAuthConsentStore(executor, options)), Layer.succeed(OAuthAuthorizationCodeStore)(makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options)), Layer.succeed(OAuthClientSecretStore)(makeEffectQbSqliteOAuthClientSecretStore(executor, options)), Layer.succeed(OAuthProviderModeTokenStore)(makeEffectQbSqliteOAuthProviderModeTokenStore(executor, options)), Layer.succeed(OAuthProviderTokenVault)(makeEffectQbSqliteOAuthProviderTokenVault(executor, options)), Layer.succeed(DomainVerificationStore)(makeEffectQbSqliteDomainVerificationStore(executor, options)), Layer.succeed(SecurityTimelineStore)(makeEffectQbSqliteSecurityTimelineStore(executor, options)), Layer.succeed(WebhookOutboxStore)(makeEffectQbSqliteWebhookOutboxStore(executor, options)), Layer.succeed(WebhookReplayStore)(makeEffectQbSqliteWebhookReplayStore(executor, options)));
4631
+ export const EffectQbSqliteAuthStorageLive = (executor, atomic, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(IdentityStore)(makeEffectQbSqliteIdentityStore(executor, atomic, options)), Layer.succeed(RegistrationStore)(makeEffectQbSqliteRegistrationStore(executor, atomic, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(AuditLogStore)(makeEffectQbSqliteAuditLogStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(LoginRiskHistory)(makeEffectQbSqliteLoginRiskHistoryStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(PasskeyCredentialStore)(makeEffectQbSqlitePasskeyCredentialStore(executor, options)), Layer.succeed(TotpFactorStore)(makeEffectQbSqliteTotpFactorStore(executor, options)), Layer.succeed(TotpSessionRotateStore)(makeEffectQbSqliteTotpSessionRotateStore(executor, atomic, options)), Layer.succeed(TotpLoginCommitStore)(makeEffectQbSqliteTotpLoginCommitStore(atomic, options)), Layer.succeed(RecoveryCodeStore)(makeEffectQbSqliteRecoveryCodeStore(executor, atomic, options)), Layer.succeed(RecoveryCodeSessionRotateStore)(makeEffectQbSqliteRecoveryCodeSessionRotateStore(executor, atomic, options)), Layer.succeed(RecoveryCodeLoginCommitStore)(makeEffectQbSqliteRecoveryCodeLoginCommitStore(atomic, options)), Layer.succeed(ApiKeyStore)(makeEffectQbSqliteApiKeyStore(executor, options)), Layer.succeed(RefreshTokenStore)(makeEffectQbSqliteRefreshTokenStore(executor, atomic, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)), Layer.succeed(OAuthAccountStore)(makeEffectQbSqliteOAuthAccountStore(executor, options)), Layer.succeed(OAuthIdentityBridgeStore)(makeEffectQbSqliteOAuthIdentityBridgeStore(executor, atomic, options)), Layer.succeed(OAuthClientStore)(makeEffectQbSqliteOAuthClientStore(executor, options)), Layer.succeed(OAuthConsentStore)(makeEffectQbSqliteOAuthConsentStore(executor, options)), Layer.succeed(OAuthAuthorizationCodeStore)(makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options)), Layer.succeed(OAuthDeviceAuthorizationStore)(makeEffectQbSqliteOAuthDeviceAuthorizationStore(executor, atomic, options)), Layer.succeed(OAuthDeviceCodeTokenCommitStore)(makeEffectQbSqliteOAuthDeviceCodeTokenCommitStore(atomic, options)), Layer.succeed(OAuthClientSecretStore)(makeEffectQbSqliteOAuthClientSecretStore(executor, options)), Layer.succeed(OAuthProviderModeTokenStore)(makeEffectQbSqliteOAuthProviderModeTokenStore(executor, options)), Layer.succeed(OAuthProviderTokenVault)(makeEffectQbSqliteOAuthProviderTokenVault(executor, options)), Layer.succeed(DomainVerificationStore)(makeEffectQbSqliteDomainVerificationStore(executor, options)), Layer.succeed(SecurityTimelineStore)(makeEffectQbSqliteSecurityTimelineStore(executor, options)), Layer.succeed(WebhookOutboxStore)(makeEffectQbSqliteWebhookOutboxStore(executor, options)), Layer.succeed(WebhookReplayStore)(makeEffectQbSqliteWebhookReplayStore(executor, options)), Layer.succeed(PermissionStore)(makeEffectQbSqlitePermissionStore(executor, options)));
2808
4632
  export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
2809
4633
  driver: Executor.driver((query) => Effect.tryPromise({
2810
4634
  try: () => database
@@ -2821,5 +4645,110 @@ export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
2821
4645
  return Effect.succeed([...(result.results ?? [])]);
2822
4646
  }))),
2823
4647
  });
2824
- export const D1EffectQbSqliteAuthStorageLive = (database, options = {}) => EffectQbSqliteAuthStorageLive(makeD1EffectQbSqliteExecutor(database), options);
4648
+ export const makeD1EffectQbSqliteAtomicPlanExecutor = (database) => {
4649
+ const execute = (plans) => {
4650
+ if (plans.length === 0) {
4651
+ return Effect.succeed([]);
4652
+ }
4653
+ return Effect.try({
4654
+ try: () => plans.map((plan, statement) => {
4655
+ try {
4656
+ return {
4657
+ plan,
4658
+ rendered: Renderer.sqlite.render(plan),
4659
+ };
4660
+ }
4661
+ catch (error) {
4662
+ throw new AtomicPlanError({
4663
+ statement,
4664
+ phase: "render",
4665
+ commitState: "not-committed",
4666
+ cause: error,
4667
+ });
4668
+ }
4669
+ }),
4670
+ catch: (cause) => cause instanceof AtomicPlanError
4671
+ ? cause
4672
+ : new AtomicPlanError({
4673
+ phase: "render",
4674
+ commitState: "not-committed",
4675
+ cause,
4676
+ }),
4677
+ }).pipe(Effect.flatMap((renderedPlans) => Effect.tryPromise({
4678
+ try: () => {
4679
+ const statements = renderedPlans.map(({ rendered }, statement) => {
4680
+ try {
4681
+ return database.prepare(rendered.sql).bind(...rendered.params);
4682
+ }
4683
+ catch (error) {
4684
+ throw new AtomicPlanError({
4685
+ statement,
4686
+ phase: "prepare",
4687
+ commitState: "not-committed",
4688
+ cause: error,
4689
+ });
4690
+ }
4691
+ });
4692
+ return database.batch(statements);
4693
+ },
4694
+ catch: (cause) => cause instanceof AtomicPlanError
4695
+ ? cause
4696
+ : new AtomicPlanError({
4697
+ phase: "execute",
4698
+ commitState: "unknown",
4699
+ cause,
4700
+ }),
4701
+ }).pipe(Effect.flatMap((results) => {
4702
+ const failed = results.findIndex((result) => result.success === false || result.error !== undefined);
4703
+ if (failed !== -1) {
4704
+ return Effect.fail(new AtomicPlanError({
4705
+ statement: failed,
4706
+ phase: "execute",
4707
+ commitState: "not-committed",
4708
+ cause: results[failed]?.error,
4709
+ }));
4710
+ }
4711
+ if (results.length !== renderedPlans.length) {
4712
+ return Effect.fail(new AtomicPlanError({
4713
+ phase: "execute",
4714
+ commitState: "unknown",
4715
+ cause: new Error(`D1 batch returned ${results.length} results for ${renderedPlans.length} statements`),
4716
+ }));
4717
+ }
4718
+ return Effect.try({
4719
+ try: () => results.map((result, statement) => {
4720
+ const entry = renderedPlans[statement];
4721
+ if (entry === undefined) {
4722
+ throw new AtomicPlanError({
4723
+ statement,
4724
+ phase: "decode",
4725
+ commitState: "committed",
4726
+ cause: new Error("Missing rendered atomic plan"),
4727
+ });
4728
+ }
4729
+ try {
4730
+ return EffectQbExecutor.decodeRows(entry.rendered, entry.plan, [...(result.results ?? [])], { driverMode: "raw" });
4731
+ }
4732
+ catch (error) {
4733
+ throw new AtomicPlanError({
4734
+ statement,
4735
+ phase: "decode",
4736
+ commitState: "committed",
4737
+ cause: error,
4738
+ });
4739
+ }
4740
+ }),
4741
+ catch: (cause) => cause instanceof AtomicPlanError
4742
+ ? cause
4743
+ : new AtomicPlanError({
4744
+ phase: "decode",
4745
+ commitState: "committed",
4746
+ cause,
4747
+ }),
4748
+ });
4749
+ }))));
4750
+ };
4751
+ return { dialect: "sqlite", execute };
4752
+ };
4753
+ export const D1EffectQbSqliteAuthStorageLive = (database, options = {}) => EffectQbSqliteAuthStorageLive(makeD1EffectQbSqliteExecutor(database), makeD1EffectQbSqliteAtomicPlanExecutor(database), options);
2825
4754
  //# sourceMappingURL=EffectQbSqliteStorage.js.map