@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,1292 @@
1
+ import { Repository } from 'typeorm';
2
+ import { RiskDetectionService } from './risk-detection.service';
3
+ import { IUser, ISession } from '../interfaces/entities.interface';
4
+ import { ClientInfo } from '../interfaces/client-info.interface';
5
+ import { NAuthConfig } from '../interfaces/config.interface';
6
+ import { NAuthLogger } from '../utils/nauth-logger';
7
+ import { RiskFactor } from '../enums/risk-factor.enum';
8
+ import { BaseSession, BaseAuthAudit } from '../entities';
9
+
10
+ /**
11
+ * Risk Detection Service Unit Tests
12
+ *
13
+ * Tests risk factor detection, double-counting prevention, error handling,
14
+ * and configuration-based trigger enabling/disabling.
15
+ *
16
+ * Platform-agnostic: Uses direct instantiation, no NestJS dependencies.
17
+ */
18
+ describe('RiskDetectionService', () => {
19
+ let service: RiskDetectionService;
20
+ let mockSessionRepository: jest.Mocked<Repository<BaseSession>>;
21
+ let mockAuditRepository: jest.Mocked<Repository<BaseAuthAudit>>;
22
+ let mockTrustedDeviceService: jest.Mocked<any>;
23
+ let mockConfig: NAuthConfig;
24
+ let mockLogger: jest.Mocked<NAuthLogger>;
25
+
26
+ const mockUser: IUser = {
27
+ id: 1,
28
+ sub: 'user-123',
29
+ email: 'test@example.com',
30
+ username: 'testuser',
31
+ phone: null,
32
+ firstName: null,
33
+ lastName: null,
34
+ passwordHash: null,
35
+ passwordChangedAt: null,
36
+ passwordHistory: null,
37
+ isEmailVerified: true,
38
+ isPhoneVerified: false,
39
+ isActive: true,
40
+ mustChangePassword: false,
41
+ isLocked: false,
42
+ lockReason: null,
43
+ lockedAt: null,
44
+ lockedUntil: null,
45
+ failedLoginAttempts: 0,
46
+ lastFailedLoginAt: null,
47
+ lastLoginAt: null,
48
+ lastLoginIp: null,
49
+ hasSocialAuth: false,
50
+ socialProviders: null,
51
+ mfaEnabled: false,
52
+ mfaMethods: null,
53
+ preferredMfaMethod: null,
54
+ backupCodes: null,
55
+ metadata: null,
56
+ createdAt: new Date(),
57
+ updatedAt: new Date(),
58
+ deletedAt: null,
59
+ };
60
+
61
+ const mockClientInfo: ClientInfo = {
62
+ ipAddress: '192.168.1.100',
63
+ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
64
+ deviceToken: 'device-123',
65
+ deviceName: 'Chrome on Windows',
66
+ deviceType: 'desktop',
67
+ ipCountry: 'US',
68
+ ipCity: 'New York',
69
+ platform: 'Windows',
70
+ browser: 'Chrome',
71
+ };
72
+
73
+ beforeEach(() => {
74
+ mockSessionRepository = {
75
+ findOne: jest.fn(),
76
+ find: jest.fn(),
77
+ } as any;
78
+
79
+ mockAuditRepository = {
80
+ findOne: jest.fn(),
81
+ find: jest.fn(),
82
+ } as any;
83
+
84
+ mockTrustedDeviceService = {
85
+ isDeviceTrusted: jest.fn(),
86
+ } as any;
87
+
88
+ mockLogger = {
89
+ log: jest.fn(),
90
+ error: jest.fn(),
91
+ warn: jest.fn(),
92
+ debug: jest.fn(),
93
+ verbose: jest.fn(),
94
+ } as any;
95
+
96
+ mockConfig = {
97
+ jwt: {
98
+ accessToken: { secret: 'test-secret', expiresIn: '15m' },
99
+ refreshToken: { secret: 'test-refresh-secret', expiresIn: '7d' },
100
+ },
101
+ mfa: {
102
+ adaptive: {
103
+ triggers: [
104
+ RiskFactor.NEW_DEVICE,
105
+ RiskFactor.NEW_IP,
106
+ RiskFactor.NEW_COUNTRY,
107
+ RiskFactor.IMPOSSIBLE_TRAVEL,
108
+ RiskFactor.SUSPICIOUS_ACTIVITY,
109
+ ],
110
+ },
111
+ },
112
+ };
113
+
114
+ // Instantiate service directly
115
+ service = new RiskDetectionService(
116
+ mockSessionRepository,
117
+ mockAuditRepository,
118
+ mockConfig,
119
+ mockLogger,
120
+ mockTrustedDeviceService,
121
+ );
122
+ });
123
+
124
+ afterEach(() => {
125
+ jest.clearAllMocks();
126
+ });
127
+
128
+ // ============================================================================
129
+ // Service Initialization
130
+ // ============================================================================
131
+
132
+ it('should be defined', () => {
133
+ expect(service).toBeDefined();
134
+ });
135
+
136
+ // ============================================================================
137
+ // detectRiskFactors() - NEW_DEVICE
138
+ // ============================================================================
139
+
140
+ describe('detectRiskFactors() - new_device', () => {
141
+ it('should detect new device when device never seen before', async () => {
142
+ // Setup: new_device check finds no session -> device is new
143
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // Device not found
144
+ // new_country check: country exists (optimized - 1 query, returns early)
145
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
146
+ // impossible_travel check: no previous session
147
+ mockSessionRepository.findOne.mockResolvedValueOnce(null);
148
+ // new_ip check: IP exists (since country exists, new_ip is still checked)
149
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // IP exists
150
+ // suspicious_activity check: no suspicious activity
151
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
152
+ mockAuditRepository.find.mockResolvedValueOnce([]);
153
+
154
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
155
+
156
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
157
+ expect(factors.length).toBe(1); // Only new_device, others are not new
158
+ });
159
+
160
+ it('should not detect new device when device seen before', async () => {
161
+ mockSessionRepository.findOne
162
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
163
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
164
+ .mockResolvedValueOnce(null); // No previous session for impossible_travel
165
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
166
+ mockAuditRepository.find.mockResolvedValueOnce([]);
167
+
168
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
169
+
170
+ expect(factors).not.toContain(RiskFactor.NEW_DEVICE);
171
+ });
172
+
173
+ it('should skip new_device check if trigger not enabled', async () => {
174
+ mockConfig.mfa!.adaptive!.triggers = [RiskFactor.NEW_IP, RiskFactor.NEW_COUNTRY];
175
+ service = new RiskDetectionService(
176
+ mockSessionRepository,
177
+ mockAuditRepository,
178
+ mockConfig,
179
+ mockLogger,
180
+ mockTrustedDeviceService,
181
+ );
182
+
183
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
184
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
185
+
186
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
187
+
188
+ expect(factors).not.toContain(RiskFactor.NEW_DEVICE);
189
+ // Should not call findOne for device check
190
+ expect(mockSessionRepository.findOne).not.toHaveBeenCalledWith(
191
+ (expect as any).objectContaining({
192
+ where: (expect as any).objectContaining({ deviceId: mockClientInfo.deviceToken }),
193
+ }),
194
+ );
195
+ });
196
+
197
+ it('should skip new_device check if deviceToken not provided', async () => {
198
+ const clientInfoWithoutDevice = { ...mockClientInfo, deviceToken: undefined };
199
+
200
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
201
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
202
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
203
+ mockAuditRepository.find.mockResolvedValueOnce([]);
204
+
205
+ const factors = await service.detectRiskFactors(mockUser, clientInfoWithoutDevice);
206
+
207
+ expect(factors).not.toContain(RiskFactor.NEW_DEVICE);
208
+ });
209
+
210
+ it('should not detect new device when device is trusted', async () => {
211
+ mockTrustedDeviceService.isDeviceTrusted.mockResolvedValue(true);
212
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
213
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
214
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
215
+ mockAuditRepository.find.mockResolvedValueOnce([]);
216
+
217
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
218
+
219
+ expect(factors).not.toContain(RiskFactor.NEW_DEVICE);
220
+ expect(mockTrustedDeviceService.isDeviceTrusted).toHaveBeenCalledWith(mockClientInfo.deviceToken, mockUser.id);
221
+ });
222
+
223
+ it('should continue to session check if trusted device check fails', async () => {
224
+ mockTrustedDeviceService.isDeviceTrusted.mockRejectedValue(new Error('Trusted device service error'));
225
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // Device not found in sessions
226
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
227
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
228
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
229
+ mockAuditRepository.find.mockResolvedValueOnce([]);
230
+
231
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
232
+
233
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
234
+ expect(mockLogger.warn).toHaveBeenCalled();
235
+ });
236
+
237
+ it('should handle device check errors gracefully', async () => {
238
+ mockSessionRepository.findOne.mockRejectedValueOnce(new Error('Database error'));
239
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
240
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
241
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
242
+ mockAuditRepository.find.mockResolvedValueOnce([]);
243
+
244
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
245
+
246
+ // Should assume new device on error (safer for security)
247
+ expect(mockLogger.warn).toHaveBeenCalled();
248
+ });
249
+ });
250
+
251
+ // ============================================================================
252
+ // detectRiskFactors() - NEW_IP
253
+ // ============================================================================
254
+
255
+ describe('detectRiskFactors() - new_ip', () => {
256
+ it('should detect new IP when IP never seen before', async () => {
257
+ mockSessionRepository.findOne
258
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
259
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
260
+ .mockResolvedValueOnce(null) // No previous session for impossible_travel
261
+ .mockResolvedValueOnce(null); // IP not found in sessions
262
+ mockAuditRepository.findOne.mockResolvedValueOnce(null); // IP not found in audit
263
+ mockAuditRepository.findOne.mockResolvedValueOnce(null); // No suspicious activity
264
+ mockAuditRepository.find.mockResolvedValueOnce([]); // No failed logins
265
+
266
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
267
+
268
+ expect(factors).toContain(RiskFactor.NEW_IP);
269
+ });
270
+
271
+ it('should not detect new IP when IP seen in sessions', async () => {
272
+ mockSessionRepository.findOne
273
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
274
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
275
+ .mockResolvedValueOnce(null) // No previous session for impossible_travel
276
+ .mockResolvedValueOnce({ id: 1 } as any); // IP found in sessions
277
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
278
+ mockAuditRepository.find.mockResolvedValueOnce([]);
279
+
280
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
281
+
282
+ expect(factors).not.toContain(RiskFactor.NEW_IP);
283
+ // Should not check audit if found in sessions
284
+ expect(mockAuditRepository.findOne).not.toHaveBeenCalledWith(
285
+ (expect as any).objectContaining({
286
+ where: (expect as any).objectContaining({ ipAddress: mockClientInfo.ipAddress }),
287
+ }),
288
+ );
289
+ });
290
+
291
+ it('should check audit trail if not found in sessions', async () => {
292
+ mockSessionRepository.findOne
293
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
294
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
295
+ .mockResolvedValueOnce(null) // No previous session for impossible_travel
296
+ .mockResolvedValueOnce(null); // IP not found in sessions
297
+ // IP check in audit: IP found in audit (not new)
298
+ mockAuditRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // IP found in audit (not suspicious activity check)
299
+ // suspicious_activity checks
300
+ mockAuditRepository.findOne.mockResolvedValueOnce(null); // No suspicious events
301
+ mockAuditRepository.find.mockResolvedValueOnce([]); // No failed logins
302
+
303
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
304
+
305
+ expect(factors).not.toContain(RiskFactor.NEW_IP);
306
+ // Should check audit for IP (isNewIp) and for suspicious activity (detectSuspiciousActivity)
307
+ expect(mockAuditRepository.findOne).toHaveBeenCalledTimes(2);
308
+ });
309
+
310
+ it('should NOT detect new_ip when new_country is detected (double-counting prevention)', async () => {
311
+ // Setup: new_country detected
312
+ mockSessionRepository.findOne
313
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
314
+ .mockResolvedValueOnce(null) // Country not found (countryExists query)
315
+ .mockResolvedValueOnce({ id: 1 } as any) // Sessions with country data exist (hasAnyCountryData)
316
+ .mockResolvedValueOnce(null); // No previous session for impossible_travel
317
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
318
+ mockAuditRepository.find.mockResolvedValueOnce([]);
319
+
320
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
321
+
322
+ expect(factors).toContain(RiskFactor.NEW_COUNTRY);
323
+ expect(factors).not.toContain(RiskFactor.NEW_IP);
324
+ // Should not check IP at all when country changed
325
+ expect(mockSessionRepository.findOne).not.toHaveBeenCalledWith(
326
+ (expect as any).objectContaining({
327
+ where: (expect as any).objectContaining({ ipAddress: mockClientInfo.ipAddress }),
328
+ }),
329
+ );
330
+ });
331
+
332
+ it('should NOT detect new_ip when impossible_travel is detected', async () => {
333
+ const lastSession = {
334
+ id: 1,
335
+ userId: 1,
336
+ ipCountry: 'GB',
337
+ ipCity: 'London',
338
+ lastActivityAt: new Date(Date.now() - 30 * 60 * 1000), // 30 minutes ago
339
+ createdAt: new Date(),
340
+ } as ISession;
341
+
342
+ mockSessionRepository.findOne
343
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
344
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists (same country)
345
+ .mockResolvedValueOnce(lastSession as any); // Previous session found (impossible travel detected)
346
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
347
+ mockAuditRepository.find.mockResolvedValueOnce([]);
348
+
349
+ const clientInfo: ClientInfo = {
350
+ ...mockClientInfo,
351
+ ipCountry: 'GB',
352
+ ipCity: 'Manchester', // Different city, same country
353
+ };
354
+
355
+ const factors = await service.detectRiskFactors(mockUser, clientInfo);
356
+
357
+ // If impossible_travel is detected, new_ip should not be checked
358
+ expect(factors).not.toContain(RiskFactor.NEW_IP);
359
+ });
360
+
361
+ it('should skip new_ip check if trigger not enabled', async () => {
362
+ mockConfig.mfa!.adaptive!.triggers = [RiskFactor.NEW_DEVICE, RiskFactor.NEW_COUNTRY];
363
+ service = new RiskDetectionService(
364
+ mockSessionRepository,
365
+ mockAuditRepository,
366
+ mockConfig,
367
+ mockLogger,
368
+ mockTrustedDeviceService,
369
+ );
370
+
371
+ mockSessionRepository.findOne
372
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
373
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
374
+ .mockResolvedValueOnce(null); // No previous session
375
+
376
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
377
+
378
+ expect(factors).not.toContain(RiskFactor.NEW_IP);
379
+ });
380
+
381
+ it('should skip new_ip check if ipAddress not provided', async () => {
382
+ const clientInfoWithoutIp = { ...mockClientInfo, ipAddress: '' as any };
383
+
384
+ mockSessionRepository.findOne
385
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
386
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
387
+ .mockResolvedValueOnce(null); // No previous session
388
+
389
+ const factors = await service.detectRiskFactors(mockUser, clientInfoWithoutIp);
390
+
391
+ expect(factors).not.toContain(RiskFactor.NEW_IP);
392
+ });
393
+
394
+ it('should handle IP check errors gracefully', async () => {
395
+ mockSessionRepository.findOne
396
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
397
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
398
+ .mockResolvedValueOnce(null) // No previous session
399
+ .mockRejectedValueOnce(new Error('Database error')); // IP check fails
400
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
401
+ mockAuditRepository.find.mockResolvedValueOnce([]);
402
+
403
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
404
+
405
+ expect(mockLogger.warn).toHaveBeenCalled();
406
+ // Should assume new IP on error (safer for security)
407
+ });
408
+ });
409
+
410
+ // ============================================================================
411
+ // detectRiskFactors() - NEW_COUNTRY
412
+ // ============================================================================
413
+
414
+ describe('detectRiskFactors() - new_country', () => {
415
+ it('should NOT detect new_country on first login (no previous sessions)', async () => {
416
+ // First login: no sessions exist, so new_country should not be flagged
417
+ mockSessionRepository.findOne
418
+ .mockResolvedValueOnce(null) // new_device: device not found (first login)
419
+ .mockResolvedValueOnce(null) // isNewCountry: country not found (countryExists query)
420
+ .mockResolvedValueOnce(null); // isNewCountry: no sessions with country data (can't determine)
421
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session for impossible_travel
422
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
423
+ mockAuditRepository.find.mockResolvedValueOnce([]);
424
+
425
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
426
+
427
+ // new_device should be detected, but new_country should NOT (no history to compare)
428
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
429
+ expect(factors).not.toContain(RiskFactor.NEW_COUNTRY);
430
+ });
431
+
432
+ it('should detect new country when country never seen before (user has previous sessions)', async () => {
433
+ mockSessionRepository.findOne
434
+ .mockResolvedValueOnce({ id: 1 } as any) // new_device: device exists
435
+ .mockResolvedValueOnce(null) // isNewCountry: country not found (countryExists query)
436
+ .mockResolvedValueOnce({ id: 1 } as any) // isNewCountry: sessions with country data exist (has country history, so country is new)
437
+ .mockResolvedValueOnce(null); // No previous session for impossible_travel
438
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
439
+ mockAuditRepository.find.mockResolvedValueOnce([]);
440
+
441
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
442
+
443
+ expect(factors).toContain(RiskFactor.NEW_COUNTRY);
444
+ });
445
+
446
+ it('should not detect new country when country seen before', async () => {
447
+ // Optimized: country exists check returns early (1 query instead of 3)
448
+ mockSessionRepository.findOne
449
+ .mockResolvedValueOnce({ id: 1 } as any) // new_device: device exists
450
+ .mockResolvedValueOnce({ id: 1 } as any) // isNewCountry: country exists (returns false immediately, no second query needed)
451
+ .mockResolvedValueOnce(null); // No previous session for impossible_travel
452
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
453
+ mockAuditRepository.find.mockResolvedValueOnce([]);
454
+
455
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
456
+
457
+ expect(factors).not.toContain(RiskFactor.NEW_COUNTRY);
458
+ });
459
+
460
+ it('should skip new_country check if trigger not enabled', async () => {
461
+ mockConfig.mfa!.adaptive!.triggers = [RiskFactor.NEW_DEVICE, RiskFactor.NEW_IP];
462
+ service = new RiskDetectionService(
463
+ mockSessionRepository,
464
+ mockAuditRepository,
465
+ mockConfig,
466
+ mockLogger,
467
+ mockTrustedDeviceService,
468
+ );
469
+
470
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Device exists
471
+
472
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
473
+
474
+ expect(factors).not.toContain(RiskFactor.NEW_COUNTRY);
475
+ });
476
+
477
+ it('should skip new_country check if ipCountry not provided', async () => {
478
+ const clientInfoWithoutCountry = { ...mockClientInfo, ipCountry: undefined };
479
+
480
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Device exists
481
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
482
+
483
+ const factors = await service.detectRiskFactors(mockUser, clientInfoWithoutCountry);
484
+
485
+ expect(factors).not.toContain(RiskFactor.NEW_COUNTRY);
486
+ });
487
+
488
+ it('should handle country check errors gracefully', async () => {
489
+ mockSessionRepository.findOne
490
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
491
+ .mockRejectedValueOnce(new Error('Database error')); // Country check fails
492
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
493
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
494
+ mockAuditRepository.find.mockResolvedValueOnce([]);
495
+
496
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
497
+
498
+ expect(mockLogger.warn).toHaveBeenCalled();
499
+ // Should assume not new country on error (safer default)
500
+ });
501
+ });
502
+
503
+ // ============================================================================
504
+ // detectRiskFactors() - IMPOSSIBLE_TRAVEL
505
+ // ============================================================================
506
+
507
+ describe('detectRiskFactors() - impossible_travel', () => {
508
+ it('should skip impossible_travel check if trigger not enabled', async () => {
509
+ mockConfig.mfa!.adaptive!.triggers = [RiskFactor.NEW_DEVICE];
510
+ service = new RiskDetectionService(
511
+ mockSessionRepository,
512
+ mockAuditRepository,
513
+ mockConfig,
514
+ mockLogger,
515
+ mockTrustedDeviceService,
516
+ );
517
+
518
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Device exists
519
+
520
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
521
+
522
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
523
+ });
524
+
525
+ it('should skip impossible_travel check if location data incomplete', async () => {
526
+ const clientInfoWithoutCity = { ...mockClientInfo, ipCity: undefined };
527
+
528
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Device exists
529
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
530
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // IP exists
531
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
532
+ mockAuditRepository.find.mockResolvedValueOnce([]);
533
+
534
+ const factors = await service.detectRiskFactors(mockUser, clientInfoWithoutCity);
535
+
536
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
537
+ });
538
+
539
+ it('should skip impossible_travel check if ipCountry missing', async () => {
540
+ const clientInfoWithoutCountry = { ...mockClientInfo, ipCountry: undefined };
541
+
542
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Device exists
543
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists (but clientInfo doesn't have it)
544
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // IP exists
545
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
546
+ mockAuditRepository.find.mockResolvedValueOnce([]);
547
+
548
+ const factors = await service.detectRiskFactors(mockUser, clientInfoWithoutCountry);
549
+
550
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
551
+ });
552
+
553
+ it('should return false for impossible_travel if no previous location', async () => {
554
+ mockSessionRepository.findOne
555
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
556
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
557
+ .mockResolvedValueOnce(null); // No previous session with location
558
+
559
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
560
+
561
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
562
+ });
563
+
564
+ it('should not detect impossible_travel if same location', async () => {
565
+ const lastSession = {
566
+ id: 1,
567
+ userId: 1,
568
+ ipCountry: 'US',
569
+ ipCity: 'New York',
570
+ lastActivityAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
571
+ createdAt: new Date(),
572
+ } as ISession;
573
+
574
+ mockSessionRepository.findOne
575
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
576
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
577
+ .mockResolvedValueOnce(lastSession as any); // Previous session with same location
578
+
579
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
580
+
581
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
582
+ });
583
+
584
+ it('should detect impossible_travel when travel speed exceeds threshold', async () => {
585
+ const lastSession = {
586
+ id: 1,
587
+ userId: 1,
588
+ ipCountry: 'US',
589
+ ipCity: 'New York',
590
+ lastActivityAt: new Date(Date.now() - 15 * 60 * 1000), // 15 minutes ago
591
+ createdAt: new Date(Date.now() - 15 * 60 * 1000), // 15 minutes ago (login time)
592
+ } as ISession;
593
+
594
+ const clientInfoDifferentCity: ClientInfo = {
595
+ ...mockClientInfo,
596
+ ipCountry: 'US',
597
+ ipCity: 'Los Angeles', // Different city, same country
598
+ };
599
+
600
+ mockSessionRepository.findOne
601
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
602
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
603
+ .mockResolvedValueOnce(lastSession as any); // Previous session with different city
604
+
605
+ // Distance calculation: same country, different city = 500 km
606
+ // Time: 15 minutes = 0.25 hours
607
+ // Speed: 500 / 0.25 = 2000 km/h > 900 km/h (default threshold) -> impossible travel
608
+ const factors = await service.detectRiskFactors(mockUser, clientInfoDifferentCity);
609
+
610
+ expect(factors).toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
611
+ });
612
+
613
+ it('should not detect impossible_travel when distance is 0 (same location)', async () => {
614
+ const lastSession = {
615
+ id: 1,
616
+ userId: 1,
617
+ ipCountry: 'US',
618
+ ipCity: 'New York',
619
+ lastActivityAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
620
+ createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago (login time)
621
+ } as ISession;
622
+
623
+ // Same city and country (distance = 0)
624
+ const clientInfoSameLocation: ClientInfo = {
625
+ ...mockClientInfo,
626
+ ipCountry: 'US',
627
+ ipCity: 'New York', // Same city
628
+ };
629
+
630
+ mockSessionRepository.findOne
631
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
632
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
633
+ .mockResolvedValueOnce(lastSession as any); // Previous session with same location
634
+
635
+ const factors = await service.detectRiskFactors(mockUser, clientInfoSameLocation);
636
+
637
+ // calculateDistance returns 0, so impossible_travel should not be detected
638
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
639
+ });
640
+
641
+ it('should detect impossible_travel for different countries', async () => {
642
+ const lastSession = {
643
+ id: 1,
644
+ userId: 1,
645
+ ipCountry: 'US',
646
+ ipCity: 'New York',
647
+ lastActivityAt: new Date(Date.now() - 30 * 60 * 1000), // 30 minutes ago
648
+ createdAt: new Date(Date.now() - 30 * 60 * 1000), // 30 minutes ago (login time)
649
+ } as ISession;
650
+
651
+ // Different country (distance = 2000 km)
652
+ const clientInfoDifferentCountry: ClientInfo = {
653
+ ...mockClientInfo,
654
+ ipCountry: 'GB',
655
+ ipCity: 'London',
656
+ ipLatitude: 51.5074,
657
+ ipLongitude: -0.1278,
658
+ };
659
+
660
+ mockSessionRepository.findOne
661
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
662
+ .mockResolvedValueOnce(null) // Country doesn't exist (new country)
663
+ .mockResolvedValueOnce({ id: 1 } as any) // hasAnyCountryData check
664
+ .mockResolvedValueOnce(lastSession as any) // Previous session for impossible_travel
665
+ .mockResolvedValueOnce({ id: 1 } as any); // hasUserLoggedInBefore for INCOMPLETE_LOCATION_DATA
666
+ mockAuditRepository.findOne.mockResolvedValueOnce(null); // No previous audit login
667
+
668
+ // Distance: 2000 km, Time: 30 minutes = 0.5 hours
669
+ // Speed: 2000 / 0.5 = 4000 km/h > 900 km/h -> impossible travel
670
+ const factors = await service.detectRiskFactors(mockUser, clientInfoDifferentCountry);
671
+
672
+ expect(factors).toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
673
+ });
674
+
675
+ it('should detect impossible_travel when time difference is less than 30 minutes', async () => {
676
+ const lastSession = {
677
+ id: 1,
678
+ userId: 1,
679
+ ipCountry: 'US',
680
+ ipCity: 'New York',
681
+ lastActivityAt: new Date(Date.now() - 20 * 60 * 1000), // 20 minutes ago
682
+ createdAt: new Date(Date.now() - 20 * 60 * 1000), // 20 minutes ago (login time)
683
+ } as ISession;
684
+
685
+ const clientInfoDifferentCity: ClientInfo = {
686
+ ...mockClientInfo,
687
+ ipCountry: 'US',
688
+ ipCity: 'Los Angeles',
689
+ };
690
+
691
+ mockSessionRepository.findOne
692
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
693
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
694
+ .mockResolvedValueOnce(lastSession as any);
695
+
696
+ const factors = await service.detectRiskFactors(mockUser, clientInfoDifferentCity);
697
+
698
+ // Time < 30 minutes with different city = impossible travel
699
+ expect(factors).toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
700
+ });
701
+
702
+ it('should use custom maxTravelSpeed from config', async () => {
703
+ mockConfig.mfa!.adaptive!.maxTravelSpeed = 500; // Lower threshold
704
+
705
+ const lastSession = {
706
+ id: 1,
707
+ userId: 1,
708
+ ipCountry: 'US',
709
+ ipCity: 'New York',
710
+ lastActivityAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
711
+ createdAt: new Date(),
712
+ } as ISession;
713
+
714
+ const clientInfoDifferentCity: ClientInfo = {
715
+ ...mockClientInfo,
716
+ ipCountry: 'US',
717
+ ipCity: 'Los Angeles',
718
+ };
719
+
720
+ service = new RiskDetectionService(
721
+ mockSessionRepository,
722
+ mockAuditRepository,
723
+ mockConfig,
724
+ mockLogger,
725
+ mockTrustedDeviceService,
726
+ );
727
+
728
+ mockSessionRepository.findOne
729
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
730
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
731
+ .mockResolvedValueOnce(lastSession as any);
732
+
733
+ // Distance: 500 km, Time: 2 hours, Speed: 250 km/h < 500 km/h -> not impossible
734
+ const factors = await service.detectRiskFactors(mockUser, clientInfoDifferentCity);
735
+
736
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
737
+ });
738
+
739
+ it('should handle impossible travel check errors gracefully', async () => {
740
+ mockSessionRepository.findOne
741
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
742
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
743
+ .mockRejectedValueOnce(new Error('Database error')); // Impossible travel check fails
744
+
745
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
746
+
747
+ expect(mockLogger.warn).toHaveBeenCalled();
748
+ // Should assume not impossible travel on error
749
+ });
750
+
751
+ it('should detect impossible_travel using Haversine formula when coordinates are available', async () => {
752
+ // Singapore to London: ~10,850 km in 10 minutes = impossible
753
+ const lastSession = {
754
+ id: 1,
755
+ userId: 1,
756
+ ipCountry: 'SG',
757
+ ipCity: 'Singapore',
758
+ ipLatitude: 1.3521,
759
+ ipLongitude: 103.8198,
760
+ createdAt: new Date(Date.now() - 10 * 60 * 1000), // 10 minutes ago
761
+ } as any;
762
+
763
+ const clientInfoWithCoordinates: ClientInfo = {
764
+ ...mockClientInfo,
765
+ ipCountry: 'GB',
766
+ ipCity: 'London',
767
+ ipLatitude: 51.5074,
768
+ ipLongitude: -0.1278,
769
+ };
770
+
771
+ mockSessionRepository.findOne
772
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
773
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
774
+ .mockResolvedValueOnce(lastSession); // Coordinates included
775
+
776
+ const factors = await service.detectRiskFactors(mockUser, clientInfoWithCoordinates);
777
+
778
+ // Distance: ~10,850 km (Haversine), Time: 10 minutes = 0.167 hours
779
+ // Speed: ~65,000 km/h >> 900 km/h -> impossible travel
780
+ expect(factors).toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
781
+ });
782
+
783
+ it('should detect impossible_travel when country changes with missing city data (conservative)', async () => {
784
+ // Country changed but no city data - should flag if < 2 hours
785
+ const lastSession = {
786
+ id: 1,
787
+ userId: 1,
788
+ ipCountry: 'SG',
789
+ ipCity: null, // Missing city
790
+ createdAt: new Date(Date.now() - 30 * 60 * 1000), // 30 minutes ago
791
+ } as ISession;
792
+
793
+ const clientInfoMissingCity: ClientInfo = {
794
+ ...mockClientInfo,
795
+ ipCountry: 'NO', // Different country
796
+ ipCity: undefined, // Missing city
797
+ };
798
+
799
+ mockSessionRepository.findOne
800
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
801
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
802
+ .mockResolvedValueOnce(lastSession as any);
803
+
804
+ const factors = await service.detectRiskFactors(mockUser, clientInfoMissingCity);
805
+
806
+ // Country changed in < 2 hours without city data -> flag as impossible
807
+ expect(factors).toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
808
+ });
809
+
810
+ it('should NOT detect impossible_travel when country changes with missing city data but enough time passed', async () => {
811
+ // Country changed but no city data - should NOT flag if > 2 hours
812
+ const lastSession = {
813
+ id: 1,
814
+ userId: 1,
815
+ ipCountry: 'SG',
816
+ ipCity: null,
817
+ createdAt: new Date(Date.now() - 3 * 60 * 60 * 1000), // 3 hours ago
818
+ } as ISession;
819
+
820
+ const clientInfoMissingCity: ClientInfo = {
821
+ ...mockClientInfo,
822
+ ipCountry: 'NO',
823
+ ipCity: undefined,
824
+ };
825
+
826
+ mockSessionRepository.findOne
827
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
828
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
829
+ .mockResolvedValueOnce(lastSession as any);
830
+
831
+ const factors = await service.detectRiskFactors(mockUser, clientInfoMissingCity);
832
+
833
+ // Country changed but > 2 hours passed -> acceptable
834
+ expect(factors).not.toContain(RiskFactor.IMPOSSIBLE_TRAVEL);
835
+ });
836
+ });
837
+
838
+ // ============================================================================
839
+ // detectRiskFactors() - INCOMPLETE_LOCATION_DATA
840
+ // ============================================================================
841
+
842
+ describe('detectRiskFactors() - incomplete_location_data', () => {
843
+ it('should detect incomplete_location_data when city is missing', async () => {
844
+ const clientInfoMissingCity: ClientInfo = {
845
+ ...mockClientInfo,
846
+ ipCountry: 'US',
847
+ ipCity: undefined,
848
+ };
849
+
850
+ mockSessionRepository.findOne
851
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
852
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
853
+ .mockResolvedValueOnce({ id: 1 } as any); // hasUserLoggedInBefore
854
+
855
+ const factors = await service.detectRiskFactors(mockUser, clientInfoMissingCity);
856
+
857
+ expect(factors).toContain(RiskFactor.INCOMPLETE_LOCATION_DATA);
858
+ });
859
+
860
+ it('should detect incomplete_location_data when coordinates are missing', async () => {
861
+ const clientInfoMissingCoordinates: ClientInfo = {
862
+ ...mockClientInfo,
863
+ ipCountry: 'US',
864
+ ipCity: 'New York',
865
+ ipLatitude: undefined,
866
+ ipLongitude: undefined,
867
+ };
868
+
869
+ mockSessionRepository.findOne
870
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
871
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
872
+ .mockResolvedValueOnce({ id: 1 } as any); // hasUserLoggedInBefore
873
+
874
+ const factors = await service.detectRiskFactors(mockUser, clientInfoMissingCoordinates);
875
+
876
+ expect(factors).toContain(RiskFactor.INCOMPLETE_LOCATION_DATA);
877
+ });
878
+
879
+ it('should NOT detect incomplete_location_data when all location data is present', async () => {
880
+ const clientInfoComplete: ClientInfo = {
881
+ ...mockClientInfo,
882
+ ipCountry: 'US',
883
+ ipCity: 'New York',
884
+ ipLatitude: 40.7128,
885
+ ipLongitude: -74.006,
886
+ };
887
+
888
+ mockSessionRepository.findOne
889
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
890
+ .mockResolvedValueOnce({ id: 1 } as any); // Country exists
891
+
892
+ const factors = await service.detectRiskFactors(mockUser, clientInfoComplete);
893
+
894
+ expect(factors).not.toContain(RiskFactor.INCOMPLETE_LOCATION_DATA);
895
+ });
896
+
897
+ it('should NOT detect incomplete_location_data when country is missing', async () => {
898
+ const clientInfoNoCountry: ClientInfo = {
899
+ ...mockClientInfo,
900
+ ipCountry: undefined,
901
+ ipCity: undefined,
902
+ };
903
+
904
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Device exists
905
+
906
+ const factors = await service.detectRiskFactors(mockUser, clientInfoNoCountry);
907
+
908
+ // Should not add incomplete_location_data when there's no country at all
909
+ expect(factors).not.toContain(RiskFactor.INCOMPLETE_LOCATION_DATA);
910
+ });
911
+ });
912
+
913
+ // ============================================================================
914
+ // detectRiskFactors() - SUSPICIOUS_ACTIVITY
915
+ // ============================================================================
916
+
917
+ describe('detectRiskFactors() - suspicious_activity', () => {
918
+ it('should detect suspicious_activity when recent suspicious events found', async () => {
919
+ // Ensure complete location data to avoid INCOMPLETE_LOCATION_DATA
920
+ const clientInfoComplete: ClientInfo = {
921
+ ...mockClientInfo,
922
+ ipCountry: 'US',
923
+ ipCity: 'New York',
924
+ ipLatitude: 40.7128,
925
+ ipLongitude: -74.006,
926
+ };
927
+
928
+ mockSessionRepository.findOne
929
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
930
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
931
+ .mockResolvedValueOnce(null) // No previous session for impossible_travel
932
+ .mockResolvedValueOnce({ id: 1 } as any); // IP exists (no new_ip)
933
+ // suspicious_activity check: findOne for suspicious events
934
+ mockAuditRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Suspicious events found
935
+ mockAuditRepository.find.mockResolvedValueOnce([]); // Failed logins (not needed if suspicious found)
936
+
937
+ const factors = await service.detectRiskFactors(mockUser, clientInfoComplete);
938
+
939
+ expect(factors).toContain(RiskFactor.SUSPICIOUS_ACTIVITY);
940
+ });
941
+
942
+ it('should detect suspicious_activity when 3+ failed logins in last hour', async () => {
943
+ mockSessionRepository.findOne
944
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
945
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
946
+ .mockResolvedValueOnce(null); // No previous session
947
+ mockAuditRepository.findOne.mockResolvedValueOnce(null); // No suspicious events
948
+ mockAuditRepository.find.mockResolvedValueOnce([{ id: 1 } as any, { id: 2 } as any, { id: 3 } as any]); // 3 failed logins
949
+
950
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
951
+
952
+ expect(factors).toContain(RiskFactor.SUSPICIOUS_ACTIVITY);
953
+ });
954
+
955
+ it('should not detect suspicious_activity when threshold not met', async () => {
956
+ mockSessionRepository.findOne
957
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
958
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
959
+ .mockResolvedValueOnce(null); // No previous session
960
+ mockAuditRepository.findOne.mockResolvedValueOnce(null); // No suspicious events
961
+ mockAuditRepository.find.mockResolvedValueOnce([{ id: 1 } as any, { id: 2 } as any]); // Only 2 failed logins (below threshold)
962
+
963
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
964
+
965
+ expect(factors).not.toContain(RiskFactor.SUSPICIOUS_ACTIVITY);
966
+ });
967
+
968
+ it('should use custom suspiciousActivityWindow from config', async () => {
969
+ mockConfig.mfa!.adaptive!.suspiciousActivityWindow = 2; // 2 hours instead of 1
970
+
971
+ service = new RiskDetectionService(
972
+ mockSessionRepository,
973
+ mockAuditRepository,
974
+ mockConfig,
975
+ mockLogger,
976
+ mockTrustedDeviceService,
977
+ );
978
+
979
+ mockSessionRepository.findOne
980
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
981
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
982
+ .mockResolvedValueOnce(null); // No previous session
983
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
984
+ mockAuditRepository.find.mockResolvedValueOnce([]);
985
+
986
+ await service.detectRiskFactors(mockUser, mockClientInfo);
987
+
988
+ // Verify find was called with correct time window (2 hours ago)
989
+ expect(mockAuditRepository.find).toHaveBeenCalled();
990
+ });
991
+
992
+ it('should skip suspicious_activity check if trigger not enabled', async () => {
993
+ mockConfig.mfa!.adaptive!.triggers = [RiskFactor.NEW_DEVICE];
994
+ service = new RiskDetectionService(
995
+ mockSessionRepository,
996
+ mockAuditRepository,
997
+ mockConfig,
998
+ mockLogger,
999
+ mockTrustedDeviceService,
1000
+ );
1001
+
1002
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Device exists
1003
+
1004
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1005
+
1006
+ expect(factors).not.toContain(RiskFactor.SUSPICIOUS_ACTIVITY);
1007
+ expect(mockAuditRepository.findOne).not.toHaveBeenCalled();
1008
+ });
1009
+
1010
+ it('should handle suspicious activity check errors gracefully', async () => {
1011
+ mockSessionRepository.findOne
1012
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
1013
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
1014
+ .mockResolvedValueOnce(null); // No previous session
1015
+ mockAuditRepository.findOne.mockRejectedValueOnce(new Error('Database error'));
1016
+
1017
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1018
+
1019
+ expect(mockLogger.warn).toHaveBeenCalled();
1020
+ // Should assume not suspicious on error
1021
+ });
1022
+ });
1023
+
1024
+ // ============================================================================
1025
+ // detectRiskFactors() - Error Handling
1026
+ // ============================================================================
1027
+
1028
+ describe('detectRiskFactors() - error handling', () => {
1029
+ it('should handle errors in individual checks gracefully', async () => {
1030
+ // First check succeeds, second fails
1031
+ mockSessionRepository.findOne
1032
+ .mockResolvedValueOnce(null) // new_device succeeds (device not found)
1033
+ .mockRejectedValueOnce(new Error('Database error')); // isNewCountry: countryExists query fails
1034
+
1035
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1036
+
1037
+ // Should still detect new_device, but handle error for new_country
1038
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
1039
+ expect(mockLogger.warn).toHaveBeenCalled();
1040
+ });
1041
+
1042
+ it('should handle errors in isNewDevice gracefully', async () => {
1043
+ mockSessionRepository.findOne.mockRejectedValueOnce(new Error('Database error'));
1044
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
1045
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
1046
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
1047
+ mockAuditRepository.find.mockResolvedValueOnce([]);
1048
+
1049
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1050
+
1051
+ // Should assume new device on error (safer for security)
1052
+ expect(mockLogger.warn).toHaveBeenCalled();
1053
+ });
1054
+
1055
+ it('should handle errors in isNewIp gracefully', async () => {
1056
+ mockSessionRepository.findOne
1057
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
1058
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
1059
+ .mockResolvedValueOnce(null) // No previous session
1060
+ .mockRejectedValueOnce(new Error('Database error')); // IP check fails
1061
+
1062
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1063
+
1064
+ expect(mockLogger.warn).toHaveBeenCalled();
1065
+ });
1066
+
1067
+ it('should handle errors in isNewCountry gracefully', async () => {
1068
+ mockSessionRepository.findOne
1069
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
1070
+ .mockRejectedValueOnce(new Error('Database error')); // Country check fails
1071
+
1072
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1073
+
1074
+ expect(mockLogger.warn).toHaveBeenCalled();
1075
+ // Should assume not new country on error (safer default)
1076
+ });
1077
+
1078
+ it('should handle errors in detectImpossibleTravel gracefully', async () => {
1079
+ mockSessionRepository.findOne
1080
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
1081
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
1082
+ .mockRejectedValueOnce(new Error('Database error')); // Impossible travel check fails
1083
+
1084
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1085
+
1086
+ expect(mockLogger.warn).toHaveBeenCalled();
1087
+ });
1088
+
1089
+ it('should handle errors in detectSuspiciousActivity gracefully', async () => {
1090
+ mockSessionRepository.findOne
1091
+ .mockResolvedValueOnce({ id: 1 } as any) // Device exists
1092
+ .mockResolvedValueOnce({ id: 1 } as any) // Country exists
1093
+ .mockResolvedValueOnce(null); // No previous session
1094
+ mockAuditRepository.findOne.mockRejectedValueOnce(new Error('Database error'));
1095
+
1096
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1097
+
1098
+ expect(mockLogger.warn).toHaveBeenCalled();
1099
+ });
1100
+
1101
+ it('should handle non-Error exceptions in individual checks', async () => {
1102
+ mockSessionRepository.findOne
1103
+ .mockResolvedValueOnce(null) // new_device succeeds
1104
+ .mockRejectedValueOnce('String error' as any); // isNewCountry fails with non-Error
1105
+
1106
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1107
+
1108
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
1109
+ expect(mockLogger.warn).toHaveBeenCalled();
1110
+ });
1111
+
1112
+ it('should return empty array on outer catch block error', async () => {
1113
+ // Make user.sub throw an error when accessed in the catch block
1114
+ const userWithError = {
1115
+ ...mockUser,
1116
+ get sub() {
1117
+ throw new Error('Accessing sub throws');
1118
+ },
1119
+ } as any;
1120
+
1121
+ // Make the first repository call throw to trigger outer catch
1122
+ // But individual checks have try-catch, so we need to make something else throw
1123
+ // Actually, if we make the config access throw, it would work
1124
+ // But a simpler approach: make the service throw during enabledTriggers access
1125
+ // by making the config property throw
1126
+ const throwingConfig = {
1127
+ ...mockConfig,
1128
+ get mfa() {
1129
+ throw new Error('Config access error');
1130
+ },
1131
+ } as any;
1132
+
1133
+ const serviceWithError = new RiskDetectionService(
1134
+ mockSessionRepository,
1135
+ mockAuditRepository,
1136
+ throwingConfig,
1137
+ mockLogger,
1138
+ mockTrustedDeviceService,
1139
+ );
1140
+
1141
+ const factors = await serviceWithError.detectRiskFactors(mockUser, mockClientInfo);
1142
+
1143
+ expect(factors).toEqual([]);
1144
+ expect(mockLogger.error).toHaveBeenCalled();
1145
+ });
1146
+
1147
+ it('should handle non-Error in outer catch block', async () => {
1148
+ const throwingConfig = {
1149
+ ...mockConfig,
1150
+ get mfa() {
1151
+ // eslint-disable-next-line no-throw-literal
1152
+ throw 'String error';
1153
+ },
1154
+ } as any;
1155
+
1156
+ const serviceWithError = new RiskDetectionService(
1157
+ mockSessionRepository,
1158
+ mockAuditRepository,
1159
+ throwingConfig,
1160
+ mockLogger,
1161
+ mockTrustedDeviceService,
1162
+ );
1163
+
1164
+ const factors = await serviceWithError.detectRiskFactors(mockUser, mockClientInfo);
1165
+
1166
+ expect(factors).toEqual([]);
1167
+ expect(mockLogger.error).toHaveBeenCalledWith(
1168
+ (expect as any).stringContaining('Risk detection failed'),
1169
+ (expect as any).any(Object),
1170
+ );
1171
+ });
1172
+ });
1173
+
1174
+ // ============================================================================
1175
+ // detectRiskFactors() - Configuration
1176
+ // ============================================================================
1177
+
1178
+ describe('detectRiskFactors() - configuration', () => {
1179
+ it('should use default triggers when not configured', async () => {
1180
+ mockConfig.mfa = undefined;
1181
+ service = new RiskDetectionService(
1182
+ mockSessionRepository,
1183
+ mockAuditRepository,
1184
+ mockConfig,
1185
+ mockLogger,
1186
+ mockTrustedDeviceService,
1187
+ );
1188
+
1189
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // Device not found
1190
+
1191
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1192
+
1193
+ // Should check default triggers: new_device, new_ip, new_country
1194
+ expect(mockSessionRepository.findOne).toHaveBeenCalled();
1195
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
1196
+ });
1197
+
1198
+ it('should respect custom trigger configuration', async () => {
1199
+ mockConfig.mfa!.adaptive!.triggers = [RiskFactor.NEW_DEVICE];
1200
+ service = new RiskDetectionService(
1201
+ mockSessionRepository,
1202
+ mockAuditRepository,
1203
+ mockConfig,
1204
+ mockLogger,
1205
+ mockTrustedDeviceService,
1206
+ );
1207
+
1208
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // Device not found
1209
+
1210
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1211
+
1212
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
1213
+ // Should not check other triggers
1214
+ expect(mockSessionRepository.findOne).toHaveBeenCalledTimes(1); // Only new_device
1215
+ });
1216
+
1217
+ it('should handle service without trusted device service', async () => {
1218
+ service = new RiskDetectionService(
1219
+ mockSessionRepository,
1220
+ mockAuditRepository,
1221
+ mockConfig,
1222
+ mockLogger,
1223
+ undefined, // No trusted device service
1224
+ );
1225
+
1226
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // Device not found
1227
+ mockSessionRepository.findOne.mockResolvedValueOnce({ id: 1 } as any); // Country exists
1228
+ mockSessionRepository.findOne.mockResolvedValueOnce(null); // No previous session
1229
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
1230
+ mockAuditRepository.find.mockResolvedValueOnce([]);
1231
+
1232
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1233
+
1234
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
1235
+ // Should not throw error
1236
+ });
1237
+ });
1238
+
1239
+ // ============================================================================
1240
+ // Edge Cases and Integration
1241
+ // ============================================================================
1242
+
1243
+ describe('Edge Cases and Integration', () => {
1244
+ it('should handle multiple risk factors detected together', async () => {
1245
+ mockSessionRepository.findOne
1246
+ .mockResolvedValueOnce(null) // new_device: device not found
1247
+ .mockResolvedValueOnce(null) // new_country: country not found
1248
+ .mockResolvedValueOnce({ id: 1 } as any) // new_country: has country history
1249
+ .mockResolvedValueOnce(null); // No previous session for impossible_travel
1250
+ mockAuditRepository.findOne.mockResolvedValueOnce(null);
1251
+ mockAuditRepository.find.mockResolvedValueOnce([]);
1252
+
1253
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1254
+
1255
+ expect(factors).toContain(RiskFactor.NEW_DEVICE);
1256
+ expect(factors).toContain(RiskFactor.NEW_COUNTRY);
1257
+ expect(factors).not.toContain(RiskFactor.NEW_IP); // Should be excluded due to new_country
1258
+ });
1259
+
1260
+ it('should handle all risk factors disabled', async () => {
1261
+ mockConfig.mfa!.adaptive!.triggers = [];
1262
+ service = new RiskDetectionService(
1263
+ mockSessionRepository,
1264
+ mockAuditRepository,
1265
+ mockConfig,
1266
+ mockLogger,
1267
+ mockTrustedDeviceService,
1268
+ );
1269
+
1270
+ const factors = await service.detectRiskFactors(mockUser, mockClientInfo);
1271
+
1272
+ expect(factors).toEqual([]);
1273
+ expect(mockSessionRepository.findOne).not.toHaveBeenCalled();
1274
+ });
1275
+
1276
+ it('should handle concurrent risk detection calls', async () => {
1277
+ mockSessionRepository.findOne.mockResolvedValue({ id: 1 } as any);
1278
+ mockAuditRepository.findOne.mockResolvedValue(null);
1279
+ mockAuditRepository.find.mockResolvedValue([]);
1280
+
1281
+ const promises = [
1282
+ service.detectRiskFactors(mockUser, mockClientInfo),
1283
+ service.detectRiskFactors(mockUser, mockClientInfo),
1284
+ ];
1285
+
1286
+ const results = await Promise.all(promises);
1287
+
1288
+ expect(results.length).toBe(2);
1289
+ expect(results[0]).toEqual(results[1]);
1290
+ });
1291
+ });
1292
+ });