@tinyrack/issuary-server 0.30.0 → 0.32.1

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 (478) 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 +438 -424
  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/client.js +1 -1
  77. package/dist/lib/config/client.js.map +1 -1
  78. package/dist/lib/config/email.d.ts +1 -0
  79. package/dist/lib/config/email.d.ts.map +1 -1
  80. package/dist/lib/config/email.js.map +1 -1
  81. package/dist/lib/database/entities.d.ts.map +1 -1
  82. package/dist/lib/database/entities.js +6 -0
  83. package/dist/lib/database/entities.js.map +1 -1
  84. package/dist/lib/openapi.d.ts +2 -0
  85. package/dist/lib/openapi.d.ts.map +1 -1
  86. package/dist/lib/openapi.js +36 -1
  87. package/dist/lib/openapi.js.map +1 -1
  88. package/dist/lib/request-ip.d.ts +4 -0
  89. package/dist/lib/request-ip.d.ts.map +1 -0
  90. package/dist/lib/request-ip.js +39 -0
  91. package/dist/lib/request-ip.js.map +1 -0
  92. package/dist/middleware/auth-budget.d.ts +3 -0
  93. package/dist/middleware/auth-budget.d.ts.map +1 -0
  94. package/dist/middleware/auth-budget.js +80 -0
  95. package/dist/middleware/auth-budget.js.map +1 -0
  96. package/dist/middleware/session.d.ts +23 -5
  97. package/dist/middleware/session.d.ts.map +1 -1
  98. package/dist/middleware/session.js +152 -12
  99. package/dist/middleware/session.js.map +1 -1
  100. package/dist/middleware/strict-input.d.ts +4 -0
  101. package/dist/middleware/strict-input.d.ts.map +1 -0
  102. package/dist/middleware/strict-input.js +144 -0
  103. package/dist/middleware/strict-input.js.map +1 -0
  104. package/dist/migrations/postgres/Migration20260910120000_api_security.d.ts +6 -0
  105. package/dist/migrations/postgres/Migration20260910120000_api_security.d.ts.map +1 -0
  106. package/dist/migrations/postgres/Migration20260910120000_api_security.js +20 -0
  107. package/dist/migrations/postgres/Migration20260910120000_api_security.js.map +1 -0
  108. package/dist/migrations/postgres/Migration20260910160000_security_followup.d.ts +6 -0
  109. package/dist/migrations/postgres/Migration20260910160000_security_followup.d.ts.map +1 -0
  110. package/dist/migrations/postgres/Migration20260910160000_security_followup.js +23 -0
  111. package/dist/migrations/postgres/Migration20260910160000_security_followup.js.map +1 -0
  112. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.d.ts +6 -0
  113. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.d.ts.map +1 -0
  114. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.js +28 -0
  115. package/dist/migrations/postgres/Migration20260910200000_authentication_epoch.js.map +1 -0
  116. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.d.ts +6 -0
  117. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.d.ts.map +1 -0
  118. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.js +19 -0
  119. package/dist/migrations/postgres/Migration20260910220000_flow_revocation.js.map +1 -0
  120. package/dist/migrations/postgres/index.d.ts.map +1 -1
  121. package/dist/migrations/postgres/index.js +8 -0
  122. package/dist/migrations/postgres/index.js.map +1 -1
  123. package/dist/migrations/sqlite/Migration20260910120000_api_security.d.ts +6 -0
  124. package/dist/migrations/sqlite/Migration20260910120000_api_security.d.ts.map +1 -0
  125. package/dist/migrations/sqlite/Migration20260910120000_api_security.js +20 -0
  126. package/dist/migrations/sqlite/Migration20260910120000_api_security.js.map +1 -0
  127. package/dist/migrations/sqlite/Migration20260910160000_security_followup.d.ts +6 -0
  128. package/dist/migrations/sqlite/Migration20260910160000_security_followup.d.ts.map +1 -0
  129. package/dist/migrations/sqlite/Migration20260910160000_security_followup.js +23 -0
  130. package/dist/migrations/sqlite/Migration20260910160000_security_followup.js.map +1 -0
  131. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.d.ts +8 -0
  132. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.d.ts.map +1 -0
  133. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.js +72 -0
  134. package/dist/migrations/sqlite/Migration20260910200000_authentication_epoch.js.map +1 -0
  135. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.d.ts +6 -0
  136. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.d.ts.map +1 -0
  137. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.js +19 -0
  138. package/dist/migrations/sqlite/Migration20260910220000_flow_revocation.js.map +1 -0
  139. package/dist/migrations/sqlite/index.d.ts.map +1 -1
  140. package/dist/migrations/sqlite/index.js +8 -0
  141. package/dist/migrations/sqlite/index.js.map +1 -1
  142. package/dist/repositories/email-verification.repository.d.ts +1 -0
  143. package/dist/repositories/email-verification.repository.d.ts.map +1 -1
  144. package/dist/repositories/email-verification.repository.js +8 -17
  145. package/dist/repositories/email-verification.repository.js.map +1 -1
  146. package/dist/repositories/oauth-code.repository.d.ts +2 -0
  147. package/dist/repositories/oauth-code.repository.d.ts.map +1 -1
  148. package/dist/repositories/oauth-code.repository.js +8 -0
  149. package/dist/repositories/oauth-code.repository.js.map +1 -1
  150. package/dist/repositories/oauth-device-code.repository.d.ts +3 -0
  151. package/dist/repositories/oauth-device-code.repository.d.ts.map +1 -1
  152. package/dist/repositories/oauth-device-code.repository.js +64 -29
  153. package/dist/repositories/oauth-device-code.repository.js.map +1 -1
  154. package/dist/repositories/password-reset.repository.d.ts +1 -0
  155. package/dist/repositories/password-reset.repository.d.ts.map +1 -1
  156. package/dist/repositories/password-reset.repository.js +6 -19
  157. package/dist/repositories/password-reset.repository.js.map +1 -1
  158. package/dist/repositories/pending-oauth-registration.repository.d.ts +2 -0
  159. package/dist/repositories/pending-oauth-registration.repository.d.ts.map +1 -1
  160. package/dist/repositories/pending-oauth-registration.repository.js +16 -2
  161. package/dist/repositories/pending-oauth-registration.repository.js.map +1 -1
  162. package/dist/repositories/terms.repository.d.ts.map +1 -1
  163. package/dist/repositories/terms.repository.js +1 -0
  164. package/dist/repositories/terms.repository.js.map +1 -1
  165. package/dist/repositories/user-consent.repository.d.ts.map +1 -1
  166. package/dist/repositories/user-consent.repository.js +9 -3
  167. package/dist/repositories/user-consent.repository.js.map +1 -1
  168. package/dist/repositories/user-terms-consent.repository.d.ts.map +1 -1
  169. package/dist/repositories/user-terms-consent.repository.js +2 -0
  170. package/dist/repositories/user-terms-consent.repository.js.map +1 -1
  171. package/dist/repositories/user-totp-recovery-code.repository.d.ts +24 -0
  172. package/dist/repositories/user-totp-recovery-code.repository.d.ts.map +1 -1
  173. package/dist/repositories/user-totp.repository.d.ts +36 -0
  174. package/dist/repositories/user-totp.repository.d.ts.map +1 -1
  175. package/dist/routes/.well-known/openid-configuration/get.d.ts.map +1 -1
  176. package/dist/routes/.well-known/openid-configuration/get.js +5 -1
  177. package/dist/routes/.well-known/openid-configuration/get.js.map +1 -1
  178. package/dist/routes/api/admin/console/index.d.ts.map +1 -1
  179. package/dist/routes/api/admin/console/index.js +29 -14
  180. package/dist/routes/api/admin/console/index.js.map +1 -1
  181. package/dist/routes/api/admin/me/get.js +2 -3
  182. package/dist/routes/api/admin/me/get.js.map +1 -1
  183. package/dist/routes/api/admin/users/index.d.ts.map +1 -1
  184. package/dist/routes/api/admin/users/index.js +59 -52
  185. package/dist/routes/api/admin/users/index.js.map +1 -1
  186. package/dist/routes/api/auth/accounts/select.post.d.ts +6 -6
  187. package/dist/routes/api/auth/email/resend/post.d.ts.map +1 -1
  188. package/dist/routes/api/auth/email/resend/post.js +3 -24
  189. package/dist/routes/api/auth/email/resend/post.js.map +1 -1
  190. package/dist/routes/api/auth/email/verify/post.d.ts.map +1 -1
  191. package/dist/routes/api/auth/email/verify/post.js +29 -12
  192. package/dist/routes/api/auth/email/verify/post.js.map +1 -1
  193. package/dist/routes/api/auth/login/post.d.ts.map +1 -1
  194. package/dist/routes/api/auth/login/post.js +4 -3
  195. package/dist/routes/api/auth/login/post.js.map +1 -1
  196. package/dist/routes/api/auth/passkey/verify/post.d.ts.map +1 -1
  197. package/dist/routes/api/auth/passkey/verify/post.js +25 -11
  198. package/dist/routes/api/auth/passkey/verify/post.js.map +1 -1
  199. package/dist/routes/api/auth/password/forgot/post.d.ts.map +1 -1
  200. package/dist/routes/api/auth/password/forgot/post.js +2 -19
  201. package/dist/routes/api/auth/password/forgot/post.js.map +1 -1
  202. package/dist/routes/api/auth/register/post.d.ts.map +1 -1
  203. package/dist/routes/api/auth/register/post.js +4 -3
  204. package/dist/routes/api/auth/register/post.js.map +1 -1
  205. package/dist/routes/api/auth/totp/recovery/verify/post.d.ts.map +1 -1
  206. package/dist/routes/api/auth/totp/recovery/verify/post.js +7 -5
  207. package/dist/routes/api/auth/totp/recovery/verify/post.js.map +1 -1
  208. package/dist/routes/api/auth/totp/verify/post.d.ts.map +1 -1
  209. package/dist/routes/api/auth/totp/verify/post.js +7 -5
  210. package/dist/routes/api/auth/totp/verify/post.js.map +1 -1
  211. package/dist/routes/api/consent/get.d.ts +3 -0
  212. package/dist/routes/api/consent/get.d.ts.map +1 -1
  213. package/dist/routes/api/consent/get.js +26 -4
  214. package/dist/routes/api/consent/get.js.map +1 -1
  215. package/dist/routes/api/consent/index.d.ts +3 -0
  216. package/dist/routes/api/consent/index.d.ts.map +1 -1
  217. package/dist/routes/api/consent/post.d.ts.map +1 -1
  218. package/dist/routes/api/consent/post.js +142 -114
  219. package/dist/routes/api/consent/post.js.map +1 -1
  220. package/dist/routes/api/docs/get.d.ts.map +1 -1
  221. package/dist/routes/api/docs/get.js +8 -1
  222. package/dist/routes/api/docs/get.js.map +1 -1
  223. package/dist/routes/api/index.d.ts +382 -379
  224. package/dist/routes/api/index.d.ts.map +1 -1
  225. package/dist/routes/api/oauth/_provider/authorize/get.d.ts.map +1 -1
  226. package/dist/routes/api/oauth/_provider/authorize/get.js +5 -1
  227. package/dist/routes/api/oauth/_provider/authorize/get.js.map +1 -1
  228. package/dist/routes/api/oauth/_provider/callback/get.d.ts.map +1 -1
  229. package/dist/routes/api/oauth/_provider/callback/get.js +30 -6
  230. package/dist/routes/api/oauth/_provider/callback/get.js.map +1 -1
  231. package/dist/routes/api/oauth/_provider/callback/post.d.ts +9 -9
  232. package/dist/routes/api/oauth/_provider/callback/post.d.ts.map +1 -1
  233. package/dist/routes/api/oauth/_provider/callback/post.js +31 -31
  234. package/dist/routes/api/oauth/_provider/callback/post.js.map +1 -1
  235. package/dist/routes/api/oauth/_provider/delete.d.ts.map +1 -1
  236. package/dist/routes/api/oauth/_provider/delete.js +14 -11
  237. package/dist/routes/api/oauth/_provider/delete.js.map +1 -1
  238. package/dist/routes/api/terms/consent/post.d.ts.map +1 -1
  239. package/dist/routes/api/terms/consent/post.js +59 -43
  240. package/dist/routes/api/terms/consent/post.js.map +1 -1
  241. package/dist/routes/api/user/delete.d.ts.map +1 -1
  242. package/dist/routes/api/user/delete.js +8 -8
  243. package/dist/routes/api/user/delete.js.map +1 -1
  244. package/dist/routes/api/user/passkeys/_id/delete.d.ts.map +1 -1
  245. package/dist/routes/api/user/passkeys/_id/delete.js +17 -29
  246. package/dist/routes/api/user/passkeys/_id/delete.js.map +1 -1
  247. package/dist/routes/api/user/passkeys/_id/patch.d.ts.map +1 -1
  248. package/dist/routes/api/user/passkeys/_id/patch.js +16 -13
  249. package/dist/routes/api/user/passkeys/_id/patch.js.map +1 -1
  250. package/dist/routes/api/user/passkeys/register/verify/post.d.ts.map +1 -1
  251. package/dist/routes/api/user/passkeys/register/verify/post.js +27 -19
  252. package/dist/routes/api/user/passkeys/register/verify/post.js.map +1 -1
  253. package/dist/routes/api/user/password/delete.d.ts.map +1 -1
  254. package/dist/routes/api/user/password/delete.js +13 -10
  255. package/dist/routes/api/user/password/delete.js.map +1 -1
  256. package/dist/routes/api/user/password/post.d.ts.map +1 -1
  257. package/dist/routes/api/user/password/post.js +13 -10
  258. package/dist/routes/api/user/password/post.js.map +1 -1
  259. package/dist/routes/api/user/password/put.d.ts.map +1 -1
  260. package/dist/routes/api/user/password/put.js +13 -10
  261. package/dist/routes/api/user/password/put.js.map +1 -1
  262. package/dist/routes/api/user/totp/confirm/post.d.ts.map +1 -1
  263. package/dist/routes/api/user/totp/confirm/post.js +33 -18
  264. package/dist/routes/api/user/totp/confirm/post.js.map +1 -1
  265. package/dist/routes/api/user/totp/delete.d.ts.map +1 -1
  266. package/dist/routes/api/user/totp/delete.js +13 -18
  267. package/dist/routes/api/user/totp/delete.js.map +1 -1
  268. package/dist/routes/api/user/totp/recovery/regenerate/post.d.ts.map +1 -1
  269. package/dist/routes/api/user/totp/recovery/regenerate/post.js +13 -10
  270. package/dist/routes/api/user/totp/recovery/regenerate/post.js.map +1 -1
  271. package/dist/routes/api/user/totp/setup/post.d.ts.map +1 -1
  272. package/dist/routes/api/user/totp/setup/post.js +13 -8
  273. package/dist/routes/api/user/totp/setup/post.js.map +1 -1
  274. package/dist/routes/api/user/totp/verify/post.d.ts.map +1 -1
  275. package/dist/routes/api/user/totp/verify/post.js +23 -12
  276. package/dist/routes/api/user/totp/verify/post.js.map +1 -1
  277. package/dist/routes/index.d.ts +243 -230
  278. package/dist/routes/index.d.ts.map +1 -1
  279. package/dist/routes/oauth/authorize/get.d.ts.map +1 -1
  280. package/dist/routes/oauth/authorize/get.js +12 -4
  281. package/dist/routes/oauth/authorize/get.js.map +1 -1
  282. package/dist/routes/oauth/device/get-post.d.ts +10 -0
  283. package/dist/routes/oauth/device/get-post.d.ts.map +1 -1
  284. package/dist/routes/oauth/device/get-post.js +32 -3
  285. package/dist/routes/oauth/device/get-post.js.map +1 -1
  286. package/dist/routes/oauth/device-authorization/post.d.ts.map +1 -1
  287. package/dist/routes/oauth/device-authorization/post.js +13 -7
  288. package/dist/routes/oauth/device-authorization/post.js.map +1 -1
  289. package/dist/routes/oauth/end-session/get.d.ts +9 -9
  290. package/dist/routes/oauth/end-session/get.d.ts.map +1 -1
  291. package/dist/routes/oauth/end-session/get.js +2 -3
  292. package/dist/routes/oauth/end-session/get.js.map +1 -1
  293. package/dist/routes/oauth/index.d.ts +19 -9
  294. package/dist/routes/oauth/index.d.ts.map +1 -1
  295. package/dist/routes/oauth/revoke/post.d.ts.map +1 -1
  296. package/dist/routes/oauth/revoke/post.js +10 -2
  297. package/dist/routes/oauth/revoke/post.js.map +1 -1
  298. package/dist/routes/oauth/token/post.d.ts.map +1 -1
  299. package/dist/routes/oauth/token/post.js +69 -57
  300. package/dist/routes/oauth/token/post.js.map +1 -1
  301. package/dist/schemas/error.d.ts +75 -0
  302. package/dist/schemas/error.d.ts.map +1 -1
  303. package/dist/schemas/error.js +3 -0
  304. package/dist/schemas/error.js.map +1 -1
  305. package/dist/schemas/field.d.ts.map +1 -1
  306. package/dist/schemas/field.js +3 -0
  307. package/dist/schemas/field.js.map +1 -1
  308. package/dist/seeders/config.seeder.d.ts +1 -1
  309. package/dist/seeders/config.seeder.d.ts.map +1 -1
  310. package/dist/seeders/config.seeder.js +100 -36
  311. package/dist/seeders/config.seeder.js.map +1 -1
  312. package/dist/services/admin-console.service.d.ts.map +1 -1
  313. package/dist/services/admin-console.service.js +137 -100
  314. package/dist/services/admin-console.service.js.map +1 -1
  315. package/dist/services/auth-budget.service.d.ts +6 -0
  316. package/dist/services/auth-budget.service.d.ts.map +1 -0
  317. package/dist/services/auth-budget.service.js +30 -0
  318. package/dist/services/auth-budget.service.js.map +1 -0
  319. package/dist/services/authentication-epoch.d.ts +5 -0
  320. package/dist/services/authentication-epoch.d.ts.map +1 -0
  321. package/dist/services/authentication-epoch.js +22 -0
  322. package/dist/services/authentication-epoch.js.map +1 -0
  323. package/dist/services/authorization-browser.service.d.ts +18 -0
  324. package/dist/services/authorization-browser.service.d.ts.map +1 -0
  325. package/dist/services/authorization-browser.service.js +28 -0
  326. package/dist/services/authorization-browser.service.js.map +1 -0
  327. package/dist/services/browser-security.service.d.ts +14 -0
  328. package/dist/services/browser-security.service.d.ts.map +1 -0
  329. package/dist/services/browser-security.service.js +85 -0
  330. package/dist/services/browser-security.service.js.map +1 -0
  331. package/dist/services/browser-session.service.d.ts +25 -0
  332. package/dist/services/browser-session.service.d.ts.map +1 -0
  333. package/dist/services/browser-session.service.js +102 -0
  334. package/dist/services/browser-session.service.js.map +1 -0
  335. package/dist/services/cleanup.service.d.ts +1 -0
  336. package/dist/services/cleanup.service.d.ts.map +1 -1
  337. package/dist/services/cleanup.service.js +31 -0
  338. package/dist/services/cleanup.service.js.map +1 -1
  339. package/dist/services/client-security.d.ts +641 -0
  340. package/dist/services/client-security.d.ts.map +1 -0
  341. package/dist/services/client-security.js +10 -0
  342. package/dist/services/client-security.js.map +1 -0
  343. package/dist/services/container.d.ts +2 -0
  344. package/dist/services/container.d.ts.map +1 -1
  345. package/dist/services/container.js +7 -3
  346. package/dist/services/container.js.map +1 -1
  347. package/dist/services/email.service.d.ts +2 -14
  348. package/dist/services/email.service.d.ts.map +1 -1
  349. package/dist/services/email.service.js +35 -29
  350. package/dist/services/email.service.js.map +1 -1
  351. package/dist/services/jwt.service.d.ts +1 -0
  352. package/dist/services/jwt.service.d.ts.map +1 -1
  353. package/dist/services/jwt.service.js +13 -1
  354. package/dist/services/jwt.service.js.map +1 -1
  355. package/dist/services/mail-queue.service.d.ts +24 -0
  356. package/dist/services/mail-queue.service.d.ts.map +1 -0
  357. package/dist/services/mail-queue.service.js +223 -0
  358. package/dist/services/mail-queue.service.js.map +1 -0
  359. package/dist/services/mikro.service.d.ts.map +1 -1
  360. package/dist/services/mikro.service.js +2 -0
  361. package/dist/services/mikro.service.js.map +1 -1
  362. package/dist/services/oauth-authorize.service.d.ts +6 -1
  363. package/dist/services/oauth-authorize.service.d.ts.map +1 -1
  364. package/dist/services/oauth-authorize.service.js +136 -77
  365. package/dist/services/oauth-authorize.service.js.map +1 -1
  366. package/dist/services/oauth-browser-security.service.d.ts +7 -0
  367. package/dist/services/oauth-browser-security.service.d.ts.map +1 -0
  368. package/dist/services/oauth-browser-security.service.js +67 -0
  369. package/dist/services/oauth-browser-security.service.js.map +1 -0
  370. package/dist/services/oauth-client.service.d.ts +24 -1
  371. package/dist/services/oauth-client.service.d.ts.map +1 -1
  372. package/dist/services/oauth-client.service.js +52 -13
  373. package/dist/services/oauth-client.service.js.map +1 -1
  374. package/dist/services/oauth-connect.service.d.ts +37 -3
  375. package/dist/services/oauth-connect.service.d.ts.map +1 -1
  376. package/dist/services/oauth-connect.service.js +236 -212
  377. package/dist/services/oauth-connect.service.js.map +1 -1
  378. package/dist/services/oauth-token.service.d.ts +6 -4
  379. package/dist/services/oauth-token.service.d.ts.map +1 -1
  380. package/dist/services/oauth-token.service.js +295 -209
  381. package/dist/services/oauth-token.service.js.map +1 -1
  382. package/dist/services/passkey.service.d.ts +26 -7
  383. package/dist/services/passkey.service.d.ts.map +1 -1
  384. package/dist/services/passkey.service.js +71 -42
  385. package/dist/services/passkey.service.js.map +1 -1
  386. package/dist/services/password-auth.service.d.ts +5 -1
  387. package/dist/services/password-auth.service.d.ts.map +1 -1
  388. package/dist/services/password-auth.service.js +79 -52
  389. package/dist/services/password-auth.service.js.map +1 -1
  390. package/dist/services/password-reset.service.d.ts.map +1 -1
  391. package/dist/services/password-reset.service.js +34 -15
  392. package/dist/services/password-reset.service.js.map +1 -1
  393. package/dist/services/security.service.d.ts +3 -1
  394. package/dist/services/security.service.d.ts.map +1 -1
  395. package/dist/services/security.service.js +11 -2
  396. package/dist/services/security.service.js.map +1 -1
  397. package/dist/services/terms-policy.service.d.ts +7 -0
  398. package/dist/services/terms-policy.service.d.ts.map +1 -0
  399. package/dist/services/terms-policy.service.js +26 -0
  400. package/dist/services/terms-policy.service.js.map +1 -0
  401. package/dist/services/terms.service.d.ts.map +1 -1
  402. package/dist/services/terms.service.js +3 -2
  403. package/dist/services/terms.service.js.map +1 -1
  404. package/dist/services/totp.service.d.ts +4 -5
  405. package/dist/services/totp.service.d.ts.map +1 -1
  406. package/dist/services/totp.service.js +116 -86
  407. package/dist/services/totp.service.js.map +1 -1
  408. package/dist/services/user-consent.service.d.ts +12 -0
  409. package/dist/services/user-consent.service.d.ts.map +1 -1
  410. package/dist/services/user-consent.service.js +19 -1
  411. package/dist/services/user-consent.service.js.map +1 -1
  412. package/dist/services/user-security.service.d.ts +15 -0
  413. package/dist/services/user-security.service.d.ts.map +1 -0
  414. package/dist/services/user-security.service.js +57 -0
  415. package/dist/services/user-security.service.js.map +1 -0
  416. package/dist/services/user.service.d.ts +6 -4
  417. package/dist/services/user.service.d.ts.map +1 -1
  418. package/dist/services/user.service.js +143 -121
  419. package/dist/services/user.service.js.map +1 -1
  420. package/frontend/client/assets/{auth-layout-B4h13HhN.js → auth-layout-sea6nww4.js} +1 -1
  421. package/frontend/client/assets/{auth-page-header-BVY4zRzF.js → auth-page-header-CV85AJLW.js} +1 -1
  422. package/frontend/client/assets/document-navigation-FZbb5ldf.js +1 -0
  423. package/frontend/client/assets/i18n-vfKY-byx.js +1 -0
  424. package/frontend/client/assets/language-selector-Jso2cQNI.js +1 -0
  425. package/frontend/client/assets/manifest-90703545.js +1 -0
  426. package/frontend/client/assets/oauth-search-DebNssaC.js +1 -0
  427. package/frontend/client/assets/{passkey-CWcdr7Vy.js → passkey-CJhT-7yO.js} +1 -1
  428. package/frontend/client/assets/{promise-EbDcmV0j.js → promise-BP9iSIs-.js} +1 -1
  429. package/frontend/client/assets/{root-C2SlHCkE.js → root-BkavuloG.js} +1 -1
  430. package/frontend/client/assets/{route-3gQ9Nsqa.js → route-B0udr9eT.js} +1 -1
  431. package/frontend/client/assets/route-B3Uv9aTa.js +1 -0
  432. package/frontend/client/assets/{route-DSD60oyb.js → route-B6hIfgxz.js} +1 -1
  433. package/frontend/client/assets/{route-DO38I3ML.js → route-BEWds2g-.js} +1 -1
  434. package/frontend/client/assets/route-Bm9LsorH.js +1 -0
  435. package/frontend/client/assets/route-Btv5Y69z.js +1 -0
  436. package/frontend/client/assets/route-BvyDzziE.js +1 -0
  437. package/frontend/client/assets/route-C6kG9u7j.js +1 -0
  438. package/frontend/client/assets/route-C7C3Z0vI.js +1 -0
  439. package/frontend/client/assets/{route-DQpLeFuG.js → route-CVyefM3x.js} +1 -1
  440. package/frontend/client/assets/{route-CkpB3-_U.js → route-CXth2Wqz.js} +1 -1
  441. package/frontend/client/assets/{route-9FLiqn21.js → route-Clxc1C2d.js} +1 -1
  442. package/frontend/client/assets/route-D0IqOurp.js +1 -0
  443. package/frontend/client/assets/route-DHmYUnJR.js +1 -0
  444. package/frontend/client/assets/route-DTjZQ_Fl.js +1 -0
  445. package/frontend/client/assets/route-D_dTeu1b.js +1 -0
  446. package/frontend/client/assets/{route-CrswLAAa.js → route-Dj8gVemC.js} +1 -1
  447. package/frontend/client/assets/{route-BUM2hTU8.js → route-DnhPlHOi.js} +1 -1
  448. package/frontend/client/assets/route-FY92YQhF.js +1 -0
  449. package/frontend/client/assets/{route-DH6kQz8L.js → route-PcLpZHbT.js} +1 -1
  450. package/frontend/client/assets/{route-bKG0wKRh.js → route-qpE7i22X.js} +1 -1
  451. package/frontend/client/assets/{route-3IIrVTOK.js → route-wK89gmoS.js} +1 -1
  452. package/frontend/client/assets/security-error-message-CMsTsiWf.js +1 -0
  453. package/frontend/client/assets/{shell-header-bar-Bg85QIAq.js → shell-header-bar-DRx10tE5.js} +1 -1
  454. package/frontend/client/assets/{terms-DV0oDTRf.js → terms-Bu9Itxxs.js} +1 -1
  455. package/frontend/client/assets/{use-totp-setup-D7XZ6Nos.js → use-totp-setup-CoOXWOcT.js} +1 -1
  456. package/frontend/server/assets/{hydration-Cvbp7-6s.js → hydration-DWMjpNav.js} +1 -1
  457. package/frontend/server/assets/{root-loader.server-DaTcVcNU.js → root-loader.server-B4OqoLGG.js} +3 -3
  458. package/frontend/server/assets/{route-runtime.server-CEa264cW.js → route-runtime.server-03_pjVDT.js} +1 -1
  459. package/frontend/server/assets/{session-QqvIIQ6R.js → session-sJkmzJie.js} +51 -57
  460. package/frontend/server/index.js +337 -403
  461. package/package.json +1 -1
  462. package/frontend/client/assets/authorization-context-banner-az4f8FCY.js +0 -1
  463. package/frontend/client/assets/document-navigation-Cnp3Alf0.js +0 -1
  464. package/frontend/client/assets/i18n-CMepi_uj.js +0 -1
  465. package/frontend/client/assets/language-selector-Chn-k7Pw.js +0 -1
  466. package/frontend/client/assets/manifest-8c4a603f.js +0 -1
  467. package/frontend/client/assets/oauth-search-DybZ6VWp.js +0 -1
  468. package/frontend/client/assets/route-BFU8OK2a.js +0 -1
  469. package/frontend/client/assets/route-BKI6LDOW.js +0 -1
  470. package/frontend/client/assets/route-CusK9Y2K.js +0 -1
  471. package/frontend/client/assets/route-DEVG2czp.js +0 -1
  472. package/frontend/client/assets/route-DjCJn53Y.js +0 -1
  473. package/frontend/client/assets/route-E-_SSP-x.js +0 -1
  474. package/frontend/client/assets/route-J9z_OG4L.js +0 -1
  475. package/frontend/client/assets/route-LjqDZz5f.js +0 -1
  476. package/frontend/client/assets/route-N586tYcn.js +0 -1
  477. package/frontend/client/assets/route-nHjY3_n0.js +0 -1
  478. package/frontend/client/assets/route-sHm4cfOd.js +0 -1
