@tinyrack/issuary-server 0.31.0 → 0.32.2

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 (455) hide show
  1. package/dist/entities/auth-budget.entity.d.ts +20 -0
  2. package/dist/entities/auth-budget.entity.d.ts.map +1 -0
  3. package/dist/entities/auth-budget.entity.js +11 -0
  4. package/dist/entities/auth-budget.entity.js.map +1 -0
  5. package/dist/entities/browser-session.entity.d.ts +32 -0
  6. package/dist/entities/browser-session.entity.d.ts.map +1 -0
  7. package/dist/entities/browser-session.entity.js +12 -0
  8. package/dist/entities/browser-session.entity.js.map +1 -0
  9. package/dist/entities/email-verification.entity.d.ts +18 -0
  10. package/dist/entities/email-verification.entity.d.ts.map +1 -1
  11. package/dist/entities/email-verification.entity.js +2 -0
  12. package/dist/entities/email-verification.entity.js.map +1 -1
  13. package/dist/entities/oauth-client.entity.d.ts +120 -0
  14. package/dist/entities/oauth-client.entity.d.ts.map +1 -1
  15. package/dist/entities/oauth-code.entity.d.ts +120 -0
  16. package/dist/entities/oauth-code.entity.d.ts.map +1 -1
  17. package/dist/entities/oauth-code.entity.js +3 -0
  18. package/dist/entities/oauth-code.entity.js.map +1 -1
  19. package/dist/entities/oauth-device-code.entity.d.ts +120 -0
  20. package/dist/entities/oauth-device-code.entity.d.ts.map +1 -1
  21. package/dist/entities/oauth-device-code.entity.js +3 -0
  22. package/dist/entities/oauth-device-code.entity.js.map +1 -1
  23. package/dist/entities/oauth-grant.entity.d.ts +52 -0
  24. package/dist/entities/oauth-grant.entity.d.ts.map +1 -0
  25. package/dist/entities/oauth-grant.entity.js +19 -0
  26. package/dist/entities/oauth-grant.entity.js.map +1 -0
  27. package/dist/entities/password-reset.entity.d.ts +18 -0
  28. package/dist/entities/password-reset.entity.d.ts.map +1 -1
  29. package/dist/entities/password-reset.entity.js +2 -0
  30. package/dist/entities/password-reset.entity.js.map +1 -1
  31. package/dist/entities/pending-oauth-registration.entity.d.ts +9 -0
  32. package/dist/entities/pending-oauth-registration.entity.d.ts.map +1 -1
  33. package/dist/entities/pending-oauth-registration.entity.js +1 -0
  34. package/dist/entities/pending-oauth-registration.entity.js.map +1 -1
  35. package/dist/entities/revoked-token.entity.d.ts +120 -0
  36. package/dist/entities/revoked-token.entity.d.ts.map +1 -1
  37. package/dist/entities/terms-content.entity.d.ts +36 -0
  38. package/dist/entities/terms-content.entity.d.ts.map +1 -1
  39. package/dist/entities/terms.entity.d.ts +36 -0
  40. package/dist/entities/terms.entity.d.ts.map +1 -1
  41. package/dist/entities/user-consent.entity.d.ts +120 -0
  42. package/dist/entities/user-consent.entity.d.ts.map +1 -1
  43. package/dist/entities/user-terms-consent.entity.d.ts +36 -0
  44. package/dist/entities/user-terms-consent.entity.d.ts.map +1 -1
  45. package/dist/entities/user-totp-recovery-code.entity.d.ts +36 -0
  46. package/dist/entities/user-totp-recovery-code.entity.d.ts.map +1 -1
  47. package/dist/entities/user-totp.entity.d.ts +36 -0
  48. package/dist/entities/user-totp.entity.d.ts.map +1 -1
  49. package/dist/entities/user-totp.entity.js +1 -0
  50. package/dist/entities/user-totp.entity.js.map +1 -1
  51. package/dist/entities/user.entity.d.ts +36 -0
  52. package/dist/entities/user.entity.d.ts.map +1 -1
  53. package/dist/entities/user.entity.js +3 -0
  54. package/dist/entities/user.entity.js.map +1 -1
  55. package/dist/entrypoints/app.d.ts +448 -437
  56. package/dist/entrypoints/app.d.ts.map +1 -1
  57. package/dist/entrypoints/app.js +78 -6
  58. package/dist/entrypoints/app.js.map +1 -1
  59. package/dist/entrypoints/database/postgres/compiled-functions.d.ts +326 -200
  60. package/dist/entrypoints/database/postgres/compiled-functions.js +1849 -518
  61. package/dist/entrypoints/database/postgres/compiled-functions.js.map +1 -1
  62. package/dist/entrypoints/database/sqlite/compiled-functions.d.ts +326 -200
  63. package/dist/entrypoints/database/sqlite/compiled-functions.js +1849 -518
  64. package/dist/entrypoints/database/sqlite/compiled-functions.js.map +1 -1
  65. package/dist/entrypoints/database/sqlite/sqlite.d.ts.map +1 -1
  66. package/dist/entrypoints/database/sqlite/sqlite.js +1 -0
  67. package/dist/entrypoints/database/sqlite/sqlite.js.map +1 -1
  68. package/dist/entrypoints/scheduler/database.d.ts +3 -1
  69. package/dist/entrypoints/scheduler/database.d.ts.map +1 -1
  70. package/dist/entrypoints/scheduler/database.js +13 -2
  71. package/dist/entrypoints/scheduler/database.js.map +1 -1
  72. package/dist/entrypoints/scheduler/distributed-runner.d.ts +4 -0
  73. package/dist/entrypoints/scheduler/distributed-runner.d.ts.map +1 -1
  74. package/dist/entrypoints/scheduler/distributed-runner.js +26 -1
  75. package/dist/entrypoints/scheduler/distributed-runner.js.map +1 -1
  76. package/dist/lib/config/email.d.ts +1 -0
  77. package/dist/lib/config/email.d.ts.map +1 -1
  78. package/dist/lib/config/email.js.map +1 -1
  79. package/dist/lib/database/entities.d.ts.map +1 -1
  80. package/dist/lib/database/entities.js +6 -0
  81. package/dist/lib/database/entities.js.map +1 -1
  82. package/dist/lib/openapi.d.ts +2 -0
  83. package/dist/lib/openapi.d.ts.map +1 -1
  84. package/dist/lib/openapi.js +36 -1
  85. package/dist/lib/openapi.js.map +1 -1
  86. package/dist/lib/request-ip.d.ts +4 -0
  87. package/dist/lib/request-ip.d.ts.map +1 -0
  88. package/dist/lib/request-ip.js +39 -0
  89. package/dist/lib/request-ip.js.map +1 -0
  90. package/dist/middleware/auth-budget.d.ts +3 -0
  91. package/dist/middleware/auth-budget.d.ts.map +1 -0
  92. package/dist/middleware/auth-budget.js +80 -0
  93. package/dist/middleware/auth-budget.js.map +1 -0
  94. package/dist/middleware/session.d.ts +23 -5
  95. package/dist/middleware/session.d.ts.map +1 -1
  96. package/dist/middleware/session.js +152 -12
  97. package/dist/middleware/session.js.map +1 -1
  98. package/dist/middleware/strict-input.d.ts +4 -0
  99. package/dist/middleware/strict-input.d.ts.map +1 -0
  100. package/dist/middleware/strict-input.js +144 -0
  101. package/dist/middleware/strict-input.js.map +1 -0
  102. package/dist/migrations/postgres/Migration20260910120000_api_security.d.ts +6 -0
  103. package/dist/migrations/postgres/Migration20260910120000_api_security.d.ts.map +1 -0
  104. package/dist/migrations/postgres/Migration20260910120000_api_security.js +20 -0
  105. package/dist/migrations/postgres/Migration20260910120000_api_security.js.map +1 -0
  106. package/dist/migrations/postgres/Migration20260910160000_security_followup.d.ts +6 -0
  107. package/dist/migrations/postgres/Migration20260910160000_security_followup.d.ts.map +1 -0
  108. package/dist/migrations/postgres/Migration20260910160000_security_followup.js +23 -0
  109. package/dist/migrations/postgres/Migration20260910160000_security_followup.js.map +1 -0
  110. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.d.ts +6 -0
  111. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.d.ts.map +1 -0
  112. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.js +28 -0
  113. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.js.map +1 -0
  114. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.d.ts +6 -0
  115. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.d.ts.map +1 -0
  116. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.js +19 -0
  117. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.js.map +1 -0
  118. package/dist/migrations/postgres/index.d.ts.map +1 -1
  119. package/dist/migrations/postgres/index.js +8 -0
  120. package/dist/migrations/postgres/index.js.map +1 -1
  121. package/dist/migrations/sqlite/Migration20260910120000_api_security.d.ts +6 -0
  122. package/dist/migrations/sqlite/Migration20260910120000_api_security.d.ts.map +1 -0
  123. package/dist/migrations/sqlite/Migration20260910120000_api_security.js +20 -0
  124. package/dist/migrations/sqlite/Migration20260910120000_api_security.js.map +1 -0
  125. package/dist/migrations/sqlite/Migration20260910160000_security_followup.d.ts +6 -0
  126. package/dist/migrations/sqlite/Migration20260910160000_security_followup.d.ts.map +1 -0
  127. package/dist/migrations/sqlite/Migration20260910160000_security_followup.js +23 -0
  128. package/dist/migrations/sqlite/Migration20260910160000_security_followup.js.map +1 -0
  129. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.d.ts +8 -0
  130. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.d.ts.map +1 -0
  131. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.js +72 -0
  132. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.js.map +1 -0
  133. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.d.ts +6 -0
  134. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.d.ts.map +1 -0
  135. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.js +19 -0
  136. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.js.map +1 -0
  137. package/dist/migrations/sqlite/index.d.ts.map +1 -1
  138. package/dist/migrations/sqlite/index.js +8 -0
  139. package/dist/migrations/sqlite/index.js.map +1 -1
  140. package/dist/repositories/email-verification.repository.d.ts +1 -0
  141. package/dist/repositories/email-verification.repository.d.ts.map +1 -1
  142. package/dist/repositories/email-verification.repository.js +8 -17
  143. package/dist/repositories/email-verification.repository.js.map +1 -1
  144. package/dist/repositories/oauth-code.repository.d.ts +2 -0
  145. package/dist/repositories/oauth-code.repository.d.ts.map +1 -1
  146. package/dist/repositories/oauth-code.repository.js +8 -0
  147. package/dist/repositories/oauth-code.repository.js.map +1 -1
  148. package/dist/repositories/oauth-device-code.repository.d.ts +3 -0
  149. package/dist/repositories/oauth-device-code.repository.d.ts.map +1 -1
  150. package/dist/repositories/oauth-device-code.repository.js +64 -29
  151. package/dist/repositories/oauth-device-code.repository.js.map +1 -1
  152. package/dist/repositories/password-reset.repository.d.ts +1 -0
  153. package/dist/repositories/password-reset.repository.d.ts.map +1 -1
  154. package/dist/repositories/password-reset.repository.js +6 -19
  155. package/dist/repositories/password-reset.repository.js.map +1 -1
  156. package/dist/repositories/pending-oauth-registration.repository.d.ts +2 -0
  157. package/dist/repositories/pending-oauth-registration.repository.d.ts.map +1 -1
  158. package/dist/repositories/pending-oauth-registration.repository.js +16 -2
  159. package/dist/repositories/pending-oauth-registration.repository.js.map +1 -1
  160. package/dist/repositories/terms.repository.d.ts.map +1 -1
  161. package/dist/repositories/terms.repository.js +1 -0
  162. package/dist/repositories/terms.repository.js.map +1 -1
  163. package/dist/repositories/user-consent.repository.d.ts.map +1 -1
  164. package/dist/repositories/user-consent.repository.js +1 -1
  165. package/dist/repositories/user-consent.repository.js.map +1 -1
  166. package/dist/repositories/user-terms-consent.repository.d.ts.map +1 -1
  167. package/dist/repositories/user-terms-consent.repository.js +2 -0
  168. package/dist/repositories/user-terms-consent.repository.js.map +1 -1
  169. package/dist/repositories/user-totp-recovery-code.repository.d.ts +24 -0
  170. package/dist/repositories/user-totp-recovery-code.repository.d.ts.map +1 -1
  171. package/dist/repositories/user-totp.repository.d.ts +36 -0
  172. package/dist/repositories/user-totp.repository.d.ts.map +1 -1
  173. package/dist/routes/.well-known/openid-configuration/get.d.ts.map +1 -1
  174. package/dist/routes/.well-known/openid-configuration/get.js +5 -1
  175. package/dist/routes/.well-known/openid-configuration/get.js.map +1 -1
  176. package/dist/routes/api/admin/console/index.d.ts.map +1 -1
  177. package/dist/routes/api/admin/console/index.js +29 -14
  178. package/dist/routes/api/admin/console/index.js.map +1 -1
  179. package/dist/routes/api/admin/me/get.js +2 -3
  180. package/dist/routes/api/admin/me/get.js.map +1 -1
  181. package/dist/routes/api/admin/users/index.d.ts.map +1 -1
  182. package/dist/routes/api/admin/users/index.js +59 -52
  183. package/dist/routes/api/admin/users/index.js.map +1 -1
  184. package/dist/routes/api/auth/accounts/remove.post.d.ts +5 -5
  185. package/dist/routes/api/auth/email/resend/post.d.ts.map +1 -1
  186. package/dist/routes/api/auth/email/resend/post.js +3 -24
  187. package/dist/routes/api/auth/email/resend/post.js.map +1 -1
  188. package/dist/routes/api/auth/email/verify/post.d.ts.map +1 -1
  189. package/dist/routes/api/auth/email/verify/post.js +29 -12
  190. package/dist/routes/api/auth/email/verify/post.js.map +1 -1
  191. package/dist/routes/api/auth/login/post.d.ts.map +1 -1
  192. package/dist/routes/api/auth/login/post.js +4 -3
  193. package/dist/routes/api/auth/login/post.js.map +1 -1
  194. package/dist/routes/api/auth/passkey/verify/post.d.ts.map +1 -1
  195. package/dist/routes/api/auth/passkey/verify/post.js +25 -11
  196. package/dist/routes/api/auth/passkey/verify/post.js.map +1 -1
  197. package/dist/routes/api/auth/password/forgot/post.d.ts.map +1 -1
  198. package/dist/routes/api/auth/password/forgot/post.js +2 -19
  199. package/dist/routes/api/auth/password/forgot/post.js.map +1 -1
  200. package/dist/routes/api/auth/register/post.d.ts.map +1 -1
  201. package/dist/routes/api/auth/register/post.js +4 -3
  202. package/dist/routes/api/auth/register/post.js.map +1 -1
  203. package/dist/routes/api/auth/totp/recovery/verify/post.d.ts.map +1 -1
  204. package/dist/routes/api/auth/totp/recovery/verify/post.js +7 -5
  205. package/dist/routes/api/auth/totp/recovery/verify/post.js.map +1 -1
  206. package/dist/routes/api/auth/totp/verify/post.d.ts.map +1 -1
  207. package/dist/routes/api/auth/totp/verify/post.js +7 -5
  208. package/dist/routes/api/auth/totp/verify/post.js.map +1 -1
  209. package/dist/routes/api/consent/post.d.ts.map +1 -1
  210. package/dist/routes/api/consent/post.js +141 -120
  211. package/dist/routes/api/consent/post.js.map +1 -1
  212. package/dist/routes/api/docs/get.d.ts.map +1 -1
  213. package/dist/routes/api/docs/get.js +8 -1
  214. package/dist/routes/api/docs/get.js.map +1 -1
  215. package/dist/routes/api/index.d.ts +379 -379
  216. package/dist/routes/api/oauth/_provider/authorize/get.d.ts.map +1 -1
  217. package/dist/routes/api/oauth/_provider/authorize/get.js +5 -1
  218. package/dist/routes/api/oauth/_provider/authorize/get.js.map +1 -1
  219. package/dist/routes/api/oauth/_provider/callback/get.d.ts +9 -9
  220. package/dist/routes/api/oauth/_provider/callback/get.d.ts.map +1 -1
  221. package/dist/routes/api/oauth/_provider/callback/get.js +30 -6
  222. package/dist/routes/api/oauth/_provider/callback/get.js.map +1 -1
  223. package/dist/routes/api/oauth/_provider/callback/post.d.ts.map +1 -1
  224. package/dist/routes/api/oauth/_provider/callback/post.js +31 -31
  225. package/dist/routes/api/oauth/_provider/callback/post.js.map +1 -1
  226. package/dist/routes/api/oauth/_provider/delete.d.ts.map +1 -1
  227. package/dist/routes/api/oauth/_provider/delete.js +14 -11
  228. package/dist/routes/api/oauth/_provider/delete.js.map +1 -1
  229. package/dist/routes/api/oauth/index.d.ts +18 -18
  230. package/dist/routes/api/terms/consent/post.d.ts.map +1 -1
  231. package/dist/routes/api/terms/consent/post.js +59 -43
  232. package/dist/routes/api/terms/consent/post.js.map +1 -1
  233. package/dist/routes/api/user/delete.d.ts.map +1 -1
  234. package/dist/routes/api/user/delete.js +8 -8
  235. package/dist/routes/api/user/delete.js.map +1 -1
  236. package/dist/routes/api/user/passkeys/_id/delete.d.ts.map +1 -1
  237. package/dist/routes/api/user/passkeys/_id/delete.js +17 -29
  238. package/dist/routes/api/user/passkeys/_id/delete.js.map +1 -1
  239. package/dist/routes/api/user/passkeys/_id/patch.d.ts.map +1 -1
  240. package/dist/routes/api/user/passkeys/_id/patch.js +16 -13
  241. package/dist/routes/api/user/passkeys/_id/patch.js.map +1 -1
  242. package/dist/routes/api/user/passkeys/register/verify/post.d.ts.map +1 -1
  243. package/dist/routes/api/user/passkeys/register/verify/post.js +27 -19
  244. package/dist/routes/api/user/passkeys/register/verify/post.js.map +1 -1
  245. package/dist/routes/api/user/password/delete.d.ts.map +1 -1
  246. package/dist/routes/api/user/password/delete.js +13 -10
  247. package/dist/routes/api/user/password/delete.js.map +1 -1
  248. package/dist/routes/api/user/password/post.d.ts.map +1 -1
  249. package/dist/routes/api/user/password/post.js +13 -10
  250. package/dist/routes/api/user/password/post.js.map +1 -1
  251. package/dist/routes/api/user/password/put.d.ts.map +1 -1
  252. package/dist/routes/api/user/password/put.js +13 -10
  253. package/dist/routes/api/user/password/put.js.map +1 -1
  254. package/dist/routes/api/user/totp/confirm/post.d.ts.map +1 -1
  255. package/dist/routes/api/user/totp/confirm/post.js +33 -18
  256. package/dist/routes/api/user/totp/confirm/post.js.map +1 -1
  257. package/dist/routes/api/user/totp/delete.d.ts.map +1 -1
  258. package/dist/routes/api/user/totp/delete.js +13 -18
  259. package/dist/routes/api/user/totp/delete.js.map +1 -1
  260. package/dist/routes/api/user/totp/recovery/regenerate/post.d.ts.map +1 -1
  261. package/dist/routes/api/user/totp/recovery/regenerate/post.js +13 -10
  262. package/dist/routes/api/user/totp/recovery/regenerate/post.js.map +1 -1
  263. package/dist/routes/api/user/totp/setup/post.d.ts.map +1 -1
  264. package/dist/routes/api/user/totp/setup/post.js +13 -8
  265. package/dist/routes/api/user/totp/setup/post.js.map +1 -1
  266. package/dist/routes/api/user/totp/verify/post.d.ts.map +1 -1
  267. package/dist/routes/api/user/totp/verify/post.js +23 -12
  268. package/dist/routes/api/user/totp/verify/post.js.map +1 -1
  269. package/dist/routes/index.d.ts +252 -242
  270. package/dist/routes/index.d.ts.map +1 -1
  271. package/dist/routes/oauth/authorize/get.d.ts.map +1 -1
  272. package/dist/routes/oauth/authorize/get.js +12 -4
  273. package/dist/routes/oauth/authorize/get.js.map +1 -1
  274. package/dist/routes/oauth/device/get-post.d.ts +10 -0
  275. package/dist/routes/oauth/device/get-post.d.ts.map +1 -1
  276. package/dist/routes/oauth/device/get-post.js +32 -3
  277. package/dist/routes/oauth/device/get-post.js.map +1 -1
  278. package/dist/routes/oauth/device-authorization/post.d.ts.map +1 -1
  279. package/dist/routes/oauth/device-authorization/post.js +13 -7
  280. package/dist/routes/oauth/device-authorization/post.js.map +1 -1
  281. package/dist/routes/oauth/end-session/get.d.ts.map +1 -1
  282. package/dist/routes/oauth/end-session/get.js +2 -3
  283. package/dist/routes/oauth/end-session/get.js.map +1 -1
  284. package/dist/routes/oauth/index.d.ts +10 -0
  285. package/dist/routes/oauth/index.d.ts.map +1 -1
  286. package/dist/routes/oauth/revoke/post.d.ts.map +1 -1
  287. package/dist/routes/oauth/revoke/post.js +10 -2
  288. package/dist/routes/oauth/revoke/post.js.map +1 -1
  289. package/dist/routes/oauth/token/post.d.ts.map +1 -1
  290. package/dist/routes/oauth/token/post.js +69 -57
  291. package/dist/routes/oauth/token/post.js.map +1 -1
  292. package/dist/schemas/error.d.ts +75 -0
  293. package/dist/schemas/error.d.ts.map +1 -1
  294. package/dist/schemas/error.js +3 -0
  295. package/dist/schemas/error.js.map +1 -1
  296. package/dist/schemas/field.d.ts.map +1 -1
  297. package/dist/schemas/field.js +3 -0
  298. package/dist/schemas/field.js.map +1 -1
  299. package/dist/seeders/config.seeder.d.ts +1 -1
  300. package/dist/seeders/config.seeder.d.ts.map +1 -1
  301. package/dist/seeders/config.seeder.js +100 -36
  302. package/dist/seeders/config.seeder.js.map +1 -1
  303. package/dist/services/admin-console.service.d.ts.map +1 -1
  304. package/dist/services/admin-console.service.js +137 -100
  305. package/dist/services/admin-console.service.js.map +1 -1
  306. package/dist/services/auth-budget.service.d.ts +6 -0
  307. package/dist/services/auth-budget.service.d.ts.map +1 -0
  308. package/dist/services/auth-budget.service.js +30 -0
  309. package/dist/services/auth-budget.service.js.map +1 -0
  310. package/dist/services/authentication-epoch.d.ts +5 -0
  311. package/dist/services/authentication-epoch.d.ts.map +1 -0
  312. package/dist/services/authentication-epoch.js +22 -0
  313. package/dist/services/authentication-epoch.js.map +1 -0
  314. package/dist/services/authorization-browser.service.d.ts +18 -0
  315. package/dist/services/authorization-browser.service.d.ts.map +1 -0
  316. package/dist/services/authorization-browser.service.js +28 -0
  317. package/dist/services/authorization-browser.service.js.map +1 -0
  318. package/dist/services/browser-security.service.d.ts +14 -0
  319. package/dist/services/browser-security.service.d.ts.map +1 -0
  320. package/dist/services/browser-security.service.js +85 -0
  321. package/dist/services/browser-security.service.js.map +1 -0
  322. package/dist/services/browser-session.service.d.ts +25 -0
  323. package/dist/services/browser-session.service.d.ts.map +1 -0
  324. package/dist/services/browser-session.service.js +102 -0
  325. package/dist/services/browser-session.service.js.map +1 -0
  326. package/dist/services/cleanup.service.d.ts +1 -0
  327. package/dist/services/cleanup.service.d.ts.map +1 -1
  328. package/dist/services/cleanup.service.js +31 -0
  329. package/dist/services/cleanup.service.js.map +1 -1
  330. package/dist/services/client-security.d.ts +641 -0
  331. package/dist/services/client-security.d.ts.map +1 -0
  332. package/dist/services/client-security.js +10 -0
  333. package/dist/services/client-security.js.map +1 -0
  334. package/dist/services/container.d.ts +2 -0
  335. package/dist/services/container.d.ts.map +1 -1
  336. package/dist/services/container.js +6 -2
  337. package/dist/services/container.js.map +1 -1
  338. package/dist/services/email.service.d.ts +2 -14
  339. package/dist/services/email.service.d.ts.map +1 -1
  340. package/dist/services/email.service.js +35 -29
  341. package/dist/services/email.service.js.map +1 -1
  342. package/dist/services/jwt.service.d.ts +1 -0
  343. package/dist/services/jwt.service.d.ts.map +1 -1
  344. package/dist/services/jwt.service.js +13 -1
  345. package/dist/services/jwt.service.js.map +1 -1
  346. package/dist/services/mail-queue.service.d.ts +24 -0
  347. package/dist/services/mail-queue.service.d.ts.map +1 -0
  348. package/dist/services/mail-queue.service.js +223 -0
  349. package/dist/services/mail-queue.service.js.map +1 -0
  350. package/dist/services/mikro.service.d.ts.map +1 -1
  351. package/dist/services/mikro.service.js +2 -0
  352. package/dist/services/mikro.service.js.map +1 -1
  353. package/dist/services/oauth-authorize.service.d.ts +3 -0
  354. package/dist/services/oauth-authorize.service.d.ts.map +1 -1
  355. package/dist/services/oauth-authorize.service.js +131 -101
  356. package/dist/services/oauth-authorize.service.js.map +1 -1
  357. package/dist/services/oauth-browser-security.service.d.ts +7 -0
  358. package/dist/services/oauth-browser-security.service.d.ts.map +1 -0
  359. package/dist/services/oauth-browser-security.service.js +67 -0
  360. package/dist/services/oauth-browser-security.service.js.map +1 -0
  361. package/dist/services/oauth-client.service.d.ts +24 -1
  362. package/dist/services/oauth-client.service.d.ts.map +1 -1
  363. package/dist/services/oauth-client.service.js +52 -13
  364. package/dist/services/oauth-client.service.js.map +1 -1
  365. package/dist/services/oauth-connect.service.d.ts +37 -3
  366. package/dist/services/oauth-connect.service.d.ts.map +1 -1
  367. package/dist/services/oauth-connect.service.js +236 -212
  368. package/dist/services/oauth-connect.service.js.map +1 -1
  369. package/dist/services/oauth-token.service.d.ts +6 -4
  370. package/dist/services/oauth-token.service.d.ts.map +1 -1
  371. package/dist/services/oauth-token.service.js +295 -209
  372. package/dist/services/oauth-token.service.js.map +1 -1
  373. package/dist/services/passkey.service.d.ts +26 -7
  374. package/dist/services/passkey.service.d.ts.map +1 -1
  375. package/dist/services/passkey.service.js +71 -42
  376. package/dist/services/passkey.service.js.map +1 -1
  377. package/dist/services/password-auth.service.d.ts +5 -1
  378. package/dist/services/password-auth.service.d.ts.map +1 -1
  379. package/dist/services/password-auth.service.js +79 -52
  380. package/dist/services/password-auth.service.js.map +1 -1
  381. package/dist/services/password-reset.service.d.ts.map +1 -1
  382. package/dist/services/password-reset.service.js +34 -15
  383. package/dist/services/password-reset.service.js.map +1 -1
  384. package/dist/services/security.service.d.ts +3 -1
  385. package/dist/services/security.service.d.ts.map +1 -1
  386. package/dist/services/security.service.js +11 -2
  387. package/dist/services/security.service.js.map +1 -1
  388. package/dist/services/terms-policy.service.d.ts +7 -0
  389. package/dist/services/terms-policy.service.d.ts.map +1 -0
  390. package/dist/services/terms-policy.service.js +26 -0
  391. package/dist/services/terms-policy.service.js.map +1 -0
  392. package/dist/services/totp.service.d.ts +4 -5
  393. package/dist/services/totp.service.d.ts.map +1 -1
  394. package/dist/services/totp.service.js +116 -86
  395. package/dist/services/totp.service.js.map +1 -1
  396. package/dist/services/user-security.service.d.ts +15 -0
  397. package/dist/services/user-security.service.d.ts.map +1 -0
  398. package/dist/services/user-security.service.js +57 -0
  399. package/dist/services/user-security.service.js.map +1 -0
  400. package/dist/services/user.service.d.ts +6 -4
  401. package/dist/services/user.service.d.ts.map +1 -1
  402. package/dist/services/user.service.js +143 -121
  403. package/dist/services/user.service.js.map +1 -1
  404. package/frontend/client/assets/{auth-layout-BedWIZ7L.js → auth-layout-sea6nww4.js} +1 -1
  405. package/frontend/client/assets/{auth-page-header-C6Z6Air-.js → auth-page-header-CV85AJLW.js} +1 -1
  406. package/frontend/client/assets/document-navigation-FZbb5ldf.js +1 -0
  407. package/frontend/client/assets/i18n-vfKY-byx.js +1 -0
  408. package/frontend/client/assets/{language-selector-CeLHnp1n.js → language-selector-Jso2cQNI.js} +1 -1
  409. package/frontend/client/assets/manifest-90703545.js +1 -0
  410. package/frontend/client/assets/{passkey-Bkqvhjwb.js → passkey-CJhT-7yO.js} +1 -1
  411. package/frontend/client/assets/{promise-CepnkPDw.js → promise-BP9iSIs-.js} +1 -1
  412. package/frontend/client/assets/{root-CM8lJbh5.js → root-BkavuloG.js} +1 -1
  413. package/frontend/client/assets/{route-Y1_yJKhW.js → route-B0udr9eT.js} +1 -1
  414. package/frontend/client/assets/{route-CdPHVY9W.js → route-B3Uv9aTa.js} +1 -1
  415. package/frontend/client/assets/{route-CF1S7UUy.js → route-B6hIfgxz.js} +1 -1
  416. package/frontend/client/assets/{route-BcXtt6V2.js → route-BEWds2g-.js} +1 -1
  417. package/frontend/client/assets/route-Bm9LsorH.js +1 -0
  418. package/frontend/client/assets/route-Btv5Y69z.js +1 -0
  419. package/frontend/client/assets/route-BvyDzziE.js +1 -0
  420. package/frontend/client/assets/route-C6kG9u7j.js +1 -0
  421. package/frontend/client/assets/{route-1rid8j-3.js → route-C7C3Z0vI.js} +1 -1
  422. package/frontend/client/assets/{route-DphbGk_G.js → route-CVyefM3x.js} +1 -1
  423. package/frontend/client/assets/{route-DZvCkU11.js → route-CXth2Wqz.js} +1 -1
  424. package/frontend/client/assets/route-D0IqOurp.js +1 -0
  425. package/frontend/client/assets/route-DHmYUnJR.js +1 -0
  426. package/frontend/client/assets/route-DTjZQ_Fl.js +1 -0
  427. package/frontend/client/assets/route-D_dTeu1b.js +1 -0
  428. package/frontend/client/assets/{route-DkoXJmYM.js → route-Dj8gVemC.js} +1 -1
  429. package/frontend/client/assets/{route-DrtpzI6u.js → route-DnhPlHOi.js} +1 -1
  430. package/frontend/client/assets/route-FY92YQhF.js +1 -0
  431. package/frontend/client/assets/{route-CKiun7AR.js → route-PcLpZHbT.js} +1 -1
  432. package/frontend/client/assets/{route-BwhQptmx.js → route-qpE7i22X.js} +1 -1
  433. package/frontend/client/assets/{route-HA9dma-h.js → route-wK89gmoS.js} +1 -1
  434. package/frontend/client/assets/security-error-message-CMsTsiWf.js +1 -0
  435. package/frontend/client/assets/{shell-header-bar-CjPlfRKT.js → shell-header-bar-DRx10tE5.js} +1 -1
  436. package/frontend/client/assets/{terms-BGJD_l_i.js → terms-Bu9Itxxs.js} +1 -1
  437. package/frontend/client/assets/{use-totp-setup-Cwuds2tR.js → use-totp-setup-CoOXWOcT.js} +1 -1
  438. package/frontend/server/assets/{hydration-B63_G0fd.js → hydration-DWMjpNav.js} +1 -1
  439. package/frontend/server/assets/{root-loader.server-DUIBlg_3.js → root-loader.server-B4OqoLGG.js} +3 -3
  440. package/frontend/server/assets/{route-runtime.server-DiqwWb8W.js → route-runtime.server-03_pjVDT.js} +1 -1
  441. package/frontend/server/assets/{session-BaAt7WMI.js → session-sJkmzJie.js} +36 -24
  442. package/frontend/server/index.js +239 -193
  443. package/package.json +1 -1
  444. package/frontend/client/assets/document-navigation-Cnp3Alf0.js +0 -1
  445. package/frontend/client/assets/i18n-Dzv9HKFm.js +0 -1
  446. package/frontend/client/assets/manifest-71025d92.js +0 -1
  447. package/frontend/client/assets/route-B4jDMFTW.js +0 -1
  448. package/frontend/client/assets/route-C-DecXPL.js +0 -1
  449. package/frontend/client/assets/route-CGTMMDiX.js +0 -1
  450. package/frontend/client/assets/route-CMJbXiDe.js +0 -1
  451. package/frontend/client/assets/route-DBpAiakW.js +0 -1
  452. package/frontend/client/assets/route-DS92RKs5.js +0 -1
  453. package/frontend/client/assets/route-Dc6G2aDX.js +0 -1
  454. package/frontend/client/assets/route-DqAmRSJE.js +0 -1
  455. package/frontend/client/assets/route-Ezzcj26w.js +0 -1
