@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,1363 @@
1
+ import { Repository, SelectQueryBuilder } from 'typeorm';
2
+ import { ChallengeService } from './challenge.service';
3
+ import { NAuthException } from '../exceptions/nauth.exception';
4
+ import { IUser, IChallengeSession } from '../interfaces/entities.interface';
5
+ import { AuthChallenge } from '../dto/auth-challenge.dto';
6
+ import { NAuthLogger } from '../utils/nauth-logger';
7
+ import { AuthAuditService } from './auth-audit.service';
8
+ import { AuthAuditEventType } from '../enums/auth-audit-event-type.enum';
9
+ import { AuthErrorCode } from '../enums/error-codes.enum';
10
+ import { BaseChallengeSession } from '../entities';
11
+ import { ClientInfoService } from './client-info.service';
12
+
13
+ /**
14
+ * Challenge Service Unit Tests
15
+ *
16
+ * Tests challenge session creation, validation, consumption, and cleanup.
17
+ * Covers all challenge types, expiration, max attempts, and edge cases.
18
+ *
19
+ * Platform-agnostic: Uses direct instantiation, no NestJS dependencies.
20
+ */
21
+ describe('ChallengeService', () => {
22
+ let service: ChallengeService;
23
+ let mockChallengeSessionRepository: jest.Mocked<Repository<BaseChallengeSession>>;
24
+ let mockClientInfoService: jest.Mocked<ClientInfoService>;
25
+ let mockAuditService: jest.Mocked<AuthAuditService>;
26
+ let mockLogger: jest.Mocked<NAuthLogger>;
27
+ let mockQueryBuilder: jest.Mocked<SelectQueryBuilder<BaseChallengeSession>>;
28
+
29
+ const mockUser: Partial<IUser> = {
30
+ id: 1,
31
+ sub: 'user-uuid-123',
32
+ email: 'test@example.com',
33
+ phone: '+1234567890',
34
+ isEmailVerified: false,
35
+ isPhoneVerified: false,
36
+ };
37
+
38
+ const mockChallengeSession: Partial<IChallengeSession> = {
39
+ id: 1,
40
+ userId: 1,
41
+ user: mockUser as IUser,
42
+ challengeName: AuthChallenge.VERIFY_EMAIL,
43
+ sessionToken: 'session-token-123',
44
+ expiresAt: new Date(Date.now() + 15 * 60 * 1000), // 15 minutes from now
45
+ isCompleted: false,
46
+ completedAt: null,
47
+ attempts: 0,
48
+ maxAttempts: 3,
49
+ ipAddress: '1.2.3.4',
50
+ userAgent: 'test-user-agent',
51
+ createdAt: new Date(),
52
+ };
53
+
54
+ beforeEach(() => {
55
+ // Create mock query builder
56
+ mockQueryBuilder = {
57
+ leftJoinAndSelect: jest.fn().mockReturnThis(),
58
+ where: jest.fn().mockReturnThis(),
59
+ select: jest.fn().mockReturnThis(),
60
+ getOne: jest.fn(),
61
+ } as any;
62
+
63
+ // Create mock repository
64
+ mockChallengeSessionRepository = {
65
+ create: jest.fn(),
66
+ save: jest.fn(),
67
+ delete: jest.fn(),
68
+ findOne: jest.fn(),
69
+ createQueryBuilder: jest.fn(() => mockQueryBuilder),
70
+ } as any;
71
+
72
+ // Create mock services
73
+
74
+ mockAuditService = {
75
+ recordEvent: jest.fn(),
76
+ } as any;
77
+
78
+ mockLogger = {
79
+ log: jest.fn(),
80
+ error: jest.fn(),
81
+ warn: jest.fn(),
82
+ debug: jest.fn(),
83
+ } as any;
84
+
85
+ mockClientInfoService = {
86
+ get: jest.fn().mockReturnValue({
87
+ ipAddress: '1.2.3.4',
88
+ userAgent: 'test-user-agent',
89
+ }),
90
+ } as any;
91
+
92
+ // Instantiate service directly
93
+ service = new ChallengeService(mockChallengeSessionRepository, mockClientInfoService, mockLogger, mockAuditService);
94
+ });
95
+
96
+ afterEach(() => {
97
+ jest.clearAllMocks();
98
+ });
99
+
100
+ // ============================================================================
101
+ // Service Initialization
102
+ // ============================================================================
103
+
104
+ it('should be defined', () => {
105
+ expect(service).toBeDefined();
106
+ });
107
+
108
+ // ============================================================================
109
+ // createChallengeSession() Method
110
+ // ============================================================================
111
+
112
+ describe('createChallengeSession', () => {
113
+ it('should create a challenge session successfully', async () => {
114
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
115
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
116
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
117
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
118
+
119
+ const result = await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL, {
120
+ email: mockUser.email,
121
+ });
122
+
123
+ expect(result).toBeDefined();
124
+ expect(result.challengeName).toBe(AuthChallenge.VERIFY_EMAIL);
125
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalled();
126
+ expect(mockChallengeSessionRepository.save).toHaveBeenCalled();
127
+ expect(mockLogger.log).toHaveBeenCalled();
128
+ });
129
+
130
+ it('should cleanup expired sessions before creating new one', async () => {
131
+ const deleteSpy = mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 2 } as any);
132
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
133
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
134
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
135
+
136
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
137
+
138
+ expect(deleteSpy).toHaveBeenCalledTimes(2); // Once for expired, once for completed
139
+ });
140
+
141
+ it('should throttle cleanup to once per 5 minutes per user', async () => {
142
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
143
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
144
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
145
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
146
+
147
+ // Create first session
148
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
149
+ const firstDeleteCount = mockChallengeSessionRepository.delete.mock.calls.length;
150
+
151
+ // Create second session immediately (should not trigger cleanup again)
152
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_PHONE);
153
+ const secondDeleteCount = mockChallengeSessionRepository.delete.mock.calls.length;
154
+
155
+ // Cleanup should only run once (first call)
156
+ expect(secondDeleteCount).toBe(firstDeleteCount);
157
+ });
158
+
159
+ it('should create session with default expiration (15 minutes)', async () => {
160
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
161
+ const createdSession = { ...mockChallengeSession, expiresAt: new Date() } as any;
162
+ mockChallengeSessionRepository.create.mockReturnValue(createdSession);
163
+ mockChallengeSessionRepository.save.mockResolvedValue(createdSession);
164
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
165
+
166
+ jest.useFakeTimers();
167
+ const now = Date.now();
168
+ jest.setSystemTime(now);
169
+
170
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
171
+
172
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalledWith(
173
+ (expect as any).objectContaining({
174
+ expiresAt: (expect as any).any(Date),
175
+ }),
176
+ );
177
+
178
+ const createdCall = mockChallengeSessionRepository.create.mock.calls[0][0] as any;
179
+ const expectedExpiry = new Date(now + 15 * 60 * 1000);
180
+ if (createdCall?.expiresAt) {
181
+ expect(createdCall.expiresAt.getTime()).toBeCloseTo(expectedExpiry.getTime(), -2); // Within 100ms
182
+ }
183
+
184
+ jest.useRealTimers();
185
+ });
186
+
187
+ it('should create session with provided metadata', async () => {
188
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
189
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
190
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
191
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
192
+
193
+ const metadata = { email: 'test@example.com', verificationTokenId: 123 };
194
+
195
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL, metadata);
196
+
197
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalledWith(
198
+ (expect as any).objectContaining({
199
+ metadata,
200
+ }),
201
+ );
202
+ });
203
+
204
+ it('should create session with provided IP and user agent', async () => {
205
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
206
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
207
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
208
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
209
+
210
+ mockClientInfoService.get.mockReturnValue({
211
+ ipAddress: '192.168.1.1',
212
+ userAgent: 'Custom-Agent',
213
+ });
214
+
215
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL, undefined);
216
+
217
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalledWith(
218
+ (expect as any).objectContaining({
219
+ ipAddress: '192.168.1.1',
220
+ userAgent: 'Custom-Agent',
221
+ }),
222
+ );
223
+ });
224
+
225
+ it('should record audit event on session creation', async () => {
226
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
227
+ const sessionWithId = { ...mockChallengeSession, id: 1 } as any;
228
+ mockChallengeSessionRepository.create.mockReturnValue(sessionWithId);
229
+ mockChallengeSessionRepository.save.mockResolvedValue(sessionWithId);
230
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
231
+
232
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
233
+
234
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
235
+ (expect as any).objectContaining({
236
+ eventType: AuthAuditEventType.CHALLENGE_CREATED,
237
+ eventStatus: 'INFO',
238
+ userId: mockUser.id,
239
+ }),
240
+ );
241
+ });
242
+
243
+ it('should handle audit service errors gracefully', async () => {
244
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
245
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
246
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
247
+ mockAuditService.recordEvent.mockRejectedValue(new Error('Audit service error'));
248
+
249
+ const result = await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
250
+
251
+ expect(result).toBeDefined();
252
+ expect(mockLogger.error).toHaveBeenCalled();
253
+ });
254
+
255
+ it('should handle non-Error audit exceptions', async () => {
256
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
257
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
258
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
259
+ mockAuditService.recordEvent.mockRejectedValue('String error' as any);
260
+
261
+ const result = await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
262
+
263
+ expect(result).toBeDefined();
264
+ expect(mockLogger.error).toHaveBeenCalledWith(
265
+ (expect as any).stringContaining('Failed to record CHALLENGE_CREATED audit event: Unknown error'),
266
+ (expect as any).any(Object),
267
+ );
268
+ });
269
+
270
+ it('should reuse existing active challenge session (deduplication)', async () => {
271
+ const existingSession = {
272
+ ...mockChallengeSession,
273
+ id: 123,
274
+ sessionToken: 'existing-token-456',
275
+ expiresAt: new Date(Date.now() + 10 * 60 * 1000), // 10 minutes from now (not expired)
276
+ isCompleted: false,
277
+ user: mockUser,
278
+ };
279
+
280
+ // Mock finding an existing active session
281
+ mockChallengeSessionRepository.findOne.mockResolvedValue(existingSession as any);
282
+
283
+ const result = await service.createChallengeSession(mockUser as IUser, AuthChallenge.MFA_SETUP_REQUIRED);
284
+
285
+ // Should return existing session
286
+ expect(result.sessionToken).toBe('existing-token-456');
287
+ expect(result.id).toBe(123);
288
+
289
+ // Should NOT create a new session
290
+ expect(mockChallengeSessionRepository.create).not.toHaveBeenCalled();
291
+ expect(mockChallengeSessionRepository.save).not.toHaveBeenCalled();
292
+
293
+ // Should NOT record a duplicate CHALLENGE_CREATED audit event
294
+ expect(mockAuditService.recordEvent).not.toHaveBeenCalled();
295
+ });
296
+
297
+ it('should create new session if existing session is expired', async () => {
298
+ const expiredSession = {
299
+ ...mockChallengeSession,
300
+ id: 123,
301
+ sessionToken: 'expired-token',
302
+ expiresAt: new Date(Date.now() - 1000), // Expired 1 second ago
303
+ isCompleted: false,
304
+ user: mockUser,
305
+ };
306
+
307
+ // Mock finding an expired session
308
+ mockChallengeSessionRepository.findOne.mockResolvedValue(expiredSession as any);
309
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 1 } as any);
310
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
311
+ mockChallengeSessionRepository.save.mockResolvedValue({ ...mockChallengeSession, id: 124 } as any);
312
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
313
+
314
+ const result = await service.createChallengeSession(mockUser as IUser, AuthChallenge.MFA_SETUP_REQUIRED);
315
+
316
+ // Should delete expired session
317
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledWith({ id: 123 });
318
+
319
+ // Should create a new session
320
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalled();
321
+ expect(mockChallengeSessionRepository.save).toHaveBeenCalled();
322
+
323
+ // Should record new CHALLENGE_CREATED audit event
324
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
325
+ (expect as any).objectContaining({
326
+ eventType: AuthAuditEventType.CHALLENGE_CREATED,
327
+ }),
328
+ );
329
+ });
330
+
331
+ it('should create new session if no existing session found', async () => {
332
+ // Mock no existing session
333
+ mockChallengeSessionRepository.findOne.mockResolvedValue(null);
334
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
335
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
336
+ mockChallengeSessionRepository.save.mockResolvedValue({ ...mockChallengeSession, id: 125 } as any);
337
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
338
+
339
+ const result = await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
340
+
341
+ // Should create a new session
342
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalled();
343
+ expect(mockChallengeSessionRepository.save).toHaveBeenCalled();
344
+
345
+ // Should record CHALLENGE_CREATED audit event
346
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
347
+ (expect as any).objectContaining({
348
+ eventType: AuthAuditEventType.CHALLENGE_CREATED,
349
+ }),
350
+ );
351
+ });
352
+
353
+ it('should use client info from ClientInfoService in audit event', async () => {
354
+ mockChallengeSessionRepository.findOne.mockResolvedValue(null);
355
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
356
+ const sessionWithId = { ...mockChallengeSession, id: 1 } as any;
357
+ mockChallengeSessionRepository.create.mockReturnValue(sessionWithId);
358
+ mockChallengeSessionRepository.save.mockResolvedValue(sessionWithId);
359
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
360
+
361
+ mockClientInfoService.get.mockReturnValue({
362
+ ipAddress: '192.168.1.100',
363
+ userAgent: 'Custom-Agent-String',
364
+ });
365
+
366
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL, undefined);
367
+
368
+ // Audit service now gets IP and userAgent from ClientInfoService automatically
369
+ // Verify that recordEvent was called (the audit service will extract client info from context)
370
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
371
+ (expect as any).objectContaining({
372
+ userId: mockUser.id,
373
+ eventType: AuthAuditEventType.CHALLENGE_CREATED,
374
+ eventStatus: 'INFO',
375
+ }),
376
+ );
377
+ // Verify that ClientInfoService was called to get client info
378
+ expect(mockClientInfoService.get).toHaveBeenCalled();
379
+ });
380
+
381
+ it('should create MFA challenge sessions with metadata', async () => {
382
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
383
+ const mfaSession = {
384
+ ...mockChallengeSession,
385
+ challengeName: AuthChallenge.MFA_REQUIRED,
386
+ } as any;
387
+ mockChallengeSessionRepository.create.mockReturnValue(mfaSession);
388
+ mockChallengeSessionRepository.save.mockResolvedValue(mfaSession);
389
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
390
+
391
+ const mfaMetadata = {
392
+ deviceId: 'device-123',
393
+ method: 'TOTP',
394
+ availableMethods: ['TOTP', 'SMS'],
395
+ };
396
+
397
+ const result = await service.createChallengeSession(mockUser as IUser, AuthChallenge.MFA_REQUIRED, mfaMetadata);
398
+
399
+ expect(result.challengeName).toBe(AuthChallenge.MFA_REQUIRED);
400
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalledWith(
401
+ (expect as any).objectContaining({
402
+ challengeName: AuthChallenge.MFA_REQUIRED,
403
+ metadata: mfaMetadata,
404
+ }),
405
+ );
406
+ });
407
+
408
+ it('should create MFA_SETUP_REQUIRED challenge sessions', async () => {
409
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
410
+ const mfaSetupSession = {
411
+ ...mockChallengeSession,
412
+ challengeName: AuthChallenge.MFA_SETUP_REQUIRED,
413
+ } as any;
414
+ mockChallengeSessionRepository.create.mockReturnValue(mfaSetupSession);
415
+ mockChallengeSessionRepository.save.mockResolvedValue(mfaSetupSession);
416
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
417
+
418
+ const setupMetadata = {
419
+ allowedMethods: ['TOTP', 'SMS', 'EMAIL'],
420
+ gracePeriodExpired: true,
421
+ };
422
+
423
+ const result = await service.createChallengeSession(
424
+ mockUser as IUser,
425
+ AuthChallenge.MFA_SETUP_REQUIRED,
426
+ setupMetadata,
427
+ );
428
+
429
+ expect(result.challengeName).toBe(AuthChallenge.MFA_SETUP_REQUIRED);
430
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalledWith(
431
+ (expect as any).objectContaining({
432
+ challengeName: AuthChallenge.MFA_SETUP_REQUIRED,
433
+ metadata: setupMetadata,
434
+ }),
435
+ );
436
+ });
437
+
438
+ it('should create FORCE_CHANGE_PASSWORD challenge sessions', async () => {
439
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
440
+ const passwordChangeSession = {
441
+ ...mockChallengeSession,
442
+ challengeName: AuthChallenge.FORCE_CHANGE_PASSWORD,
443
+ } as any;
444
+ mockChallengeSessionRepository.create.mockReturnValue(passwordChangeSession);
445
+ mockChallengeSessionRepository.save.mockResolvedValue(passwordChangeSession);
446
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
447
+
448
+ const passwordChangeMetadata = {
449
+ reason: 'admin_forced',
450
+ passwordExpired: true,
451
+ instructions: 'You must change your password before continuing',
452
+ };
453
+
454
+ const result = await service.createChallengeSession(
455
+ mockUser as IUser,
456
+ AuthChallenge.FORCE_CHANGE_PASSWORD,
457
+ passwordChangeMetadata,
458
+ );
459
+
460
+ expect(result.challengeName).toBe(AuthChallenge.FORCE_CHANGE_PASSWORD);
461
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalledWith(
462
+ (expect as any).objectContaining({
463
+ challengeName: AuthChallenge.FORCE_CHANGE_PASSWORD,
464
+ metadata: passwordChangeMetadata,
465
+ }),
466
+ );
467
+ });
468
+
469
+ // VERIFY_EMAIL_AND_PHONE removed - challenges are sequential (VERIFY_EMAIL first, then VERIFY_PHONE)
470
+ // This test is no longer needed as the challenge system works sequentially
471
+
472
+ it('should trigger cleanup after 5 minutes have passed', async () => {
473
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
474
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
475
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
476
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
477
+
478
+ jest.useFakeTimers();
479
+ const now = Date.now();
480
+ jest.setSystemTime(now);
481
+
482
+ // Create first session
483
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
484
+ const firstDeleteCount = mockChallengeSessionRepository.delete.mock.calls.length;
485
+
486
+ // Advance time by 5 minutes and 1 second
487
+ jest.setSystemTime(now + 5 * 60 * 1000 + 1000);
488
+
489
+ // Create second session (should trigger cleanup again)
490
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_PHONE);
491
+ const secondDeleteCount = mockChallengeSessionRepository.delete.mock.calls.length;
492
+
493
+ // Cleanup should run again after 5 minutes
494
+ expect(secondDeleteCount).toBeGreaterThan(firstDeleteCount);
495
+
496
+ jest.useRealTimers();
497
+ });
498
+
499
+ it('should create session for all challenge types', async () => {
500
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
501
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
502
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
503
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
504
+
505
+ const challengeTypes = [
506
+ AuthChallenge.VERIFY_EMAIL,
507
+ AuthChallenge.VERIFY_PHONE,
508
+ AuthChallenge.MFA_REQUIRED,
509
+ AuthChallenge.MFA_SETUP_REQUIRED,
510
+ AuthChallenge.FORCE_CHANGE_PASSWORD,
511
+ ];
512
+
513
+ for (const challengeType of challengeTypes) {
514
+ await service.createChallengeSession(mockUser as IUser, challengeType);
515
+ expect(mockChallengeSessionRepository.create).toHaveBeenCalledWith(
516
+ (expect as any).objectContaining({
517
+ challengeName: challengeType,
518
+ }),
519
+ );
520
+ jest.clearAllMocks();
521
+ }
522
+ });
523
+ });
524
+
525
+ // ============================================================================
526
+ // validateSession() Method
527
+ // ============================================================================
528
+
529
+ describe('validateSession', () => {
530
+ it('should validate a valid session', async () => {
531
+ const validSession = {
532
+ ...mockChallengeSession,
533
+ expiresAt: new Date(Date.now() + 60000), // 1 minute from now
534
+ };
535
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
536
+
537
+ const result = await service.validateSession('session-token-123', AuthChallenge.VERIFY_EMAIL);
538
+
539
+ expect(result).toBeDefined();
540
+ expect(result.sessionToken).toBe('session-token-123');
541
+ expect(mockChallengeSessionRepository.findOne).toHaveBeenCalledWith({
542
+ where: { sessionToken: 'session-token-123' },
543
+ relations: ['user'],
544
+ });
545
+ });
546
+
547
+ it('should throw NAuthException if session not found', async () => {
548
+ mockChallengeSessionRepository.findOne.mockResolvedValue(null);
549
+
550
+ try {
551
+ await service.validateSession('invalid-token');
552
+ fail('Should have thrown NAuthException');
553
+ } catch (error) {
554
+ expect(error).toBeInstanceOf(NAuthException);
555
+ expect((error as NAuthException).code).toBe(AuthErrorCode.CHALLENGE_INVALID);
556
+ expect(mockLogger.warn).toHaveBeenCalled();
557
+ }
558
+ });
559
+
560
+ it('should throw NAuthException if session expired', async () => {
561
+ const expiredSession = {
562
+ ...mockChallengeSession,
563
+ expiresAt: new Date(Date.now() - 1000), // 1 second ago
564
+ };
565
+ mockChallengeSessionRepository.findOne.mockResolvedValue(expiredSession as any);
566
+
567
+ try {
568
+ await service.validateSession('session-token-123');
569
+ fail('Should have thrown NAuthException');
570
+ } catch (error) {
571
+ expect(error).toBeInstanceOf(NAuthException);
572
+ expect((error as NAuthException).code).toBe(AuthErrorCode.CHALLENGE_EXPIRED);
573
+ expect((error as NAuthException).message).toContain('expired');
574
+ expect(mockLogger.warn).toHaveBeenCalled();
575
+ }
576
+ });
577
+
578
+ it('should throw NAuthException if session expires exactly at current time', async () => {
579
+ jest.useFakeTimers();
580
+ const now = new Date();
581
+ jest.setSystemTime(now);
582
+
583
+ const exactlyExpiredSession = {
584
+ ...mockChallengeSession,
585
+ expiresAt: new Date(now.getTime() - 1), // 1ms ago
586
+ };
587
+ mockChallengeSessionRepository.findOne.mockResolvedValue(exactlyExpiredSession as any);
588
+
589
+ try {
590
+ await service.validateSession('session-token-123');
591
+ fail('Should have thrown NAuthException');
592
+ } catch (error) {
593
+ expect(error).toBeInstanceOf(NAuthException);
594
+ expect((error as NAuthException).code).toBe(AuthErrorCode.CHALLENGE_EXPIRED);
595
+ }
596
+
597
+ jest.useRealTimers();
598
+ });
599
+
600
+ it('should throw NAuthException if session already completed', async () => {
601
+ const completedSession = {
602
+ ...mockChallengeSession,
603
+ isCompleted: true,
604
+ };
605
+ mockChallengeSessionRepository.findOne.mockResolvedValue(completedSession as any);
606
+
607
+ try {
608
+ await service.validateSession('session-token-123');
609
+ fail('Should have thrown NAuthException');
610
+ } catch (error) {
611
+ expect(error).toBeInstanceOf(NAuthException);
612
+ expect((error as NAuthException).code).toBe(AuthErrorCode.CHALLENGE_ALREADY_COMPLETED);
613
+ expect((error as NAuthException).message).toContain('already been completed');
614
+ expect(mockLogger.warn).toHaveBeenCalled();
615
+ }
616
+ });
617
+
618
+ it('should throw NAuthException if max attempts exceeded', async () => {
619
+ const maxAttemptsSession = {
620
+ ...mockChallengeSession,
621
+ attempts: 3,
622
+ maxAttempts: 3,
623
+ };
624
+ mockChallengeSessionRepository.findOne.mockResolvedValue(maxAttemptsSession as any);
625
+
626
+ try {
627
+ await service.validateSession('session-token-123');
628
+ fail('Should have thrown NAuthException');
629
+ } catch (error) {
630
+ expect(error).toBeInstanceOf(NAuthException);
631
+ expect((error as NAuthException).code).toBe(AuthErrorCode.CHALLENGE_MAX_ATTEMPTS);
632
+ expect((error as NAuthException).message).toContain('Maximum challenge attempts exceeded');
633
+ expect(mockLogger.warn).toHaveBeenCalled();
634
+ }
635
+ });
636
+
637
+ it('should throw NAuthException if attempts exceed max attempts', async () => {
638
+ const overMaxAttemptsSession = {
639
+ ...mockChallengeSession,
640
+ attempts: 4,
641
+ maxAttempts: 3,
642
+ };
643
+ mockChallengeSessionRepository.findOne.mockResolvedValue(overMaxAttemptsSession as any);
644
+
645
+ try {
646
+ await service.validateSession('session-token-123');
647
+ fail('Should have thrown NAuthException');
648
+ } catch (error) {
649
+ expect(error).toBeInstanceOf(NAuthException);
650
+ expect((error as NAuthException).code).toBe(AuthErrorCode.CHALLENGE_MAX_ATTEMPTS);
651
+ }
652
+ });
653
+
654
+ it('should validate session when attempts are just below max', async () => {
655
+ const validSession = {
656
+ ...mockChallengeSession,
657
+ expiresAt: new Date(Date.now() + 60000),
658
+ attempts: 2,
659
+ maxAttempts: 3,
660
+ };
661
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
662
+
663
+ const result = await service.validateSession('session-token-123');
664
+
665
+ expect(result).toBeDefined();
666
+ expect(result.attempts).toBe(2);
667
+ });
668
+
669
+ it('should throw NAuthException if challenge type mismatch', async () => {
670
+ mockChallengeSessionRepository.findOne.mockResolvedValue(mockChallengeSession as any);
671
+
672
+ try {
673
+ await service.validateSession('session-token-123', AuthChallenge.VERIFY_PHONE);
674
+ fail('Should have thrown NAuthException');
675
+ } catch (error) {
676
+ expect(error).toBeInstanceOf(NAuthException);
677
+ expect((error as NAuthException).code).toBe(AuthErrorCode.CHALLENGE_TYPE_MISMATCH);
678
+ expect((error as NAuthException).message).toContain('Invalid challenge type');
679
+ expect(mockLogger.warn).toHaveBeenCalled();
680
+ }
681
+ });
682
+
683
+ it('should validate session without expected challenge type', async () => {
684
+ const validSession = {
685
+ ...mockChallengeSession,
686
+ expiresAt: new Date(Date.now() + 60000),
687
+ };
688
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
689
+
690
+ const result = await service.validateSession('session-token-123');
691
+
692
+ expect(result).toBeDefined();
693
+ expect(result.sessionToken).toBe('session-token-123');
694
+ });
695
+
696
+ it('should load session with user relation', async () => {
697
+ const validSession = {
698
+ ...mockChallengeSession,
699
+ expiresAt: new Date(Date.now() + 60000),
700
+ };
701
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
702
+
703
+ await service.validateSession('session-token-123');
704
+
705
+ // Verify findOne is called with relations to load user
706
+ expect(mockChallengeSessionRepository.findOne).toHaveBeenCalledWith({
707
+ where: { sessionToken: 'session-token-123' },
708
+ relations: ['user'],
709
+ });
710
+ });
711
+ });
712
+
713
+ // ============================================================================
714
+ // incrementAttempts() Method
715
+ // ============================================================================
716
+
717
+ describe('incrementAttempts', () => {
718
+ it('should increment attempt counter', async () => {
719
+ const session = { ...mockChallengeSession, attempts: 1 } as IChallengeSession;
720
+ const updatedSession = { ...session, attempts: 2 };
721
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
722
+
723
+ const result = await service.incrementAttempts(session);
724
+
725
+ expect(result.attempts).toBe(2);
726
+ expect(mockChallengeSessionRepository.save).toHaveBeenCalledWith(session);
727
+ });
728
+
729
+ it('should record audit event when max attempts exceeded', async () => {
730
+ const session = {
731
+ ...mockChallengeSession,
732
+ attempts: 2,
733
+ maxAttempts: 3,
734
+ } as IChallengeSession;
735
+ const updatedSession = { ...session, attempts: 3 };
736
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
737
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
738
+
739
+ await service.incrementAttempts(session);
740
+
741
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
742
+ (expect as any).objectContaining({
743
+ eventType: AuthAuditEventType.CHALLENGE_ATTEMPT_FAILED,
744
+ eventStatus: 'FAILURE',
745
+ reason: 'max_attempts_exceeded',
746
+ }),
747
+ );
748
+ });
749
+
750
+ it('should not record audit event when max attempts not exceeded', async () => {
751
+ const session = {
752
+ ...mockChallengeSession,
753
+ attempts: 1,
754
+ maxAttempts: 3,
755
+ } as IChallengeSession;
756
+ const updatedSession = { ...session, attempts: 2 };
757
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
758
+
759
+ await service.incrementAttempts(session);
760
+
761
+ expect(mockAuditService.recordEvent).not.toHaveBeenCalled();
762
+ });
763
+
764
+ it('should handle audit service errors gracefully', async () => {
765
+ const session = {
766
+ ...mockChallengeSession,
767
+ attempts: 2,
768
+ maxAttempts: 3,
769
+ } as IChallengeSession;
770
+ const updatedSession = { ...session, attempts: 3 };
771
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
772
+ mockAuditService.recordEvent.mockRejectedValue(new Error('Audit error'));
773
+
774
+ const result = await service.incrementAttempts(session);
775
+
776
+ expect(result.attempts).toBe(3);
777
+ expect(mockLogger.error).toHaveBeenCalled();
778
+ });
779
+
780
+ it('should handle non-Error audit exceptions in incrementAttempts', async () => {
781
+ const session = {
782
+ ...mockChallengeSession,
783
+ attempts: 2,
784
+ maxAttempts: 3,
785
+ } as IChallengeSession;
786
+ const updatedSession = { ...session, attempts: 3 };
787
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
788
+ mockAuditService.recordEvent.mockRejectedValue('String error' as any);
789
+
790
+ const result = await service.incrementAttempts(session);
791
+
792
+ expect(result.attempts).toBe(3);
793
+ expect(mockLogger.error).toHaveBeenCalledWith(
794
+ (expect as any).stringContaining('Failed to record CHALLENGE_ATTEMPT_FAILED audit event: Unknown error'),
795
+ (expect as any).any(Object),
796
+ );
797
+ });
798
+
799
+ it('should record audit event with session IP and userAgent when available', async () => {
800
+ const session = {
801
+ ...mockChallengeSession,
802
+ attempts: 2,
803
+ maxAttempts: 3,
804
+ ipAddress: '10.20.30.40',
805
+ userAgent: 'custom-browser-agent',
806
+ } as IChallengeSession;
807
+ const updatedSession = { ...session, attempts: 3 };
808
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
809
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
810
+
811
+ await service.incrementAttempts(session);
812
+
813
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
814
+ (expect as any).objectContaining({
815
+ ipAddress: '10.20.30.40',
816
+ userAgent: 'custom-browser-agent',
817
+ }),
818
+ );
819
+ });
820
+
821
+ it('should record audit event with undefined IP/userAgent when session values are null', async () => {
822
+ const session = {
823
+ ...mockChallengeSession,
824
+ attempts: 2,
825
+ maxAttempts: 3,
826
+ ipAddress: null,
827
+ userAgent: null,
828
+ } as IChallengeSession;
829
+ const updatedSession = { ...session, attempts: 3 };
830
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
831
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
832
+
833
+ await service.incrementAttempts(session);
834
+
835
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
836
+ (expect as any).objectContaining({
837
+ ipAddress: undefined,
838
+ userAgent: undefined,
839
+ }),
840
+ );
841
+ });
842
+
843
+ it('should handle audit logging when incrementAttempts reaches exactly max attempts', async () => {
844
+ const session = {
845
+ ...mockChallengeSession,
846
+ attempts: 2,
847
+ maxAttempts: 3,
848
+ } as IChallengeSession;
849
+ const updatedSession = { ...session, attempts: 3 };
850
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession as any);
851
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
852
+
853
+ await service.incrementAttempts(session);
854
+
855
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
856
+ (expect as any).objectContaining({
857
+ eventType: AuthAuditEventType.CHALLENGE_ATTEMPT_FAILED,
858
+ description: (expect as any).stringContaining('maximum attempts (3) exceeded'),
859
+ }),
860
+ );
861
+ });
862
+
863
+ it('should handle session without user gracefully', async () => {
864
+ const sessionWithoutUser = {
865
+ ...mockChallengeSession,
866
+ attempts: 2,
867
+ maxAttempts: 3,
868
+ user: undefined,
869
+ } as any;
870
+ const updatedSession = { ...sessionWithoutUser, attempts: 3 };
871
+ mockChallengeSessionRepository.save.mockResolvedValue(updatedSession);
872
+
873
+ await service.incrementAttempts(sessionWithoutUser);
874
+
875
+ // Should not throw, but audit may not be recorded
876
+ expect(mockChallengeSessionRepository.save).toHaveBeenCalled();
877
+ });
878
+ });
879
+
880
+ // ============================================================================
881
+ // validateAndConsumeSession() Method
882
+ // ============================================================================
883
+
884
+ describe('validateAndConsumeSession', () => {
885
+ it('should validate and mark session as completed', async () => {
886
+ const validSession = {
887
+ ...mockChallengeSession,
888
+ expiresAt: new Date(Date.now() + 60000),
889
+ };
890
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
891
+ const completedSession = { ...validSession, isCompleted: true, completedAt: new Date() };
892
+ mockChallengeSessionRepository.save.mockResolvedValue(completedSession as any);
893
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
894
+
895
+ const result = await service.validateAndConsumeSession('session-token-123', AuthChallenge.VERIFY_EMAIL);
896
+
897
+ expect(result.isCompleted).toBe(true);
898
+ expect(result.completedAt).toBeDefined();
899
+ expect(mockChallengeSessionRepository.save).toHaveBeenCalled();
900
+ expect(mockLogger.log).toHaveBeenCalled();
901
+ });
902
+
903
+ it('should record audit event on session completion', async () => {
904
+ const validSession = {
905
+ ...mockChallengeSession,
906
+ expiresAt: new Date(Date.now() + 60000),
907
+ };
908
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
909
+ const completedSession = { ...validSession, isCompleted: true };
910
+ mockChallengeSessionRepository.save.mockResolvedValue(completedSession as any);
911
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
912
+
913
+ await service.validateAndConsumeSession('session-token-123', AuthChallenge.VERIFY_EMAIL);
914
+
915
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
916
+ (expect as any).objectContaining({
917
+ eventType: AuthAuditEventType.CHALLENGE_COMPLETED,
918
+ eventStatus: 'SUCCESS',
919
+ userId: mockUser.id,
920
+ }),
921
+ );
922
+ });
923
+
924
+ it('should handle audit service errors gracefully', async () => {
925
+ const validSession = {
926
+ ...mockChallengeSession,
927
+ expiresAt: new Date(Date.now() + 60000),
928
+ };
929
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
930
+ const completedSession = { ...validSession, isCompleted: true };
931
+ mockChallengeSessionRepository.save.mockResolvedValue(completedSession as any);
932
+ mockAuditService.recordEvent.mockRejectedValue(new Error('Audit error'));
933
+
934
+ const result = await service.validateAndConsumeSession('session-token-123', AuthChallenge.VERIFY_EMAIL);
935
+
936
+ expect(result.isCompleted).toBe(true);
937
+ expect(mockLogger.error).toHaveBeenCalled();
938
+ });
939
+
940
+ it('should handle non-Error audit exceptions in validateAndConsumeSession', async () => {
941
+ const validSession = {
942
+ ...mockChallengeSession,
943
+ expiresAt: new Date(Date.now() + 60000),
944
+ };
945
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
946
+ const completedSession = { ...validSession, isCompleted: true };
947
+ mockChallengeSessionRepository.save.mockResolvedValue(completedSession as any);
948
+ mockAuditService.recordEvent.mockRejectedValue('String error' as any);
949
+
950
+ const result = await service.validateAndConsumeSession('session-token-123', AuthChallenge.VERIFY_EMAIL);
951
+
952
+ expect(result.isCompleted).toBe(true);
953
+ expect(mockLogger.error).toHaveBeenCalledWith(
954
+ (expect as any).stringContaining('Failed to record CHALLENGE_COMPLETED audit event: Unknown error'),
955
+ (expect as any).any(Object),
956
+ );
957
+ });
958
+
959
+ it('should handle validateAndConsumeSession with null session IP and userAgent', async () => {
960
+ const validSession = {
961
+ ...mockChallengeSession,
962
+ expiresAt: new Date(Date.now() + 60000),
963
+ ipAddress: null,
964
+ userAgent: null,
965
+ };
966
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
967
+ const completedSession = { ...validSession, isCompleted: true };
968
+ mockChallengeSessionRepository.save.mockResolvedValue(completedSession as any);
969
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
970
+
971
+ await service.validateAndConsumeSession('session-token-123', AuthChallenge.VERIFY_EMAIL);
972
+
973
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
974
+ (expect as any).objectContaining({
975
+ ipAddress: undefined,
976
+ userAgent: undefined,
977
+ }),
978
+ );
979
+ });
980
+
981
+ it('should throw if validation fails', async () => {
982
+ mockChallengeSessionRepository.findOne.mockResolvedValue(null);
983
+
984
+ try {
985
+ await service.validateAndConsumeSession('invalid-token', AuthChallenge.VERIFY_EMAIL);
986
+ fail('Should have thrown NAuthException');
987
+ } catch (error) {
988
+ expect(error).toBeInstanceOf(NAuthException);
989
+ expect(mockChallengeSessionRepository.save).not.toHaveBeenCalled();
990
+ }
991
+ });
992
+
993
+ it('should use session IP and user agent in audit event', async () => {
994
+ const validSession = {
995
+ ...mockChallengeSession,
996
+ expiresAt: new Date(Date.now() + 60000),
997
+ ipAddress: '5.6.7.8',
998
+ userAgent: 'session-agent',
999
+ };
1000
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
1001
+ const completedSession = { ...validSession, isCompleted: true };
1002
+ mockChallengeSessionRepository.save.mockResolvedValue(completedSession as any);
1003
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
1004
+
1005
+ await service.validateAndConsumeSession('session-token-123', AuthChallenge.VERIFY_EMAIL);
1006
+
1007
+ expect(mockAuditService.recordEvent).toHaveBeenCalledWith(
1008
+ (expect as any).objectContaining({
1009
+ ipAddress: '5.6.7.8',
1010
+ userAgent: 'session-agent',
1011
+ }),
1012
+ );
1013
+ });
1014
+ });
1015
+
1016
+ // ============================================================================
1017
+ // cleanupExpiredSessions() Method
1018
+ // ============================================================================
1019
+
1020
+ describe('cleanupExpiredSessions', () => {
1021
+ it('should delete expired and completed sessions', async () => {
1022
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 5 } as any);
1023
+
1024
+ await service.cleanupExpiredSessions(1);
1025
+
1026
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledTimes(2); // Once for expired, once for completed
1027
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledWith(
1028
+ (expect as any).objectContaining({
1029
+ userId: 1,
1030
+ }),
1031
+ );
1032
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledWith(
1033
+ (expect as any).objectContaining({
1034
+ userId: 1,
1035
+ isCompleted: true,
1036
+ }),
1037
+ );
1038
+ });
1039
+
1040
+ it('should handle cleanup with no sessions to delete', async () => {
1041
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
1042
+
1043
+ await service.cleanupExpiredSessions(1);
1044
+
1045
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledTimes(2);
1046
+ });
1047
+
1048
+ it('should use LessThan for expiration check', async () => {
1049
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
1050
+
1051
+ await service.cleanupExpiredSessions(1);
1052
+
1053
+ // LessThan is a TypeORM operator - we can't easily test it, but we verify the call was made
1054
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalled();
1055
+ });
1056
+ });
1057
+
1058
+ // ============================================================================
1059
+ // cleanupAllExpiredSessions() Method
1060
+ // ============================================================================
1061
+
1062
+ describe('cleanupAllExpiredSessions', () => {
1063
+ it('should delete all expired sessions and return count', async () => {
1064
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 10 } as any);
1065
+
1066
+ const result = await service.cleanupAllExpiredSessions();
1067
+
1068
+ expect(result).toBe(10);
1069
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledWith((expect as any).objectContaining({}));
1070
+ expect(mockLogger.log).toHaveBeenCalledWith(
1071
+ (expect as any).stringContaining('Cleaned up 10 expired challenge sessions'),
1072
+ );
1073
+ });
1074
+
1075
+ it('should return 0 when no sessions deleted', async () => {
1076
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
1077
+
1078
+ const result = await service.cleanupAllExpiredSessions();
1079
+
1080
+ expect(result).toBe(0);
1081
+ expect(mockLogger.log).toHaveBeenCalledWith(
1082
+ (expect as any).stringContaining('Cleaned up 0 expired challenge sessions'),
1083
+ );
1084
+ });
1085
+
1086
+ it('should handle delete result without affected property', async () => {
1087
+ mockChallengeSessionRepository.delete.mockResolvedValue({} as any);
1088
+
1089
+ const result = await service.cleanupAllExpiredSessions();
1090
+
1091
+ expect(result).toBe(0);
1092
+ });
1093
+ });
1094
+
1095
+ // ============================================================================
1096
+ // deleteUserChallengeSessions() Method
1097
+ // ============================================================================
1098
+
1099
+ describe('deleteUserChallengeSessions', () => {
1100
+ it('should delete active challenge sessions by type', async () => {
1101
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 2 } as any);
1102
+
1103
+ const result = await service.deleteUserChallengeSessions(1, AuthChallenge.MFA_SETUP_REQUIRED);
1104
+
1105
+ expect(result).toBe(2);
1106
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledWith({
1107
+ userId: 1,
1108
+ challengeName: AuthChallenge.MFA_SETUP_REQUIRED,
1109
+ isCompleted: false,
1110
+ });
1111
+ expect(mockLogger.log).toHaveBeenCalledWith(
1112
+ (expect as any).stringContaining('Deleted 2 MFA_SETUP_REQUIRED challenge session(s)'),
1113
+ );
1114
+ });
1115
+
1116
+ it('should return 0 when no sessions deleted', async () => {
1117
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
1118
+
1119
+ const result = await service.deleteUserChallengeSessions(1, AuthChallenge.VERIFY_EMAIL);
1120
+
1121
+ expect(result).toBe(0);
1122
+ expect(mockLogger.log).not.toHaveBeenCalled();
1123
+ });
1124
+
1125
+ it('should delete sessions for all challenge types', async () => {
1126
+ const challengeTypes = [
1127
+ AuthChallenge.VERIFY_EMAIL,
1128
+ AuthChallenge.VERIFY_PHONE,
1129
+ AuthChallenge.MFA_REQUIRED,
1130
+ AuthChallenge.MFA_SETUP_REQUIRED,
1131
+ AuthChallenge.FORCE_CHANGE_PASSWORD,
1132
+ ];
1133
+
1134
+ for (const challengeType of challengeTypes) {
1135
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 1 } as any);
1136
+ const result = await service.deleteUserChallengeSessions(1, challengeType);
1137
+ expect(result).toBe(1);
1138
+ expect(mockChallengeSessionRepository.delete).toHaveBeenCalledWith(
1139
+ (expect as any).objectContaining({
1140
+ challengeName: challengeType,
1141
+ }),
1142
+ );
1143
+ }
1144
+ });
1145
+ });
1146
+
1147
+ // ============================================================================
1148
+ // Helper Methods
1149
+ // ============================================================================
1150
+
1151
+ describe('maskEmail', () => {
1152
+ it('should mask email address correctly', () => {
1153
+ const masked = service.maskEmail('john.doe@example.com');
1154
+ expect(masked).toBe('j***@example.com');
1155
+ });
1156
+
1157
+ it('should handle single character local part', () => {
1158
+ const masked = service.maskEmail('j@example.com');
1159
+ expect(masked).toBe('j***@example.com');
1160
+ });
1161
+
1162
+ it('should handle invalid email gracefully', () => {
1163
+ const masked = service.maskEmail('invalid-email');
1164
+ expect(masked).toBe('invalid-email');
1165
+ });
1166
+
1167
+ it('should handle email without @ symbol', () => {
1168
+ const masked = service.maskEmail('noatdomain');
1169
+ expect(masked).toBe('noatdomain');
1170
+ });
1171
+
1172
+ it('should handle empty email', () => {
1173
+ const masked = service.maskEmail('');
1174
+ expect(masked).toBe('');
1175
+ });
1176
+
1177
+ it('should mask long email addresses', () => {
1178
+ const masked = service.maskEmail('verylongemailaddress@example.com');
1179
+ expect(masked).toBe('v***@example.com');
1180
+ });
1181
+
1182
+ it('should handle email with empty local part', () => {
1183
+ const masked = service.maskEmail('@example.com');
1184
+ // When split('@'), first part is empty string, so localPart[0] is undefined
1185
+ // Implementation concatenates undefined with '***@' resulting in 'undefined***@example.com'
1186
+ expect(masked).toBe('undefined***@example.com');
1187
+ });
1188
+
1189
+ it('should handle email with multiple @ symbols', () => {
1190
+ // split('@') on 'invalid@email@example.com' creates ['invalid', 'email', 'example.com']
1191
+ // Takes first element as localPart and second as domain
1192
+ const masked = service.maskEmail('invalid@email@example.com');
1193
+ expect(masked).toBe('i***@email');
1194
+ });
1195
+
1196
+ it('should handle email with special characters in local part', () => {
1197
+ const masked = service.maskEmail('user+tag@example.com');
1198
+ expect(masked).toBe('u***@example.com');
1199
+ });
1200
+ });
1201
+
1202
+ describe('maskPhone', () => {
1203
+ it('should mask phone number correctly', () => {
1204
+ const masked = service.maskPhone('+1234567890');
1205
+ expect(masked).toBe('***-***-7890');
1206
+ });
1207
+
1208
+ it('should handle phone with formatting', () => {
1209
+ const masked = service.maskPhone('+1 (234) 567-8901');
1210
+ expect(masked).toBe('***-***-8901');
1211
+ });
1212
+
1213
+ it('should handle short phone numbers', () => {
1214
+ const masked = service.maskPhone('123');
1215
+ expect(masked).toBe('123');
1216
+ });
1217
+
1218
+ it('should handle phone with exactly 4 digits', () => {
1219
+ const masked = service.maskPhone('1234');
1220
+ expect(masked).toBe('***-***-1234');
1221
+ });
1222
+
1223
+ it('should handle phone with less than 4 digits', () => {
1224
+ const masked = service.maskPhone('12');
1225
+ expect(masked).toBe('12');
1226
+ });
1227
+
1228
+ it('should handle phone with only special characters', () => {
1229
+ const masked = service.maskPhone('+--()');
1230
+ expect(masked).toBe('+--()');
1231
+ });
1232
+
1233
+ it('should handle international phone numbers', () => {
1234
+ const masked = service.maskPhone('+441234567890');
1235
+ expect(masked).toBe('***-***-7890');
1236
+ });
1237
+
1238
+ it('should handle empty phone', () => {
1239
+ const masked = service.maskPhone('');
1240
+ expect(masked).toBe('');
1241
+ });
1242
+
1243
+ it('should handle phone with only letters', () => {
1244
+ const masked = service.maskPhone('abc');
1245
+ expect(masked).toBe('abc');
1246
+ });
1247
+
1248
+ it('should handle phone with mixed characters', () => {
1249
+ // maskPhone extracts digits only: '+1-800-CALL-NOW' -> '1800' -> last 4 = '1800'
1250
+ const masked = service.maskPhone('+1-800-CALL-NOW');
1251
+ expect(masked).toBe('***-***-1800');
1252
+ });
1253
+
1254
+ it('should handle phone with exactly 3 digits', () => {
1255
+ const masked = service.maskPhone('123');
1256
+ expect(masked).toBe('123');
1257
+ });
1258
+ });
1259
+
1260
+ // ============================================================================
1261
+ // Edge Cases and Error Handling
1262
+ // ============================================================================
1263
+
1264
+ describe('Edge Cases', () => {
1265
+ it('should handle service without audit service', async () => {
1266
+ const serviceWithoutAudit = new ChallengeService(
1267
+ mockChallengeSessionRepository,
1268
+ mockClientInfoService,
1269
+ mockLogger,
1270
+ undefined, // No audit service
1271
+ );
1272
+
1273
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
1274
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
1275
+ mockChallengeSessionRepository.save.mockResolvedValue(mockChallengeSession as any);
1276
+
1277
+ const result = await serviceWithoutAudit.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
1278
+
1279
+ expect(result).toBeDefined();
1280
+ // Should not throw
1281
+ });
1282
+
1283
+ it('should handle repository save errors', async () => {
1284
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
1285
+ mockChallengeSessionRepository.create.mockReturnValue(mockChallengeSession as any);
1286
+ mockChallengeSessionRepository.save.mockRejectedValue(new Error('Database error'));
1287
+
1288
+ try {
1289
+ await service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL);
1290
+ fail('Should have thrown error');
1291
+ } catch (error) {
1292
+ expect((error as Error).message).toContain('Database error');
1293
+ }
1294
+ });
1295
+
1296
+ it('should handle concurrent session creation', async () => {
1297
+ mockChallengeSessionRepository.delete.mockResolvedValue({ affected: 0 } as any);
1298
+ mockChallengeSessionRepository.create
1299
+ .mockReturnValueOnce({ ...mockChallengeSession, challengeName: AuthChallenge.VERIFY_EMAIL } as any)
1300
+ .mockReturnValueOnce({ ...mockChallengeSession, challengeName: AuthChallenge.VERIFY_PHONE } as any);
1301
+ mockChallengeSessionRepository.save
1302
+ .mockResolvedValueOnce({ ...mockChallengeSession, challengeName: AuthChallenge.VERIFY_EMAIL } as any)
1303
+ .mockResolvedValueOnce({ ...mockChallengeSession, challengeName: AuthChallenge.VERIFY_PHONE } as any);
1304
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
1305
+
1306
+ const promises = [
1307
+ service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_EMAIL),
1308
+ service.createChallengeSession(mockUser as IUser, AuthChallenge.VERIFY_PHONE),
1309
+ ];
1310
+
1311
+ const results = await Promise.all(promises);
1312
+
1313
+ expect(results.length).toBe(2);
1314
+ expect(results[0].challengeName).toBe(AuthChallenge.VERIFY_EMAIL);
1315
+ expect(results[1].challengeName).toBe(AuthChallenge.VERIFY_PHONE);
1316
+ });
1317
+
1318
+ it('should handle validateAndConsumeSession for all challenge types', async () => {
1319
+ const challengeTypes = [
1320
+ AuthChallenge.VERIFY_EMAIL,
1321
+ AuthChallenge.VERIFY_PHONE,
1322
+ AuthChallenge.MFA_REQUIRED,
1323
+ AuthChallenge.MFA_SETUP_REQUIRED,
1324
+ AuthChallenge.FORCE_CHANGE_PASSWORD,
1325
+ ];
1326
+
1327
+ for (const challengeType of challengeTypes) {
1328
+ const validSession = {
1329
+ ...mockChallengeSession,
1330
+ challengeName: challengeType,
1331
+ expiresAt: new Date(Date.now() + 60000),
1332
+ };
1333
+ mockChallengeSessionRepository.findOne.mockResolvedValue(validSession as any);
1334
+ const completedSession = { ...validSession, isCompleted: true };
1335
+ mockChallengeSessionRepository.save.mockResolvedValue(completedSession as any);
1336
+ mockAuditService.recordEvent.mockResolvedValue({} as any);
1337
+
1338
+ const result = await service.validateAndConsumeSession('session-token-123', challengeType);
1339
+
1340
+ expect(result.isCompleted).toBe(true);
1341
+ expect(result.challengeName).toBe(challengeType);
1342
+ jest.clearAllMocks();
1343
+ }
1344
+ });
1345
+
1346
+ it('should handle session validation with null user gracefully', async () => {
1347
+ const sessionWithoutUser = {
1348
+ ...mockChallengeSession,
1349
+ expiresAt: new Date(Date.now() + 60000),
1350
+ user: null,
1351
+ };
1352
+ mockChallengeSessionRepository.findOne.mockResolvedValue(sessionWithoutUser as any);
1353
+
1354
+ try {
1355
+ await service.validateSession('session-token-123');
1356
+ // Should not throw if user is null but session is valid
1357
+ } catch (error) {
1358
+ // Only expiresAt check might fail if user is needed for logging
1359
+ // But validation should still work
1360
+ }
1361
+ });
1362
+ });
1363
+ });