@@ -1,6 +1,10 @@
1
+ import { raw } from '@mikro-orm/core';
2
+ import { OAuthGrantEntitySchema } from '../entities/oauth-grant.entity.js';
1
3
  import { stringToBytes, toArrayBuffer, toBase64Url } from '../lib/base64url.js';
2
4
  import { validatePKCE } from '../lib/pkce.js';
5
+ import { IssuaryError } from '../schemas/error.js';
3
6
  import { e } from '../schemas/error.js';
7
+ import { lockOAuthClient } from './client-security.js';
4
8
  /**
5
9
  * OAuth Token Service
6
10
  *
@@ -17,7 +21,6 @@ export class OAuthTokenService {
17
21
  oauthClientService;
18
22
  jwtService;
19
23
  securityService;
20
- refreshRotationLocks = new Map();
21
24
  constructor(config, mikro, userService, oauthClientService, jwtService, securityService) {
22
25
  this.config = config;
23
26
  this.mikro = mikro;
@@ -42,69 +45,88 @@ export class OAuthTokenService {
42
45
  async exchangeAuthorizationCode(params) {
43
46
  const { code, redirectUri, clientId, codeVerifier } = params;
44
47
  // 1. Look up client to get primary key (clientId in request is the business key)
45
- const client = await this.oauthClientService.findByClientId(clientId);
46
- // 2. Verify and consume the authorization code
47
- // Authorization codes are single-use (RFC 6749 §4.1.2)
48
48
  const codeHash = await this.securityService.hashOpaqueToken('oauth-code', code);
49
- const consumedAt = new Date();
50
- const codeEntity = await this.mikro.oauthCode.consumeAuthorizationCode({
51
- clientId: client.id,
52
- codeHash,
53
- consumedAt,
54
- });
55
- if (!codeEntity) {
56
- throw new e.InvalidAuthorizationCode.Error();
57
- }
58
- // 3. Populate user relation
59
- await this.mikro.em.populate(codeEntity, ['user']);
60
- // 4. Validate redirect_uri matches (RFC 6749 §4.1.3)
61
- // This prevents authorization code interception attacks
62
- if (codeEntity.redirectUri !== redirectUri) {
63
- throw new e.RedirectUriMismatch.Error();
64
- }
65
- // 5. Validate S256 PKCE when the authorization request used PKCE.
66
- // Public clients are required to use PKCE at the authorization endpoint;
67
- // confidential clients may omit PKCE and rely on client authentication at
68
- // the token endpoint, but any supplied challenge must still be valid S256.
69
- if (!codeEntity.codeChallenge) {
70
- if (await this.oauthClientService.isPublicClient(clientId)) {
71
- throw new e.InvalidPKCEVerifier.Error();
49
+ const result = await this.mikro.em.transactional(async () => {
50
+ const client = await this.oauthClientService.lockAuthenticatedClient(params.authentication, clientId, 'authorization_code');
51
+ const codeEntity = await this.mikro.oauthCode.findOne({ client: client.id, codeHash }, { refresh: true });
52
+ if (!codeEntity ||
53
+ codeEntity.client_epoch !== client.tokenEpoch ||
54
+ (!codeEntity.consumedAt && codeEntity.expiredAt <= new Date()))
55
+ throw new e.InvalidAuthorizationCode.Error();
56
+ this.oauthClientService.validateScopes(client, codeEntity.scope);
57
+ // 3. Populate user relation
58
+ await this.mikro.em.populate(codeEntity, ['user']);
59
+ // 4. Validate redirect_uri matches (RFC 6749 §4.1.3)
60
+ // This prevents authorization code interception attacks
61
+ if (codeEntity.redirectUri !== redirectUri) {
62
+ throw new e.RedirectUriMismatch.Error();
72
63
  }
73
- }
74
- else {
75
- if (codeEntity.codeChallengeMethod !== 'S256') {
76
- throw new e.InvalidPKCEVerifier.Error();
64
+ this.oauthClientService.validateRedirectUri(client, redirectUri);
65
+ // 5. Validate S256 PKCE when the authorization request used PKCE.
66
+ // Public clients are required to use PKCE at the authorization endpoint;
67
+ // confidential clients may omit PKCE and rely on client authentication at
68
+ // the token endpoint, but any supplied challenge must still be valid S256.
69
+ if (!codeEntity.codeChallenge) {
70
+ if (await this.oauthClientService.isPublicClient(clientId)) {
71
+ throw new e.InvalidPKCEVerifier.Error();
72
+ }
77
73
  }
78
- if (!codeVerifier) {
79
- throw new e.MissingCodeVerifier.Error();
74
+ else {
75
+ if (codeEntity.codeChallengeMethod !== 'S256') {
76
+ throw new e.InvalidPKCEVerifier.Error();
77
+ }
78
+ if (!codeVerifier) {
79
+ throw new e.MissingCodeVerifier.Error();
80
+ }
81
+ const isPKCEValid = await validatePKCE(codeVerifier, codeEntity.codeChallenge, codeEntity.codeChallengeMethod);
82
+ if (!isPKCEValid) {
83
+ throw new e.InvalidPKCEVerifier.Error();
84
+ }
80
85
  }
81
- const isPKCEValid = await validatePKCE(codeVerifier, codeEntity.codeChallenge, codeEntity.codeChallengeMethod);
82
- if (!isPKCEValid) {
83
- throw new e.InvalidPKCEVerifier.Error();
86
+ // 6. Get user data from relation (load via Ref)
87
+ const user = await codeEntity.user.load();
88
+ if (!user) {
89
+ throw new e.UserNotFound.Error();
84
90
  }
85
- }
86
- // 6. Get user data from relation (load via Ref)
87
- const user = await codeEntity.user.load();
88
- if (!user) {
89
- throw new e.UserNotFound.Error();
90
- }
91
- // 7. Build token response
92
- return this.buildTokenResponse({
93
- userSub: user.sub,
94
- userEmail: user.email,
95
- userEmailVerified: user.email_verified,
96
- clientId: client.clientId,
97
- clientEpoch: client.tokenEpoch,
98
- scope: codeEntity.scope,
99
- issueRefreshToken: client.grantTypes.includes('refresh_token') &&
100
- codeEntity.scope.includes('offline_access'),
101
- nonce: codeEntity.nonce,
102
- // Pass OIDC authentication metadata from the authorization code
103
- // Only include when defined and non-null (exactOptionalPropertyTypes)
104
- ...(codeEntity.authTime != null && {
105
- authTime: codeEntity.authTime,
106
- }),
91
+ if (user.deleted_at || codeEntity.user_epoch !== user.token_epoch)
92
+ throw new e.InvalidAuthorizationCode.Error();
93
+ if (codeEntity.consumedAt) {
94
+ if (codeEntity.grant_id)
95
+ await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: codeEntity.grant_id, client_id: client.clientId }, { revoked_at: new Date() });
96
+ return new e.InvalidAuthorizationCode.Error();
97
+ }
98
+ const consumed = await this.mikro.oauthCode.consumeAuthorizationCode({
99
+ clientId: client.id,
100
+ codeHash,
101
+ consumedAt: new Date(),
102
+ });
103
+ if (!consumed)
104
+ return new e.InvalidAuthorizationCode.Error();
105
+ const grantId = crypto.randomUUID();
106
+ await this.mikro.oauthCode.nativeUpdate({ id: codeEntity.id }, { grant_id: grantId });
107
+ // 7. Build token response
108
+ return this.buildTokenResponse({
109
+ grantId,
110
+ userSub: user.sub,
111
+ userEpoch: user.token_epoch ?? undefined,
112
+ userEmail: user.email,
113
+ userEmailVerified: user.email_verified,
114
+ clientId: client.clientId,
115
+ clientEpoch: client.tokenEpoch,
116
+ scope: codeEntity.scope,
117
+ issueRefreshToken: client.grantTypes.includes('refresh_token') &&
118
+ codeEntity.scope.includes('offline_access'),
119
+ nonce: codeEntity.nonce,
120
+ // Pass OIDC authentication metadata from the authorization code
121
+ // Only include when defined and non-null (exactOptionalPropertyTypes)
122
+ ...(codeEntity.authTime != null && {
123
+ authTime: codeEntity.authTime,
124
+ }),
125
+ });
107
126
  });
127
+ if (result instanceof IssuaryError)
128
+ throw result;
129
+ return result;
108
130
  }
109
131
  /**
110
132
  * Refresh access token using refresh token
@@ -123,83 +145,114 @@ export class OAuthTokenService {
123
145
  * @throws {ClientIdMismatch} - Client ID doesn't match original token request
124
146
  */