@@ -3,6 +3,7 @@ import z from 'zod';
3
3
  import { isEmailAllowed } from '../lib/email-pattern.js';
4
4
  import { generatePKCE } from '../lib/pkce.js';
5
5
  import { e, IssuaryError } from '../schemas/error.js';
6
+ import { authenticationMethods, withUserSecurity, } from './user-security.service.js';
6
7
  function parseEmailVerified(value) {
7
8
  if (typeof value === 'boolean') {
8
9
  return value;
@@ -67,7 +68,7 @@ export class OAuthConnectService {
67
68
  /**
68
69
  * Process an OAuth callback: validate session, exchange tokens, and
69
70
  * determine the appropriate next action (redirect, login, link, terms).
70
- * Pure business logic no session writes or HTTP responses.
71
+ * The caller completes database mutations and browser state in one transaction.
71
72
  */
72
73
  async processOAuthCallback(params) {
73
74
  const { provider, code, state, oauthSession, userSub, requestUrl } = params;
@@ -88,12 +89,23 @@ export class OAuthConnectService {
88
89
  if (!userSub) {
89
90
  throw new e.Unauthorized.Error();
90
91
  }
91
- await this.linkOAuthAccount(userSub, provider, tokens, userInfo);
92
+ if (oauthSession.linkSubject !== userSub ||
93
+ !oauthSession.linkEpoch ||
94
+ !params.completeLink)
95
+ throw new e.Unauthorized.Error();
96
+ await params.completeLink(async () => {
97
+ const user = await this.mikro.user.findOneOrFail({ sub: userSub }, { refresh: true });
98
+ if (user.token_epoch !== oauthSession.linkEpoch)
99
+ throw new e.Unauthorized.Error();
100
+ await this.linkOAuthAccount(userSub, provider, tokens, userInfo);
101
+ });
92
102
  const returnUrl = oauthSession.returnUrl || '/profile';
93
103
  return { action: 'link_complete', returnUrl };
94
104
  }
105
+ // Capture the linked identity and its authentication epoch in one joined read.
106
+ const loginProof = await this.prepareAuthentication(provider, userInfo);
95
107
  // Check if this would be a new user
96
- const isNewUser = await this.isNewOAuthUser(provider, userInfo);
108
+ const isNewUser = loginProof.kind === 'registration';
97
109
  // For new users, check registration enabled and email allowlist
98
110
  if (isNewUser) {
99
111
  const { enabled, allowed_email_patterns } = this.config.registration;
@@ -111,71 +123,77 @@ export class OAuthConnectService {
111
123
  // Load terms once and reuse
112
124
  const allTerms = await this.termsService.getGlobalTerms();
113
125
  const explicitTerms = await this.termsService.getExplicitTerms(allTerms);
114
- if (isNewUser && explicitTerms.length > 0) {
115
- // New user with explicit terms: persist to DB
116
- const pendingToken = await this.mikro.pendingOAuthRegistration.createPendingRegistration({
117
- providerId: provider,
118
- accessToken: tokens.access_token,
119
- refreshToken: tokens.refresh_token,
120
- expiresIn: tokens.expires_in,
121
- tokenType: tokens.token_type,
122
- userInfo: {
123
- id: userInfo.id,
124
- email: userInfo.email,
125
- email_verified: userInfo.email_verified,
126
- name: userInfo.name,
127
- picture: userInfo.picture,
128
- },
129
- returnUrl: oauthSession.returnUrl,
130
- expiresAt: new Date(Date.now() + 60 * 60 * 1000),
131
- });
132
- const termsUrl = new URL('/terms', `${this.config.server.public_origin}`);
133
- termsUrl.searchParams.set('mode', 'complete_registration');
134
- termsUrl.searchParams.set('registration_token', pendingToken);
135
- if (oauthSession.returnUrl) {
136
- termsUrl.searchParams.set('redirect', oauthSession.returnUrl);
126
+ const authenticate = async () => {
127
+ if (isNewUser && explicitTerms.length > 0) {
128
+ await this.assertNewIdentity(provider, userInfo);
129
+ // New user with explicit terms: persist to DB
130
+ const pendingToken = await this.mikro.pendingOAuthRegistration.createPendingRegistration({
131
+ providerId: provider,
132
+ accessToken: tokens.access_token,
133
+ refreshToken: tokens.refresh_token,
134
+ expiresIn: tokens.expires_in,
135
+ tokenType: tokens.token_type,
136
+ userInfo: {
137
+ id: userInfo.id,
138
+ email: userInfo.email,
139
+ email_verified: userInfo.email_verified,
140
+ name: userInfo.name,
141
+ picture: userInfo.picture,
142
+ },
143
+ returnUrl: oauthSession.returnUrl,
144
+ expiresAt: new Date(Date.now() + 60 * 60 * 1000),
145
+ });
146
+ const termsUrl = new URL('/terms', `${this.config.server.public_origin}`);
147
+ termsUrl.searchParams.set('mode', 'complete_registration');
148
+ termsUrl.searchParams.set('registration_token', pendingToken);
149
+ if (oauthSession.returnUrl) {
150
+ termsUrl.searchParams.set('redirect', oauthSession.returnUrl);
151
+ }
152
+ return { action: 'terms_redirect', url: termsUrl.toString() };
137
153
  }
138
- return { action: 'terms_redirect', url: termsUrl.toString() };
139
- }
140
- // Login/Register mode: authenticate with OAuth
141
- try {
142
- const result = await this.authenticateWithOAuth(provider, tokens, userInfo);
143
- // Check if existing user needs to see terms page
144
- if (!result.isNewUser && explicitTerms.length > 0) {
145
- const pendingTerms = await this.termsService.getPendingRequiredTerms(result.user.sub);
146
- const explicitTermIds = new Set(explicitTerms.map((t) => t.id));
147
- const shouldRedirectToTerms = pendingTerms.some((id) => explicitTermIds.has(id));
148
- if (shouldRedirectToTerms) {
149
- const url = new URL(requestUrl);
150
- const termsUrl = new URL('/terms', `${url.protocol}//${url.host}`);
151
- if (oauthSession.returnUrl) {
152
- termsUrl.searchParams.set('redirect', oauthSession.returnUrl);
154
+ // Result construction and session issuance share the caller's transaction.
155
+ try {
156
+ const result = await this.authenticateWithOAuth(provider, tokens, userInfo, loginProof);
157
+ // Check if existing user needs to see terms page
158
+ if (!result.isNewUser && explicitTerms.length > 0) {
159
+ const pendingTerms = await this.termsService.getPendingRequiredTerms(result.user.sub);
160
+ const explicitTermIds = new Set(explicitTerms.map((t) => t.id));
161
+ const shouldRedirectToTerms = pendingTerms.some((id) => explicitTermIds.has(id));
162
+ if (shouldRedirectToTerms) {
163
+ const url = new URL(requestUrl);
164
+ const termsUrl = new URL('/terms', `${url.protocol}//${url.host}`);
165
+ if (oauthSession.returnUrl) {
166
+ termsUrl.searchParams.set('redirect', oauthSession.returnUrl);
167
+ }
168
+ return {
169
+ action: 'login_terms_redirect',
170
+ userSub: result.user.sub,
171
+ userEpoch: result.user.token_epoch,
172
+ termsUrl: termsUrl.toString(),
173
+ };
153
174
  }
154
- return {
155
- action: 'login_terms_redirect',
156
- userSub: result.user.sub,
157
- termsUrl: termsUrl.toString(),
158
- };
159
175
  }
176
+ return {
177
+ action: 'login_complete',
178
+ userSub: result.user.sub,
179
+ userEpoch: result.user.token_epoch,
180
+ returnUrl: oauthSession.returnUrl,
181
+ };
160
182
  }
161
- return {
162
- action: 'login_complete',
163
- userSub: result.user.sub,
164
- returnUrl: oauthSession.returnUrl,
165
- };
166
- }
167
- catch (err) {
168
- if (err instanceof IssuaryError &&
169
- err.code === 'REGISTRATION_EMAIL_NOT_ALLOWED') {
170
- const errorUrl = new URL('/login', this.config.server.public_origin);
171
- errorUrl.searchParams.set('oauth_error', 'registration_email_not_allowed');
172
- if (oauthSession.returnUrl) {
173
- errorUrl.searchParams.set('redirect', oauthSession.returnUrl);
183
+ catch (err) {
184
+ if (err instanceof IssuaryError &&
185
+ err.code === 'REGISTRATION_EMAIL_NOT_ALLOWED') {
186
+ const errorUrl = new URL('/login', this.config.server.public_origin);
187
+ errorUrl.searchParams.set('oauth_error', 'registration_email_not_allowed');
188
+ if (oauthSession.returnUrl) {
189
+ errorUrl.searchParams.set('redirect', oauthSession.returnUrl);
190
+ }
191
+ return { action: 'error_redirect', url: errorUrl.toString() };
174
192
  }
175
- return { action: 'error_redirect', url: errorUrl.toString() };
193
+ throw err;
176
194
  }
177
- throw err;
178
- }
195
+ };
196
+ return params.completeAuthentication(loginProof, authenticate);
179
197
  }
180
198
  /**
181
199
  * Get all enabled OAuth providers
@@ -428,71 +446,110 @@ export class OAuthConnectService {
428
446
  picture: picture || undefined,
429
447
  };
430
448
  }
449
+ async prepareExistingAuthentication(providerId, providerUserId) {
450
+ const link = await this.mikro.userOAuth.findOne({
451
+ provider_name: providerId,
452
+ provider_user_id: providerUserId,
453
+ }, { populate: ['user'], strategy: 'joined', refresh: true });
454
+ if (!link)
455
+ return null;
456
+ const user = link.user.getEntity();
457
+ return {
458
+ kind: 'linked',
459
+ linkId: link.id,
460
+ providerId,
461
+ providerUserId,
462
+ userSub: user.sub,
463
+ userEpoch: user.token_epoch,
464
+ };
465
+ }
466
+ async prepareAuthentication(providerId, userInfo) {
467
+ const linked = await this.prepareExistingAuthentication(providerId, userInfo.id);
468
+ if (linked)
469
+ return linked;
470
+ const user = await this.mikro.user.findOne({ email: userInfo.email }, { refresh: true });
471
+ if (user)
472
+ return {
473
+ kind: 'auto_link',
474
+ providerId,
475
+ providerUserId: userInfo.id,
476
+ userSub: user.sub,
477
+ userEpoch: user.token_epoch,
478
+ email: userInfo.email,
479
+ };
480
+ return {
481
+ kind: 'registration',
482
+ providerId,
483
+ providerUserId: userInfo.id,
484
+ email: userInfo.email,
485
+ };
486
+ }
487
+ async assertNewIdentity(providerId, userInfo) {
488
+ if ((await this.mikro.user.findOne({ email: userInfo.email })) ||
489
+ (await this.mikro.userOAuth.findByProviderUserId(providerId, userInfo.id)))
490
+ throw new e.OAuthSessionExpired.Error();
491
+ }
431
492
  /**
432
493
  * Authenticate with OAuth - login or register user
433
494
  */
434
- async authenticateWithOAuth(providerId, tokens, userInfo) {
495
+ async authenticateWithOAuth(providerId, tokens, userInfo, preparedProof) {
435
496
  const provider = this.getProvider(providerId);
436
497
  // Reject if the OAuth provider has not verified the user's email
437
498
  if (!userInfo.email_verified) {
438
499
  throw new e.OAuthEmailNotVerified.Error();
439
500
  }
440
- // Check if OAuth account is already linked
441
- const existingOAuth = await this.mikro.userOAuth.findByProviderUserId(providerId, userInfo.id);
442
- if (existingOAuth) {
443
- // Update tokens and return existing user
444
- await this.mikro.userOAuth.updateTokens(existingOAuth, {
445
- accessToken: tokens.access_token,
446
- refreshToken: tokens.refresh_token || '',
447
- expiresAt: tokens.expires_in
448
- ? new Date(Date.now() + tokens.expires_in * 1000)
449
- : null,
450
- });
451
- // Load user entity from Ref
452
- const user = await existingOAuth.user.load({
453
- populate: ['password_hash'],
454
- });
455
- if (!user) {
456
- throw new e.UserNotFound.Error();
457
- }
458
- return {
459
- isNewUser: false,
460
- user: await this.userService.getSessionUserBySub(user.sub),
461
- };
462
- }
463
- // Check if user with same email exists in database
464
- // Config users are now synced to DB, so we only need to check the database
465
- const existingUser = await this.mikro.user.findOne({
466
- email: userInfo.email,
467
- });
468
- if (existingUser) {
469
- // Handle email conflict based on strategy
470
- if (provider.email_conflict_strategy === 'require_link') {
501
+ const proof = preparedProof ?? (await this.prepareAuthentication(providerId, userInfo));
502
+ if (proof.providerId !== providerId || proof.providerUserId !== userInfo.id)
503
+ throw new e.OAuthSessionExpired.Error();
504
+ if (proof.kind === 'linked') {
505
+ return withUserSecurity(this.mikro, proof.userSub, async (user) => {
506
+ if (user.deleted_at || user.token_epoch !== proof.userEpoch)
507
+ throw new e.OAuthSessionExpired.Error();
508
+ const link = await this.mikro.userOAuth.findOne({
509
+ id: proof.linkId,
510
+ user: proof.userSub,
511
+ provider_name: providerId,
512
+ provider_user_id: userInfo.id,
513
+ }, { refresh: true });
514
+ if (!link ||
515
+ proof.providerId !== providerId ||
516
+ proof.providerUserId !== userInfo.id)
517
+ throw new e.OAuthSessionExpired.Error();
518
+ await this.mikro.userOAuth.updateTokens(link, {
519
+ accessToken: tokens.access_token,
520
+ refreshToken: tokens.refresh_token || '',
521
+ expiresAt: tokens.expires_in
522
+ ? new Date(Date.now() + tokens.expires_in * 1000)
523
+ : null,
524
+ });
525
+ return {
526
+ isNewUser: false,
527
+ user: await this.authenticationResultUser(user),
528
+ };
529
+ }, { includeDeleted: true });
530
+ }
531
+ if (proof.email !== userInfo.email)
532
+ throw new e.OAuthSessionExpired.Error();
533
+ if (proof.kind === 'auto_link') {
534
+ if (provider.email_conflict_strategy === 'require_link')
471
535
  throw new e.OAuthEmailConflict.Error();
472
- }
473
- // auto_link strategy - link to existing user if email is verified
474
- if (!existingUser.email_verified) {
475
- // Mark email as verified since OAuth provider verified it
476
- existingUser.email_verified = true;
477
- }
478
- // Link OAuth account (only for database-managed users)
479
- // Config-managed users can still be linked since they're in DB now
480
- await this.mikro.userOAuth.linkAccount({
481
- userSub: existingUser.sub,
482
- providerName: providerId,
483
- providerUserId: userInfo.id,
484
- accessToken: tokens.access_token,
485
- refreshToken: tokens.refresh_token || '',
486
- expiresAt: tokens.expires_in
487
- ? new Date(Date.now() + tokens.expires_in * 1000)
488
- : null,
489
- });
490
- await this.mikro.em.flush();
491
- return {
492
- isNewUser: false,
493
- user: await this.userService.getSessionUserBySub(existingUser.sub),
494
- };
495
- }
536
+ return withUserSecurity(this.mikro, proof.userSub, async (fresh) => {
537
+ if (fresh.deleted_at ||
538
+ fresh.token_epoch !== proof.userEpoch ||
539
+ fresh.email !== proof.email)
540
+ throw new e.OAuthSessionExpired.Error();
541
+ if (await this.mikro.userOAuth.findByProviderUserId(providerId, userInfo.id))
542
+ throw new e.OAuthSessionExpired.Error();
543
+ fresh.email_verified = true;
544
+ await this.linkOAuthAccount(fresh.sub, providerId, tokens, userInfo);
545
+ await this.mikro.em.flush();
546
+ return {
547
+ isNewUser: false,
548
+ user: await this.authenticationResultUser(fresh),
549
+ };
550
+ }, { includeDeleted: true });
551
+ }
552
+ await this.assertNewIdentity(providerId, userInfo);
496
553
  // Check if registration is enabled and email is allowed
497
554
  if (!this.config.registration.enabled) {
498
555
  throw new e.RegistrationDisabled.Error();
@@ -527,7 +584,7 @@ export class OAuthConnectService {
527
584
  });
528
585
  return {
529
586
  isNewUser: true,
530
- user: await this.userService.getSessionUserBySub(newUser.sub),
587
+ user: await this.authenticationResultUser(newUser),
531
588
  };
532
589
  }
533
590
  /**
@@ -557,61 +614,15 @@ export class OAuthConnectService {
557
614
  if (!userInfo.email_verified) {
558
615
  throw new e.OAuthEmailNotVerified.Error();
559
616
  }
560
- // Double-check that user doesn't exist (in case of race condition)
617
+ // A registration token proves a new-account flow, never a fresh login to
618
+ // an account created or recovered since that flow began.
619
+ this.getProvider(providerId);
561
620
  const existingOAuth = await this.mikro.userOAuth.findByProviderUserId(providerId, userInfo.id);
562
- if (existingOAuth) {
563
- // User was created in the meantime, just return existing user
564
- const user = await existingOAuth.user.load({
565
- populate: ['password_hash'],
566
- });
567
- if (!user) {
568
- throw new e.UserNotFound.Error();
569
- }
570
- return {
571
- isNewUser: false,
572
- user: await this.userService.getSessionUserBySub(user.sub),
573
- };
574
- }
575
- // Check if user with same email exists
576
621
  const existingUser = await this.mikro.user.findOne({
577
622
  email: userInfo.email,
578
623
  });
579
- if (existingUser) {
580
- // Handle as auto_link - link OAuth to existing user
581
- const provider = this.getProvider(providerId);
582
- if (provider.email_conflict_strategy === 'require_link') {
583
- throw new e.OAuthEmailConflict.Error();
584
- }
585
- if (!existingUser.email_verified) {
586
- existingUser.email_verified = true;
587
- }
588
- await this.mikro.userOAuth.linkAccount({
589
- userSub: existingUser.sub,
590
- providerName: providerId,
591
- providerUserId: userInfo.id,
592
- accessToken: tokens.access_token,
593
- refreshToken: tokens.refresh_token || '',
594
- expiresAt: tokens.expires_in
595
- ? new Date(Date.now() + tokens.expires_in * 1000)
596
- : null,
597
- });
598
- // Record all consents for existing user (load terms once)
599
- const terms = await this.termsService.getGlobalTerms();
600
- await this.termsService.recordConsents({
601
- userSub: existingUser.sub,
602
- consents,
603
- terms,
604
- });
605
- await this.termsService.recordImplicitConsents({
606
- userSub: existingUser.sub,
607
- terms,
608
- });
609
- await this.mikro.em.flush();
610
- return {
611
- isNewUser: false,
612
- user: await this.userService.getSessionUserBySub(existingUser.sub),
613
- };
614
- }
624
+ if (existingOAuth || existingUser)
625
+ throw new e.OAuthSessionExpired.Error();
615
626
  // Check if registration is enabled and email is allowed
616
627
  if (!this.config.registration.enabled) {
617
628
  throw new e.RegistrationDisabled.Error();
@@ -650,69 +661,82 @@ export class OAuthConnectService {
650
661
  userSub: newUser.sub,
651
662
  terms,
652
663
  });
664
+ await this.mikro.pendingOAuthRegistration.invalidateIdentity(providerId, userInfo.id, userInfo.email);
653
665
  return {
654
666
  isNewUser: true,
655
- user: await this.userService.getSessionUserBySub(newUser.sub),
667
+ user: await this.authenticationResultUser(newUser),
656
668
  };
657
669
  }
658
670
  /**
659
671
  * Link OAuth account to existing user
660
672
  */
673
+ async authenticationResultUser(user) {
674
+ const token_epoch = user.token_epoch;
675
+ return {
676
+ ...(await this.userService.getSessionUserBySub(user.sub)),
677
+ token_epoch,
678
+ };
679
+ }
661
680
  async linkOAuthAccount(userSub, providerId, tokens, userInfo) {
662
- // Check if OAuth account is already linked to another user
663
- const existingOAuth = await this.mikro.userOAuth.findByProviderUserId(providerId, userInfo.id);
664
- if (existingOAuth && existingOAuth.user.sub !== userSub) {
665
- throw new e.OAuthAccountAlreadyLinked.Error();
666
- }
667
- // Get user
668
- const user = await this.mikro.user.findOneOrFail({ sub: userSub }, { failHandler: () => new e.UserNotFound.Error() });
669
- // Check if already linked
670
- const existingLink = await this.mikro.userOAuth.findByUserAndProvider(userSub, providerId);
671
- if (existingLink) {
672
- // Update tokens
673
- await this.mikro.userOAuth.updateTokens(existingLink, {
681
+ return withUserSecurity(this.mikro, userSub, async () => {
682
+ // Check if OAuth account is already linked to another user
683
+ const existingOAuth = await this.mikro.userOAuth.findByProviderUserId(providerId, userInfo.id);
684
+ if (existingOAuth && existingOAuth.user.sub !== userSub) {
685
+ throw new e.OAuthAccountAlreadyLinked.Error();
686
+ }
687
+ // Get user
688
+ const user = await this.mikro.user.findOneOrFail({ sub: userSub }, { failHandler: () => new e.UserNotFound.Error() });
689
+ // Check if already linked
690
+ const existingLink = await this.mikro.userOAuth.findByUserAndProvider(userSub, providerId);
691
+ if (existingLink) {
692
+ // Update tokens
693
+ await this.mikro.userOAuth.updateTokens(existingLink, {
694
+ accessToken: tokens.access_token,
695
+ refreshToken: tokens.refresh_token || '',
696
+ expiresAt: tokens.expires_in
697
+ ? new Date(Date.now() + tokens.expires_in * 1000)
698
+ : null,
699
+ });
700
+ return;
701
+ }
702
+ // Link OAuth account
703
+ await this.mikro.userOAuth.linkAccount({
704
+ userSub: user.sub,
705
+ providerName: providerId,
706
+ providerUserId: userInfo.id,
674
707
  accessToken: tokens.access_token,
675
708
  refreshToken: tokens.refresh_token || '',
676
709
  expiresAt: tokens.expires_in
677
710
  ? new Date(Date.now() + tokens.expires_in * 1000)
678
711
  : null,
679
712
  });
680
- return;
681
- }
682
- // Link OAuth account
683
- await this.mikro.userOAuth.linkAccount({
684
- userSub: user.sub,
685
- providerName: providerId,
686
- providerUserId: userInfo.id,
687
- accessToken: tokens.access_token,
688
- refreshToken: tokens.refresh_token || '',
689
- expiresAt: tokens.expires_in
690
- ? new Date(Date.now() + tokens.expires_in * 1000)
691
- : null,
692
713
  });
693
714
  }
694
715
  /**
695
716
  * Unlink OAuth account from user
696
717
  */
697
718
  async unlinkOAuthAccount(userSub, providerId) {
698
- // Get user from database (config users are now synced to DB)
699
- const user = await this.mikro.user.findOneOrFail({ sub: userSub }, {
700
- failHandler: () => new e.UserNotFound.Error(),
701
- populate: ['password_hash'],
719
+ return withUserSecurity(this.mikro, userSub, async () => {
720
+ // Get user from database (config users are now synced to DB)
721
+ const user = await this.mikro.user.findOneOrFail({ sub: userSub }, {
722
+ failHandler: () => new e.UserNotFound.Error(),
723
+ populate: ['password_hash'],
724
+ });
725
+ // Check if OAuth account is linked
726
+ const oauthAccount = await this.mikro.userOAuth.findByUserAndProvider(userSub, providerId);
727
+ if (!oauthAccount) {
728
+ throw new e.OAuthAccountNotLinked.Error();
729
+ }
730
+ // Check if this is the last auth method
731
+ const methods = await authenticationMethods(this.mikro, this.config, user);
732
+ const enabled = this.config.identity_providers.some((provider) => provider.id === providerId && provider.enabled);
733
+ if (methods.oauth - Number(enabled) <= 0 &&
734
+ !methods.password &&
735
+ methods.passkeys === 0) {
736
+ throw new e.CannotUnlinkLastAuthMethod.Error();
737
+ }
738
+ await this.mikro.userOAuth.unlinkAccount(userSub, providerId);
702
739
  });
703
- // Check if OAuth account is linked
704
- const oauthAccount = await this.mikro.userOAuth.findByUserAndProvider(userSub, providerId);
705
- if (!oauthAccount) {
706
- throw new e.OAuthAccountNotLinked.Error();
707
- }
708
- // Check if this is the last auth method
709
- const oauthCount = await this.mikro.userOAuth.countByUser(userSub);
710
- const hasPassword = user.hasPassword();
711
- // If only one OAuth and no password, can't unlink
712
- if (oauthCount <= 1 && !hasPassword) {
713
- throw new e.CannotUnlinkLastAuthMethod.Error();
714
- }
715
- await this.mikro.userOAuth.unlinkAccount(userSub, providerId);
716
740
  }
717
741
  /**
718
742
  * Get all OAuth accounts linked to a user