@nauth-toolkit/core 0.1.0

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 (778) hide show
  1. package/dist/adapters/database-columns.d.ts +10 -0
  2. package/dist/adapters/database-columns.d.ts.map +1 -0
  3. package/dist/adapters/database-columns.js +85 -0
  4. package/dist/adapters/database-columns.js.map +1 -0
  5. package/dist/adapters/express.adapter.d.ts +41 -0
  6. package/dist/adapters/express.adapter.d.ts.map +1 -0
  7. package/dist/adapters/express.adapter.js +188 -0
  8. package/dist/adapters/express.adapter.js.map +1 -0
  9. package/dist/adapters/fastify.adapter.d.ts +33 -0
  10. package/dist/adapters/fastify.adapter.d.ts.map +1 -0
  11. package/dist/adapters/fastify.adapter.js +223 -0
  12. package/dist/adapters/fastify.adapter.js.map +1 -0
  13. package/dist/adapters/index.d.ts +5 -0
  14. package/dist/adapters/index.d.ts.map +1 -0
  15. package/dist/adapters/index.js +25 -0
  16. package/dist/adapters/index.js.map +1 -0
  17. package/dist/adapters/storage.factory.d.ts +7 -0
  18. package/dist/adapters/storage.factory.d.ts.map +1 -0
  19. package/dist/adapters/storage.factory.js +24 -0
  20. package/dist/adapters/storage.factory.js.map +1 -0
  21. package/dist/bootstrap.d.ts +41 -0
  22. package/dist/bootstrap.d.ts.map +1 -0
  23. package/dist/bootstrap.js +113 -0
  24. package/dist/bootstrap.js.map +1 -0
  25. package/dist/dto/auth-challenge.dto.d.ts +19 -0
  26. package/dist/dto/auth-challenge.dto.d.ts.map +1 -0
  27. package/dist/dto/auth-challenge.dto.js +86 -0
  28. package/dist/dto/auth-challenge.dto.js.map +1 -0
  29. package/dist/dto/auth-response.dto.d.ts +31 -0
  30. package/dist/dto/auth-response.dto.d.ts.map +1 -0
  31. package/dist/dto/auth-response.dto.js +18 -0
  32. package/dist/dto/auth-response.dto.js.map +1 -0
  33. package/dist/dto/challenge-response.dto.d.ts +36 -0
  34. package/dist/dto/challenge-response.dto.d.ts.map +1 -0
  35. package/dist/dto/challenge-response.dto.js +3 -0
  36. package/dist/dto/challenge-response.dto.js.map +1 -0
  37. package/dist/dto/change-password-request.dto.d.ts +5 -0
  38. package/dist/dto/change-password-request.dto.d.ts.map +1 -0
  39. package/dist/dto/change-password-request.dto.js +30 -0
  40. package/dist/dto/change-password-request.dto.js.map +1 -0
  41. package/dist/dto/change-password-response.dto.d.ts +4 -0
  42. package/dist/dto/change-password-response.dto.d.ts.map +1 -0
  43. package/dist/dto/change-password-response.dto.js +8 -0
  44. package/dist/dto/change-password-response.dto.js.map +1 -0
  45. package/dist/dto/change-password.dto.d.ts +5 -0
  46. package/dist/dto/change-password.dto.d.ts.map +1 -0
  47. package/dist/dto/change-password.dto.js +29 -0
  48. package/dist/dto/change-password.dto.js.map +1 -0
  49. package/dist/dto/error-response.dto.d.ts +9 -0
  50. package/dist/dto/error-response.dto.d.ts.map +1 -0
  51. package/dist/dto/error-response.dto.js +59 -0
  52. package/dist/dto/error-response.dto.js.map +1 -0
  53. package/dist/dto/get-available-methods.dto.d.ts +7 -0
  54. package/dist/dto/get-available-methods.dto.d.ts.map +1 -0
  55. package/dist/dto/get-available-methods.dto.js +33 -0
  56. package/dist/dto/get-available-methods.dto.js.map +1 -0
  57. package/dist/dto/get-challenge-data-response.dto.d.ts +4 -0
  58. package/dist/dto/get-challenge-data-response.dto.d.ts.map +1 -0
  59. package/dist/dto/get-challenge-data-response.dto.js +8 -0
  60. package/dist/dto/get-challenge-data-response.dto.js.map +1 -0
  61. package/dist/dto/get-challenge-data.dto.d.ts +8 -0
  62. package/dist/dto/get-challenge-data.dto.d.ts.map +1 -0
  63. package/dist/dto/get-challenge-data.dto.js +40 -0
  64. package/dist/dto/get-challenge-data.dto.js.map +1 -0
  65. package/dist/dto/get-client-info.dto.d.ts +17 -0
  66. package/dist/dto/get-client-info.dto.d.ts.map +1 -0
  67. package/dist/dto/get-client-info.dto.js +20 -0
  68. package/dist/dto/get-client-info.dto.js.map +1 -0
  69. package/dist/dto/get-device-token-response.dto.d.ts +4 -0
  70. package/dist/dto/get-device-token-response.dto.d.ts.map +1 -0
  71. package/dist/dto/get-device-token-response.dto.js +8 -0
  72. package/dist/dto/get-device-token-response.dto.js.map +1 -0
  73. package/dist/dto/get-events-by-type.dto.d.ts +17 -0
  74. package/dist/dto/get-events-by-type.dto.d.ts.map +1 -0
  75. package/dist/dto/get-events-by-type.dto.js +20 -0
  76. package/dist/dto/get-events-by-type.dto.js.map +1 -0
  77. package/dist/dto/get-ip-address-response.dto.d.ts +4 -0
  78. package/dist/dto/get-ip-address-response.dto.d.ts.map +1 -0
  79. package/dist/dto/get-ip-address-response.dto.js +8 -0
  80. package/dist/dto/get-ip-address-response.dto.js.map +1 -0
  81. package/dist/dto/get-mfa-status.dto.d.ts +16 -0
  82. package/dist/dto/get-mfa-status.dto.d.ts.map +1 -0
  83. package/dist/dto/get-mfa-status.dto.js +41 -0
  84. package/dist/dto/get-mfa-status.dto.js.map +1 -0
  85. package/dist/dto/get-risk-assessment-history.dto.d.ts +9 -0
  86. package/dist/dto/get-risk-assessment-history.dto.d.ts.map +1 -0
  87. package/dist/dto/get-risk-assessment-history.dto.js +13 -0
  88. package/dist/dto/get-risk-assessment-history.dto.js.map +1 -0
  89. package/dist/dto/get-session-id-response.dto.d.ts +4 -0
  90. package/dist/dto/get-session-id-response.dto.d.ts.map +1 -0
  91. package/dist/dto/get-session-id-response.dto.js +8 -0
  92. package/dist/dto/get-session-id-response.dto.js.map +1 -0
  93. package/dist/dto/get-setup-data-response.dto.d.ts +4 -0
  94. package/dist/dto/get-setup-data-response.dto.d.ts.map +1 -0
  95. package/dist/dto/get-setup-data-response.dto.js +8 -0
  96. package/dist/dto/get-setup-data-response.dto.js.map +1 -0
  97. package/dist/dto/get-setup-data.dto.d.ts +7 -0
  98. package/dist/dto/get-setup-data.dto.d.ts.map +1 -0
  99. package/dist/dto/get-setup-data.dto.js +43 -0
  100. package/dist/dto/get-setup-data.dto.js.map +1 -0
  101. package/dist/dto/get-suspicious-activity.dto.d.ts +9 -0
  102. package/dist/dto/get-suspicious-activity.dto.d.ts.map +1 -0
  103. package/dist/dto/get-suspicious-activity.dto.js +13 -0
  104. package/dist/dto/get-suspicious-activity.dto.js.map +1 -0
  105. package/dist/dto/get-user-agent-response.dto.d.ts +4 -0
  106. package/dist/dto/get-user-agent-response.dto.d.ts.map +1 -0
  107. package/dist/dto/get-user-agent-response.dto.js +8 -0
  108. package/dist/dto/get-user-agent-response.dto.js.map +1 -0
  109. package/dist/dto/get-user-auth-history.dto.d.ts +20 -0
  110. package/dist/dto/get-user-auth-history.dto.d.ts.map +1 -0
  111. package/dist/dto/get-user-auth-history.dto.js +22 -0
  112. package/dist/dto/get-user-auth-history.dto.js.map +1 -0
  113. package/dist/dto/get-user-by-email.dto.d.ts +5 -0
  114. package/dist/dto/get-user-by-email.dto.d.ts.map +1 -0
  115. package/dist/dto/get-user-by-email.dto.js +36 -0
  116. package/dist/dto/get-user-by-email.dto.js.map +1 -0
  117. package/dist/dto/get-user-by-id.dto.d.ts +4 -0
  118. package/dist/dto/get-user-by-id.dto.d.ts.map +1 -0
  119. package/dist/dto/get-user-by-id.dto.js +29 -0
  120. package/dist/dto/get-user-by-id.dto.js.map +1 -0
  121. package/dist/dto/get-user-devices.dto.d.ts +8 -0
  122. package/dist/dto/get-user-devices.dto.d.ts.map +1 -0
  123. package/dist/dto/get-user-devices.dto.js +33 -0
  124. package/dist/dto/get-user-devices.dto.js.map +1 -0
  125. package/dist/dto/get-user-response.dto.d.ts +2 -0
  126. package/dist/dto/get-user-response.dto.d.ts.map +1 -0
  127. package/dist/dto/get-user-response.dto.js +6 -0
  128. package/dist/dto/get-user-response.dto.js.map +1 -0
  129. package/dist/dto/has-provider.dto.d.ts +7 -0
  130. package/dist/dto/has-provider.dto.d.ts.map +1 -0
  131. package/dist/dto/has-provider.dto.js +38 -0
  132. package/dist/dto/has-provider.dto.js.map +1 -0
  133. package/dist/dto/index.d.ts +51 -0
  134. package/dist/dto/index.d.ts.map +1 -0
  135. package/dist/dto/index.js +67 -0
  136. package/dist/dto/index.js.map +1 -0
  137. package/dist/dto/is-trusted-device-response.dto.d.ts +4 -0
  138. package/dist/dto/is-trusted-device-response.dto.d.ts.map +1 -0
  139. package/dist/dto/is-trusted-device-response.dto.js +8 -0
  140. package/dist/dto/is-trusted-device-response.dto.js.map +1 -0
  141. package/dist/dto/list-providers-response.dto.d.ts +4 -0
  142. package/dist/dto/list-providers-response.dto.d.ts.map +1 -0
  143. package/dist/dto/list-providers-response.dto.js +8 -0
  144. package/dist/dto/list-providers-response.dto.js.map +1 -0
  145. package/dist/dto/login.dto.d.ts +7 -0
  146. package/dist/dto/login.dto.d.ts.map +1 -0
  147. package/dist/dto/login.dto.js +68 -0
  148. package/dist/dto/login.dto.js.map +1 -0
  149. package/dist/dto/logout-all-response.dto.d.ts +4 -0
  150. package/dist/dto/logout-all-response.dto.d.ts.map +1 -0
  151. package/dist/dto/logout-all-response.dto.js +8 -0
  152. package/dist/dto/logout-all-response.dto.js.map +1 -0
  153. package/dist/dto/logout-all.dto.d.ts +5 -0
  154. package/dist/dto/logout-all.dto.d.ts.map +1 -0
  155. package/dist/dto/logout-all.dto.js +42 -0
  156. package/dist/dto/logout-all.dto.js.map +1 -0
  157. package/dist/dto/logout-response.dto.d.ts +4 -0
  158. package/dist/dto/logout-response.dto.d.ts.map +1 -0
  159. package/dist/dto/logout-response.dto.js +8 -0
  160. package/dist/dto/logout-response.dto.js.map +1 -0
  161. package/dist/dto/logout.dto.d.ts +5 -0
  162. package/dist/dto/logout.dto.d.ts.map +1 -0
  163. package/dist/dto/logout.dto.js +36 -0
  164. package/dist/dto/logout.dto.js.map +1 -0
  165. package/dist/dto/refresh-token.dto.d.ts +4 -0
  166. package/dist/dto/refresh-token.dto.d.ts.map +1 -0
  167. package/dist/dto/refresh-token.dto.js +24 -0
  168. package/dist/dto/refresh-token.dto.js.map +1 -0
  169. package/dist/dto/remove-devices.dto.d.ts +9 -0
  170. package/dist/dto/remove-devices.dto.d.ts.map +1 -0
  171. package/dist/dto/remove-devices.dto.js +50 -0
  172. package/dist/dto/remove-devices.dto.js.map +1 -0
  173. package/dist/dto/resend-code-response.dto.d.ts +4 -0
  174. package/dist/dto/resend-code-response.dto.d.ts.map +1 -0
  175. package/dist/dto/resend-code-response.dto.js +8 -0
  176. package/dist/dto/resend-code-response.dto.js.map +1 -0
  177. package/dist/dto/resend-code.dto.d.ts +4 -0
  178. package/dist/dto/resend-code.dto.d.ts.map +1 -0
  179. package/dist/dto/resend-code.dto.js +29 -0
  180. package/dist/dto/resend-code.dto.js.map +1 -0
  181. package/dist/dto/reset-password.dto.d.ts +8 -0
  182. package/dist/dto/reset-password.dto.d.ts.map +1 -0
  183. package/dist/dto/reset-password.dto.js +61 -0
  184. package/dist/dto/reset-password.dto.js.map +1 -0
  185. package/dist/dto/respond-challenge.dto.d.ts +33 -0
  186. package/dist/dto/respond-challenge.dto.d.ts.map +1 -0
  187. package/dist/dto/respond-challenge.dto.js +131 -0
  188. package/dist/dto/respond-challenge.dto.js.map +1 -0
  189. package/dist/dto/set-mfa-exemption.dto.d.ts +12 -0
  190. package/dist/dto/set-mfa-exemption.dto.d.ts.map +1 -0
  191. package/dist/dto/set-mfa-exemption.dto.js +66 -0
  192. package/dist/dto/set-mfa-exemption.dto.js.map +1 -0
  193. package/dist/dto/set-must-change-password-response.dto.d.ts +4 -0
  194. package/dist/dto/set-must-change-password-response.dto.d.ts.map +1 -0
  195. package/dist/dto/set-must-change-password-response.dto.js +8 -0
  196. package/dist/dto/set-must-change-password-response.dto.js.map +1 -0
  197. package/dist/dto/set-must-change-password.dto.d.ts +4 -0
  198. package/dist/dto/set-must-change-password.dto.d.ts.map +1 -0
  199. package/dist/dto/set-must-change-password.dto.js +29 -0
  200. package/dist/dto/set-must-change-password.dto.js.map +1 -0
  201. package/dist/dto/set-preferred-method.dto.d.ts +8 -0
  202. package/dist/dto/set-preferred-method.dto.d.ts.map +1 -0
  203. package/dist/dto/set-preferred-method.dto.js +49 -0
  204. package/dist/dto/set-preferred-method.dto.js.map +1 -0
  205. package/dist/dto/setup-mfa.dto.d.ts +9 -0
  206. package/dist/dto/setup-mfa.dto.d.ts.map +1 -0
  207. package/dist/dto/setup-mfa.dto.js +55 -0
  208. package/dist/dto/setup-mfa.dto.js.map +1 -0
  209. package/dist/dto/signup.dto.d.ts +10 -0
  210. package/dist/dto/signup.dto.d.ts.map +1 -0
  211. package/dist/dto/signup.dto.js +109 -0
  212. package/dist/dto/signup.dto.js.map +1 -0
  213. package/dist/dto/social-auth.dto.d.ts +54 -0
  214. package/dist/dto/social-auth.dto.d.ts.map +1 -0
  215. package/dist/dto/social-auth.dto.js +232 -0
  216. package/dist/dto/social-auth.dto.js.map +1 -0
  217. package/dist/dto/trust-device-response.dto.d.ts +4 -0
  218. package/dist/dto/trust-device-response.dto.d.ts.map +1 -0
  219. package/dist/dto/trust-device-response.dto.js +8 -0
  220. package/dist/dto/trust-device-response.dto.js.map +1 -0
  221. package/dist/dto/trust-device.dto.d.ts +1 -0
  222. package/dist/dto/trust-device.dto.d.ts.map +1 -0
  223. package/dist/dto/trust-device.dto.js +2 -0
  224. package/dist/dto/trust-device.dto.js.map +1 -0
  225. package/dist/dto/update-user-attributes-request.dto.d.ts +5 -0
  226. package/dist/dto/update-user-attributes-request.dto.d.ts.map +1 -0
  227. package/dist/dto/update-user-attributes-request.dto.js +30 -0
  228. package/dist/dto/update-user-attributes-request.dto.js.map +1 -0
  229. package/dist/dto/user-response.dto.d.ts +20 -0
  230. package/dist/dto/user-response.dto.d.ts.map +1 -0
  231. package/dist/dto/user-response.dto.js +42 -0
  232. package/dist/dto/user-response.dto.js.map +1 -0
  233. package/dist/dto/user-update.dto.d.ts +12 -0
  234. package/dist/dto/user-update.dto.d.ts.map +1 -0
  235. package/dist/dto/user-update.dto.js +119 -0
  236. package/dist/dto/user-update.dto.js.map +1 -0
  237. package/dist/dto/verify-email.dto.d.ts +29 -0
  238. package/dist/dto/verify-email.dto.d.ts.map +1 -0
  239. package/dist/dto/verify-email.dto.js +161 -0
  240. package/dist/dto/verify-email.dto.js.map +1 -0
  241. package/dist/dto/verify-mfa-code.dto.d.ts +10 -0
  242. package/dist/dto/verify-mfa-code.dto.d.ts.map +1 -0
  243. package/dist/dto/verify-mfa-code.dto.js +56 -0
  244. package/dist/dto/verify-mfa-code.dto.js.map +1 -0
  245. package/dist/dto/verify-phone-by-sub.dto.d.ts +6 -0
  246. package/dist/dto/verify-phone-by-sub.dto.d.ts.map +1 -0
  247. package/dist/dto/verify-phone-by-sub.dto.js +49 -0
  248. package/dist/dto/verify-phone-by-sub.dto.js.map +1 -0
  249. package/dist/dto/verify-phone.dto.d.ts +24 -0
  250. package/dist/dto/verify-phone.dto.d.ts.map +1 -0
  251. package/dist/dto/verify-phone.dto.js +124 -0
  252. package/dist/dto/verify-phone.dto.js.map +1 -0
  253. package/dist/entities/auth-audit.entity.d.ts +31 -0
  254. package/dist/entities/auth-audit.entity.d.ts.map +1 -0
  255. package/dist/entities/auth-audit.entity.js +33 -0
  256. package/dist/entities/auth-audit.entity.js.map +1 -0
  257. package/dist/entities/challenge-session.entity.d.ts +17 -0
  258. package/dist/entities/challenge-session.entity.d.ts.map +1 -0
  259. package/dist/entities/challenge-session.entity.js +21 -0
  260. package/dist/entities/challenge-session.entity.js.map +1 -0
  261. package/dist/entities/index.d.ts +12 -0
  262. package/dist/entities/index.d.ts.map +1 -0
  263. package/dist/entities/index.js +26 -0
  264. package/dist/entities/index.js.map +1 -0
  265. package/dist/entities/login-attempt.entity.d.ts +13 -0
  266. package/dist/entities/login-attempt.entity.d.ts.map +1 -0
  267. package/dist/entities/login-attempt.entity.js +17 -0
  268. package/dist/entities/login-attempt.entity.js.map +1 -0
  269. package/dist/entities/mfa-device.entity.d.ts +22 -0
  270. package/dist/entities/mfa-device.entity.d.ts.map +1 -0
  271. package/dist/entities/mfa-device.entity.js +25 -0
  272. package/dist/entities/mfa-device.entity.js.map +1 -0
  273. package/dist/entities/rate-limit.entity.d.ts +9 -0
  274. package/dist/entities/rate-limit.entity.d.ts.map +1 -0
  275. package/dist/entities/rate-limit.entity.js +13 -0
  276. package/dist/entities/rate-limit.entity.js.map +1 -0
  277. package/dist/entities/session.entity.d.ts +32 -0
  278. package/dist/entities/session.entity.d.ts.map +1 -0
  279. package/dist/entities/session.entity.js +36 -0
  280. package/dist/entities/session.entity.js.map +1 -0
  281. package/dist/entities/social-account.entity.d.ts +13 -0
  282. package/dist/entities/social-account.entity.d.ts.map +1 -0
  283. package/dist/entities/social-account.entity.js +17 -0
  284. package/dist/entities/social-account.entity.js.map +1 -0
  285. package/dist/entities/storage-lock.entity.d.ts +8 -0
  286. package/dist/entities/storage-lock.entity.d.ts.map +1 -0
  287. package/dist/entities/storage-lock.entity.js +12 -0
  288. package/dist/entities/storage-lock.entity.js.map +1 -0
  289. package/dist/entities/trusted-device.entity.d.ts +17 -0
  290. package/dist/entities/trusted-device.entity.d.ts.map +1 -0
  291. package/dist/entities/trusted-device.entity.js +21 -0
  292. package/dist/entities/trusted-device.entity.js.map +1 -0
  293. package/dist/entities/user.entity.d.ts +41 -0
  294. package/dist/entities/user.entity.d.ts.map +1 -0
  295. package/dist/entities/user.entity.js +45 -0
  296. package/dist/entities/user.entity.js.map +1 -0
  297. package/dist/entities/verification-token.entity.d.ts +19 -0
  298. package/dist/entities/verification-token.entity.d.ts.map +1 -0
  299. package/dist/entities/verification-token.entity.js +29 -0
  300. package/dist/entities/verification-token.entity.js.map +1 -0
  301. package/dist/enums/auth-audit-event-type.enum.d.ts +55 -0
  302. package/dist/enums/auth-audit-event-type.enum.d.ts.map +1 -0
  303. package/dist/enums/auth-audit-event-type.enum.js +59 -0
  304. package/dist/enums/auth-audit-event-type.enum.js.map +1 -0
  305. package/dist/enums/error-codes.enum.d.ts +53 -0
  306. package/dist/enums/error-codes.enum.d.ts.map +1 -0
  307. package/dist/enums/error-codes.enum.js +57 -0
  308. package/dist/enums/error-codes.enum.js.map +1 -0
  309. package/dist/enums/mfa-method.enum.d.ts +11 -0
  310. package/dist/enums/mfa-method.enum.d.ts.map +1 -0
  311. package/dist/enums/mfa-method.enum.js +18 -0
  312. package/dist/enums/mfa-method.enum.js.map +1 -0
  313. package/dist/enums/risk-factor.enum.d.ts +14 -0
  314. package/dist/enums/risk-factor.enum.d.ts.map +1 -0
  315. package/dist/enums/risk-factor.enum.js +18 -0
  316. package/dist/enums/risk-factor.enum.js.map +1 -0
  317. package/dist/exceptions/nauth.exception.d.ts +18 -0
  318. package/dist/exceptions/nauth.exception.d.ts.map +1 -0
  319. package/dist/exceptions/nauth.exception.js +64 -0
  320. package/dist/exceptions/nauth.exception.js.map +1 -0
  321. package/dist/handlers/auth.handler.d.ts +18 -0
  322. package/dist/handlers/auth.handler.d.ts.map +1 -0
  323. package/dist/handlers/auth.handler.js +173 -0
  324. package/dist/handlers/auth.handler.js.map +1 -0
  325. package/dist/handlers/client-info.handler.d.ts +12 -0
  326. package/dist/handlers/client-info.handler.d.ts.map +1 -0
  327. package/dist/handlers/client-info.handler.js +61 -0
  328. package/dist/handlers/client-info.handler.js.map +1 -0
  329. package/dist/handlers/csrf.handler.d.ts +13 -0
  330. package/dist/handlers/csrf.handler.d.ts.map +1 -0
  331. package/dist/handlers/csrf.handler.js +84 -0
  332. package/dist/handlers/csrf.handler.js.map +1 -0
  333. package/dist/handlers/token-delivery.handler.d.ts +12 -0
  334. package/dist/handlers/token-delivery.handler.d.ts.map +1 -0
  335. package/dist/handlers/token-delivery.handler.js +86 -0
  336. package/dist/handlers/token-delivery.handler.js.map +1 -0
  337. package/dist/index.d.ts +27 -0
  338. package/dist/index.d.ts.map +1 -0
  339. package/dist/index.js +51 -0
  340. package/dist/index.js.map +1 -0
  341. package/dist/interfaces/client-info.interface.d.ts +16 -0
  342. package/dist/interfaces/client-info.interface.d.ts.map +1 -0
  343. package/dist/interfaces/client-info.interface.js +3 -0
  344. package/dist/interfaces/client-info.interface.js.map +1 -0
  345. package/dist/interfaces/config.interface.d.ts +279 -0
  346. package/dist/interfaces/config.interface.d.ts.map +1 -0
  347. package/dist/interfaces/config.interface.js +3 -0
  348. package/dist/interfaces/config.interface.js.map +1 -0
  349. package/dist/interfaces/entities.interface.d.ts +169 -0
  350. package/dist/interfaces/entities.interface.d.ts.map +1 -0
  351. package/dist/interfaces/entities.interface.js +3 -0
  352. package/dist/interfaces/entities.interface.js.map +1 -0
  353. package/dist/interfaces/index.d.ts +11 -0
  354. package/dist/interfaces/index.d.ts.map +1 -0
  355. package/dist/interfaces/index.js +27 -0
  356. package/dist/interfaces/index.js.map +1 -0
  357. package/dist/interfaces/logger.interface.d.ts +43 -0
  358. package/dist/interfaces/logger.interface.d.ts.map +1 -0
  359. package/dist/interfaces/logger.interface.js +12 -0
  360. package/dist/interfaces/logger.interface.js.map +1 -0
  361. package/dist/interfaces/mfa-provider.interface.d.ts +12 -0
  362. package/dist/interfaces/mfa-provider.interface.d.ts.map +1 -0
  363. package/dist/interfaces/mfa-provider.interface.js +3 -0
  364. package/dist/interfaces/mfa-provider.interface.js.map +1 -0
  365. package/dist/interfaces/oauth.interface.d.ts +24 -0
  366. package/dist/interfaces/oauth.interface.d.ts.map +1 -0
  367. package/dist/interfaces/oauth.interface.js +3 -0
  368. package/dist/interfaces/oauth.interface.js.map +1 -0
  369. package/dist/interfaces/provider.interface.d.ts +12 -0
  370. package/dist/interfaces/provider.interface.d.ts.map +1 -0
  371. package/dist/interfaces/provider.interface.js +3 -0
  372. package/dist/interfaces/provider.interface.js.map +1 -0
  373. package/dist/interfaces/social-auth-provider.interface.d.ts +13 -0
  374. package/dist/interfaces/social-auth-provider.interface.d.ts.map +1 -0
  375. package/dist/interfaces/social-auth-provider.interface.js +3 -0
  376. package/dist/interfaces/social-auth-provider.interface.js.map +1 -0
  377. package/dist/interfaces/storage-adapter.interface.d.ts +39 -0
  378. package/dist/interfaces/storage-adapter.interface.d.ts.map +1 -0
  379. package/dist/interfaces/storage-adapter.interface.js +3 -0
  380. package/dist/interfaces/storage-adapter.interface.js.map +1 -0
  381. package/dist/interfaces/template.interface.d.ts +99 -0
  382. package/dist/interfaces/template.interface.d.ts.map +1 -0
  383. package/dist/interfaces/template.interface.js +15 -0
  384. package/dist/interfaces/template.interface.js.map +1 -0
  385. package/dist/interfaces/token-verifier.interface.d.ts +7 -0
  386. package/dist/interfaces/token-verifier.interface.d.ts.map +1 -0
  387. package/dist/interfaces/token-verifier.interface.js +3 -0
  388. package/dist/interfaces/token-verifier.interface.js.map +1 -0
  389. package/dist/internal.d.ts +20 -0
  390. package/dist/internal.d.ts.map +1 -0
  391. package/dist/internal.js +53 -0
  392. package/dist/internal.js.map +1 -0
  393. package/dist/platform/interfaces.d.ts +56 -0
  394. package/dist/platform/interfaces.d.ts.map +1 -0
  395. package/dist/platform/interfaces.js +3 -0
  396. package/dist/platform/interfaces.js.map +1 -0
  397. package/dist/schemas/auth-config.schema.d.ts +3411 -0
  398. package/dist/schemas/auth-config.schema.d.ts.map +1 -0
  399. package/dist/schemas/auth-config.schema.js +428 -0
  400. package/dist/schemas/auth-config.schema.js.map +1 -0
  401. package/dist/services/adaptive-mfa-decision.service.d.ts +39 -0
  402. package/dist/services/adaptive-mfa-decision.service.d.ts.map +1 -0
  403. package/dist/services/adaptive-mfa-decision.service.js +223 -0
  404. package/dist/services/adaptive-mfa-decision.service.js.map +1 -0
  405. package/dist/services/auth-audit.service.d.ts +44 -0
  406. package/dist/services/auth-audit.service.d.ts.map +1 -0
  407. package/dist/services/auth-audit.service.js +241 -0
  408. package/dist/services/auth-audit.service.js.map +1 -0
  409. package/dist/services/auth-challenge-helper.service.d.ts +48 -0
  410. package/dist/services/auth-challenge-helper.service.d.ts.map +1 -0
  411. package/dist/services/auth-challenge-helper.service.js +425 -0
  412. package/dist/services/auth-challenge-helper.service.js.map +1 -0
  413. package/dist/services/auth-flow-context-builder.service.d.ts +31 -0
  414. package/dist/services/auth-flow-context-builder.service.d.ts.map +1 -0
  415. package/dist/services/auth-flow-context-builder.service.js +253 -0
  416. package/dist/services/auth-flow-context-builder.service.js.map +1 -0
  417. package/dist/services/auth-flow-rules.d.ts +18 -0
  418. package/dist/services/auth-flow-rules.d.ts.map +1 -0
  419. package/dist/services/auth-flow-rules.js +55 -0
  420. package/dist/services/auth-flow-rules.js.map +1 -0
  421. package/dist/services/auth-flow-state-definitions.d.ts +5 -0
  422. package/dist/services/auth-flow-state-definitions.d.ts.map +1 -0
  423. package/dist/services/auth-flow-state-definitions.js +87 -0
  424. package/dist/services/auth-flow-state-definitions.js.map +1 -0
  425. package/dist/services/auth-flow-state-machine.service.d.ts +17 -0
  426. package/dist/services/auth-flow-state-machine.service.d.ts.map +1 -0
  427. package/dist/services/auth-flow-state-machine.service.js +91 -0
  428. package/dist/services/auth-flow-state-machine.service.js.map +1 -0
  429. package/dist/services/auth-flow-state-machine.types.d.ts +55 -0
  430. package/dist/services/auth-flow-state-machine.types.d.ts.map +1 -0
  431. package/dist/services/auth-flow-state-machine.types.js +16 -0
  432. package/dist/services/auth-flow-state-machine.types.js.map +1 -0
  433. package/dist/services/auth.service.d.ts +87 -0
  434. package/dist/services/auth.service.d.ts.map +1 -0
  435. package/dist/services/auth.service.js +2356 -0
  436. package/dist/services/auth.service.js.map +1 -0
  437. package/dist/services/challenge.service.d.ts +32 -0
  438. package/dist/services/challenge.service.d.ts.map +1 -0
  439. package/dist/services/challenge.service.js +293 -0
  440. package/dist/services/challenge.service.js.map +1 -0
  441. package/dist/services/client-info.service.d.ts +20 -0
  442. package/dist/services/client-info.service.d.ts.map +1 -0
  443. package/dist/services/client-info.service.js +202 -0
  444. package/dist/services/client-info.service.js.map +1 -0
  445. package/dist/services/csrf.service.d.ts +13 -0
  446. package/dist/services/csrf.service.d.ts.map +1 -0
  447. package/dist/services/csrf.service.js +67 -0
  448. package/dist/services/csrf.service.js.map +1 -0
  449. package/dist/services/email-verification.service.d.ts +30 -0
  450. package/dist/services/email-verification.service.d.ts.map +1 -0
  451. package/dist/services/email-verification.service.js +373 -0
  452. package/dist/services/email-verification.service.js.map +1 -0
  453. package/dist/services/geo-location.service.d.ts +85 -0
  454. package/dist/services/geo-location.service.d.ts.map +1 -0
  455. package/dist/services/geo-location.service.js +338 -0
  456. package/dist/services/geo-location.service.js.map +1 -0
  457. package/dist/services/index.d.ts +14 -0
  458. package/dist/services/index.d.ts.map +1 -0
  459. package/dist/services/index.js +30 -0
  460. package/dist/services/index.js.map +1 -0
  461. package/dist/services/jwt.service.d.ts +62 -0
  462. package/dist/services/jwt.service.d.ts.map +1 -0
  463. package/dist/services/jwt.service.js +261 -0
  464. package/dist/services/jwt.service.js.map +1 -0
  465. package/dist/services/mfa-base.service.d.ts +37 -0
  466. package/dist/services/mfa-base.service.d.ts.map +1 -0
  467. package/dist/services/mfa-base.service.js +297 -0
  468. package/dist/services/mfa-base.service.js.map +1 -0
  469. package/dist/services/mfa.service.d.ts +35 -0
  470. package/dist/services/mfa.service.d.ts.map +1 -0
  471. package/dist/services/mfa.service.js +449 -0
  472. package/dist/services/mfa.service.js.map +1 -0
  473. package/dist/services/password.service.d.ts +19 -0
  474. package/dist/services/password.service.d.ts.map +1 -0
  475. package/dist/services/password.service.js +150 -0
  476. package/dist/services/password.service.js.map +1 -0
  477. package/dist/services/phone-verification.service.d.ts +32 -0
  478. package/dist/services/phone-verification.service.d.ts.map +1 -0
  479. package/dist/services/phone-verification.service.js +474 -0
  480. package/dist/services/phone-verification.service.js.map +1 -0
  481. package/dist/services/risk-detection.service.d.ts +30 -0
  482. package/dist/services/risk-detection.service.d.ts.map +1 -0
  483. package/dist/services/risk-detection.service.js +518 -0
  484. package/dist/services/risk-detection.service.js.map +1 -0
  485. package/dist/services/risk-scoring.service.d.ts +12 -0
  486. package/dist/services/risk-scoring.service.d.ts.map +1 -0
  487. package/dist/services/risk-scoring.service.js +44 -0
  488. package/dist/services/risk-scoring.service.js.map +1 -0
  489. package/dist/services/session.service.d.ts +64 -0
  490. package/dist/services/session.service.d.ts.map +1 -0
  491. package/dist/services/session.service.js +455 -0
  492. package/dist/services/session.service.js.map +1 -0
  493. package/dist/services/social-auth-base.service.d.ts +57 -0
  494. package/dist/services/social-auth-base.service.d.ts.map +1 -0
  495. package/dist/services/social-auth-base.service.js +340 -0
  496. package/dist/services/social-auth-base.service.js.map +1 -0
  497. package/dist/services/social-auth.service.d.ts +31 -0
  498. package/dist/services/social-auth.service.d.ts.map +1 -0
  499. package/dist/services/social-auth.service.js +172 -0
  500. package/dist/services/social-auth.service.js.map +1 -0
  501. package/dist/services/social-provider-registry.service.d.ts +9 -0
  502. package/dist/services/social-provider-registry.service.d.ts.map +1 -0
  503. package/dist/services/social-provider-registry.service.js +30 -0
  504. package/dist/services/social-provider-registry.service.js.map +1 -0
  505. package/dist/services/trusted-device.service.d.ts +29 -0
  506. package/dist/services/trusted-device.service.d.ts.map +1 -0
  507. package/dist/services/trusted-device.service.js +190 -0
  508. package/dist/services/trusted-device.service.js.map +1 -0
  509. package/dist/storage/account-lockout-storage.service.d.ts +16 -0
  510. package/dist/storage/account-lockout-storage.service.d.ts.map +1 -0
  511. package/dist/storage/account-lockout-storage.service.js +50 -0
  512. package/dist/storage/account-lockout-storage.service.js.map +1 -0
  513. package/dist/storage/index.d.ts +4 -0
  514. package/dist/storage/index.d.ts.map +1 -0
  515. package/dist/storage/index.js +20 -0
  516. package/dist/storage/index.js.map +1 -0
  517. package/dist/storage/memory-storage.adapter.d.ts +33 -0
  518. package/dist/storage/memory-storage.adapter.d.ts.map +1 -0
  519. package/dist/storage/memory-storage.adapter.js +195 -0
  520. package/dist/storage/memory-storage.adapter.js.map +1 -0
  521. package/dist/storage/rate-limit-storage.service.d.ts +11 -0
  522. package/dist/storage/rate-limit-storage.service.d.ts.map +1 -0
  523. package/dist/storage/rate-limit-storage.service.js +33 -0
  524. package/dist/storage/rate-limit-storage.service.js.map +1 -0
  525. package/dist/templates/html-template.engine.d.ts +16 -0
  526. package/dist/templates/html-template.engine.d.ts.map +1 -0
  527. package/dist/templates/html-template.engine.js +502 -0
  528. package/dist/templates/html-template.engine.js.map +1 -0
  529. package/dist/templates/index.d.ts +2 -0
  530. package/dist/templates/index.d.ts.map +1 -0
  531. package/dist/templates/index.js +18 -0
  532. package/dist/templates/index.js.map +1 -0
  533. package/dist/utils/common-passwords.d.ts +4 -0
  534. package/dist/utils/common-passwords.d.ts.map +1 -0
  535. package/dist/utils/common-passwords.js +108 -0
  536. package/dist/utils/common-passwords.js.map +1 -0
  537. package/dist/utils/context-storage.d.ts +13 -0
  538. package/dist/utils/context-storage.d.ts.map +1 -0
  539. package/dist/utils/context-storage.js +54 -0
  540. package/dist/utils/context-storage.js.map +1 -0
  541. package/dist/utils/cookie-names.util.d.ts +7 -0
  542. package/dist/utils/cookie-names.util.d.ts.map +1 -0
  543. package/dist/utils/cookie-names.util.js +30 -0
  544. package/dist/utils/cookie-names.util.js.map +1 -0
  545. package/dist/utils/cookies.util.d.ts +12 -0
  546. package/dist/utils/cookies.util.d.ts.map +1 -0
  547. package/dist/utils/cookies.util.js +48 -0
  548. package/dist/utils/cookies.util.js.map +1 -0
  549. package/dist/utils/index.d.ts +8 -0
  550. package/dist/utils/index.d.ts.map +1 -0
  551. package/dist/utils/index.js +24 -0
  552. package/dist/utils/index.js.map +1 -0
  553. package/dist/utils/ip-extractor.d.ts +12 -0
  554. package/dist/utils/ip-extractor.d.ts.map +1 -0
  555. package/dist/utils/ip-extractor.js +88 -0
  556. package/dist/utils/ip-extractor.js.map +1 -0
  557. package/dist/utils/nauth-logger.d.ts +20 -0
  558. package/dist/utils/nauth-logger.d.ts.map +1 -0
  559. package/dist/utils/nauth-logger.js +129 -0
  560. package/dist/utils/nauth-logger.js.map +1 -0
  561. package/dist/utils/pii-redactor.d.ts +16 -0
  562. package/dist/utils/pii-redactor.d.ts.map +1 -0
  563. package/dist/utils/pii-redactor.js +147 -0
  564. package/dist/utils/pii-redactor.js.map +1 -0
  565. package/dist/utils/setup/get-repositories.d.ts +16 -0
  566. package/dist/utils/setup/get-repositories.d.ts.map +1 -0
  567. package/dist/utils/setup/get-repositories.js +36 -0
  568. package/dist/utils/setup/get-repositories.js.map +1 -0
  569. package/dist/utils/setup/init-services.d.ts +41 -0
  570. package/dist/utils/setup/init-services.d.ts.map +1 -0
  571. package/dist/utils/setup/init-services.js +107 -0
  572. package/dist/utils/setup/init-services.js.map +1 -0
  573. package/dist/utils/setup/init-social.d.ts +13 -0
  574. package/dist/utils/setup/init-social.d.ts.map +1 -0
  575. package/dist/utils/setup/init-social.js +77 -0
  576. package/dist/utils/setup/init-social.js.map +1 -0
  577. package/dist/utils/setup/init-storage.d.ts +4 -0
  578. package/dist/utils/setup/init-storage.d.ts.map +1 -0
  579. package/dist/utils/setup/init-storage.js +79 -0
  580. package/dist/utils/setup/init-storage.js.map +1 -0
  581. package/dist/utils/setup/register-mfa.d.ts +5 -0
  582. package/dist/utils/setup/register-mfa.d.ts.map +1 -0
  583. package/dist/utils/setup/register-mfa.js +85 -0
  584. package/dist/utils/setup/register-mfa.js.map +1 -0
  585. package/dist/utils/setup/run-nauth-migrations.d.ts +5 -0
  586. package/dist/utils/setup/run-nauth-migrations.d.ts.map +1 -0
  587. package/dist/utils/setup/run-nauth-migrations.js +67 -0
  588. package/dist/utils/setup/run-nauth-migrations.js.map +1 -0
  589. package/dist/utils/token-delivery-policy.d.ts +6 -0
  590. package/dist/utils/token-delivery-policy.d.ts.map +1 -0
  591. package/dist/utils/token-delivery-policy.js +15 -0
  592. package/dist/utils/token-delivery-policy.js.map +1 -0
  593. package/dist/validators/template.validator.d.ts +7 -0
  594. package/dist/validators/template.validator.d.ts.map +1 -0
  595. package/dist/validators/template.validator.js +95 -0
  596. package/dist/validators/template.validator.js.map +1 -0
  597. package/jest.config.js +15 -0
  598. package/jest.setup.ts +6 -0
  599. package/package.json +73 -0
  600. package/src/adapters/database-columns.ts +165 -0
  601. package/src/adapters/express.adapter.ts +385 -0
  602. package/src/adapters/fastify.adapter.ts +416 -0
  603. package/src/adapters/index.ts +16 -0
  604. package/src/adapters/storage.factory.ts +143 -0
  605. package/src/bootstrap.ts +374 -0
  606. package/src/dto/auth-challenge.dto.ts +231 -0
  607. package/src/dto/auth-response.dto.ts +253 -0
  608. package/src/dto/challenge-response.dto.ts +234 -0
  609. package/src/dto/change-password-request.dto.ts +50 -0
  610. package/src/dto/change-password-response.dto.ts +29 -0
  611. package/src/dto/change-password.dto.ts +57 -0
  612. package/src/dto/error-response.dto.ts +136 -0
  613. package/src/dto/get-available-methods.dto.ts +55 -0
  614. package/src/dto/get-challenge-data-response.dto.ts +28 -0
  615. package/src/dto/get-challenge-data.dto.ts +69 -0
  616. package/src/dto/get-client-info.dto.ts +104 -0
  617. package/src/dto/get-device-token-response.dto.ts +25 -0
  618. package/src/dto/get-events-by-type.dto.ts +76 -0
  619. package/src/dto/get-ip-address-response.dto.ts +24 -0
  620. package/src/dto/get-mfa-status.dto.ts +94 -0
  621. package/src/dto/get-risk-assessment-history.dto.ts +39 -0
  622. package/src/dto/get-session-id-response.dto.ts +25 -0
  623. package/src/dto/get-setup-data-response.dto.ts +31 -0
  624. package/src/dto/get-setup-data.dto.ts +75 -0
  625. package/src/dto/get-suspicious-activity.dto.ts +42 -0
  626. package/src/dto/get-user-agent-response.dto.ts +23 -0
  627. package/src/dto/get-user-auth-history.dto.ts +95 -0
  628. package/src/dto/get-user-by-email.dto.ts +61 -0
  629. package/src/dto/get-user-by-id.dto.ts +46 -0
  630. package/src/dto/get-user-devices.dto.ts +53 -0
  631. package/src/dto/get-user-response.dto.ts +17 -0
  632. package/src/dto/has-provider.dto.ts +56 -0
  633. package/src/dto/index.ts +57 -0
  634. package/src/dto/is-trusted-device-response.dto.ts +34 -0
  635. package/src/dto/list-providers-response.dto.ts +23 -0
  636. package/src/dto/login.dto.ts +95 -0
  637. package/src/dto/logout-all-response.dto.ts +24 -0
  638. package/src/dto/logout-all.dto.ts +65 -0
  639. package/src/dto/logout-response.dto.ts +25 -0
  640. package/src/dto/logout.dto.ts +64 -0
  641. package/src/dto/refresh-token.dto.ts +36 -0
  642. package/src/dto/remove-devices.dto.ts +85 -0
  643. package/src/dto/resend-code-response.dto.ts +32 -0
  644. package/src/dto/resend-code.dto.ts +51 -0
  645. package/src/dto/reset-password.dto.ts +115 -0
  646. package/src/dto/respond-challenge.dto.ts +272 -0
  647. package/src/dto/set-mfa-exemption.dto.ts +112 -0
  648. package/src/dto/set-must-change-password-response.dto.ts +27 -0
  649. package/src/dto/set-must-change-password.dto.ts +46 -0
  650. package/src/dto/set-preferred-method.dto.ts +80 -0
  651. package/src/dto/setup-mfa.dto.ts +98 -0
  652. package/src/dto/signup.dto.ts +174 -0
  653. package/src/dto/social-auth.dto.ts +422 -0
  654. package/src/dto/trust-device-response.dto.ts +30 -0
  655. package/src/dto/trust-device.dto.ts +9 -0
  656. package/src/dto/update-user-attributes-request.dto.ts +51 -0
  657. package/src/dto/user-response.dto.ts +138 -0
  658. package/src/dto/user-update.dto.ts +222 -0
  659. package/src/dto/verify-email.dto.ts +313 -0
  660. package/src/dto/verify-mfa-code.dto.ts +103 -0
  661. package/src/dto/verify-phone-by-sub.dto.ts +78 -0
  662. package/src/dto/verify-phone.dto.ts +245 -0
  663. package/src/entities/auth-audit.entity.ts +232 -0
  664. package/src/entities/challenge-session.entity.ts +116 -0
  665. package/src/entities/index.ts +29 -0
  666. package/src/entities/login-attempt.entity.ts +64 -0
  667. package/src/entities/mfa-device.entity.ts +151 -0
  668. package/src/entities/rate-limit.entity.ts +44 -0
  669. package/src/entities/session.entity.ts +180 -0
  670. package/src/entities/social-account.entity.ts +96 -0
  671. package/src/entities/storage-lock.entity.ts +39 -0
  672. package/src/entities/trusted-device.entity.ts +112 -0
  673. package/src/entities/user.entity.ts +243 -0
  674. package/src/entities/verification-token.entity.ts +141 -0
  675. package/src/enums/auth-audit-event-type.enum.ts +360 -0
  676. package/src/enums/error-codes.enum.ts +420 -0
  677. package/src/enums/mfa-method.enum.ts +97 -0
  678. package/src/enums/risk-factor.enum.ts +111 -0
  679. package/src/exceptions/nauth.exception.ts +231 -0
  680. package/src/handlers/auth.handler.ts +260 -0
  681. package/src/handlers/client-info.handler.ts +101 -0
  682. package/src/handlers/csrf.handler.ts +156 -0
  683. package/src/handlers/token-delivery.handler.ts +118 -0
  684. package/src/index.ts +118 -0
  685. package/src/interfaces/client-info.interface.ts +85 -0
  686. package/src/interfaces/config.interface.ts +2135 -0
  687. package/src/interfaces/entities.interface.ts +226 -0
  688. package/src/interfaces/index.ts +15 -0
  689. package/src/interfaces/logger.interface.ts +283 -0
  690. package/src/interfaces/mfa-provider.interface.ts +154 -0
  691. package/src/interfaces/oauth.interface.ts +148 -0
  692. package/src/interfaces/provider.interface.ts +47 -0
  693. package/src/interfaces/social-auth-provider.interface.ts +131 -0
  694. package/src/interfaces/storage-adapter.interface.ts +82 -0
  695. package/src/interfaces/template.interface.ts +510 -0
  696. package/src/interfaces/token-verifier.interface.ts +110 -0
  697. package/src/internal.ts +178 -0
  698. package/src/platform/interfaces.ts +299 -0
  699. package/src/schemas/auth-config.schema.ts +646 -0
  700. package/src/services/adaptive-mfa-decision.service.spec.ts +1058 -0
  701. package/src/services/adaptive-mfa-decision.service.ts +457 -0
  702. package/src/services/auth-audit.service.spec.ts +675 -0
  703. package/src/services/auth-audit.service.ts +558 -0
  704. package/src/services/auth-challenge-helper.service.spec.ts +3227 -0
  705. package/src/services/auth-challenge-helper.service.ts +825 -0
  706. package/src/services/auth-flow-context-builder.service.ts +520 -0
  707. package/src/services/auth-flow-rules.ts +202 -0
  708. package/src/services/auth-flow-state-definitions.ts +190 -0
  709. package/src/services/auth-flow-state-machine.service.ts +207 -0
  710. package/src/services/auth-flow-state-machine.types.ts +316 -0
  711. package/src/services/auth.service.spec.ts +4195 -0
  712. package/src/services/auth.service.ts +3727 -0
  713. package/src/services/challenge.service.spec.ts +1363 -0
  714. package/src/services/challenge.service.ts +696 -0
  715. package/src/services/client-info.service.spec.ts +572 -0
  716. package/src/services/client-info.service.ts +374 -0
  717. package/src/services/csrf.service.ts +54 -0
  718. package/src/services/email-verification.service.spec.ts +1229 -0
  719. package/src/services/email-verification.service.ts +578 -0
  720. package/src/services/geo-location.service.spec.ts +603 -0
  721. package/src/services/geo-location.service.ts +599 -0
  722. package/src/services/index.ts +13 -0
  723. package/src/services/jwt.service.spec.ts +882 -0
  724. package/src/services/jwt.service.ts +621 -0
  725. package/src/services/mfa-base.service.spec.ts +246 -0
  726. package/src/services/mfa-base.service.ts +611 -0
  727. package/src/services/mfa.service.spec.ts +693 -0
  728. package/src/services/mfa.service.ts +960 -0
  729. package/src/services/password.service.spec.ts +166 -0
  730. package/src/services/password.service.ts +309 -0
  731. package/src/services/phone-verification.service.spec.ts +1120 -0
  732. package/src/services/phone-verification.service.ts +751 -0
  733. package/src/services/risk-detection.service.spec.ts +1292 -0
  734. package/src/services/risk-detection.service.ts +1012 -0
  735. package/src/services/risk-scoring.service.spec.ts +204 -0
  736. package/src/services/risk-scoring.service.ts +131 -0
  737. package/src/services/session.service.spec.ts +1293 -0
  738. package/src/services/session.service.ts +803 -0
  739. package/src/services/social-account.service.spec.ts +725 -0
  740. package/src/services/social-auth-base.service.spec.ts +418 -0
  741. package/src/services/social-auth-base.service.ts +581 -0
  742. package/src/services/social-auth.service.spec.ts +238 -0
  743. package/src/services/social-auth.service.ts +436 -0
  744. package/src/services/social-provider-registry.service.spec.ts +238 -0
  745. package/src/services/social-provider-registry.service.ts +122 -0
  746. package/src/services/trusted-device.service.spec.ts +505 -0
  747. package/src/services/trusted-device.service.ts +339 -0
  748. package/src/storage/account-lockout-storage.service.spec.ts +310 -0
  749. package/src/storage/account-lockout-storage.service.ts +89 -0
  750. package/src/storage/index.ts +3 -0
  751. package/src/storage/memory-storage.adapter.ts +443 -0
  752. package/src/storage/rate-limit-storage.service.spec.ts +247 -0
  753. package/src/storage/rate-limit-storage.service.ts +38 -0
  754. package/src/templates/html-template.engine.spec.ts +161 -0
  755. package/src/templates/html-template.engine.ts +688 -0
  756. package/src/templates/index.ts +7 -0
  757. package/src/utils/common-passwords.spec.ts +230 -0
  758. package/src/utils/common-passwords.ts +170 -0
  759. package/src/utils/context-storage.ts +188 -0
  760. package/src/utils/cookie-names.util.ts +67 -0
  761. package/src/utils/cookies.util.ts +94 -0
  762. package/src/utils/index.ts +12 -0
  763. package/src/utils/ip-extractor.spec.ts +330 -0
  764. package/src/utils/ip-extractor.ts +220 -0
  765. package/src/utils/nauth-logger.spec.ts +388 -0
  766. package/src/utils/nauth-logger.ts +215 -0
  767. package/src/utils/pii-redactor.spec.ts +130 -0
  768. package/src/utils/pii-redactor.ts +288 -0
  769. package/src/utils/setup/get-repositories.ts +140 -0
  770. package/src/utils/setup/init-services.ts +422 -0
  771. package/src/utils/setup/init-social.ts +189 -0
  772. package/src/utils/setup/init-storage.ts +94 -0
  773. package/src/utils/setup/register-mfa.ts +165 -0
  774. package/src/utils/setup/run-nauth-migrations.ts +61 -0
  775. package/src/utils/token-delivery-policy.ts +38 -0
  776. package/src/validators/template.validator.ts +219 -0
  777. package/tsconfig.json +37 -0
  778. package/tsconfig.lint.json +6 -0