125
147
  async refreshAccessToken(params) {
126
- const decodedRefreshToken = this.jwtService.decodeToken(params.refreshToken);
127
- const refreshTokenJti = typeof decodedRefreshToken?.jti === 'string'
128
- ? decodedRefreshToken.jti
129
- : undefined;
130
- if (refreshTokenJti) {
131
- return this.withRefreshTokenRotationLock(refreshTokenJti, () => this.refreshAccessTokenLocked(params));
132
- }
133
- return this.refreshAccessTokenLocked(params);
148
+ const decoded = await this.jwtService.verifyRefreshTokenForReuseDetection(params.refreshToken);
149
+ if (decoded.client_id !== params.clientId)
150
+ throw new e.ClientIdMismatch.Error();
151
+ if (!decoded.grant_id || !decoded.jti)
152
+ throw new e.InvalidRefreshToken.Error();
153
+ const grantId = decoded.grant_id;
154
+ const result = await this.mikro.em.transactional(async () => {
155
+ await this.oauthClientService.lockAuthenticatedClient(params.authentication, params.clientId, 'refresh_token');
156
+ const locked = await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: grantId, client_id: params.clientId, user_sub: decoded.sub }, { revision: raw('revision + 1') });
157
+ if (locked !== 1)
158
+ throw new e.InvalidRefreshToken.Error();
159
+ const grant = await this.mikro.em.findOneOrFail(OAuthGrantEntitySchema, { id: grantId }, { refresh: true });
160
+ if (grant.revoked_at || grant.expires_at <= new Date())
161
+ throw new e.InvalidRefreshToken.Error();
162
+ if (grant.current_refresh_jti !== decoded.jti) {
163
+ await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: grant.id }, { revoked_at: new Date() });
164
+ return new e.InvalidRefreshToken.Error();
165
+ }
166
+ return this.refreshAccessTokenLocked(params);
167
+ });
168
+ if (result instanceof IssuaryError)
169
+ throw result;
170
+ return result;
134
171
  }
135
172
  async issueClientCredentialsToken(params) {
136
- const client = await this.oauthClientService.findByClientId(params.clientId);
137
- const scopeString = params.scope.join(' ');
138
- const accessToken = await this.jwtService.signAccessToken({
139
- typ: 'access_token',
140
- sub: params.clientId,
141
- client_id: params.clientId,
142
- grant_type: 'client_credentials',
143
- scope: scopeString,
144
- aud: this.config.server.public_origin,
145
- grant_id: crypto.randomUUID(),
146
- ...(client.tokenEpoch && { client_epoch: client.tokenEpoch }),
173
+ return this.mikro.em.transactional(async () => {
174
+ const client = await this.oauthClientService.lockAuthenticatedClient(params.authentication, params.clientId, 'client_credentials');
175
+ if (params.authentication.kind !== 'confidential')
176
+ throw new e.InvalidClientCredentials.Error();
177
+ this.oauthClientService.validateScopes(client, params.scope);
178
+ const scopeString = params.scope.join(' ');
179
+ const accessToken = await this.jwtService.signAccessToken({
180
+ typ: 'access_token',
181
+ sub: params.clientId,
182
+ client_id: params.clientId,
183
+ grant_type: 'client_credentials',
184
+ scope: scopeString,
185
+ aud: this.config.server.public_origin,
186
+ ...(client.tokenEpoch && { client_epoch: client.tokenEpoch }),
187
+ });
188
+ return {
189
+ access_token: accessToken,
190
+ token_type: 'Bearer',
191
+ expires_in: this.config.tokens.access_token_ttl,
192
+ scope: scopeString,
193
+ };
147
194
  });
148
- return {
149
- access_token: accessToken,
150
- token_type: 'Bearer',
151
- expires_in: this.config.tokens.access_token_ttl,
152
- scope: scopeString,
153
- };
154
195
  }
155
196
  async exchangeDeviceCode(params) {
156
- const client = await this.oauthClientService.findByClientId(params.clientId);
157
197
  const deviceCodeHash = await this.securityService.hashOpaqueToken('oauth-device-code', params.deviceCode);
158
- const deviceCode = await this.mikro.oauthDeviceCode.findByClientAndDeviceCodeHash(client.id, deviceCodeHash);
159
- if (!deviceCode) {
160
- throw new e.InvalidDeviceCode.Error();
161
- }
162
- if (deviceCode.expiresAt < new Date()) {
163
- throw new e.ExpiredToken.Error();
164
- }
165
- if (deviceCode.deniedAt) {
166
- throw new e.AccessDenied.Error();
167
- }
168
- if (!deviceCode.authorizedUser) {
169
- const pollResult = await this.mikro.oauthDeviceCode.recordPendingPoll({
170
- id: deviceCode.id,
171
- polledAt: new Date(),
172
- });
173
- if (pollResult === 'slow_down') {
174
- throw new e.SlowDown.Error();
198
+ const result = await this.mikro.em.transactional(async () => {
199
+ const client = await this.oauthClientService.lockAuthenticatedClient(params.authentication, params.clientId, 'urn:ietf:params:oauth:grant-type:device_code');
200
+ const deviceCode = await this.mikro.oauthDeviceCode.findByClientAndDeviceCodeHash(client.id, deviceCodeHash);
201
+ if (!deviceCode || deviceCode.client_epoch !== client.tokenEpoch) {
202
+ throw new e.InvalidDeviceCode.Error();
175
203
  }
176
- if (pollResult === 'authorization_pending') {
204
+ this.oauthClientService.validateScopes(client, deviceCode.scope);
205
+ if (deviceCode.expiresAt <= new Date()) {
206
+ throw new e.ExpiredToken.Error();
207
+ }
208
+ if (deviceCode.deniedAt) {
209
+ throw new e.AccessDenied.Error();
210
+ }
211
+ if (!deviceCode.authorizedUser) {
212
+ const pollResult = await this.mikro.oauthDeviceCode.recordPendingPoll({
213
+ id: deviceCode.id,
214
+ polledAt: new Date(),
215
+ });
216
+ if (pollResult === 'slow_down') {
217
+ return new e.SlowDown.Error();
218
+ }
219
+ if (pollResult === 'authorization_pending') {
220
+ return new e.AuthorizationPending.Error();
221
+ }
222
+ throw new e.InvalidDeviceCode.Error();
223
+ }
224
+ const consumedAt = new Date();
225
+ const consumed = await this.mikro.oauthDeviceCode.consumeAuthorizedDeviceCode(deviceCode.id, consumedAt);
226
+ if (!consumed) {
227
+ throw new e.InvalidDeviceCode.Error();
228
+ }
229
+ deviceCode.consumedAt = consumedAt;
230
+ await this.mikro.em.populate(deviceCode, ['authorizedUser']);
231
+ const user = deviceCode.authorizedUser;
232
+ if (!user) {
177
233
  throw new e.AuthorizationPending.Error();
178
234
  }
179
- throw new e.InvalidDeviceCode.Error();
180
- }
181
- const consumedAt = new Date();
182
- const consumed = await this.mikro.oauthDeviceCode.consumeAuthorizedDeviceCode(deviceCode.id, consumedAt);
183
- if (!consumed) {
184
- throw new e.InvalidDeviceCode.Error();
185
- }
186
- deviceCode.consumedAt = consumedAt;
187
- await this.mikro.em.populate(deviceCode, ['authorizedUser']);
188
- const user = deviceCode.authorizedUser;
189
- if (!user) {
190
- throw new e.AuthorizationPending.Error();
191
- }
192
- return this.buildTokenResponse({
193
- userSub: user.sub,
194
- userEmail: user.email,
195
- userEmailVerified: user.email_verified,
196
- clientId: client.clientId,
197
- clientEpoch: client.tokenEpoch,
198
- scope: deviceCode.scope,
199
- issueRefreshToken: client.grantTypes.includes('refresh_token') &&
200
- deviceCode.scope.includes('offline_access'),
201
- authTime: Math.floor((deviceCode.authorizedAt?.getTime() ?? Date.now()) / 1000),
235
+ if (user.deleted_at || deviceCode.user_epoch !== user.token_epoch)
236
+ throw new e.InvalidDeviceCode.Error();
237
+ const grantId = crypto.randomUUID();
238
+ await this.mikro.oauthDeviceCode.nativeUpdate({ id: deviceCode.id }, { grant_id: grantId });
239
+ return this.buildTokenResponse({
240
+ grantId,
241
+ userSub: user.sub,
242
+ userEpoch: user.token_epoch ?? undefined,
243
+ userEmail: user.email,
244
+ userEmailVerified: user.email_verified,
245
+ clientId: client.clientId,
246
+ clientEpoch: client.tokenEpoch,
247
+ scope: deviceCode.scope,
248
+ issueRefreshToken: client.grantTypes.includes('refresh_token') &&
249
+ deviceCode.scope.includes('offline_access'),
250
+ authTime: Math.floor((deviceCode.authorizedAt?.getTime() ?? Date.now()) / 1000),
251
+ });
202
252
  });
253
+ if (result instanceof Error)
254
+ throw result;
255
+ return result;
203
256
  }
204
257
  async refreshAccessTokenLocked(params) {
205
258
  const { refreshToken, clientId } = params;
@@ -224,36 +277,36 @@ export class OAuthTokenService {
224
277
  const client = await this.oauthClientService.findByClientId(clientId);
225
278
  const refreshTokenScopes = refreshPayload.scope.split(' ');
226
279
  const requestedScopes = params.scope ?? refreshTokenScopes;
280
+ this.oauthClientService.validateScopes(client, requestedScopes);
227
281
  const invalidScopes = requestedScopes.filter((scope) => !refreshTokenScopes.includes(scope));
228
282
  if (invalidScopes.length > 0) {
229
283
  throw new e.InvalidScope.Error({ invalidScopes });
230
284
  }
231
- // 5. Refresh Token Rotation: Revoke the old refresh token
232
- // This is a security best practice per OAuth 2.0 Security BCP §4.14.2
233
- // If an attacker tries to use a stolen refresh token after the legitimate
234
- // user has already used it, the token will be rejected as revoked.
235
- if (!refreshPayload.jti || !refreshPayload.exp) {
285
+ if (!refreshPayload.jti || !refreshPayload.exp || !refreshPayload.grant_id)
236
286
  throw new e.InvalidRefreshToken.Error();
237
- }
238
- const didRevokeRefreshToken = await this.mikro.revokedToken.revokeTokenOnce({
287
+ const consumed = await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, {
288
+ id: refreshPayload.grant_id,
289
+ current_refresh_jti: refreshPayload.jti,
290
+ revoked_at: null,
291
+ }, { current_refresh_jti: null });
292
+ if (consumed !== 1)
293
+ throw new e.InvalidRefreshToken.Error();
294
+ await this.mikro.revokedToken.revokeTokenOnce({
239
295
  jti: refreshPayload.jti,
240
296
  token_type: 'refresh_token',
241
- clientId: client.id, // Use entity primary key
297
+ clientId: client.id,
242
298
  userSub: userData.sub,
243
299
  expires_at: new Date(refreshPayload.exp * 1000),
244
300
  });
245
- if (!didRevokeRefreshToken) {
246
- await this.revokeRefreshTokenFamily(refreshPayload, client.id);
247
- throw new e.InvalidRefreshToken.Error();
248
- }
249
301
  // 6. Build token response with new access and refresh tokens
250
302
  // (no nonce in refresh flow)
251
303
  return this.buildTokenResponse({
252
304
  userSub: userData.sub,
305
+ userEpoch: refreshPayload.user_epoch,
253
306
  userEmail: userData.email,
254
307
  userEmailVerified: userData.email_verified,
255
308
  clientId: client.clientId,
256
- clientEpoch: client.tokenEpoch,
309
+ clientEpoch: refreshPayload.client_epoch ?? null,
257
310
  scope: requestedScopes,
258
311
  issueRefreshToken: true,
259
312
  grantId: refreshPayload.grant_id,
@@ -359,81 +412,85 @@ export class OAuthTokenService {
359
412
  * @param tokenTypeHint - Hint about token type (access_token or refresh_token)
360
413
  * @returns void - Always succeeds per RFC 7009 §2.1
361
414
  */
362
- async revokeToken(token, tokenTypeHint, requestingClientId) {
415
+ async revokeToken(token, tokenTypeHint, requestingClientId, authentication) {
363
416
  const verification = await this.verifyTokenForRevocation(token, tokenTypeHint);
364
417
  if (!verification) {
365
418
  return;
366
419
  }
367
- const { payload, tokenType } = verification;
368
- if (requestingClientId && payload.client_id !== requestingClientId) {
369
- return;
370
- }
371
- const { jti, exp } = payload;
372
- if (!jti || !exp) {
373
- return;
374
- }
375
- const expiresAt = new Date(exp * 1000);
376
- // Check if already revoked
377
- const isAlreadyRevoked = await this.mikro.revokedToken.isRevoked(jti);
378
- if (isAlreadyRevoked) {
379
- return;
380
- }
381
- // Look up user and client entities to get primary keys
382
- // Note: clientId from token is the business key, we need the entity's primary key
383
- const userEntity = await this.mikro.user.findOne({ sub: payload.sub });
384
- const clientEntity = await this.mikro.oauthClient.findOne({
385
- clientId: payload.client_id,
386
- });
387
- if (!clientEntity) {
388
- // Client no longer exists, but we still return success per RFC 7009.
389
- return;
390
- }
391
- const isClientCredentialsAccessToken = tokenType === 'access_token' &&
392
- 'grant_type' in payload &&
393
- payload.grant_type === 'client_credentials';
394
- if (!userEntity && !isClientCredentialsAccessToken) {
395
- // User no longer exists, but we still return success per RFC 7009.
396
- return;
397
- }
398
- // Revoke the token (using primary keys for FK references)
399
- await this.mikro.revokedToken.revokeToken({
400
- jti,
401
- token_type: tokenType,
402
- clientId: clientEntity.id, // Use entity's primary key
403
- ...(userEntity !== null && { userSub: userEntity.sub }),
404
- expires_at: expiresAt,
405
- });
406
- if (tokenType === 'refresh_token') {
407
- await this.revokeRefreshTokenFamily(payload, clientEntity.id);
408
- }
409
- // RFC 7009 §2.1: "If the particular token is a refresh token and the
410
- // authorization server supports the revocation of access tokens, then
411
- // the authorization server SHOULD also invalidate all access tokens
412
- // based on the same authorization grant."
413
- //
414
- // Since we can't enumerate all access tokens issued for this refresh token,
415
- // the revocation check happens at token verification time via jti lookup.
416
- // Access tokens will be rejected when their jti is in the revoked_tokens table.
417
- }
418
- async withRefreshTokenRotationLock(refreshTokenJti, operation) {
419
- const previousLock = this.refreshRotationLocks.get(refreshTokenJti);
420
- let releaseLock = () => { };
421
- const currentLock = new Promise((resolve) => {
422
- releaseLock = resolve;
423
- });
424
- this.refreshRotationLocks.set(refreshTokenJti, currentLock);
425
- if (previousLock) {
426
- await previousLock;
427
- }
428
- try {
429
- return await operation();
430
- }
431
- finally {
432
- releaseLock();
433
- if (this.refreshRotationLocks.get(refreshTokenJti) === currentLock) {
434
- this.refreshRotationLocks.delete(refreshTokenJti);
420
+ return this.mikro.em.transactional(async () => {
421
+ if (authentication)
422
+ await this.oauthClientService.lockAuthenticatedClient(authentication, requestingClientId ?? authentication.clientId);
423
+ const owner = await this.mikro.oauthClient.findOne({
424
+ clientId: verification.payload.client_id,
425
+ });
426
+ if (!owner)
427
+ return;
428
+ await lockOAuthClient(this.mikro.em, owner.id);
429
+ const fresh = await this.verifyTokenForRevocation(token, tokenTypeHint);
430
+ if (!fresh)
431
+ return;
432
+ const { payload, tokenType } = fresh;
433
+ if (requestingClientId && payload.client_id !== requestingClientId) {
434
+ return;
435
435
  }
436
- }
436
+ const { jti, exp } = payload;
437
+ if (!jti || !exp) {
438
+ return;
439
+ }
440
+ const expiresAt = new Date(exp * 1000);
441
+ // Check if already revoked
442
+ const isAlreadyRevoked = await this.mikro.revokedToken.isRevoked(jti);
443
+ if (isAlreadyRevoked && tokenType !== 'refresh_token') {
444
+ return;
445
+ }
446
+ // Look up user and client entities to get primary keys
447
+ // Note: clientId from token is the business key, we need the entity's primary key
448
+ const userEntity = await this.mikro.user.findOne({ sub: payload.sub });
449
+ const clientEntity = await this.mikro.oauthClient.findOne({
450
+ clientId: payload.client_id,
451
+ });
452
+ if (!clientEntity) {
453
+ // Client no longer exists, but we still return success per RFC 7009.
454
+ return;
455
+ }
456
+ const isClientCredentialsAccessToken = tokenType === 'access_token' &&
457
+ 'grant_type' in payload &&
458
+ payload.grant_type === 'client_credentials';
459
+ if (!userEntity && !isClientCredentialsAccessToken) {
460
+ // User no longer exists, but we still return success per RFC 7009.
461
+ return;
462
+ }
463
+ if (tokenType === 'refresh_token') {
464
+ if (!payload.grant_id)
465
+ return;
466
+ const locked = await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, {
467
+ id: payload.grant_id,
468
+ client_id: payload.client_id,
469
+ user_sub: payload.sub,
470
+ }, { revision: raw('revision + 1') });
471
+ if (locked !== 1)
472
+ return;
473
+ }
474
+ // Revoke the token (using primary keys for FK references)
475
+ await this.mikro.revokedToken.revokeToken({
476
+ jti,
477
+ token_type: tokenType,
478
+ clientId: clientEntity.id, // Use entity's primary key
479
+ ...(userEntity !== null && { userSub: userEntity.sub }),
480
+ expires_at: expiresAt,
481
+ });
482
+ if (tokenType === 'refresh_token') {
483
+ await this.revokeRefreshTokenFamily(payload, clientEntity.id);
484
+ }
485
+ // RFC 7009 §2.1: "If the particular token is a refresh token and the
486
+ // authorization server supports the revocation of access tokens, then
487
+ // the authorization server SHOULD also invalidate all access tokens
488
+ // based on the same authorization grant."
489
+ //
490
+ // Since we can't enumerate all access tokens issued for this refresh token,
491
+ // the revocation check happens at token verification time via jti lookup.
492
+ // Access tokens will be rejected when their jti is in the revoked_tokens table.
493
+ });
437
494
  }
438
495
  async revokeRefreshTokenFamilyIfReused(refreshToken, clientId) {
439
496
  try {
@@ -458,6 +515,7 @@ export class OAuthTokenService {
458
515
  }
459
516
  const tokenExpiresAt = payload.exp * 1000;
460
517
  const familyExpiresAt = Date.now() + this.config.tokens.refresh_token_ttl * 1000;
518
+ await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: payload.grant_id, client_id: payload.client_id }, { revoked_at: new Date() });
461
519
  await this.mikro.revokedToken.revokeGrant({
462
520
  grantId: payload.grant_id,
463
521
  clientId: clientEntityId,
@@ -471,7 +529,7 @@ export class OAuthTokenService {
471
529
  tokenType: 'access_token',
472
530
  });
473
531
  const verifyRefreshToken = async () => ({
474
- payload: await this.jwtService.verifyRefreshToken(token),
532
+ payload: await this.jwtService.verifyRefreshTokenForReuseDetection(token),
475
533
  tokenType: 'refresh_token',
476
534
  });
477
535
  const attempts = tokenTypeHint === 'refresh_token'
@@ -518,6 +576,7 @@ export class OAuthTokenService {
518
576
  const accessToken = await this.jwtService.signAccessToken({
519
577
  typ: 'access_token',
520
578
  sub: userSub,
579
+ user_epoch: params.userEpoch,
521
580
  client_id: clientId,
522
581
  scope: scopeString,
523
582
  aud: this.config.server.public_origin,
@@ -534,6 +593,7 @@ export class OAuthTokenService {
534
593
  response.refresh_token = await this.jwtService.signRefreshToken({
535
594
  typ: 'refresh_token',
536
595
  sub: userSub,
596
+ user_epoch: params.userEpoch,
537
597
  client_id: clientId,
538
598
  scope: scopeString,
539
599
  grant_id: grantId,
@@ -566,6 +626,32 @@ export class OAuthTokenService {
566
626
  }
567
627
  response.id_token = await this.jwtService.signIdToken(idTokenPayload);
568
628
  }
629
+ const refreshJti = response.refresh_token
630
+ ? this.jwtService.decodeToken(response.refresh_token)?.jti
631
+ : undefined;
632
+ const expiresAt = new Date(Date.now() +
633
+ Math.max(this.config.tokens.access_token_ttl, issueRefreshToken ? this.config.tokens.refresh_token_ttl : 0) *
634
+ 1000);
635
+ const existing = await this.mikro.em.findOne(OAuthGrantEntitySchema, { id: grantId }, { refresh: true });
636
+ if (existing) {
637
+ if (existing.revoked_at)
638
+ throw new e.InvalidRefreshToken.Error();
639
+ await this.mikro.em.nativeUpdate(OAuthGrantEntitySchema, { id: grantId, revoked_at: null }, {
640
+ current_refresh_jti: typeof refreshJti === 'string' ? refreshJti : null,
641
+ expires_at: new Date(Math.max(existing.expires_at.getTime(), expiresAt.getTime())),
642
+ });
643
+ }
644
+ else {
645
+ await this.mikro.em.insert(OAuthGrantEntitySchema, {
646
+ id: grantId,
647
+ user_sub: userSub,
648
+ client_id: clientId,
649
+ current_refresh_jti: typeof refreshJti === 'string' ? refreshJti : null,
650
+ expires_at: expiresAt,
651
+ revoked_at: null,
652
+ revision: 0,
653
+ });
654
+ }
569
655
  return response;
570
656
  }
571
657
  }