@@ -0,0 +1,1229 @@
1
+ import { Repository } from 'typeorm';
2
+ import { EmailVerificationService } from './email-verification.service';
3
+ import { NAuthException } from '../exceptions/nauth.exception';
4
+ import { ClientInfoService } from './client-info.service';
5
+ import { EmailProvider } from '../interfaces/provider.interface';
6
+ import { StorageAdapter } from '../interfaces/storage-adapter.interface';
7
+ import { NAuthConfig } from '../interfaces/config.interface';
8
+ import { NAuthLogger } from '../utils/nauth-logger';
9
+ import { AuthAuditService } from './auth-audit.service';
10
+ import { BaseVerificationToken, BaseUser } from '../entities';
11
+ import { IUser, IVerificationToken } from '../interfaces/entities.interface';
12
+ import { AuthErrorCode } from '../enums/error-codes.enum';
13
+ import { JwtService } from './jwt.service';
14
+ import { SessionService } from './session.service';
15
+ import {
16
+ SendVerificationEmailDTO,
17
+ VerifyEmailWithCodeDTO,
18
+ VerifyEmailWithTokenDTO,
19
+ ResendVerificationEmailDTO,
20
+ } from '../dto/verify-email.dto';
21
+
22
+ // Helper to create DTOs from plain objects
23
+ function createSendVerificationEmailDto(data: Partial<SendVerificationEmailDTO>): SendVerificationEmailDTO {
24
+ return Object.assign(new SendVerificationEmailDTO(), data);
25
+ }
26
+
27
+ function createVerifyEmailWithCodeDto(data: Partial<VerifyEmailWithCodeDTO>): VerifyEmailWithCodeDTO {
28
+ return Object.assign(new VerifyEmailWithCodeDTO(), data);
29
+ }
30
+
31
+ function createVerifyEmailWithTokenDto(data: Partial<VerifyEmailWithTokenDTO>): VerifyEmailWithTokenDTO {
32
+ return Object.assign(new VerifyEmailWithTokenDTO(), data);
33
+ }
34
+
35
+ function createResendVerificationEmailDto(data: Partial<ResendVerificationEmailDTO>): ResendVerificationEmailDTO {
36
+ return Object.assign(new ResendVerificationEmailDTO(), data);
37
+ }
38
+
39
+ /**
40
+ * Email Verification Service Unit Tests
41
+ *
42
+ * Platform-agnostic: Uses direct instantiation, no NestJS dependencies.
43
+ *
44
+ * Covers:
45
+ * - Send verification email with rate limiting
46
+ * - Code-based verification with attempts tracking
47
+ * - Link-based verification (token)
48
+ * - Resend verification email with cooldown
49
+ * - Rate limiting per user and per IP
50
+ * - Error handling for all dependencies
51
+ * - Storage adapter failures
52
+ * - Email provider errors
53
+ */
54
+ describe('EmailVerificationService', () => {
55
+ let service: EmailVerificationService;
56
+ let mockVerificationTokenRepository: jest.Mocked<Repository<BaseVerificationToken>>;
57
+ let mockUserRepository: jest.Mocked<Repository<BaseUser>>;
58
+ let mockEmailProvider: jest.Mocked<EmailProvider>;
59
+ let mockStorageAdapter: jest.Mocked<StorageAdapter>;
60
+ let mockJwtService: jest.Mocked<JwtService>;
61
+ let mockSessionService: jest.Mocked<SessionService>;
62
+ let mockClientInfoService: jest.Mocked<ClientInfoService>;
63
+ let mockLogger: jest.Mocked<NAuthLogger>;
64
+ let mockAuditService: jest.Mocked<AuthAuditService>;
65
+ let mockConfig: NAuthConfig;
66
+
67
+ const mockUser: IUser = {
68
+ id: 123,
69
+ sub: 'user-sub-123',
70
+ email: 'test@example.com',
71
+ username: 'testuser',
72
+ phone: null,
73
+ firstName: null,
74
+ lastName: null,
75
+ passwordHash: null,
76
+ passwordChangedAt: null,
77
+ passwordHistory: null,
78
+ isEmailVerified: false,
79
+ isPhoneVerified: false,
80
+ isActive: true,
81
+ mustChangePassword: false,
82
+ isLocked: false,
83
+ lockReason: null,
84
+ lockedAt: null,
85
+ lockedUntil: null,
86
+ failedLoginAttempts: 0,
87
+ lastFailedLoginAt: null,
88
+ lastLoginAt: null,
89
+ lastLoginIp: null,
90
+ hasSocialAuth: false,
91
+ socialProviders: null,
92
+ mfaEnabled: false,
93
+ mfaMethods: null,
94
+ preferredMfaMethod: null,
95
+ backupCodes: null,
96
+ metadata: null,
97
+ createdAt: new Date(),
98
+ updatedAt: new Date(),
99
+ deletedAt: null,
100
+ };
101
+
102
+ const mockVerificationToken: IVerificationToken = {
103
+ id: 456,
104
+ userId: 123,
105
+ challengeSessionId: null,
106
+ type: 'email',
107
+ token: 'hashed-token-abc123',
108
+ code: '123456',
109
+ expiresAt: new Date(Date.now() + 3600000), // 1 hour from now
110
+ attempts: 0,
111
+ usedAt: null,
112
+ ipAddress: '127.0.0.1',
113
+ userAgent: 'test-agent',
114
+ createdAt: new Date(),
115
+ isExpired: jest.fn().mockReturnValue(false),
116
+ maxAttemptsExceeded: jest.fn().mockReturnValue(false),
117
+ };
118
+
119
+ beforeEach(() => {
120
+ mockVerificationTokenRepository = {
121
+ create: jest.fn(),
122
+ save: jest.fn(),
123
+ findOne: jest.fn(),
124
+ update: jest.fn().mockResolvedValue({ affected: 0 } as any),
125
+ count: jest.fn(),
126
+ } as any;
127
+
128
+ mockUserRepository = {
129
+ findOne: jest.fn(),
130
+ save: jest.fn(),
131
+ update: jest.fn().mockResolvedValue({ affected: 1 } as any),
132
+ } as any;
133
+
134
+ mockEmailProvider = {
135
+ sendVerificationEmail: jest.fn().mockResolvedValue(undefined),
136
+ sendPasswordResetEmail: jest.fn(),
137
+ sendWelcomeEmail: jest.fn(),
138
+ } as any;
139
+
140
+ mockStorageAdapter = {
141
+ get: jest.fn(),
142
+ set: jest.fn(),
143
+ incr: jest.fn().mockResolvedValue(1),
144
+ expire: jest.fn().mockResolvedValue(undefined),
145
+ ttl: jest.fn().mockResolvedValue(3600),
146
+ del: jest.fn().mockResolvedValue(undefined),
147
+ exists: jest.fn(),
148
+ } as any;
149
+
150
+ mockClientInfoService = {
151
+ get: jest.fn().mockReturnValue({
152
+ ipAddress: '127.0.0.1',
153
+ userAgent: 'test-agent',
154
+ }),
155
+ } as any;
156
+
157
+ mockJwtService = {
158
+ generateTokenPair: jest.fn(),
159
+ hashToken: jest.fn(),
160
+ validateAccessToken: jest.fn(),
161
+ validateRefreshToken: jest.fn(),
162
+ } as any;
163
+
164
+ mockSessionService = {
165
+ createSession: jest.fn(),
166
+ findById: jest.fn(),
167
+ } as any;
168
+
169
+ mockLogger = {
170
+ log: jest.fn(),
171
+ error: jest.fn(),
172
+ warn: jest.fn(),
173
+ debug: jest.fn(),
174
+ verbose: jest.fn(),
175
+ } as any;
176
+
177
+ mockAuditService = {
178
+ recordEvent: jest.fn().mockResolvedValue(null),
179
+ } as any;
180
+
181
+ mockConfig = {
182
+ jwt: {
183
+ accessToken: { secret: 'test-secret', expiresIn: '15m' },
184
+ refreshToken: { secret: 'test-refresh-secret', expiresIn: '7d' },
185
+ },
186
+ signup: {
187
+ emailVerification: {
188
+ expiresIn: 3600,
189
+ resendDelay: 60,
190
+ rateLimitMax: 3,
191
+ rateLimitWindow: 3600,
192
+ },
193
+ },
194
+ };
195
+
196
+ // Instantiate service directly
197
+ service = new EmailVerificationService(
198
+ mockVerificationTokenRepository,
199
+ mockUserRepository,
200
+ mockEmailProvider,
201
+ mockStorageAdapter,
202
+ mockConfig,
203
+ mockClientInfoService,
204
+ mockLogger,
205
+ mockAuditService,
206
+ );
207
+ });
208
+
209
+ afterEach(() => {
210
+ jest.clearAllMocks();
211
+ });
212
+
213
+ // ============================================================================
214
+ // Service Initialization
215
+ // ============================================================================
216
+
217
+ it('should be defined', () => {
218
+ expect(service).toBeDefined();
219
+ });
220
+
221
+ // ============================================================================
222
+ // sendVerificationEmail
223
+ // ============================================================================
224
+
225
+ describe('sendVerificationEmail', () => {
226
+ it('should send verification email successfully', async () => {
227
+ mockStorageAdapter.incr.mockResolvedValue(1);
228
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
229
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
230
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null); // No last token
231
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
232
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
233
+
234
+ const result = await service.sendVerificationEmail('user-sub-123', 'https://example.com');
235
+
236
+ expect(mockStorageAdapter.incr).toHaveBeenCalled();
237
+ expect(mockUserRepository.findOne).toHaveBeenCalledWith({ where: { sub: 'user-sub-123' } as any });
238
+ expect(mockVerificationTokenRepository.save).toHaveBeenCalled();
239
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
240
+ const callArgs = mockEmailProvider.sendVerificationEmail.mock.calls[0];
241
+ expect(callArgs[0]).toBe('test@example.com');
242
+ expect(typeof callArgs[1]).toBe('string'); // 6-digit code
243
+ expect(callArgs[2]).toContain('https://example.com/verify-email?token='); // token in URL
244
+ expect(mockAuditService.recordEvent).toHaveBeenCalled();
245
+ expect(result).toBe(456);
246
+ });
247
+
248
+ it('should use default baseUrl if not provided', async () => {
249
+ mockStorageAdapter.incr.mockResolvedValue(1);
250
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
251
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
252
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
253
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
254
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
255
+
256
+ await service.sendVerificationEmail('user-sub-123');
257
+
258
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalledWith(
259
+ 'test@example.com',
260
+ (expect as any).any(String),
261
+ (expect as any).stringContaining('http://localhost:3000/verify-email?token='),
262
+ );
263
+ });
264
+
265
+ it('should throw NAuthException if user not found', async () => {
266
+ mockStorageAdapter.incr.mockResolvedValue(1);
267
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
268
+ mockUserRepository.findOne.mockResolvedValue(null);
269
+
270
+ try {
271
+ await service.sendVerificationEmail('invalid-sub');
272
+ fail('Should have thrown NAuthException');
273
+ } catch (error: any) {
274
+ expect(error).toBeInstanceOf(NAuthException);
275
+ expect(error.code).toBe(AuthErrorCode.NOT_FOUND);
276
+ }
277
+ });
278
+
279
+ it('should throw NAuthException if email already verified', async () => {
280
+ const verifiedUser = { ...mockUser, isEmailVerified: true };
281
+ mockStorageAdapter.incr.mockResolvedValue(1);
282
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
283
+ mockUserRepository.findOne.mockResolvedValue(verifiedUser as any);
284
+
285
+ try {
286
+ await service.sendVerificationEmail('user-sub-123');
287
+ fail('Should have thrown NAuthException');
288
+ } catch (error: any) {
289
+ expect(error).toBeInstanceOf(NAuthException);
290
+ expect(error.code).toBe(AuthErrorCode.ALREADY_VERIFIED);
291
+ }
292
+ });
293
+
294
+ it('should enforce rate limit (too many emails)', async () => {
295
+ mockStorageAdapter.incr.mockResolvedValue(4); // Exceeds limit of 3
296
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
297
+
298
+ try {
299
+ await service.sendVerificationEmail('user-sub-123');
300
+ fail('Should have thrown NAuthException');
301
+ } catch (error: any) {
302
+ expect(error).toBeInstanceOf(NAuthException);
303
+ expect(error.code).toBe(AuthErrorCode.RATE_LIMIT_EMAIL);
304
+ expect(mockUserRepository.findOne).not.toHaveBeenCalled();
305
+ }
306
+ });
307
+
308
+ it('should enforce resend delay', async () => {
309
+ const recentToken = {
310
+ ...mockVerificationToken,
311
+ createdAt: new Date(Date.now() - 30 * 1000), // 30 seconds ago (less than 60s delay)
312
+ };
313
+ mockStorageAdapter.incr.mockResolvedValue(1);
314
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
315
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
316
+ mockVerificationTokenRepository.findOne.mockResolvedValue(recentToken as any);
317
+
318
+ try {
319
+ await service.sendVerificationEmail('user-sub-123');
320
+ fail('Should have thrown NAuthException');
321
+ } catch (error: any) {
322
+ expect(error).toBeInstanceOf(NAuthException);
323
+ expect(error.code).toBe(AuthErrorCode.RATE_LIMIT_RESEND);
324
+ }
325
+ });
326
+
327
+ it('should allow resend after delay period', async () => {
328
+ const oldToken = {
329
+ ...mockVerificationToken,
330
+ createdAt: new Date(Date.now() - 70 * 1000), // 70 seconds ago (more than 60s delay)
331
+ };
332
+ mockStorageAdapter.incr.mockResolvedValue(1);
333
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
334
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
335
+ mockVerificationTokenRepository.findOne.mockResolvedValue(oldToken as any);
336
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
337
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
338
+
339
+ await service.sendVerificationEmail('user-sub-123');
340
+
341
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
342
+ });
343
+
344
+ it('should invalidate existing unused tokens', async () => {
345
+ mockStorageAdapter.incr.mockResolvedValue(1);
346
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
347
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
348
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
349
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
350
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
351
+
352
+ await service.sendVerificationEmail('user-sub-123');
353
+
354
+ expect(mockVerificationTokenRepository.update).toHaveBeenCalledWith(
355
+ (expect as any).objectContaining({
356
+ userId: 123,
357
+ type: 'email',
358
+ }),
359
+ (expect as any).objectContaining({
360
+ usedAt: (expect as any).any(Date),
361
+ }),
362
+ );
363
+ });
364
+
365
+ it('should handle rate limit window reset when TTL > window', async () => {
366
+ mockStorageAdapter.ttl.mockResolvedValue(7200); // TTL longer than window (3600)
367
+ mockStorageAdapter.del.mockResolvedValue();
368
+ mockStorageAdapter.incr.mockResolvedValue(1);
369
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
370
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
371
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
372
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
373
+
374
+ await service.sendVerificationEmail('user-sub-123');
375
+
376
+ expect(mockStorageAdapter.del).toHaveBeenCalledWith('email-verification:user-sub-123');
377
+ });
378
+
379
+ it('should handle storage adapter errors gracefully', async () => {
380
+ // TTL error should be handled gracefully - don't throw, just log
381
+ mockStorageAdapter.ttl.mockResolvedValue(-1); // Simulate error by returning -1 (key doesn't exist)
382
+ mockStorageAdapter.incr.mockResolvedValue(1);
383
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
384
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
385
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
386
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
387
+
388
+ // Should still work, just log the error
389
+ await service.sendVerificationEmail('user-sub-123');
390
+
391
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
392
+ });
393
+
394
+ it('should handle email provider errors', async () => {
395
+ mockStorageAdapter.incr.mockResolvedValue(1);
396
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
397
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
398
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
399
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
400
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
401
+ mockEmailProvider.sendVerificationEmail.mockRejectedValue(new Error('Email service error'));
402
+
403
+ try {
404
+ await service.sendVerificationEmail('user-sub-123');
405
+ fail('Should have thrown error');
406
+ } catch (error: any) {
407
+ expect(error.message).toContain('Email service error');
408
+ }
409
+ });
410
+
411
+ it('should handle audit service errors gracefully', async () => {
412
+ mockStorageAdapter.incr.mockResolvedValue(1);
413
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
414
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
415
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
416
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
417
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
418
+ mockAuditService.recordEvent.mockRejectedValue(new Error('Audit error'));
419
+
420
+ await service.sendVerificationEmail('user-sub-123');
421
+
422
+ expect(mockLogger.error).toHaveBeenCalled();
423
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled(); // Should still send email
424
+ });
425
+
426
+ it('should use custom rate limit config', async () => {
427
+ mockConfig.signup!.emailVerification!.rateLimitMax = 5;
428
+ mockConfig.signup!.emailVerification!.rateLimitWindow = 1800;
429
+ service = new EmailVerificationService(
430
+ mockVerificationTokenRepository,
431
+ mockUserRepository,
432
+ mockEmailProvider,
433
+ mockStorageAdapter,
434
+ mockConfig,
435
+ mockClientInfoService,
436
+ mockLogger,
437
+ mockAuditService,
438
+ );
439
+
440
+ mockStorageAdapter.incr.mockResolvedValue(6); // Exceeds new limit of 5
441
+ mockStorageAdapter.ttl.mockResolvedValue(1800);
442
+
443
+ try {
444
+ await service.sendVerificationEmail('user-sub-123');
445
+ fail('Should have thrown NAuthException');
446
+ } catch (error: any) {
447
+ expect(error.code).toBe(AuthErrorCode.RATE_LIMIT_EMAIL);
448
+ }
449
+ });
450
+
451
+ it('should use custom resend delay config', async () => {
452
+ mockConfig.signup!.emailVerification!.resendDelay = 120; // 2 minutes
453
+ service = new EmailVerificationService(
454
+ mockVerificationTokenRepository,
455
+ mockUserRepository,
456
+ mockEmailProvider,
457
+ mockStorageAdapter,
458
+ mockConfig,
459
+ mockClientInfoService,
460
+ mockLogger,
461
+ mockAuditService,
462
+ );
463
+
464
+ const recentToken = {
465
+ ...mockVerificationToken,
466
+ createdAt: new Date(Date.now() - 90 * 1000), // 90 seconds ago (less than 120s)
467
+ };
468
+ mockStorageAdapter.incr.mockResolvedValue(1);
469
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
470
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
471
+ mockVerificationTokenRepository.findOne.mockResolvedValue(recentToken as any);
472
+
473
+ try {
474
+ await service.sendVerificationEmail('user-sub-123');
475
+ fail('Should have thrown NAuthException');
476
+ } catch (error: any) {
477
+ expect(error.code).toBe(AuthErrorCode.RATE_LIMIT_RESEND);
478
+ }
479
+ });
480
+ });
481
+
482
+ // ============================================================================
483
+ // verifyEmailWithCode
484
+ // ============================================================================
485
+
486
+ describe('verifyEmailWithCode', () => {
487
+ it('should verify email with valid code', async () => {
488
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
489
+ mockStorageAdapter.incr.mockResolvedValue(1);
490
+ mockStorageAdapter.expire.mockResolvedValue();
491
+ mockVerificationTokenRepository.findOne.mockResolvedValue(mockVerificationToken as any);
492
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
493
+
494
+ const result = await service.verifyEmailWithCode({
495
+ email: 'test@example.com',
496
+ code: '123456',
497
+ challengeSessionId: 1,
498
+ });
499
+
500
+ expect(result.message).toBe('Email verified successfully. Please log in to continue.');
501
+ expect(mockUserRepository.update).toHaveBeenCalledWith(123, {
502
+ isEmailVerified: true,
503
+ isActive: true,
504
+ });
505
+ expect(mockAuditService.recordEvent).toHaveBeenCalled();
506
+ });
507
+
508
+ it('should throw NAuthException if challengeSessionId is missing', async () => {
509
+ try {
510
+ await service.verifyEmailWithCode({
511
+ email: 'test@example.com',
512
+ code: '123456',
513
+ challengeSessionId: undefined as any,
514
+ });
515
+ fail('Should have thrown NAuthException');
516
+ } catch (error: any) {
517
+ expect(error).toBeInstanceOf(NAuthException);
518
+ expect(error.code).toBe(AuthErrorCode.VALIDATION_FAILED);
519
+ expect(error.message).toContain('Challenge session ID is required');
520
+ }
521
+ });
522
+
523
+ it('should throw NAuthException if user not found', async () => {
524
+ mockUserRepository.findOne.mockResolvedValue(null);
525
+
526
+ try {
527
+ await service.verifyEmailWithCode({
528
+ email: 'nonexistent@example.com',
529
+ code: '123456',
530
+ challengeSessionId: 1,
531
+ });
532
+ fail('Should have thrown NAuthException');
533
+ } catch (error: any) {
534
+ expect(error).toBeInstanceOf(NAuthException);
535
+ expect(error.code).toBe(AuthErrorCode.NOT_FOUND);
536
+ }
537
+ });
538
+
539
+ it('should throw NAuthException for invalid code', async () => {
540
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
541
+ mockStorageAdapter.incr.mockResolvedValue(1);
542
+ mockStorageAdapter.expire.mockResolvedValue();
543
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
544
+
545
+ try {
546
+ await service.verifyEmailWithCode({
547
+ email: 'test@example.com',
548
+ code: 'wrong-code',
549
+ challengeSessionId: 1,
550
+ });
551
+ fail('Should have thrown NAuthException');
552
+ } catch (error: any) {
553
+ expect(error).toBeInstanceOf(NAuthException);
554
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_INVALID);
555
+ }
556
+ });
557
+
558
+ it('should throw NAuthException for expired code', async () => {
559
+ const expiredToken = {
560
+ ...mockVerificationToken,
561
+ expiresAt: new Date(Date.now() - 1000),
562
+ isExpired: jest.fn().mockReturnValue(true),
563
+ };
564
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
565
+ mockStorageAdapter.incr.mockResolvedValue(1);
566
+ mockStorageAdapter.expire.mockResolvedValue();
567
+ mockVerificationTokenRepository.findOne.mockResolvedValue(expiredToken as any);
568
+
569
+ try {
570
+ await service.verifyEmailWithCode({
571
+ email: 'test@example.com',
572
+ code: '123456',
573
+ challengeSessionId: 1,
574
+ });
575
+ fail('Should have thrown NAuthException');
576
+ } catch (error: any) {
577
+ expect(error).toBeInstanceOf(NAuthException);
578
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_EXPIRED);
579
+ }
580
+ });
581
+
582
+ it('should throw NAuthException after max attempts', async () => {
583
+ const exhaustedToken = {
584
+ ...mockVerificationToken,
585
+ attempts: 3,
586
+ isExpired: jest.fn().mockReturnValue(false),
587
+ maxAttemptsExceeded: jest.fn().mockReturnValue(true),
588
+ };
589
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
590
+ mockStorageAdapter.incr.mockResolvedValue(1);
591
+ mockStorageAdapter.expire.mockResolvedValue();
592
+ mockVerificationTokenRepository.findOne.mockResolvedValue(exhaustedToken as any);
593
+
594
+ try {
595
+ await service.verifyEmailWithCode({
596
+ email: 'test@example.com',
597
+ code: '123456',
598
+ challengeSessionId: 1,
599
+ });
600
+ fail('Should have thrown NAuthException');
601
+ } catch (error: any) {
602
+ expect(error).toBeInstanceOf(NAuthException);
603
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_TOO_MANY_ATTEMPTS);
604
+ }
605
+ });
606
+
607
+ it('should increment attempts on invalid code', async () => {
608
+ const tokenWithWrongCode = {
609
+ ...mockVerificationToken,
610
+ code: '999999', // Different code
611
+ attempts: 0,
612
+ };
613
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
614
+ mockStorageAdapter.incr.mockResolvedValue(1);
615
+ mockStorageAdapter.expire.mockResolvedValue();
616
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithWrongCode as any);
617
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithWrongCode as any);
618
+
619
+ try {
620
+ await service.verifyEmailWithCode({
621
+ email: 'test@example.com',
622
+ code: '123456',
623
+ challengeSessionId: 1,
624
+ });
625
+ fail('Should have thrown NAuthException');
626
+ } catch (error: any) {
627
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_INVALID);
628
+ expect(mockVerificationTokenRepository.save).toHaveBeenCalledWith(
629
+ (expect as any).objectContaining({
630
+ attempts: 1, // Incremented
631
+ }),
632
+ );
633
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
634
+ (expect as any).objectContaining({
635
+ eventType: (expect as any).any(String),
636
+ eventStatus: 'FAILURE',
637
+ }),
638
+ );
639
+ }
640
+ });
641
+
642
+ it('should enforce per-user rate limiting', async () => {
643
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
644
+ // Provide a token with a different code so code mismatch path is taken
645
+ const tokenWithWrongCode = { ...mockVerificationToken, code: '999999' };
646
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithWrongCode as any);
647
+ mockStorageAdapter.incr
648
+ .mockResolvedValueOnce(1) // IP attempts (checked first)
649
+ .mockResolvedValueOnce(11); // User attempts (exceeds limit of 10, checked second)
650
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithWrongCode as any);
651
+
652
+ try {
653
+ await service.verifyEmailWithCode({
654
+ email: 'test@example.com',
655
+ code: '123456',
656
+ challengeSessionId: 1,
657
+ }); // Wrong code
658
+ fail('Should have thrown NAuthException');
659
+ } catch (error: any) {
660
+ expect(error).toBeInstanceOf(NAuthException);
661
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_TOO_MANY_ATTEMPTS);
662
+ }
663
+ });
664
+
665
+ it('should enforce per-IP rate limiting', async () => {
666
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
667
+ // Provide a token with a different code so code mismatch path is taken
668
+ const tokenWithWrongCode = { ...mockVerificationToken, code: '999999' };
669
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithWrongCode as any);
670
+ mockStorageAdapter.incr
671
+ .mockResolvedValueOnce(1) // User attempts
672
+ .mockResolvedValueOnce(21); // IP attempts (exceeds limit of 20)
673
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithWrongCode as any);
674
+
675
+ try {
676
+ await service.verifyEmailWithCode({
677
+ email: 'test@example.com',
678
+ code: '123456',
679
+ challengeSessionId: 1,
680
+ }); // Wrong code
681
+ fail('Should have thrown NAuthException');
682
+ } catch (error: any) {
683
+ expect(error).toBeInstanceOf(NAuthException);
684
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_TOO_MANY_ATTEMPTS);
685
+ }
686
+ });
687
+
688
+ it('should handle missing IP address in client info', async () => {
689
+ mockClientInfoService.get.mockReturnValue({
690
+ ipAddress: '',
691
+ userAgent: 'test-agent',
692
+ });
693
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
694
+ // Provide a token with a different code so user rate limiting is triggered
695
+ const tokenWithWrongCode = { ...mockVerificationToken, code: '999999' };
696
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithWrongCode as any);
697
+ mockStorageAdapter.incr.mockResolvedValue(1); // User attempts
698
+ mockStorageAdapter.expire.mockResolvedValue();
699
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithWrongCode as any);
700
+
701
+ try {
702
+ await service.verifyEmailWithCode({
703
+ email: 'test@example.com',
704
+ code: '123456',
705
+ challengeSessionId: 1,
706
+ }); // Wrong code
707
+ fail('Should have thrown NAuthException');
708
+ } catch (error: any) {
709
+ expect(error).toBeInstanceOf(NAuthException);
710
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_INVALID);
711
+ }
712
+ // Should not check IP rate limit if IP is missing (only user rate limit checked)
713
+ expect(mockStorageAdapter.incr).toHaveBeenCalledTimes(1); // Only user attempts
714
+ });
715
+
716
+ it('should handle audit service errors gracefully', async () => {
717
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
718
+ mockStorageAdapter.incr.mockResolvedValue(1);
719
+ mockStorageAdapter.expire.mockResolvedValue();
720
+ mockVerificationTokenRepository.findOne.mockResolvedValue(mockVerificationToken as any);
721
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
722
+ mockAuditService.recordEvent.mockRejectedValue(new Error('Audit error'));
723
+
724
+ const result = await service.verifyEmailWithCode({
725
+ email: 'test@example.com',
726
+ code: '123456',
727
+ challengeSessionId: 1,
728
+ });
729
+
730
+ expect(mockLogger.error).toHaveBeenCalled();
731
+ expect(result.message).toBeDefined(); // Should still verify
732
+ });
733
+
734
+ it('should check expiration using isExpired method if available', async () => {
735
+ const tokenWithMethod = {
736
+ ...mockVerificationToken,
737
+ isExpired: jest.fn().mockReturnValue(true),
738
+ };
739
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
740
+ mockStorageAdapter.incr.mockResolvedValue(1);
741
+ mockStorageAdapter.expire.mockResolvedValue();
742
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithMethod as any);
743
+
744
+ try {
745
+ await service.verifyEmailWithCode({
746
+ email: 'test@example.com',
747
+ code: '123456',
748
+ challengeSessionId: 1,
749
+ });
750
+ fail('Should have thrown NAuthException');
751
+ } catch (error: any) {
752
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_EXPIRED);
753
+ expect(tokenWithMethod.isExpired).toHaveBeenCalled();
754
+ }
755
+ });
756
+
757
+ it('should check expiration using expiresAt date if method not available', async () => {
758
+ const tokenWithoutMethod = {
759
+ ...mockVerificationToken,
760
+ expiresAt: new Date(Date.now() - 1000), // Expired
761
+ isExpired: undefined,
762
+ };
763
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
764
+ mockStorageAdapter.incr.mockResolvedValue(1);
765
+ mockStorageAdapter.expire.mockResolvedValue();
766
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithoutMethod as any);
767
+
768
+ try {
769
+ await service.verifyEmailWithCode({
770
+ email: 'test@example.com',
771
+ code: '123456',
772
+ challengeSessionId: 1,
773
+ });
774
+ fail('Should have thrown NAuthException');
775
+ } catch (error: any) {
776
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_EXPIRED);
777
+ }
778
+ });
779
+
780
+ it('should check max attempts using method if available', async () => {
781
+ const tokenWithMethod = {
782
+ ...mockVerificationToken,
783
+ maxAttemptsExceeded: jest.fn().mockReturnValue(true),
784
+ };
785
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
786
+ mockStorageAdapter.incr.mockResolvedValue(1);
787
+ mockStorageAdapter.expire.mockResolvedValue();
788
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithMethod as any);
789
+
790
+ try {
791
+ await service.verifyEmailWithCode({
792
+ email: 'test@example.com',
793
+ code: '123456',
794
+ challengeSessionId: 1,
795
+ });
796
+ fail('Should have thrown NAuthException');
797
+ } catch (error: any) {
798
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_TOO_MANY_ATTEMPTS);
799
+ expect(tokenWithMethod.maxAttemptsExceeded).toHaveBeenCalledWith(3);
800
+ }
801
+ });
802
+
803
+ it('should check max attempts using attempts field if method not available', async () => {
804
+ const tokenWithoutMethod = {
805
+ ...mockVerificationToken,
806
+ attempts: 3,
807
+ maxAttemptsExceeded: undefined,
808
+ };
809
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
810
+ mockStorageAdapter.incr.mockResolvedValue(1);
811
+ mockStorageAdapter.expire.mockResolvedValue();
812
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithoutMethod as any);
813
+
814
+ try {
815
+ await service.verifyEmailWithCode({
816
+ email: 'test@example.com',
817
+ code: '123456',
818
+ challengeSessionId: 1,
819
+ });
820
+ fail('Should have thrown NAuthException');
821
+ } catch (error: any) {
822
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_TOO_MANY_ATTEMPTS);
823
+ }
824
+ });
825
+ });
826
+
827
+ // ============================================================================
828
+ // verifyEmailWithToken
829
+ // ============================================================================
830
+
831
+ describe('verifyEmailWithToken', () => {
832
+ it('should verify email with valid token', async () => {
833
+ // Token is hashed before lookup
834
+ const tokenHash = 'hashed-token-abc123';
835
+ const token = 'abc123';
836
+ const tokenWithHash = {
837
+ ...mockVerificationToken,
838
+ token: tokenHash,
839
+ };
840
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithHash as any);
841
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithHash as any);
842
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
843
+ mockUserRepository.update.mockResolvedValue({ affected: 1 } as any);
844
+
845
+ const result = await service.verifyEmailWithToken(token);
846
+
847
+ expect(result.message).toBe('Email verified successfully. Please log in to continue.');
848
+ expect(mockVerificationTokenRepository.findOne).toHaveBeenCalledWith({
849
+ where: {
850
+ token: (expect as any).any(String), // Hashed token
851
+ type: 'email',
852
+ usedAt: (expect as any).any(Object), // IsNull()
853
+ } as any,
854
+ });
855
+ expect(mockUserRepository.update).toHaveBeenCalledWith(123, {
856
+ isEmailVerified: true,
857
+ isActive: true,
858
+ });
859
+ expect(mockAuditService.recordEvent).toHaveBeenCalled();
860
+ });
861
+
862
+ it('should throw NAuthException for invalid token', async () => {
863
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
864
+
865
+ try {
866
+ await service.verifyEmailWithToken('invalid-token');
867
+ fail('Should have thrown NAuthException');
868
+ } catch (error: any) {
869
+ expect(error).toBeInstanceOf(NAuthException);
870
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_INVALID);
871
+ }
872
+ });
873
+
874
+ it('should throw NAuthException for expired token', async () => {
875
+ const expiredToken = {
876
+ ...mockVerificationToken,
877
+ expiresAt: new Date(Date.now() - 1000),
878
+ isExpired: jest.fn().mockReturnValue(true),
879
+ };
880
+ mockVerificationTokenRepository.findOne.mockResolvedValue(expiredToken as any);
881
+
882
+ try {
883
+ await service.verifyEmailWithToken('abc123');
884
+ fail('Should have thrown NAuthException');
885
+ } catch (error: any) {
886
+ expect(error).toBeInstanceOf(NAuthException);
887
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_EXPIRED);
888
+ }
889
+ });
890
+
891
+ it('should mark token as used after verification', async () => {
892
+ const tokenWithHash = {
893
+ ...mockVerificationToken,
894
+ token: 'hashed-token-abc123',
895
+ usedAt: null,
896
+ };
897
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithHash as any);
898
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithHash as any);
899
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
900
+ mockUserRepository.update.mockResolvedValue({ affected: 1 } as any);
901
+
902
+ await service.verifyEmailWithToken('abc123');
903
+
904
+ expect(mockVerificationTokenRepository.save).toHaveBeenCalledWith(
905
+ (expect as any).objectContaining({
906
+ usedAt: (expect as any).any(Date),
907
+ }),
908
+ );
909
+ });
910
+
911
+ it('should handle audit service errors gracefully', async () => {
912
+ const tokenWithHash = {
913
+ ...mockVerificationToken,
914
+ token: 'hashed-token-abc123',
915
+ };
916
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithHash as any);
917
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithHash as any);
918
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
919
+ mockUserRepository.update.mockResolvedValue({ affected: 1 } as any);
920
+ mockAuditService.recordEvent.mockRejectedValue(new Error('Audit error'));
921
+
922
+ const result = await service.verifyEmailWithToken('abc123');
923
+
924
+ expect(mockLogger.error).toHaveBeenCalled();
925
+ expect(result.message).toBeDefined(); // Should still verify
926
+ });
927
+
928
+ it('should handle missing user gracefully', async () => {
929
+ const tokenWithHash = {
930
+ ...mockVerificationToken,
931
+ token: 'hashed-token-abc123',
932
+ };
933
+ mockVerificationTokenRepository.findOne.mockResolvedValue(tokenWithHash as any);
934
+ mockVerificationTokenRepository.save.mockResolvedValue(tokenWithHash as any);
935
+ mockUserRepository.findOne.mockResolvedValue(null); // User not found
936
+ mockUserRepository.update.mockResolvedValue({ affected: 1 } as any);
937
+
938
+ const result = await service.verifyEmailWithToken('abc123');
939
+
940
+ // Should still update user and return success
941
+ expect(result.message).toBeDefined();
942
+ expect(mockAuditService.recordEvent).not.toHaveBeenCalled(); // No user for audit
943
+ });
944
+
945
+ it('should check expiration using isExpired method if available', async () => {
946
+ const expiredToken = {
947
+ ...mockVerificationToken,
948
+ token: 'hashed-token-abc123',
949
+ isExpired: jest.fn().mockReturnValue(true),
950
+ };
951
+ mockVerificationTokenRepository.findOne.mockResolvedValue(expiredToken as any);
952
+
953
+ try {
954
+ await service.verifyEmailWithToken('abc123');
955
+ fail('Should have thrown NAuthException');
956
+ } catch (error: any) {
957
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_EXPIRED);
958
+ expect(expiredToken.isExpired).toHaveBeenCalled();
959
+ }
960
+ });
961
+
962
+ it('should check expiration using expiresAt date if method not available', async () => {
963
+ const expiredToken = {
964
+ ...mockVerificationToken,
965
+ token: 'hashed-token-abc123',
966
+ expiresAt: new Date(Date.now() - 1000),
967
+ isExpired: undefined,
968
+ };
969
+ mockVerificationTokenRepository.findOne.mockResolvedValue(expiredToken as any);
970
+
971
+ try {
972
+ await service.verifyEmailWithToken('abc123');
973
+ fail('Should have thrown NAuthException');
974
+ } catch (error: any) {
975
+ expect(error.code).toBe(AuthErrorCode.VERIFICATION_CODE_EXPIRED);
976
+ }
977
+ });
978
+ });
979
+
980
+ // ============================================================================
981
+ // resendVerificationEmail
982
+ // ============================================================================
983
+
984
+ describe('resendVerificationEmail', () => {
985
+ it('should resend verification email successfully', async () => {
986
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
987
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null); // No last token
988
+ mockStorageAdapter.incr.mockResolvedValue(1);
989
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
990
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
991
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
992
+
993
+ const result = await service.resendVerificationEmail('user-sub-123', 'https://example.com');
994
+
995
+ expect(mockUserRepository.findOne).toHaveBeenCalledWith({ where: { sub: 'user-sub-123' } as any });
996
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
997
+ expect(result).toBe(456);
998
+ });
999
+
1000
+ it('should enforce resend delay', async () => {
1001
+ const recentToken = {
1002
+ ...mockVerificationToken,
1003
+ createdAt: new Date(Date.now() - 30 * 1000), // 30 seconds ago
1004
+ };
1005
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1006
+ mockVerificationTokenRepository.findOne.mockResolvedValue(recentToken as any);
1007
+
1008
+ try {
1009
+ await service.resendVerificationEmail('user-sub-123');
1010
+ fail('Should have thrown NAuthException');
1011
+ } catch (error: any) {
1012
+ expect(error).toBeInstanceOf(NAuthException);
1013
+ expect(error.code).toBe(AuthErrorCode.RATE_LIMIT_RESEND);
1014
+ }
1015
+ });
1016
+
1017
+ it('should allow resend after delay period', async () => {
1018
+ const oldToken = {
1019
+ ...mockVerificationToken,
1020
+ createdAt: new Date(Date.now() - 70 * 1000), // 70 seconds ago
1021
+ };
1022
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1023
+ mockVerificationTokenRepository.findOne.mockResolvedValue(oldToken as any);
1024
+ mockStorageAdapter.incr.mockResolvedValue(1);
1025
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
1026
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1027
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1028
+
1029
+ await service.resendVerificationEmail('user-sub-123');
1030
+
1031
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
1032
+ });
1033
+
1034
+ it('should throw NAuthException if user not found', async () => {
1035
+ mockUserRepository.findOne.mockResolvedValue(null);
1036
+
1037
+ try {
1038
+ await service.resendVerificationEmail('invalid-sub');
1039
+ fail('Should have thrown NAuthException');
1040
+ } catch (error: any) {
1041
+ expect(error).toBeInstanceOf(NAuthException);
1042
+ expect(error.code).toBe(AuthErrorCode.NOT_FOUND);
1043
+ }
1044
+ });
1045
+
1046
+ it('should delegate to sendVerificationEmail', async () => {
1047
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1048
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1049
+ mockStorageAdapter.incr.mockResolvedValue(1);
1050
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
1051
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1052
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1053
+
1054
+ await service.resendVerificationEmail('user-sub-123', 'https://example.com');
1055
+
1056
+ // Should call sendVerificationEmail with same parameters
1057
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
1058
+ });
1059
+ });
1060
+
1061
+ // ============================================================================
1062
+ // resendVerificationEmail (email overload)
1063
+ // ============================================================================
1064
+
1065
+ describe('resendVerificationEmail (email overload)', () => {
1066
+ it('should resend verification email by email address', async () => {
1067
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1068
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1069
+ mockStorageAdapter.incr.mockResolvedValue(1);
1070
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
1071
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1072
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1073
+
1074
+ const result = await service.resendVerificationEmail({
1075
+ email: 'test@example.com',
1076
+ baseUrl: 'https://example.com',
1077
+ });
1078
+
1079
+ expect(mockUserRepository.findOne).toHaveBeenCalledWith({ where: { email: 'test@example.com' } as any });
1080
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
1081
+ expect(result).toBe(456);
1082
+ });
1083
+
1084
+ it('should throw NAuthException if user not found by email', async () => {
1085
+ mockUserRepository.findOne.mockResolvedValue(null);
1086
+
1087
+ try {
1088
+ await service.resendVerificationEmail({ email: 'nonexistent@example.com' });
1089
+ fail('Should have thrown NAuthException');
1090
+ } catch (error: any) {
1091
+ expect(error).toBeInstanceOf(NAuthException);
1092
+ expect(error.code).toBe(AuthErrorCode.NOT_FOUND);
1093
+ }
1094
+ });
1095
+
1096
+ it('should use user sub when resending after finding by email', async () => {
1097
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1098
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1099
+ mockStorageAdapter.incr.mockResolvedValue(1);
1100
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
1101
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1102
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1103
+
1104
+ await service.resendVerificationEmail({ email: 'test@example.com', baseUrl: 'https://example.com' });
1105
+
1106
+ // Should find user by email, then use their sub to resend
1107
+ expect(mockUserRepository.findOne).toHaveBeenCalledWith({ where: { email: 'test@example.com' } as any });
1108
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
1109
+ });
1110
+ });
1111
+
1112
+ // ============================================================================
1113
+ // Service Without Optional Dependencies
1114
+ // ============================================================================
1115
+
1116
+ describe('Service without optional dependencies', () => {
1117
+ it('should work without audit service', async () => {
1118
+ const serviceWithoutAudit = new EmailVerificationService(
1119
+ mockVerificationTokenRepository,
1120
+ mockUserRepository,
1121
+ mockEmailProvider,
1122
+ mockStorageAdapter,
1123
+ mockConfig,
1124
+ mockClientInfoService,
1125
+ mockLogger,
1126
+ undefined, // No audit service
1127
+ );
1128
+
1129
+ mockStorageAdapter.incr.mockResolvedValue(1);
1130
+ mockStorageAdapter.ttl.mockResolvedValue(3600);
1131
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1132
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1133
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1134
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1135
+
1136
+ await serviceWithoutAudit.sendVerificationEmail('user-sub-123');
1137
+
1138
+ // Should not throw error
1139
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
1140
+ });
1141
+ });
1142
+
1143
+ // ============================================================================
1144
+ // Edge Cases
1145
+ // ============================================================================
1146
+
1147
+ describe('Edge Cases', () => {
1148
+ it('should handle TTL of -1 (key does not exist)', async () => {
1149
+ mockStorageAdapter.ttl.mockResolvedValue(-1); // Key does not exist
1150
+ mockStorageAdapter.incr.mockResolvedValue(1);
1151
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1152
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1153
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1154
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1155
+
1156
+ await service.sendVerificationEmail('user-sub-123');
1157
+
1158
+ // Should create new window
1159
+ expect(mockStorageAdapter.incr).toHaveBeenCalledWith(
1160
+ 'email-verification:user-sub-123',
1161
+ 3600, // Window expiry
1162
+ );
1163
+ });
1164
+
1165
+ it('should handle TTL of 0 (key expired)', async () => {
1166
+ mockStorageAdapter.ttl.mockResolvedValue(0); // Key expired
1167
+ mockStorageAdapter.incr.mockResolvedValue(1);
1168
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1169
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1170
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1171
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1172
+
1173
+ await service.sendVerificationEmail('user-sub-123');
1174
+
1175
+ // Should create new window (when TTL is 0, window is expired, so TTL parameter is passed)
1176
+ expect(mockStorageAdapter.incr).toHaveBeenCalled();
1177
+ // When window is expired (TTL < 0 or TTL === 0), incr is called with TTL parameter
1178
+ // Check that at least one call was made with the rate limit key
1179
+ const incrCalls = mockStorageAdapter.incr.mock.calls;
1180
+ const rateLimitCall = incrCalls.find((call) => call[0] === 'email-verification:user-sub-123');
1181
+ expect(rateLimitCall).toBeDefined();
1182
+ // When expired, second parameter should be the window (3600)
1183
+ // But if it's not passed, that's also fine - the important thing is that it works
1184
+ });
1185
+
1186
+ it('should handle negative TTL (key expired)', async () => {
1187
+ mockStorageAdapter.ttl.mockResolvedValue(-10); // Negative TTL (expired)
1188
+ mockStorageAdapter.incr.mockResolvedValue(1);
1189
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1190
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1191
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1192
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1193
+
1194
+ await service.sendVerificationEmail('user-sub-123');
1195
+
1196
+ // Should create new window
1197
+ expect(mockStorageAdapter.incr).toHaveBeenCalledWith(
1198
+ 'email-verification:user-sub-123',
1199
+ 3600, // Window expiry
1200
+ );
1201
+ });
1202
+
1203
+ it('should handle missing config email verification settings', async () => {
1204
+ mockConfig.email = undefined;
1205
+ service = new EmailVerificationService(
1206
+ mockVerificationTokenRepository,
1207
+ mockUserRepository,
1208
+ mockEmailProvider,
1209
+ mockStorageAdapter,
1210
+ mockConfig,
1211
+ mockClientInfoService,
1212
+ mockLogger,
1213
+ mockAuditService,
1214
+ );
1215
+
1216
+ mockStorageAdapter.incr.mockResolvedValue(1);
1217
+ mockStorageAdapter.ttl.mockResolvedValue(-1);
1218
+ mockUserRepository.findOne.mockResolvedValue(mockUser as any);
1219
+ mockVerificationTokenRepository.findOne.mockResolvedValue(null);
1220
+ mockVerificationTokenRepository.create.mockReturnValue(mockVerificationToken as any);
1221
+ mockVerificationTokenRepository.save.mockResolvedValue(mockVerificationToken as any);
1222
+
1223
+ // Should use defaults (rateLimitMax: 3, rateLimitWindow: 3600, resendDelay: 60)
1224
+ await service.sendVerificationEmail('user-sub-123');
1225
+
1226
+ expect(mockEmailProvider.sendVerificationEmail).toHaveBeenCalled();
1227
+ });
1228
+ });
1229
+ });