@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,2356 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AuthService = void 0;
37
+ const auth_audit_event_type_enum_1 = require("../enums/auth-audit-event-type.enum");
38
+ const risk_factor_enum_1 = require("../enums/risk-factor.enum");
39
+ const context_storage_1 = require("../utils/context-storage");
40
+ const user_response_dto_1 = require("../dto/user-response.dto");
41
+ const auth_challenge_dto_1 = require("../dto/auth-challenge.dto");
42
+ const verify_email_dto_1 = require("../dto/verify-email.dto");
43
+ const verify_phone_dto_1 = require("../dto/verify-phone.dto");
44
+ const verify_phone_by_sub_dto_1 = require("../dto/verify-phone-by-sub.dto");
45
+ const nauth_exception_1 = require("../exceptions/nauth.exception");
46
+ const error_codes_enum_1 = require("../enums/error-codes.enum");
47
+ const mfa_method_enum_1 = require("../enums/mfa-method.enum");
48
+ const crypto = __importStar(require("crypto"));
49
+ const DUMMY_ARGON2_HASH = '$argon2id$v=19$m=65536,t=3,p=4$RFVNTVlfU0FMVF9GT1JfVElNSU5H$dummyhashfordummyhashfordummyhash1234567890';
50
+ class AuthService {
51
+ userRepository;
52
+ loginAttemptRepository;
53
+ passwordService;
54
+ jwtService;
55
+ sessionService;
56
+ challengeService;
57
+ challengeHelper;
58
+ emailVerificationService;
59
+ clientInfoService;
60
+ accountLockoutStorage;
61
+ config;
62
+ logger;
63
+ auditService;
64
+ phoneVerificationService;
65
+ mfaService;
66
+ mfaDeviceRepository;
67
+ trustedDeviceService;
68
+ constructor(userRepository, loginAttemptRepository, passwordService, jwtService, sessionService, challengeService, challengeHelper, emailVerificationService, clientInfoService, accountLockoutStorage, config, logger, auditService, phoneVerificationService, mfaService, mfaDeviceRepository, trustedDeviceService) {
69
+ this.userRepository = userRepository;
70
+ this.loginAttemptRepository = loginAttemptRepository;
71
+ this.passwordService = passwordService;
72
+ this.jwtService = jwtService;
73
+ this.sessionService = sessionService;
74
+ this.challengeService = challengeService;
75
+ this.challengeHelper = challengeHelper;
76
+ this.emailVerificationService = emailVerificationService;
77
+ this.clientInfoService = clientInfoService;
78
+ this.accountLockoutStorage = accountLockoutStorage;
79
+ this.config = config;
80
+ this.logger = logger;
81
+ this.auditService = auditService;
82
+ this.phoneVerificationService = phoneVerificationService;
83
+ this.mfaService = mfaService;
84
+ this.mfaDeviceRepository = mfaDeviceRepository;
85
+ this.trustedDeviceService = trustedDeviceService;
86
+ this.logger?.log?.('AuthService initialized');
87
+ }
88
+ async signup(dto) {
89
+ const clientInfo = this.clientInfoService.get();
90
+ this.logger?.log?.(`Signup attempt for email: ${dto.email}`);
91
+ this.logger?.debug?.(`Signup details: { email: ${dto.email}, username: ${dto.username || 'none'}, ip: ${clientInfo.ipAddress} }`);
92
+ if (this.config.signup?.enabled === false) {
93
+ this.logger?.warn?.(`Signup blocked - signup is disabled`);
94
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SIGNUP_DISABLED, 'Signups are currently disabled');
95
+ }
96
+ this.logger?.debug?.(`Checking if user exists: ${dto.email}`);
97
+ const existingUserByEmail = await this.userRepository.findOne({
98
+ where: { email: dto.email },
99
+ });
100
+ if (existingUserByEmail) {
101
+ this.logger?.warn?.(`Signup failed - user already exists: ${dto.email}`);
102
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.EMAIL_EXISTS, 'User with this email already exists');
103
+ }
104
+ if (dto.username) {
105
+ this.logger?.debug?.(`Checking if username exists: ${dto.username}`);
106
+ const existingUserByUsername = await this.userRepository.findOne({
107
+ where: { username: dto.username },
108
+ });
109
+ if (existingUserByUsername) {
110
+ this.logger?.warn?.(`Signup failed - username already exists: ${dto.username}`);
111
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.USERNAME_EXISTS, 'Username is already taken');
112
+ }
113
+ }
114
+ if (dto.phone && !this.config.signup?.allowDuplicatePhones) {
115
+ this.logger?.debug?.(`Checking if phone exists: ${dto.phone}`);
116
+ const existingUserByPhone = await this.userRepository.findOne({
117
+ where: { phone: dto.phone },
118
+ });
119
+ if (existingUserByPhone) {
120
+ this.logger?.warn?.(`Signup failed - phone already exists: ${dto.phone}`);
121
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PHONE_EXISTS, 'Phone number is already registered');
122
+ }
123
+ }
124
+ this.logger?.debug?.('Validating password against policy');
125
+ const passwordValidation = await this.passwordService.validatePassword(dto.password, {
126
+ email: dto.email,
127
+ username: dto.username,
128
+ });
129
+ if (!passwordValidation.valid) {
130
+ this.logger?.warn?.(`Password validation failed for ${dto.email}: ${passwordValidation.errors.join(', ')}`);
131
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.WEAK_PASSWORD, passwordValidation.errors.join(', '), {
132
+ errors: passwordValidation.errors,
133
+ });
134
+ }
135
+ const passwordHash = await this.passwordService.hashPassword(dto.password);
136
+ const verificationMethod = this.config.signup?.verificationMethod;
137
+ if ((verificationMethod === 'phone' || verificationMethod === 'both') && !dto.phone) {
138
+ this.logger?.warn?.(`Signup failed - phone required for verification method: ${verificationMethod}`);
139
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PHONE_REQUIRED, 'Phone number is required for the selected verification method', { verificationMethod });
140
+ }
141
+ this.logger?.debug?.(`Creating user record for: ${dto.email} || ${dto.username} || ${dto.phone}`);
142
+ const user = this.userRepository.create({
143
+ email: dto.email,
144
+ username: dto.username,
145
+ firstName: dto.firstName,
146
+ lastName: dto.lastName,
147
+ phone: dto.phone,
148
+ passwordHash,
149
+ passwordChangedAt: new Date(),
150
+ isEmailVerified: false,
151
+ isPhoneVerified: false,
152
+ isActive: true,
153
+ metadata: dto.metadata,
154
+ });
155
+ let savedUser;
156
+ try {
157
+ savedUser = (await this.userRepository.save(user));
158
+ this.logger?.log?.(`User created successfully: ${dto.email} (sub: ${savedUser.sub})`);
159
+ try {
160
+ await this.auditService?.recordEvent({
161
+ userId: savedUser.id,
162
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.ACCOUNT_CREATED,
163
+ eventStatus: 'INFO',
164
+ authMethod: 'password',
165
+ metadata: {
166
+ email: savedUser.email,
167
+ username: savedUser.username || null,
168
+ verificationMethod,
169
+ },
170
+ });
171
+ }
172
+ catch (auditError) {
173
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
174
+ this.logger?.error?.(`Failed to record ACCOUNT_CREATED audit event: ${errorMessage}`, {
175
+ error: auditError,
176
+ userId: savedUser.id,
177
+ });
178
+ }
179
+ }
180
+ catch (error) {
181
+ if (error && typeof error === 'object' && 'code' in error && error.code === '23505') {
182
+ const dbError = error;
183
+ if (dbError.detail?.includes('email')) {
184
+ this.logger?.warn?.(`Signup failed - email constraint violation: ${dto.email}`);
185
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.EMAIL_EXISTS, 'User with this email already exists');
186
+ }
187
+ else if (dbError.detail?.includes('username')) {
188
+ this.logger?.warn?.(`Signup failed - username constraint violation: ${dto.username}`);
189
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.USERNAME_EXISTS, 'Username is already taken');
190
+ }
191
+ else if (dbError.detail?.includes('phone')) {
192
+ this.logger?.warn?.(`Signup failed - phone constraint violation: ${dto.phone}`);
193
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PHONE_EXISTS, 'Phone number is already registered');
194
+ }
195
+ else {
196
+ this.logger?.error?.(`Signup failed - database constraint violation: ${dbError.message}`);
197
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.EMAIL_EXISTS, 'User with this information already exists', {
198
+ conflictType: 'unknown',
199
+ });
200
+ }
201
+ }
202
+ const errorMessage = error instanceof Error ? error.message : 'Unknown database error';
203
+ this.logger?.error?.(`Signup failed - database error: ${errorMessage}`);
204
+ throw error;
205
+ }
206
+ if (this.config.hooks?.afterSignup) {
207
+ await this.config.hooks.afterSignup(savedUser, { requiresVerification: verificationMethod !== 'none' });
208
+ }
209
+ const response = await this.challengeHelper.determineAuthResponse({
210
+ user: savedUser,
211
+ config: this.config,
212
+ deviceToken: clientInfo.deviceToken,
213
+ });
214
+ if (response.challengeName) {
215
+ this.logger?.log?.(`Challenge required for user ${savedUser.sub}: ${response.challengeName}`);
216
+ }
217
+ else {
218
+ this.logger?.log?.(`Signup successful - tokens issued for: ${dto.email}`);
219
+ }
220
+ return response;
221
+ }
222
+ async login(dto) {
223
+ const clientInfo = this.clientInfoService.get();
224
+ const fireAndForget = this.config.auditLogs?.fireAndForget === true;
225
+ this.logger?.log?.(`Login attempt for: ${dto.identifier}`);
226
+ this.logger?.debug?.(`Login details: { identifier: ${dto.identifier}, ip: ${clientInfo.ipAddress}, deviceToken: ${clientInfo.deviceToken ? 'present' : 'none'} }`);
227
+ if (this.config.lockout?.enabled) {
228
+ const clientInfo = this.clientInfoService.get();
229
+ const ipAddress = clientInfo.ipAddress;
230
+ if (ipAddress) {
231
+ this.logger?.debug?.(`Checking IP lockout status for: ${ipAddress}`);
232
+ const isLocked = await this.accountLockoutStorage.isAccountLocked(ipAddress);
233
+ if (isLocked) {
234
+ this.logger?.warn?.(`Login blocked - IP locked: ${ipAddress}`);
235
+ await this.recordLoginAttempt(dto.identifier, false, 'ip_locked');
236
+ if (fireAndForget) {
237
+ this.auditService
238
+ ?.recordEvent({
239
+ userSub: dto.identifier,
240
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_BLOCKED,
241
+ eventStatus: 'FAILURE',
242
+ authMethod: 'password',
243
+ reason: 'ip_locked',
244
+ description: 'Login blocked - IP address locked due to too many failed attempts',
245
+ })
246
+ .catch((err) => {
247
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
248
+ this.logger?.error?.(`Failed to record LOGIN_BLOCKED audit event (fire-and-forget): ${errorMessage}`, {
249
+ error: err,
250
+ identifier: dto.identifier,
251
+ });
252
+ });
253
+ }
254
+ else {
255
+ try {
256
+ await this.auditService?.recordEvent({
257
+ userSub: dto.identifier,
258
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_BLOCKED,
259
+ eventStatus: 'FAILURE',
260
+ authMethod: 'password',
261
+ reason: 'ip_locked',
262
+ description: 'Login blocked - IP address locked due to too many failed attempts',
263
+ });
264
+ }
265
+ catch (auditError) {
266
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
267
+ this.logger?.error?.(`Failed to record LOGIN_BLOCKED audit event (IP locked): ${errorMessage}`, {
268
+ error: auditError,
269
+ });
270
+ }
271
+ }
272
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.RATE_LIMIT_LOGIN, 'Too many failed attempts from this IP. Please try again later.');
273
+ }
274
+ }
275
+ }
276
+ const identifierType = this.config.login?.identifierType;
277
+ if (identifierType) {
278
+ this.logger?.debug?.(`Validating identifier type for: ${dto.identifier}, allowed type: ${identifierType}`);
279
+ const isValidIdentifier = this.validateIdentifierType(dto.identifier, identifierType);
280
+ if (!isValidIdentifier) {
281
+ this.logger?.warn?.(`Login rejected - identifier type mismatch. Identifier: ${dto.identifier}, Required: ${identifierType}`);
282
+ await this.handleFailedLogin(dto.identifier, 'identifier_type_mismatch');
283
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INVALID_CREDENTIALS, `Login with this identifier type is not allowed. Expected: ${identifierType}`);
284
+ }
285
+ }
286
+ this.logger?.debug?.(`Finding user by identifier: ${dto.identifier}`);
287
+ const user = await this.findUserByIdentifier(dto.identifier, identifierType);
288
+ const hashToVerify = user?.passwordHash || DUMMY_ARGON2_HASH;
289
+ this.logger?.debug?.('Verifying password');
290
+ const isPasswordValid = await this.passwordService.verifyPassword(dto.password, hashToVerify);
291
+ if (!user || !user.passwordHash || !isPasswordValid) {
292
+ this.logger?.warn?.(`Login failed - invalid credentials for: ${dto.identifier}`);
293
+ await this.handleFailedLogin(dto.identifier, 'invalid_credentials');
294
+ if (user) {
295
+ if (fireAndForget) {
296
+ this.auditService
297
+ ?.recordEvent({
298
+ userId: user.id,
299
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_FAILED,
300
+ eventStatus: 'FAILURE',
301
+ authMethod: 'password',
302
+ reason: 'invalid_credentials',
303
+ description: 'Invalid password or user not found',
304
+ })
305
+ .catch((err) => {
306
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
307
+ this.logger?.error?.(`Failed to record LOGIN_FAILED audit event (fire-and-forget): ${errorMessage}`, {
308
+ error: err,
309
+ userId: user.id,
310
+ userSub: user.sub,
311
+ });
312
+ });
313
+ }
314
+ else {
315
+ try {
316
+ await this.auditService?.recordEvent({
317
+ userId: user.id,
318
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_FAILED,
319
+ eventStatus: 'FAILURE',
320
+ authMethod: 'password',
321
+ reason: 'invalid_credentials',
322
+ description: 'Invalid password or user not found',
323
+ });
324
+ }
325
+ catch (auditError) {
326
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
327
+ this.logger?.error?.(`Failed to record LOGIN_FAILED audit event: ${errorMessage}`, {
328
+ error: auditError,
329
+ userId: user?.id,
330
+ });
331
+ }
332
+ }
333
+ }
334
+ if (user && !user.passwordHash && user.socialProviders && user.socialProviders.length > 0) {
335
+ const provider = user.socialProviders[0];
336
+ const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
337
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INVALID_CREDENTIALS, `Invalid credentials - use your ${providerName} account`, {
338
+ suggestedProvider: providerName,
339
+ });
340
+ }
341
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INVALID_CREDENTIALS, 'Invalid credentials');
342
+ }
343
+ const expiryDays = this.config.password?.expiryDays;
344
+ if (expiryDays && expiryDays > 0 && user.passwordChangedAt) {
345
+ const expiryDate = new Date(user.passwordChangedAt);
346
+ expiryDate.setDate(expiryDate.getDate() + expiryDays);
347
+ const now = new Date();
348
+ if (now > expiryDate) {
349
+ this.logger?.warn?.(`Password expired for user: ${user.sub}. Changed: ${user.passwordChangedAt}, Expiry: ${expiryDate}`);
350
+ await this.userRepository.update(user.id, {
351
+ mustChangePassword: true,
352
+ });
353
+ user.mustChangePassword = true;
354
+ const response = await this.challengeHelper.determineAuthResponse({
355
+ user,
356
+ config: this.config,
357
+ deviceToken: clientInfo.deviceToken,
358
+ isSocialLogin: false,
359
+ });
360
+ if (response.challengeName) {
361
+ this.logger?.warn?.(`Login blocked - password expired, challenge: ${response.challengeName} for ${dto.identifier}`);
362
+ return response;
363
+ }
364
+ }
365
+ }
366
+ try {
367
+ await this.auditService?.recordEvent({
368
+ userId: user.id,
369
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_ATTEMPT,
370
+ eventStatus: 'INFO',
371
+ authMethod: 'password',
372
+ description: 'Password verification successful',
373
+ });
374
+ }
375
+ catch (auditError) {
376
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
377
+ this.logger?.error?.(`Failed to record LOGIN_ATTEMPT audit event: ${errorMessage}`, {
378
+ error: auditError,
379
+ userId: user.id,
380
+ });
381
+ }
382
+ const response = await this.challengeHelper.determineAuthResponse({
383
+ user,
384
+ config: this.config,
385
+ deviceToken: clientInfo.deviceToken,
386
+ isSocialLogin: false,
387
+ });
388
+ if (response.challengeName) {
389
+ const reasonMap = {
390
+ [auth_challenge_dto_1.AuthChallenge.VERIFY_EMAIL]: 'verification_required',
391
+ [auth_challenge_dto_1.AuthChallenge.VERIFY_PHONE]: 'verification_required',
392
+ [auth_challenge_dto_1.AuthChallenge.MFA_SETUP_REQUIRED]: 'mfa_setup_required',
393
+ [auth_challenge_dto_1.AuthChallenge.FORCE_CHANGE_PASSWORD]: 'password_change_required',
394
+ [auth_challenge_dto_1.AuthChallenge.MFA_REQUIRED]: 'mfa_required',
395
+ };
396
+ this.logger?.warn?.(`Login blocked - pending challenge: ${response.challengeName} for ${dto.identifier} (sub: ${user.sub})`);
397
+ await this.recordLoginAttempt(dto.identifier, false, reasonMap[response.challengeName] || 'challenge_required', user.id);
398
+ return response;
399
+ }
400
+ if (response.accessToken && response.refreshToken) {
401
+ this.logger?.debug?.(`Login successful - session already created by challenge helper for ${dto.identifier} (sub: ${user.sub})`);
402
+ await this.recordLoginAttempt(dto.identifier, true, undefined, user.id);
403
+ this.logger?.log?.(`Login successful for: ${dto.identifier} (sub: ${user.sub}) from ${clientInfo.ipAddress}`);
404
+ await this.userRepository.update(user.id, {
405
+ lastLoginAt: new Date(),
406
+ lastLoginIp: clientInfo.ipAddress,
407
+ failedLoginAttempts: 0,
408
+ });
409
+ if (this.config.lockout?.enabled && this.config.lockout.resetOnSuccess) {
410
+ const ipAddress = clientInfo.ipAddress;
411
+ if (ipAddress) {
412
+ this.logger?.debug?.(`Resetting failed login attempts for IP: ${ipAddress}`);
413
+ await this.accountLockoutStorage.resetFailedAttempts(ipAddress);
414
+ }
415
+ }
416
+ let sessionId;
417
+ let deviceId;
418
+ try {
419
+ const tokenPayload = this.jwtService.decodeToken(response.accessToken);
420
+ if (tokenPayload?.sessionId) {
421
+ sessionId = parseInt(String(tokenPayload.sessionId), 10);
422
+ }
423
+ if (sessionId) {
424
+ const session = await this.sessionService.findById(sessionId);
425
+ if (session && session.deviceId) {
426
+ deviceId = session.deviceId;
427
+ }
428
+ }
429
+ }
430
+ catch (error) {
431
+ this.logger?.debug?.('Failed to extract sessionId/deviceId from token for audit');
432
+ }
433
+ const isTrustedDevice = response.trusted || false;
434
+ const mfaBypassed = false;
435
+ const mfaBypassReason = null;
436
+ if (fireAndForget) {
437
+ this.auditService
438
+ ?.recordEvent({
439
+ userId: user.id,
440
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
441
+ eventStatus: 'SUCCESS',
442
+ sessionId: sessionId || undefined,
443
+ deviceId: deviceId || undefined,
444
+ authMethod: 'password',
445
+ metadata: { trustedDevice: isTrustedDevice, mfaBypassed, mfaBypassReason },
446
+ })
447
+ .catch((err) => {
448
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
449
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event (fire-and-forget): ${errorMessage}`, {
450
+ error: err,
451
+ userId: user.id,
452
+ userSub: user.sub,
453
+ });
454
+ });
455
+ }
456
+ else {
457
+ try {
458
+ await this.auditService?.recordEvent({
459
+ userId: user.id,
460
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
461
+ eventStatus: 'SUCCESS',
462
+ sessionId: sessionId || undefined,
463
+ deviceId: deviceId || undefined,
464
+ authMethod: 'password',
465
+ metadata: { trustedDevice: isTrustedDevice, mfaBypassed, mfaBypassReason },
466
+ });
467
+ }
468
+ catch (auditError) {
469
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
470
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event: ${errorMessage}`, {
471
+ error: auditError,
472
+ userId: user.id,
473
+ });
474
+ }
475
+ }
476
+ return response;
477
+ }
478
+ let isTrustedDevice = false;
479
+ let mfaBypassed = false;
480
+ let mfaBypassReason = null;
481
+ if (this.config.mfa?.rememberDevices &&
482
+ this.config.mfa?.rememberDevices !== 'never' &&
483
+ this.trustedDeviceService &&
484
+ clientInfo.deviceToken) {
485
+ isTrustedDevice = await this.trustedDeviceService.isDeviceTrusted(clientInfo.deviceToken, user.id);
486
+ }
487
+ const userEntityDebug = user;
488
+ const userMfaExempt = userEntityDebug.mfaExempt === true || userEntityDebug.mfaExempt === 'true';
489
+ if (!response.challengeName && this.config.mfa) {
490
+ const enforcement = this.config.mfa.enforcement || 'OPTIONAL';
491
+ const wouldRequireMFA = (enforcement === 'OPTIONAL' && user.mfaEnabled) || enforcement === 'REQUIRED' || enforcement === 'ADAPTIVE';
492
+ if (wouldRequireMFA) {
493
+ if (isTrustedDevice &&
494
+ this.config.mfa.bypassMFAForTrustedDevices === true &&
495
+ enforcement !== 'ADAPTIVE' &&
496
+ !userMfaExempt) {
497
+ mfaBypassed = true;
498
+ mfaBypassReason = 'trusted_device';
499
+ this.logger?.debug?.(`MFA bypassed for trusted device - user ${user.sub}`);
500
+ }
501
+ else if (userMfaExempt) {
502
+ mfaBypassed = true;
503
+ mfaBypassReason = 'mfa_exempt';
504
+ this.logger?.debug?.(`MFA bypassed due to exemption - user ${user.sub}`);
505
+ }
506
+ }
507
+ }
508
+ if (!user.isActive) {
509
+ this.logger?.warn?.(`Login failed - account inactive: ${dto.identifier} (sub: ${user.sub})`);
510
+ await this.recordLoginAttempt(dto.identifier, false, 'account_inactive', user.id);
511
+ try {
512
+ await this.auditService?.recordEvent({
513
+ userId: user.id,
514
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_BLOCKED,
515
+ eventStatus: 'FAILURE',
516
+ authMethod: 'password',
517
+ reason: 'account_inactive',
518
+ description: 'Login blocked - account is inactive',
519
+ });
520
+ }
521
+ catch (auditError) {
522
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
523
+ this.logger?.error?.(`Failed to record LOGIN_BLOCKED audit event (account inactive): ${errorMessage}`, {
524
+ error: auditError,
525
+ userId: user.id,
526
+ });
527
+ }
528
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.ACCOUNT_INACTIVE, 'Account is inactive. Please contact support.');
529
+ }
530
+ if (this.config.lockout?.enabled && this.config.lockout.resetOnSuccess) {
531
+ const ipAddress = clientInfo.ipAddress;
532
+ if (ipAddress) {
533
+ this.logger?.debug?.(`Resetting failed login attempts for IP: ${ipAddress}`);
534
+ await this.accountLockoutStorage.resetFailedAttempts(ipAddress);
535
+ }
536
+ }
537
+ const validatedDeviceId = crypto.randomUUID();
538
+ this.logger?.debug?.(`Generated server-side deviceId: ${validatedDeviceId}`);
539
+ const tokenFamily = this.jwtService.generateTokenFamily();
540
+ if (this.config.session?.disallowMultipleSessions) {
541
+ this.logger?.debug?.(`Single session mode enabled - revoking other sessions for user: ${user.sub}`);
542
+ const revokedCount = await this.sessionService.revokeAllUserSessions(user.id, 'Login from new session');
543
+ if (revokedCount > 0) {
544
+ this.logger?.log?.(`Revoked ${revokedCount} other active session(s) for user: ${user.sub}`);
545
+ }
546
+ }
547
+ this.logger?.debug?.(`Creating login session for user: ${user.sub}`);
548
+ const atomic = await this.sessionService.createSessionAtomic({
549
+ userId: user.id,
550
+ tokenFamily,
551
+ deviceId: validatedDeviceId,
552
+ deviceName: dto.deviceName,
553
+ deviceType: dto.deviceType,
554
+ isRemembered: false,
555
+ expiresAt: this.sessionService.getSessionExpirationDate(),
556
+ authMethod: 'password',
557
+ }, async (sessionId) => {
558
+ const pair = await this.jwtService.generateTokenPair({
559
+ userId: user.sub,
560
+ email: user.email,
561
+ sessionId: sessionId.toString(),
562
+ tokenFamily,
563
+ });
564
+ return {
565
+ accessTokenHash: this.jwtService.hashToken(pair.accessToken),
566
+ refreshTokenHash: this.jwtService.hashToken(pair.refreshToken),
567
+ extra: pair,
568
+ };
569
+ });
570
+ const session = atomic.session;
571
+ const tokens = atomic.extra;
572
+ this.logger?.debug?.(`Session created: ${session.id}`);
573
+ await this.userRepository.update(user.id, {
574
+ lastLoginAt: new Date(),
575
+ lastLoginIp: clientInfo.ipAddress,
576
+ failedLoginAttempts: 0,
577
+ });
578
+ await this.recordLoginAttempt(dto.identifier, true, undefined, user.id);
579
+ this.logger?.log?.(`Login successful for: ${dto.identifier} (sub: ${user.sub}) from ${clientInfo.ipAddress}`);
580
+ if (fireAndForget) {
581
+ this.auditService
582
+ ?.recordEvent({
583
+ userId: user.id,
584
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
585
+ eventStatus: 'SUCCESS',
586
+ sessionId: session.id,
587
+ deviceId: validatedDeviceId || undefined,
588
+ authMethod: 'password',
589
+ metadata: { trustedDevice: isTrustedDevice, mfaBypassed, mfaBypassReason },
590
+ })
591
+ .catch((err) => {
592
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
593
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event (fire-and-forget): ${errorMessage}`, {
594
+ error: err,
595
+ userId: user.id,
596
+ userSub: user.sub,
597
+ });
598
+ });
599
+ }
600
+ else {
601
+ try {
602
+ await this.auditService?.recordEvent({
603
+ userId: user.id,
604
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
605
+ eventStatus: 'SUCCESS',
606
+ sessionId: session.id,
607
+ deviceId: validatedDeviceId || undefined,
608
+ authMethod: 'password',
609
+ metadata: { trustedDevice: isTrustedDevice, mfaBypassed, mfaBypassReason },
610
+ });
611
+ }
612
+ catch (auditError) {
613
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
614
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event: ${errorMessage}`, {
615
+ error: auditError,
616
+ userId: user.id,
617
+ });
618
+ }
619
+ }
620
+ let deviceToken;
621
+ let isTrusted = false;
622
+ if (this.config.mfa?.rememberDevices && this.config.mfa?.rememberDevices !== 'never' && this.trustedDeviceService) {
623
+ const rememberDevicesMode = this.config.mfa.rememberDevices;
624
+ if (clientInfo.deviceToken) {
625
+ isTrusted = await this.trustedDeviceService.isDeviceTrusted(clientInfo.deviceToken, user.id);
626
+ if (isTrusted) {
627
+ deviceToken = clientInfo.deviceToken;
628
+ this.logger?.debug?.(`Device already trusted for user ${user.sub}`);
629
+ }
630
+ }
631
+ if (rememberDevicesMode === 'always' && !isTrusted) {
632
+ try {
633
+ deviceToken = await this.trustedDeviceService.createTrustedDevice(user.id, dto.deviceName || clientInfo.deviceName, dto.deviceType || clientInfo.deviceType, clientInfo.ipAddress, clientInfo.userAgent, clientInfo.platform, clientInfo.browser);
634
+ isTrusted = true;
635
+ this.logger?.debug?.(`Auto-created trusted device token for user ${user.sub} (always mode)`);
636
+ }
637
+ catch (error) {
638
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
639
+ this.logger?.warn?.(`Failed to create trusted device token: ${errorMessage}`, { error });
640
+ }
641
+ }
642
+ }
643
+ const accessTokenValidation = await this.jwtService.validateAccessToken(tokens.accessToken);
644
+ const refreshTokenValidation = await this.jwtService.validateRefreshToken(tokens.refreshToken);
645
+ const userDto = user_response_dto_1.UserResponseDto.fromEntity(user);
646
+ const authResponse = {
647
+ user: {
648
+ sub: userDto.sub,
649
+ email: userDto.email,
650
+ firstName: userDto.firstName,
651
+ lastName: userDto.lastName,
652
+ phone: userDto.phone ?? undefined,
653
+ isEmailVerified: userDto.isEmailVerified,
654
+ isPhoneVerified: userDto.isPhoneVerified ?? undefined,
655
+ socialProviders: userDto.socialProviders && userDto.socialProviders.length > 0 ? userDto.socialProviders : undefined,
656
+ },
657
+ accessToken: tokens.accessToken,
658
+ refreshToken: tokens.refreshToken,
659
+ accessTokenExpiresAt: accessTokenValidation.payload?.exp || 0,
660
+ refreshTokenExpiresAt: refreshTokenValidation.payload?.exp || 0,
661
+ trusted: isTrusted,
662
+ deviceToken,
663
+ };
664
+ return authResponse;
665
+ }
666
+ async respondToChallenge(dto) {
667
+ const responseData = dto;
668
+ const { session, type } = responseData;
669
+ const requestTrace = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
670
+ this.logger?.log?.(`[${requestTrace}] Challenge response received: type=${type}, session=${session?.substring(0, 8)}...`);
671
+ const challengeSession = await this.challengeService.validateSession(session);
672
+ this.validateChallengeTypeMatch(challengeSession.challengeName, type);
673
+ this.validateChallengeParams(type, responseData);
674
+ switch (type) {
675
+ case 'VERIFY_EMAIL':
676
+ return await this.handleVerifyEmail(challengeSession, responseData.code);
677
+ case 'VERIFY_PHONE':
678
+ return await this.handleVerifyPhone(challengeSession, responseData);
679
+ case 'MFA_REQUIRED':
680
+ return await this.handleMFAVerification(challengeSession, responseData);
681
+ case 'FORCE_CHANGE_PASSWORD':
682
+ return await this.handleForceChangePassword(challengeSession, responseData.newPassword);
683
+ case 'MFA_SETUP_REQUIRED':
684
+ return await this.handleMFASetup(challengeSession, responseData);
685
+ default:
686
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, `Unknown challenge type: ${type}`);
687
+ }
688
+ }
689
+ validateChallengeTypeMatch(expected, provided) {
690
+ if (expected !== provided) {
691
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, `Challenge type mismatch: expected ${expected}, got ${provided}`);
692
+ }
693
+ }
694
+ validateChallengeParams(type, data) {
695
+ switch (type) {
696
+ case 'VERIFY_EMAIL': {
697
+ const response = data;
698
+ if (!response.code || typeof response.code !== 'string') {
699
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Verification code is required', { field: 'code' });
700
+ }
701
+ break;
702
+ }
703
+ case 'VERIFY_PHONE': {
704
+ const response = data;
705
+ const hasCode = 'code' in response && response.code;
706
+ const hasPhone = 'phone' in response && response.phone;
707
+ if (!hasCode && !hasPhone) {
708
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Either phone number or verification code is required', { fields: ['phone', 'code'] });
709
+ }
710
+ break;
711
+ }
712
+ case 'MFA_REQUIRED': {
713
+ const response = data;
714
+ if (!response.method) {
715
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'MFA method is required', { field: 'method' });
716
+ }
717
+ if (response.method === 'passkey') {
718
+ const passkeyResponse = response;
719
+ if (!passkeyResponse.credential) {
720
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Passkey credential is required', {
721
+ field: 'credential',
722
+ });
723
+ }
724
+ }
725
+ else {
726
+ const codeResponse = response;
727
+ if (!codeResponse.code || typeof codeResponse.code !== 'string') {
728
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'MFA code is required', { field: 'code' });
729
+ }
730
+ }
731
+ break;
732
+ }
733
+ case 'FORCE_CHANGE_PASSWORD': {
734
+ const response = data;
735
+ if (!response.newPassword || typeof response.newPassword !== 'string') {
736
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'New password is required', {
737
+ field: 'newPassword',
738
+ });
739
+ }
740
+ break;
741
+ }
742
+ case 'MFA_SETUP_REQUIRED': {
743
+ const response = data;
744
+ if (!response.method) {
745
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'MFA setup method is required', {
746
+ field: 'method',
747
+ });
748
+ }
749
+ if (!response.setupData || typeof response.setupData !== 'object') {
750
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'MFA setup data is required', {
751
+ field: 'setupData',
752
+ });
753
+ }
754
+ break;
755
+ }
756
+ }
757
+ }
758
+ async handleVerifyEmail(challengeSession, code) {
759
+ const user = challengeSession.user;
760
+ if (!user) {
761
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.CHALLENGE_INVALID, 'User not found in challenge session');
762
+ }
763
+ this.logger?.log?.(`Verifying email for user: ${user.sub}`);
764
+ const verifyDto = Object.assign(new verify_email_dto_1.VerifyEmailWithCodeDTO(), {
765
+ email: user.email,
766
+ code,
767
+ challengeSessionId: challengeSession.id,
768
+ });
769
+ const result = await this.emailVerificationService.verifyEmailWithCode(verifyDto);
770
+ const isVerified = result.message === 'Email verified successfully. Please log in to continue.';
771
+ if (!isVerified) {
772
+ await this.challengeService.incrementAttempts(challengeSession);
773
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VERIFICATION_CODE_INVALID, 'Invalid verification code');
774
+ }
775
+ await this.challengeService.validateAndConsumeSession(challengeSession.sessionToken, auth_challenge_dto_1.AuthChallenge.VERIFY_EMAIL);
776
+ const updatedUser = await this.userRepository.findOne({ where: { sub: user.sub } });
777
+ if (!updatedUser) {
778
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found after email verification');
779
+ }
780
+ const clientInfo = this.clientInfoService.get();
781
+ const authMethod = challengeSession.metadata?.authMethod || 'password';
782
+ const authProvider = challengeSession.metadata?.authProvider;
783
+ const isSocialLogin = authMethod === 'social';
784
+ const response = await this.challengeHelper.determineAuthResponse({
785
+ user: updatedUser,
786
+ config: this.config,
787
+ deviceToken: clientInfo.deviceToken,
788
+ isSocialLogin,
789
+ skipMFAVerification: false,
790
+ authProvider,
791
+ });
792
+ if (response.challengeName) {
793
+ this.logger?.log?.(`Additional challenge required: ${response.challengeName}`);
794
+ }
795
+ else {
796
+ this.logger?.log?.(`Email verified, auth completed for: ${user.email}`);
797
+ }
798
+ return response;
799
+ }
800
+ async handleVerifyPhone(challengeSession, data) {
801
+ const user = challengeSession.user;
802
+ if (!user) {
803
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.CHALLENGE_INVALID, 'User not found in challenge session');
804
+ }
805
+ if ('phone' in data && data.phone) {
806
+ const phone = data.phone;
807
+ this.logger?.log?.(`Collecting phone number for user: ${user.sub}`);
808
+ const phoneRegex = /^\+[1-9]\d{1,14}$/;
809
+ if (!phoneRegex.test(phone)) {
810
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INVALID_PHONE_FORMAT, 'Invalid phone number format. Use E.164 format (e.g., +1234567890)');
811
+ }
812
+ await this.userRepository.update({ sub: user.sub }, { phone });
813
+ this.logger?.log?.(`Phone number added for user ${user.sub}: ${phone}`);
814
+ let smsError;
815
+ if (this.phoneVerificationService) {
816
+ this.logger?.log?.(`Sending verification SMS to newly added phone: ${phone}`);
817
+ try {
818
+ const smsDto = Object.assign(new verify_phone_dto_1.SendVerificationSMSDTO(), {
819
+ sub: user.sub,
820
+ challengeSessionId: challengeSession.id,
821
+ });
822
+ await this.phoneVerificationService.sendVerificationSMS(smsDto);
823
+ this.logger?.log?.(`Verification SMS sent successfully to: ${phone}`);
824
+ }
825
+ catch (error) {
826
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
827
+ this.logger?.error?.(`Failed to send verification SMS to ${phone}: ${errorMessage}`);
828
+ smsError = errorMessage;
829
+ }
830
+ }
831
+ else {
832
+ this.logger?.warn?.(`Phone verification SMS not sent - PhoneVerificationService not available. ` +
833
+ 'Phone verification requires an SMS provider to be configured.');
834
+ }
835
+ const authMethod = challengeSession.metadata?.authMethod || 'password';
836
+ const authProvider = challengeSession.metadata?.authProvider;
837
+ const challengeResponse = await this.challengeHelper.createChallengeResponse({ ...user, phone }, auth_challenge_dto_1.AuthChallenge.VERIFY_PHONE, this.config, authMethod, authProvider, true);
838
+ if (smsError) {
839
+ challengeResponse.challengeParameters = challengeResponse.challengeParameters || {};
840
+ challengeResponse.challengeParameters.smsError = smsError;
841
+ }
842
+ return challengeResponse;
843
+ }
844
+ else {
845
+ const code = data.code;
846
+ this.logger?.log?.(`Verifying phone for user: ${user.sub}`);
847
+ if (!user.phone) {
848
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Phone number not yet provided. Submit phone number first.');
849
+ }
850
+ const verifyDto = Object.assign(new verify_phone_by_sub_dto_1.VerifyPhoneWithCodeBySubDTO(), {
851
+ sub: user.sub,
852
+ code,
853
+ challengeSessionId: challengeSession.id,
854
+ });
855
+ const result = await this.phoneVerificationService.verifyPhoneWithCodeBySub(verifyDto);
856
+ const isVerified = result.message === 'Phone verified successfully. Please log in to continue.';
857
+ if (!isVerified) {
858
+ await this.challengeService.incrementAttempts(challengeSession);
859
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VERIFICATION_CODE_INVALID, 'Invalid verification code');
860
+ }
861
+ await this.challengeService.validateAndConsumeSession(challengeSession.sessionToken, auth_challenge_dto_1.AuthChallenge.VERIFY_PHONE);
862
+ const updatedUser = await this.userRepository.findOne({ where: { sub: user.sub } });
863
+ if (!updatedUser) {
864
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found after phone verification');
865
+ }
866
+ const clientInfo = this.clientInfoService.get();
867
+ const authMethod = challengeSession.metadata?.authMethod || 'password';
868
+ const authProvider = challengeSession.metadata?.authProvider;
869
+ const isSocialLogin = authMethod === 'social';
870
+ const response = await this.challengeHelper.determineAuthResponse({
871
+ user: updatedUser,
872
+ config: this.config,
873
+ deviceToken: clientInfo.deviceToken,
874
+ isSocialLogin,
875
+ skipMFAVerification: false,
876
+ authProvider,
877
+ });
878
+ if (response.challengeName) {
879
+ this.logger?.log?.(`Additional challenge required: ${response.challengeName}`);
880
+ }
881
+ else {
882
+ this.logger?.log?.(`Phone verified, auth completed for: ${user.email}`);
883
+ const fireAndForget = this.config.auditLogs?.fireAndForget !== false;
884
+ if (fireAndForget) {
885
+ this.auditService
886
+ ?.recordEvent({
887
+ userId: user.id,
888
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
889
+ eventStatus: 'SUCCESS',
890
+ authMethod: isSocialLogin ? authProvider || 'social' : 'password',
891
+ metadata: {
892
+ completedAfterPhoneVerification: true,
893
+ },
894
+ })
895
+ .catch((err) => {
896
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
897
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after phone verification (fire-and-forget): ${errorMessage}`, {
898
+ error: err,
899
+ userId: user.id,
900
+ userSub: user.sub,
901
+ });
902
+ });
903
+ }
904
+ else {
905
+ try {
906
+ await this.auditService?.recordEvent({
907
+ userId: user.id,
908
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
909
+ eventStatus: 'SUCCESS',
910
+ authMethod: isSocialLogin ? authProvider || 'social' : 'password',
911
+ metadata: {
912
+ completedAfterPhoneVerification: true,
913
+ },
914
+ });
915
+ }
916
+ catch (auditError) {
917
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
918
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after phone verification: ${errorMessage}`, {
919
+ error: auditError,
920
+ userId: user.id,
921
+ });
922
+ }
923
+ }
924
+ }
925
+ return response;
926
+ }
927
+ }
928
+ async handleMFAVerification(challengeSession, data) {
929
+ const user = challengeSession.user;
930
+ if (!user) {
931
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.CHALLENGE_INVALID, 'User not found in challenge session');
932
+ }
933
+ const method = data.method;
934
+ this.logger?.log?.(`MFA verification attempt: method=${method}, user=${user.sub}`);
935
+ if (!this.mfaService) {
936
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INTERNAL_ERROR, 'MFA service is not available');
937
+ }
938
+ const clientInfo = this.clientInfoService.get();
939
+ let isValid = false;
940
+ if (method === 'passkey') {
941
+ const passkeyData = data;
942
+ const credential = passkeyData.credential;
943
+ const expectedChallenge = challengeSession.metadata?.passkeyChallenge;
944
+ if (!expectedChallenge) {
945
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.CHALLENGE_INVALID, 'No passkey challenge found in session');
946
+ }
947
+ const wrappedCredential = { credential, expectedChallenge };
948
+ const verifyResult = await this.mfaService.verifyCode({
949
+ sub: user.sub,
950
+ methodName: mfa_method_enum_1.MFAMethod.PASSKEY,
951
+ code: wrappedCredential,
952
+ });
953
+ isValid = verifyResult.valid;
954
+ }
955
+ else {
956
+ const codeData = data;
957
+ const code = codeData.code;
958
+ const verifyResult = await this.mfaService.verifyCode({
959
+ sub: user.sub,
960
+ methodName: method,
961
+ code,
962
+ });
963
+ isValid = verifyResult.valid;
964
+ }
965
+ if (!isValid) {
966
+ this.logger?.warn?.(`MFA verification failed for user: ${user.sub}`);
967
+ if (this.config.auditLogs?.fireAndForget) {
968
+ this.auditService
969
+ ?.recordEvent({
970
+ userId: user.id,
971
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.MFA_VERIFICATION_FAILED,
972
+ eventStatus: 'FAILURE',
973
+ challengeSessionId: challengeSession.id,
974
+ authMethod: method,
975
+ metadata: { mfaMethod: method },
976
+ })
977
+ .catch((err) => {
978
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
979
+ this.logger?.error?.(`Failed to record MFA_VERIFICATION_FAILED audit event (fire-and-forget): ${errorMessage}`, {
980
+ error: err,
981
+ userId: user.id,
982
+ userSub: user.sub,
983
+ });
984
+ });
985
+ }
986
+ else {
987
+ try {
988
+ await this.auditService?.recordEvent({
989
+ userId: user.id,
990
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.MFA_VERIFICATION_FAILED,
991
+ eventStatus: 'FAILURE',
992
+ challengeSessionId: challengeSession.id,
993
+ authMethod: method,
994
+ metadata: { mfaMethod: method },
995
+ });
996
+ }
997
+ catch (auditError) {
998
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
999
+ this.logger?.error?.(`Failed to record MFA_VERIFICATION_FAILED audit event: ${errorMessage}`, {
1000
+ error: auditError,
1001
+ userId: user.id,
1002
+ });
1003
+ }
1004
+ }
1005
+ await this.challengeService.incrementAttempts(challengeSession);
1006
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VERIFICATION_CODE_INVALID, 'Invalid MFA code');
1007
+ }
1008
+ this.logger?.log?.(`MFA verified successfully for user: ${user.sub}`);
1009
+ if (this.config.auditLogs?.fireAndForget) {
1010
+ this.auditService
1011
+ ?.recordEvent({
1012
+ userId: user.id,
1013
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.MFA_VERIFICATION_SUCCESS,
1014
+ eventStatus: 'SUCCESS',
1015
+ challengeSessionId: challengeSession.id,
1016
+ authMethod: method,
1017
+ metadata: { mfaMethod: method },
1018
+ })
1019
+ .catch((err) => {
1020
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
1021
+ this.logger?.error?.(`Failed to record MFA_VERIFICATION_SUCCESS audit event (fire-and-forget): ${errorMessage}`, {
1022
+ error: err,
1023
+ userId: user.id,
1024
+ userSub: user.sub,
1025
+ });
1026
+ });
1027
+ }
1028
+ else {
1029
+ try {
1030
+ await this.auditService?.recordEvent({
1031
+ userId: user.id,
1032
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.MFA_VERIFICATION_SUCCESS,
1033
+ eventStatus: 'SUCCESS',
1034
+ challengeSessionId: challengeSession.id,
1035
+ authMethod: method,
1036
+ metadata: { mfaMethod: method },
1037
+ });
1038
+ }
1039
+ catch (auditError) {
1040
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1041
+ this.logger?.error?.(`Failed to record MFA_VERIFICATION_SUCCESS audit event: ${errorMessage}`, {
1042
+ error: auditError,
1043
+ userId: user.id,
1044
+ });
1045
+ }
1046
+ }
1047
+ await this.challengeService.updateMetadata(challengeSession.sessionToken, {
1048
+ mfaMethod: method,
1049
+ });
1050
+ await this.challengeService.validateAndConsumeSession(challengeSession.sessionToken, auth_challenge_dto_1.AuthChallenge.MFA_REQUIRED);
1051
+ const authMethod = challengeSession.metadata?.authMethod || 'password';
1052
+ const authProvider = challengeSession.metadata?.authProvider;
1053
+ const isSocialLogin = authMethod === 'social';
1054
+ let deviceToken = clientInfo.deviceToken;
1055
+ let isTrustedDevice = false;
1056
+ if (this.trustedDeviceService && this.config.mfa?.rememberDevices && this.config.mfa.rememberDevices !== 'never') {
1057
+ const rememberMode = this.config.mfa.rememberDevices;
1058
+ if (deviceToken) {
1059
+ try {
1060
+ isTrustedDevice = await this.trustedDeviceService.isDeviceTrusted(deviceToken, user.id);
1061
+ if (isTrustedDevice) {
1062
+ this.logger?.debug?.(`MFA flow: existing trusted device token detected for user ${user.sub} (token reused)`);
1063
+ }
1064
+ }
1065
+ catch (error) {
1066
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1067
+ this.logger?.warn?.(`MFA flow: failed to validate existing trusted device token for user ${user.sub}: ${errorMessage}`, { error });
1068
+ }
1069
+ }
1070
+ if (rememberMode === 'always' && !isTrustedDevice) {
1071
+ try {
1072
+ deviceToken = await this.trustedDeviceService.createTrustedDevice(user.id, clientInfo.deviceName, clientInfo.deviceType, clientInfo.ipAddress, clientInfo.userAgent, clientInfo.platform, clientInfo.browser);
1073
+ isTrustedDevice = true;
1074
+ this.logger?.debug?.(`MFA flow: auto-created trusted device token for user ${user.sub} (rememberDevices='always')`);
1075
+ }
1076
+ catch (error) {
1077
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1078
+ this.logger?.warn?.(`MFA flow: failed to create trusted device token for user ${user.sub}: ${errorMessage}`, {
1079
+ error,
1080
+ });
1081
+ }
1082
+ }
1083
+ }
1084
+ const response = await this.challengeHelper.determineAuthResponse({
1085
+ user,
1086
+ config: this.config,
1087
+ deviceToken,
1088
+ isSocialLogin,
1089
+ skipMFAVerification: true,
1090
+ authProvider,
1091
+ });
1092
+ if (isTrustedDevice) {
1093
+ response.trusted = response.trusted ?? true;
1094
+ }
1095
+ if (deviceToken && !response.deviceToken) {
1096
+ response.deviceToken = deviceToken;
1097
+ }
1098
+ if (response.challengeName) {
1099
+ this.logger?.log?.(`Additional challenge required: ${response.challengeName}`);
1100
+ }
1101
+ else {
1102
+ this.logger?.log?.(`MFA verified, auth completed for: ${user.email}`);
1103
+ const fireAndForget = this.config.auditLogs?.fireAndForget !== false;
1104
+ if (fireAndForget) {
1105
+ this.auditService
1106
+ ?.recordEvent({
1107
+ userId: user.id,
1108
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
1109
+ eventStatus: 'SUCCESS',
1110
+ authMethod: isSocialLogin ? authProvider || 'social' : 'password',
1111
+ metadata: {
1112
+ completedAfterMFA: true,
1113
+ },
1114
+ })
1115
+ .catch((err) => {
1116
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
1117
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after MFA (fire-and-forget): ${errorMessage}`, {
1118
+ error: err,
1119
+ userId: user.id,
1120
+ userSub: user.sub,
1121
+ });
1122
+ });
1123
+ }
1124
+ else {
1125
+ try {
1126
+ await this.auditService?.recordEvent({
1127
+ userId: user.id,
1128
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
1129
+ eventStatus: 'SUCCESS',
1130
+ authMethod: isSocialLogin ? authProvider || 'social' : 'password',
1131
+ metadata: {
1132
+ completedAfterMFA: true,
1133
+ },
1134
+ });
1135
+ }
1136
+ catch (auditError) {
1137
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1138
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after MFA: ${errorMessage}`, {
1139
+ error: auditError,
1140
+ userId: user.id,
1141
+ });
1142
+ }
1143
+ }
1144
+ }
1145
+ return response;
1146
+ }
1147
+ async handleForceChangePassword(challengeSession, newPassword) {
1148
+ const user = challengeSession.user;
1149
+ if (!user) {
1150
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.CHALLENGE_INVALID, 'User not found in challenge session');
1151
+ }
1152
+ this.logger?.log?.(`Changing password for user: ${user.sub}`);
1153
+ const validation = await this.passwordService.validatePassword(newPassword, {
1154
+ email: user.email,
1155
+ username: user.username || undefined,
1156
+ });
1157
+ if (!validation.valid) {
1158
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.WEAK_PASSWORD, validation.errors.join(', '), {
1159
+ errors: validation.errors,
1160
+ });
1161
+ }
1162
+ if (this.config.password?.historyCount) {
1163
+ const historyToCheck = user.passwordHistory || [];
1164
+ const allPreviousPasswords = user.passwordHash ? [user.passwordHash, ...historyToCheck] : historyToCheck;
1165
+ const isReused = await this.passwordService.isPasswordInHistory(newPassword, allPreviousPasswords);
1166
+ if (isReused) {
1167
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PASSWORD_REUSED, 'You have used this password recently. Please choose a different password.');
1168
+ }
1169
+ }
1170
+ const newHash = await this.passwordService.hashPassword(newPassword);
1171
+ const newHistory = this.passwordService.addToHistory(user.passwordHistory || [], user.passwordHash);
1172
+ user.passwordHash = newHash;
1173
+ user.passwordChangedAt = new Date();
1174
+ user.passwordHistory = newHistory;
1175
+ user.mustChangePassword = false;
1176
+ await this.userRepository.save(user);
1177
+ this.logger?.log?.(`Password changed successfully for user: ${user.sub}`);
1178
+ await this.challengeService.validateAndConsumeSession(challengeSession.sessionToken, auth_challenge_dto_1.AuthChallenge.FORCE_CHANGE_PASSWORD);
1179
+ const updatedUser = await this.userRepository.findOne({ where: { sub: user.sub } });
1180
+ if (!updatedUser) {
1181
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found after password update');
1182
+ }
1183
+ const clientInfo = this.clientInfoService.get();
1184
+ const authMethod = challengeSession.metadata?.authMethod || 'password';
1185
+ const authProvider = challengeSession.metadata?.authProvider;
1186
+ const isSocialLogin = authMethod === 'social';
1187
+ const response = await this.challengeHelper.determineAuthResponse({
1188
+ user: updatedUser,
1189
+ config: this.config,
1190
+ deviceToken: clientInfo.deviceToken,
1191
+ isSocialLogin,
1192
+ skipMFAVerification: false,
1193
+ authProvider,
1194
+ });
1195
+ if (response.challengeName) {
1196
+ this.logger?.log?.(`Additional challenge required: ${response.challengeName}`);
1197
+ }
1198
+ else {
1199
+ this.logger?.log?.(`Password changed, auth completed for: ${user.email}`);
1200
+ const fireAndForget = this.config.auditLogs?.fireAndForget !== false;
1201
+ if (fireAndForget) {
1202
+ this.auditService
1203
+ ?.recordEvent({
1204
+ userId: user.id,
1205
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
1206
+ eventStatus: 'SUCCESS',
1207
+ authMethod: isSocialLogin ? authProvider || 'social' : 'password',
1208
+ metadata: {
1209
+ completedAfterPasswordChange: true,
1210
+ },
1211
+ })
1212
+ .catch((err) => {
1213
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
1214
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after password change (fire-and-forget): ${errorMessage}`, {
1215
+ error: err,
1216
+ userId: user.id,
1217
+ userSub: user.sub,
1218
+ });
1219
+ });
1220
+ }
1221
+ else {
1222
+ try {
1223
+ await this.auditService?.recordEvent({
1224
+ userId: user.id,
1225
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
1226
+ eventStatus: 'SUCCESS',
1227
+ authMethod: isSocialLogin ? authProvider || 'social' : 'password',
1228
+ metadata: {
1229
+ completedAfterPasswordChange: true,
1230
+ },
1231
+ });
1232
+ }
1233
+ catch (auditError) {
1234
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1235
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after password change: ${errorMessage}`, {
1236
+ error: auditError,
1237
+ userId: user.id,
1238
+ });
1239
+ }
1240
+ }
1241
+ }
1242
+ return response;
1243
+ }
1244
+ async handleMFASetup(challengeSession, data) {
1245
+ const user = challengeSession.user;
1246
+ if (!user) {
1247
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.CHALLENGE_INVALID, 'User not found in challenge session');
1248
+ }
1249
+ const method = data.method;
1250
+ const setupData = data.setupData;
1251
+ const requestTrace = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
1252
+ this.logger?.log?.(`[${requestTrace}] MFA setup attempt: method=${method}, user=${user.sub}`);
1253
+ if (!this.mfaService) {
1254
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INTERNAL_ERROR, 'MFA service is not available');
1255
+ }
1256
+ const provider = this.mfaService.getProvider(method);
1257
+ let deviceId;
1258
+ try {
1259
+ deviceId = await provider.verifySetup(user, setupData);
1260
+ this.logger?.log?.(`MFA device setup completed: method=${method}, deviceId=${deviceId}`);
1261
+ }
1262
+ catch (error) {
1263
+ this.logger?.warn?.(`MFA setup verification failed: method=${method}, user=${user.sub}`);
1264
+ await this.challengeService.incrementAttempts(challengeSession);
1265
+ throw error;
1266
+ }
1267
+ await this.challengeService.updateMetadata(challengeSession.sessionToken, {
1268
+ mfaMethod: method,
1269
+ });
1270
+ await this.challengeService.validateAndConsumeSession(challengeSession.sessionToken, auth_challenge_dto_1.AuthChallenge.MFA_SETUP_REQUIRED);
1271
+ const updatedUser = await this.userRepository.findOne({ where: { sub: user.sub } });
1272
+ if (!updatedUser) {
1273
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found after MFA setup');
1274
+ }
1275
+ const clientInfo = this.clientInfoService.get();
1276
+ const response = await this.challengeHelper.determineAuthResponse({
1277
+ user: updatedUser,
1278
+ config: this.config,
1279
+ deviceToken: clientInfo.deviceToken,
1280
+ isSocialLogin: false,
1281
+ skipMFAVerification: true,
1282
+ });
1283
+ if (response.challengeName) {
1284
+ this.logger?.log?.(`Additional challenge required: ${response.challengeName}`);
1285
+ }
1286
+ else {
1287
+ this.logger?.log?.(`MFA setup completed, auth completed for: ${user.email}`);
1288
+ const fireAndForget = this.config.auditLogs?.fireAndForget !== false;
1289
+ if (fireAndForget) {
1290
+ this.auditService
1291
+ ?.recordEvent({
1292
+ userId: user.id,
1293
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
1294
+ eventStatus: 'SUCCESS',
1295
+ authMethod: 'password',
1296
+ metadata: {
1297
+ completedAfterMFASetup: true,
1298
+ },
1299
+ })
1300
+ .catch((err) => {
1301
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
1302
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after MFA setup (fire-and-forget): ${errorMessage}`, {
1303
+ error: err,
1304
+ userId: user.id,
1305
+ userSub: user.sub,
1306
+ });
1307
+ });
1308
+ }
1309
+ else {
1310
+ try {
1311
+ await this.auditService?.recordEvent({
1312
+ userId: user.id,
1313
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.LOGIN_SUCCESS,
1314
+ eventStatus: 'SUCCESS',
1315
+ authMethod: 'password',
1316
+ metadata: {
1317
+ completedAfterMFASetup: true,
1318
+ },
1319
+ });
1320
+ }
1321
+ catch (auditError) {
1322
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1323
+ this.logger?.error?.(`Failed to record LOGIN_SUCCESS audit event after MFA setup: ${errorMessage}`, {
1324
+ error: auditError,
1325
+ userId: user.id,
1326
+ });
1327
+ }
1328
+ }
1329
+ }
1330
+ return response;
1331
+ }
1332
+ async resendCode(dto) {
1333
+ this.logger?.debug?.(`Resending verification code: session=${dto.session}`);
1334
+ const challengeSession = await this.challengeService.validateSession(dto.session);
1335
+ const user = challengeSession.user;
1336
+ if (!user) {
1337
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Challenge session has no associated user');
1338
+ }
1339
+ switch (challengeSession.challengeName) {
1340
+ case auth_challenge_dto_1.AuthChallenge.VERIFY_EMAIL: {
1341
+ const resendDto = Object.assign(new verify_email_dto_1.ResendVerificationEmailDTO(), { sub: user.sub });
1342
+ await this.emailVerificationService.resendVerificationEmail(resendDto);
1343
+ const maskedEmail = this.maskEmail(user.email);
1344
+ this.logger?.debug?.(`Email verification code resent: user=${user.sub}, email=${maskedEmail}`);
1345
+ return { destination: maskedEmail };
1346
+ }
1347
+ case auth_challenge_dto_1.AuthChallenge.VERIFY_PHONE: {
1348
+ if (!user.phone) {
1349
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Phone number not yet provided. Submit phone number first.');
1350
+ }
1351
+ if (!this.phoneVerificationService) {
1352
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INTERNAL_ERROR, 'Phone verification service is not available');
1353
+ }
1354
+ const resendDto = Object.assign(new verify_phone_dto_1.ResendVerificationSMSDTO(), { sub: user.sub });
1355
+ await this.phoneVerificationService.resendVerificationSMS(resendDto);
1356
+ const maskedPhone = this.maskPhone(user.phone);
1357
+ this.logger?.debug?.(`Phone verification code resent: user=${user.sub}, phone=${maskedPhone}`);
1358
+ return { destination: maskedPhone };
1359
+ }
1360
+ case auth_challenge_dto_1.AuthChallenge.MFA_REQUIRED: {
1361
+ const metadata = challengeSession.metadata;
1362
+ const method = metadata?.method;
1363
+ if (!method) {
1364
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, 'Cannot resend MFA code: method not specified in session');
1365
+ }
1366
+ if (method === 'sms' || method === 'email') {
1367
+ if (method === 'sms' && this.phoneVerificationService) {
1368
+ const smsDto = Object.assign(new verify_phone_dto_1.SendVerificationSMSDTO(), {
1369
+ sub: user.sub,
1370
+ skipAlreadyVerifiedCheck: true,
1371
+ challengeSessionId: challengeSession.id,
1372
+ });
1373
+ await this.phoneVerificationService.sendVerificationSMS(smsDto);
1374
+ this.logger?.debug?.(`SMS MFA code resent: user=${user.sub}`);
1375
+ const maskedPhone = user.phone ? this.maskPhone(user.phone) : '***-***-****';
1376
+ return { destination: maskedPhone };
1377
+ }
1378
+ if (method === 'email' && this.emailVerificationService) {
1379
+ const emailDto = Object.assign(new verify_email_dto_1.ResendVerificationEmailDTO(), {
1380
+ sub: user.sub,
1381
+ challengeSessionId: challengeSession.id,
1382
+ });
1383
+ await this.emailVerificationService.resendVerificationEmail(emailDto);
1384
+ this.logger?.debug?.(`Email MFA code resent: user=${user.sub}`);
1385
+ const maskedEmail = user.email ? this.maskEmail(user.email) : 'u***r@example.com';
1386
+ return { destination: maskedEmail };
1387
+ }
1388
+ if (!this.mfaService) {
1389
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INTERNAL_ERROR, 'MFA service is not available');
1390
+ }
1391
+ const provider = this.mfaService.getProvider(method);
1392
+ if (!provider.sendChallenge) {
1393
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, `${method.toUpperCase()} MFA provider does not support sending challenges`);
1394
+ }
1395
+ const result = await provider.sendChallenge(user);
1396
+ this.logger?.debug?.(`${method.toUpperCase()} MFA code resent: user=${user.sub}`);
1397
+ return { destination: result };
1398
+ }
1399
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, `Cannot resend code for MFA method '${method}'. Only SMS and Email support code resending.`);
1400
+ }
1401
+ default:
1402
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, `Cannot resend code for challenge type '${challengeSession.challengeName}'`);
1403
+ }
1404
+ }
1405
+ maskEmail(email) {
1406
+ const [localPart, domain] = email.split('@');
1407
+ if (localPart.length <= 2) {
1408
+ return `${localPart[0]}***@${domain}`;
1409
+ }
1410
+ return `${localPart[0]}***${localPart[localPart.length - 1]}@${domain}`;
1411
+ }
1412
+ maskPhone(phone) {
1413
+ const digits = phone.replace(/\D/g, '');
1414
+ const lastFour = digits.slice(-4);
1415
+ return `***-***-${lastFour}`;
1416
+ }
1417
+ async trustDevice() {
1418
+ if (this.config.mfa?.rememberDevices !== 'user_opt_in') {
1419
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.FORBIDDEN, 'Trust device feature is only available in user_opt_in mode');
1420
+ }
1421
+ if (!this.trustedDeviceService) {
1422
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.INTERNAL_ERROR, 'Trusted device service not available');
1423
+ }
1424
+ const clientInfo = this.clientInfoService.get();
1425
+ const sessionId = clientInfo.sessionId;
1426
+ if (!sessionId) {
1427
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session ID not found in request context. Ensure the request is authenticated.');
1428
+ }
1429
+ const session = await this.sessionService.findById(sessionId);
1430
+ if (!session || session.isRevoked) {
1431
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session not found or revoked');
1432
+ }
1433
+ const user = await this.userRepository.findOne({ where: { id: session.userId } });
1434
+ if (!user) {
1435
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
1436
+ }
1437
+ const userId = typeof user.id === 'number' ? user.id : parseInt(String(user.id), 10);
1438
+ if (clientInfo.deviceToken) {
1439
+ const isAlreadyTrusted = await this.trustedDeviceService.isDeviceTrusted(clientInfo.deviceToken, userId);
1440
+ if (isAlreadyTrusted) {
1441
+ this.logger?.debug?.(`Device already trusted for user ${user.sub}`);
1442
+ return { deviceToken: clientInfo.deviceToken };
1443
+ }
1444
+ try {
1445
+ await this.trustedDeviceService.revokeTrustedDevice(clientInfo.deviceToken, userId);
1446
+ this.logger?.debug?.(`Revoked existing untrusted device token for user ${user.sub}`);
1447
+ }
1448
+ catch {
1449
+ }
1450
+ }
1451
+ const deviceToken = await this.trustedDeviceService.createTrustedDevice(userId, session.deviceName || clientInfo.deviceName, session.deviceType || clientInfo.deviceType, session.ipAddress || clientInfo.ipAddress, session.userAgent || clientInfo.userAgent, clientInfo.platform, clientInfo.browser);
1452
+ this.logger?.log?.(`Device trusted for user ${user.sub} (user opt-in)`);
1453
+ try {
1454
+ const userId = typeof user.id === 'number' ? user.id : parseInt(String(user.id), 10);
1455
+ await this.auditService?.recordEvent({
1456
+ userId,
1457
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.DEVICE_TRUSTED,
1458
+ eventStatus: 'SUCCESS',
1459
+ deviceId: deviceToken,
1460
+ sessionId: session.id,
1461
+ description: `Device trusted by user (opt-in) - ${session.deviceName || 'Unknown device'}`,
1462
+ metadata: {
1463
+ rememberDeviceDays: this.config.mfa?.rememberDeviceDays || 30,
1464
+ trustedUntil: new Date(Date.now() + (this.config.mfa?.rememberDeviceDays || 30) * 24 * 60 * 60 * 1000).toISOString(),
1465
+ },
1466
+ });
1467
+ }
1468
+ catch (auditError) {
1469
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1470
+ this.logger?.error?.(`Failed to record DEVICE_TRUSTED audit event: ${errorMessage}`, {
1471
+ error: auditError,
1472
+ userId: user.id,
1473
+ });
1474
+ }
1475
+ return { deviceToken };
1476
+ }
1477
+ async isTrustedDevice() {
1478
+ if (!this.trustedDeviceService) {
1479
+ return { trusted: false };
1480
+ }
1481
+ const clientInfo = this.clientInfoService.get();
1482
+ const sessionId = clientInfo.sessionId;
1483
+ if (!sessionId) {
1484
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session ID not found in request context. Ensure the request is authenticated.');
1485
+ }
1486
+ const session = await this.sessionService.findById(sessionId);
1487
+ if (!session || session.isRevoked) {
1488
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session not found or revoked');
1489
+ }
1490
+ const user = await this.userRepository.findOne({ where: { id: session.userId } });
1491
+ if (!user) {
1492
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
1493
+ }
1494
+ const userId = typeof user.id === 'number' ? user.id : parseInt(String(user.id), 10);
1495
+ const deviceToken = clientInfo.deviceToken;
1496
+ if (!deviceToken) {
1497
+ return { trusted: false };
1498
+ }
1499
+ const isTrusted = await this.trustedDeviceService.isDeviceTrusted(deviceToken, userId);
1500
+ return { trusted: isTrusted };
1501
+ }
1502
+ async refreshToken(dto) {
1503
+ const tokenHash = this.jwtService.hashToken(dto.refreshToken);
1504
+ const session = await this.sessionService.findByRefreshToken(tokenHash);
1505
+ if (!session || session.isRevoked) {
1506
+ const validation = await this.jwtService.validateRefreshToken(dto.refreshToken);
1507
+ const userId = validation.payload?.sub || 'unknown';
1508
+ this.logger?.debug?.(`Session not found or revoked for user ${userId}. Possible issue where token are not cleared on logout`);
1509
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session not found or revoked');
1510
+ }
1511
+ const lockKey = `session-refresh:${session.id}`;
1512
+ this.logger?.debug?.(`[REFRESH DEBUG] Attempting to acquire lock ${lockKey} for token hash ${tokenHash.substring(0, 16)}...`);
1513
+ let lockAcquired = false;
1514
+ try {
1515
+ const lockStartTime = Date.now();
1516
+ lockAcquired = await this.sessionService.acquireRefreshLock(lockKey, 10000);
1517
+ const lockDuration = Date.now() - lockStartTime;
1518
+ if (!lockAcquired) {
1519
+ this.logger?.warn?.(`[REFRESH DEBUG] Lock ${lockKey} NOT acquired - refresh already in progress for session ${session.id}`);
1520
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.RATE_LIMIT_LOGIN, 'Token refresh already in progress', {
1521
+ retryAfter: 5,
1522
+ });
1523
+ }
1524
+ this.logger?.debug?.(`[REFRESH DEBUG] Lock ${lockKey} acquired successfully in ${lockDuration}ms for token hash ${tokenHash.substring(0, 16)}...`);
1525
+ if (this.config.jwt.refreshToken.reuseDetection) {
1526
+ const isAlreadyUsed = await this.sessionService.isRefreshTokenUsed(tokenHash);
1527
+ if (isAlreadyUsed) {
1528
+ const tokenPayload = this.jwtService.decodeToken(dto.refreshToken);
1529
+ const tokenSessionId = tokenPayload?.sessionId;
1530
+ const currentSession = (await this.sessionService.findByIdLight(session.id));
1531
+ if (!currentSession || currentSession.isRevoked) {
1532
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session not found or revoked');
1533
+ }
1534
+ if (tokenSessionId && tokenSessionId === session.id.toString()) {
1535
+ this.logger?.debug?.(`[REFRESH DEBUG] Token hash ${tokenHash.substring(0, 16)}... already used for same session ${session.id} - cookie race detected, returning current tokens`);
1536
+ const user = (await this.userRepository.findOne({
1537
+ where: { id: currentSession.userId },
1538
+ }));
1539
+ if (!user) {
1540
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
1541
+ }
1542
+ const newTokens = await this.jwtService.generateTokenPair({
1543
+ userId: user.sub,
1544
+ email: user.email,
1545
+ sessionId: currentSession.id.toString(),
1546
+ tokenFamily: currentSession.tokenFamily,
1547
+ });
1548
+ await this.sessionService.updateTokens(currentSession.id, this.jwtService.hashToken(newTokens.accessToken), this.jwtService.hashToken(newTokens.refreshToken));
1549
+ const accessTokenValidation = await this.jwtService.validateAccessToken(newTokens.accessToken);
1550
+ const refreshTokenValidation = await this.jwtService.validateRefreshToken(newTokens.refreshToken);
1551
+ return {
1552
+ accessToken: newTokens.accessToken,
1553
+ refreshToken: newTokens.refreshToken,
1554
+ accessTokenExpiresAt: accessTokenValidation.payload?.exp || 0,
1555
+ refreshTokenExpiresAt: refreshTokenValidation.payload?.exp || 0,
1556
+ };
1557
+ }
1558
+ else {
1559
+ this.logger?.error?.(`[REFRESH DEBUG] Token hash ${tokenHash.substring(0, 16)}... already used for different session - ATTACK DETECTED! Token sessionId: ${tokenSessionId}, Found session: ${session.id}. Revoking session ${session.id}`);
1560
+ await this.sessionService.revokeSession(session.id, 'Token reuse detected - possible token theft');
1561
+ let userForAudit = null;
1562
+ try {
1563
+ userForAudit = (await this.userRepository.findOne({
1564
+ where: { id: session.userId },
1565
+ }));
1566
+ if (userForAudit) {
1567
+ await this.auditService?.recordEvent({
1568
+ userId: userForAudit.id,
1569
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.SUSPICIOUS_ACTIVITY,
1570
+ eventStatus: 'SUSPICIOUS',
1571
+ riskFactor: 90,
1572
+ riskFactors: [risk_factor_enum_1.RiskFactor.TOKEN_THEFT_ATTEMPT, risk_factor_enum_1.RiskFactor.REFRESH_TOKEN_REUSE_DIFFERENT_SESSION],
1573
+ reason: 'Refresh token reuse from different session',
1574
+ description: 'Refresh token from another session attempted to be used. Session revoked as security measure.',
1575
+ metadata: {
1576
+ sessionId: session.id,
1577
+ tokenSessionId,
1578
+ tokenHash: `${tokenHash.substring(0, 16)}...`,
1579
+ detectedAt: new Date().toISOString(),
1580
+ action: 'session_revoked',
1581
+ },
1582
+ });
1583
+ }
1584
+ }
1585
+ catch (auditError) {
1586
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1587
+ this.logger?.error?.(`Failed to record SUSPICIOUS_ACTIVITY audit event (token reuse): ${errorMessage}`, {
1588
+ error: auditError,
1589
+ userId: userForAudit?.id || session.userId,
1590
+ });
1591
+ }
1592
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.TOKEN_INVALID, 'Refresh token has already been used');
1593
+ }
1594
+ }
1595
+ }
1596
+ const validation = await this.jwtService.validateRefreshToken(dto.refreshToken);
1597
+ if (!validation.valid || !validation.payload) {
1598
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.TOKEN_INVALID, 'Invalid refresh token');
1599
+ }
1600
+ const payload = validation.payload;
1601
+ const lockedSession = (await this.sessionService.findByIdLight(session.id));
1602
+ if (!lockedSession || lockedSession.isRevoked || lockedSession.id !== session.id) {
1603
+ this.logger?.debug?.(`Session changed after lock acquisition for user ${payload.sub}. Session may have been revoked.`);
1604
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session not found or revoked');
1605
+ }
1606
+ if (this.config.jwt.refreshToken.reuseDetection) {
1607
+ const refreshTokenTTL = this.jwtService.getRefreshTokenTTL();
1608
+ const marked = await this.sessionService.markRefreshTokenAsUsed(tokenHash, refreshTokenTTL);
1609
+ if (!marked) {
1610
+ this.logger?.error?.(`Token reuse detected for user ${payload.sub} - atomic mark failed, revoking entire token family ${payload.tokenFamily}`);
1611
+ try {
1612
+ const userForAudit = (await this.userRepository.findOne({
1613
+ where: { sub: payload.sub },
1614
+ }));
1615
+ if (userForAudit) {
1616
+ await this.auditService?.recordEvent({
1617
+ userId: userForAudit.id,
1618
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.SUSPICIOUS_ACTIVITY,
1619
+ eventStatus: 'SUSPICIOUS',
1620
+ riskFactor: 75,
1621
+ riskFactors: [risk_factor_enum_1.RiskFactor.TOKEN_REUSE_ATTEMPT],
1622
+ reason: 'Token reuse attempt blocked',
1623
+ description: 'Refresh token reuse attempt detected via atomic operation. Legitimate user session preserved.',
1624
+ metadata: {
1625
+ tokenFamily: payload.tokenFamily,
1626
+ detectedAt: new Date().toISOString(),
1627
+ action: 'reuse_blocked_atomic',
1628
+ },
1629
+ });
1630
+ }
1631
+ }
1632
+ catch (auditError) {
1633
+ this.logger?.warn?.('Failed to record SUSPICIOUS_ACTIVITY audit event', { error: auditError });
1634
+ }
1635
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.TOKEN_INVALID, 'Refresh token has already been used');
1636
+ }
1637
+ this.logger?.debug?.(`Marked refresh token as used for session ${lockedSession.id}`);
1638
+ }
1639
+ const newTokens = await this.jwtService.generateTokenPair({
1640
+ userId: payload.sub,
1641
+ email: payload.email,
1642
+ sessionId: lockedSession.id.toString(),
1643
+ tokenFamily: payload.tokenFamily,
1644
+ });
1645
+ await this.sessionService.updateTokens(lockedSession.id, this.jwtService.hashToken(newTokens.accessToken), this.jwtService.hashToken(newTokens.refreshToken));
1646
+ this.logger?.log?.(`Token refreshed successfully for user ${payload.sub}`);
1647
+ const accessTokenValidation = await this.jwtService.validateAccessToken(newTokens.accessToken);
1648
+ const refreshTokenValidation = await this.jwtService.validateRefreshToken(newTokens.refreshToken);
1649
+ return {
1650
+ accessToken: newTokens.accessToken,
1651
+ refreshToken: newTokens.refreshToken,
1652
+ accessTokenExpiresAt: accessTokenValidation.payload?.exp || 0,
1653
+ refreshTokenExpiresAt: refreshTokenValidation.payload?.exp || 0,
1654
+ };
1655
+ }
1656
+ finally {
1657
+ if (lockAcquired) {
1658
+ await this.sessionService.releaseRefreshLock(lockKey);
1659
+ this.logger?.debug?.(`[REFRESH DEBUG] Released lock ${lockKey}`);
1660
+ }
1661
+ }
1662
+ }
1663
+ async logout(dto) {
1664
+ const clientInfo = this.clientInfoService.get();
1665
+ let sessionId = clientInfo.sessionId;
1666
+ if (!sessionId) {
1667
+ const jwtPayload = context_storage_1.ContextStorage.get('JWT_PAYLOAD');
1668
+ if (jwtPayload?.sessionId) {
1669
+ const sessionIdStr = String(jwtPayload.sessionId);
1670
+ const sessionIdNumber = parseInt(sessionIdStr, 10);
1671
+ if (!isNaN(sessionIdNumber) && sessionIdNumber > 0) {
1672
+ sessionId = sessionIdNumber;
1673
+ const clientInfoInContext = context_storage_1.ContextStorage.get('CLIENT_INFO');
1674
+ if (clientInfoInContext) {
1675
+ clientInfoInContext.sessionId = sessionIdNumber;
1676
+ context_storage_1.ContextStorage.set('CLIENT_INFO', clientInfoInContext);
1677
+ }
1678
+ }
1679
+ }
1680
+ }
1681
+ if (!sessionId) {
1682
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.SESSION_NOT_FOUND, 'Session ID not found in request context. Ensure the request is authenticated.');
1683
+ }
1684
+ const auditMetadata = dto.forgetMe
1685
+ ? {
1686
+ deviceForgotten: true,
1687
+ reason: 'User requested device to be forgotten on logout',
1688
+ }
1689
+ : undefined;
1690
+ await this.sessionService.revokeSession(sessionId, 'User logout', auditMetadata);
1691
+ if (dto.forgetMe &&
1692
+ this.config.mfa?.rememberDevices &&
1693
+ this.config.mfa?.rememberDevices !== 'never' &&
1694
+ this.trustedDeviceService) {
1695
+ if (clientInfo.deviceToken) {
1696
+ try {
1697
+ const session = await this.sessionService.findById(sessionId);
1698
+ if (session) {
1699
+ await this.trustedDeviceService.revokeTrustedDevice(clientInfo.deviceToken, session.userId);
1700
+ this.logger?.log?.(`Revoked trusted device token for user (forgetMe=true)`);
1701
+ const user = await this.userRepository.findOne({ where: { id: session.userId } });
1702
+ if (user) {
1703
+ try {
1704
+ const userId = typeof user.id === 'number' ? user.id : parseInt(String(user.id), 10);
1705
+ await this.auditService?.recordEvent({
1706
+ userId,
1707
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.DEVICE_UNTRUSTED,
1708
+ eventStatus: 'SUCCESS',
1709
+ sessionId: session.id,
1710
+ description: `Device untrusted by user (forgetMe=true) - ${session.deviceName || 'Unknown device'}`,
1711
+ metadata: {
1712
+ reason: 'user_logout_forget_me',
1713
+ },
1714
+ });
1715
+ }
1716
+ catch (auditError) {
1717
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1718
+ this.logger?.error?.(`Failed to record DEVICE_UNTRUSTED audit event: ${errorMessage}`, {
1719
+ error: auditError,
1720
+ userId: session.userId,
1721
+ });
1722
+ }
1723
+ }
1724
+ }
1725
+ }
1726
+ catch (error) {
1727
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1728
+ this.logger?.debug?.(`Failed to revoke trusted device token on logout: ${errorMessage}`, { error });
1729
+ }
1730
+ }
1731
+ }
1732
+ const response = this.clientInfoService.getResponse();
1733
+ if (response && this.config.tokenDelivery?.method !== 'json') {
1734
+ this.clearAuthCookies(response, dto.forgetMe ?? false);
1735
+ this.logger?.debug?.('Auth cookies cleared automatically on logout');
1736
+ }
1737
+ return { success: true };
1738
+ }
1739
+ clearAuthCookies(response, forgetDevice) {
1740
+ if (!response.clearCookie) {
1741
+ return;
1742
+ }
1743
+ const cookieOptions = this.config.tokenDelivery?.cookieOptions || {};
1744
+ const prefix = this.config.tokenDelivery?.cookieNamePrefix || 'nauth';
1745
+ response.clearCookie(`${prefix}_access_token`, cookieOptions);
1746
+ response.clearCookie(`${prefix}_refresh_token`, cookieOptions);
1747
+ const csrfCookieOptions = {
1748
+ ...cookieOptions,
1749
+ httpOnly: false,
1750
+ };
1751
+ const csrfCookieName = this.config.security?.csrf?.cookieName || `${prefix}_csrf_token`;
1752
+ response.clearCookie(csrfCookieName, csrfCookieOptions);
1753
+ if (forgetDevice) {
1754
+ response.clearCookie(`${prefix}_device_token`, cookieOptions);
1755
+ }
1756
+ }
1757
+ async logoutAll(dto) {
1758
+ const user = (await this.userRepository.findOne({ where: { sub: dto.sub } }));
1759
+ if (!user) {
1760
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
1761
+ }
1762
+ const revokedCount = await this.sessionService.revokeAllUserSessions(user.id, 'Global signout');
1763
+ let revokedDevicesCount = 0;
1764
+ let revokedDevices = [];
1765
+ if (dto.forgetDevices &&
1766
+ this.config.mfa?.rememberDevices &&
1767
+ this.config.mfa?.rememberDevices !== 'never' &&
1768
+ this.trustedDeviceService) {
1769
+ try {
1770
+ const deviceRevocationResult = await this.trustedDeviceService.revokeAllTrustedDevices(user.id);
1771
+ revokedDevicesCount = deviceRevocationResult.revokedCount;
1772
+ revokedDevices = deviceRevocationResult.devices;
1773
+ this.logger?.log?.(`Revoked ${revokedDevicesCount} trusted device(s) for user ${user.sub} (forgetDevices=true)`);
1774
+ if (revokedDevicesCount > 0 && this.auditService) {
1775
+ try {
1776
+ const userId = typeof user.id === 'number' ? user.id : parseInt(String(user.id), 10);
1777
+ await this.auditService.recordEvent({
1778
+ userId,
1779
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.DEVICE_UNTRUSTED,
1780
+ eventStatus: 'SUCCESS',
1781
+ description: `Global signout: All trusted devices revoked (${revokedDevicesCount} device(s))`,
1782
+ metadata: {
1783
+ reason: 'global_logout_forget_devices',
1784
+ revokedDevicesCount,
1785
+ devices: revokedDevices.map((d) => ({
1786
+ id: d.id,
1787
+ deviceName: d.deviceName,
1788
+ lastUsedAt: d.lastUsedAt?.toISOString() || null,
1789
+ trustedUntil: d.trustedUntil?.toISOString() || null,
1790
+ })),
1791
+ },
1792
+ });
1793
+ }
1794
+ catch (auditError) {
1795
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1796
+ this.logger?.error?.(`Failed to record DEVICE_UNTRUSTED audit event: ${errorMessage}`, {
1797
+ error: auditError,
1798
+ userId: user.id,
1799
+ });
1800
+ }
1801
+ }
1802
+ }
1803
+ catch (error) {
1804
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1805
+ this.logger?.debug?.(`Failed to revoke trusted devices on global logout: ${errorMessage}`, { error });
1806
+ }
1807
+ }
1808
+ if (this.auditService && revokedCount > 0) {
1809
+ try {
1810
+ const userId = typeof user.id === 'number' ? user.id : parseInt(String(user.id), 10);
1811
+ const description = dto.forgetDevices && revokedDevicesCount > 0
1812
+ ? `Global signout: ${revokedCount} session(s) revoked, ${revokedDevicesCount} trusted device(s) forgotten`
1813
+ : `Global signout: ${revokedCount} session(s) revoked`;
1814
+ await this.auditService.recordEvent({
1815
+ userId,
1816
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.GLOBAL_SIGNOUT,
1817
+ eventStatus: 'INFO',
1818
+ reason: 'Global signout',
1819
+ description,
1820
+ metadata: {
1821
+ revokedCount,
1822
+ forgetDevices: dto.forgetDevices ?? false,
1823
+ ...(dto.forgetDevices && revokedDevicesCount > 0
1824
+ ? {
1825
+ revokedDevicesCount,
1826
+ devices: revokedDevices.map((d) => ({
1827
+ id: d.id,
1828
+ deviceName: d.deviceName,
1829
+ lastUsedAt: d.lastUsedAt?.toISOString() || null,
1830
+ trustedUntil: d.trustedUntil?.toISOString() || null,
1831
+ })),
1832
+ }
1833
+ : {}),
1834
+ },
1835
+ });
1836
+ }
1837
+ catch (auditError) {
1838
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1839
+ this.logger?.error?.(`Failed to record GLOBAL_SIGNOUT audit event: ${errorMessage}`, {
1840
+ error: auditError,
1841
+ userId: user.id,
1842
+ });
1843
+ }
1844
+ }
1845
+ const response = this.clientInfoService.getResponse();
1846
+ if (response && this.config.tokenDelivery?.method !== 'json') {
1847
+ this.clearAuthCookies(response, dto.forgetDevices ?? false);
1848
+ this.logger?.debug?.('Auth cookies cleared automatically on global logout');
1849
+ }
1850
+ return { revokedCount };
1851
+ }
1852
+ async changePassword(dto) {
1853
+ const user = (await this.userRepository.findOne({ where: { sub: dto.sub } }));
1854
+ if (!user || !user.passwordHash) {
1855
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
1856
+ }
1857
+ if (this.config.hooks?.beforePasswordChange) {
1858
+ const result = await this.config.hooks.beforePasswordChange(dto.sub, dto.oldPassword);
1859
+ if (result === false) {
1860
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PASSWORD_CHANGE_NOT_ALLOWED, 'Password change not allowed');
1861
+ }
1862
+ }
1863
+ const isValid = await this.passwordService.verifyPassword(dto.oldPassword, user.passwordHash);
1864
+ if (!isValid) {
1865
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PASSWORD_INCORRECT, 'Current password is incorrect');
1866
+ }
1867
+ const validation = await this.passwordService.validatePassword(dto.newPassword, {
1868
+ email: user.email,
1869
+ username: user.username || undefined,
1870
+ });
1871
+ if (!validation.valid) {
1872
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.WEAK_PASSWORD, validation.errors.join(', '), {
1873
+ errors: validation.errors,
1874
+ });
1875
+ }
1876
+ if (this.config.password?.historyCount) {
1877
+ const historyToCheck = user.passwordHistory || [];
1878
+ const allPreviousPasswords = user.passwordHash ? [user.passwordHash, ...historyToCheck] : historyToCheck;
1879
+ const isReused = await this.passwordService.isPasswordInHistory(dto.newPassword, allPreviousPasswords);
1880
+ if (isReused) {
1881
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PASSWORD_REUSED, 'You have used this password recently. Please choose a different password.');
1882
+ }
1883
+ }
1884
+ const newHash = await this.passwordService.hashPassword(dto.newPassword);
1885
+ const newHistory = this.passwordService.addToHistory(user.passwordHistory || [], user.passwordHash);
1886
+ user.passwordHash = newHash;
1887
+ user.passwordChangedAt = new Date();
1888
+ user.passwordHistory = newHistory;
1889
+ await this.userRepository.save(user);
1890
+ if (this.config.hooks?.afterPasswordChange) {
1891
+ await this.config.hooks.afterPasswordChange(dto.sub);
1892
+ }
1893
+ await this.sessionService.revokeAllUserSessions(user.id, 'Password changed');
1894
+ try {
1895
+ await this.auditService?.recordEvent({
1896
+ userId: user.id,
1897
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.PASSWORD_CHANGED,
1898
+ eventStatus: 'SUCCESS',
1899
+ });
1900
+ }
1901
+ catch (auditError) {
1902
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1903
+ this.logger?.error?.(`Failed to record PASSWORD_CHANGED audit event: ${errorMessage}`, {
1904
+ error: auditError,
1905
+ userId: user.id,
1906
+ });
1907
+ }
1908
+ return { success: true };
1909
+ }
1910
+ async updateUserAttributes(dto) {
1911
+ const user = (await this.userRepository.findOne({ where: { sub: dto.sub } }));
1912
+ if (!user) {
1913
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
1914
+ }
1915
+ await this.validateUniquenessConstraints(user.id, dto);
1916
+ const updateFields = {};
1917
+ if (dto.firstName !== undefined) {
1918
+ updateFields.firstName = dto.firstName;
1919
+ }
1920
+ if (dto.lastName !== undefined) {
1921
+ updateFields.lastName = dto.lastName;
1922
+ }
1923
+ if (dto.username !== undefined) {
1924
+ updateFields.username = dto.username;
1925
+ }
1926
+ if (dto.email !== undefined) {
1927
+ const oldEmail = user.email;
1928
+ updateFields.email = dto.email;
1929
+ if (dto.email !== user.email) {
1930
+ if (!dto.retainVerification) {
1931
+ updateFields.isEmailVerified = false;
1932
+ }
1933
+ else {
1934
+ updateFields.isEmailVerified = user.isEmailVerified;
1935
+ }
1936
+ if (oldEmail && this.mfaDeviceRepository) {
1937
+ try {
1938
+ const emailDevices = (await this.mfaDeviceRepository.find({
1939
+ where: {
1940
+ userId: user.id,
1941
+ type: mfa_method_enum_1.MFAMethod.EMAIL,
1942
+ isActive: true,
1943
+ },
1944
+ }));
1945
+ if (emailDevices.length > 0) {
1946
+ this.logger?.log?.(`Deleting ${emailDevices.length} Email MFA device(s) for user ${user.sub} due to email address change (old: ${oldEmail}, new: ${dto.email})`);
1947
+ for (const device of emailDevices) {
1948
+ const deviceId = device.id;
1949
+ await this.mfaDeviceRepository.delete(deviceId);
1950
+ }
1951
+ if (this.auditService) {
1952
+ try {
1953
+ await this.auditService.recordEvent({
1954
+ userId: user.id,
1955
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.MFA_DEVICE_REMOVED,
1956
+ eventStatus: 'INFO',
1957
+ reason: 'email_changed',
1958
+ description: `Email MFA device(s) removed due to email address change (${oldEmail} → ${dto.email})`,
1959
+ metadata: {
1960
+ method: mfa_method_enum_1.MFAMethod.EMAIL,
1961
+ deletedCount: emailDevices.length,
1962
+ oldEmail,
1963
+ newEmail: dto.email,
1964
+ reason: 'email_address_changed_requires_reverification',
1965
+ },
1966
+ });
1967
+ }
1968
+ catch (auditError) {
1969
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
1970
+ this.logger?.error?.(`Failed to record MFA_DEVICE_REMOVED audit event for email change: ${errorMessage}`, { error: auditError, userId: user.id });
1971
+ }
1972
+ }
1973
+ const allActiveDevices = (await this.mfaDeviceRepository.find({
1974
+ where: {
1975
+ userId: user.id,
1976
+ isActive: true,
1977
+ },
1978
+ }));
1979
+ if (allActiveDevices.length === 0 && user.mfaEnabled) {
1980
+ updateFields.mfaEnabled = false;
1981
+ updateFields.mfaMethods = [];
1982
+ updateFields.preferredMfaMethod = null;
1983
+ this.logger?.log?.(`MFA disabled for user ${user.sub} - no active MFA devices remaining after email change`);
1984
+ }
1985
+ else {
1986
+ this.logger?.log?.(`User ${user.sub} still has ${allActiveDevices.length} active MFA device(s) - MFA remains enabled`);
1987
+ }
1988
+ }
1989
+ }
1990
+ catch (error) {
1991
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1992
+ this.logger?.warn?.(`Failed to handle MFA device deactivation during email change for user ${user.sub}: ${errorMessage}`);
1993
+ }
1994
+ }
1995
+ }
1996
+ }
1997
+ if (dto.phone !== undefined) {
1998
+ const oldPhone = user.phone;
1999
+ updateFields.phone = dto.phone;
2000
+ if (dto.phone !== user.phone) {
2001
+ if (!dto.retainVerification) {
2002
+ updateFields.isPhoneVerified = false;
2003
+ }
2004
+ else {
2005
+ updateFields.isPhoneVerified = user.isPhoneVerified;
2006
+ }
2007
+ if (oldPhone && this.mfaDeviceRepository) {
2008
+ try {
2009
+ const smsDevices = (await this.mfaDeviceRepository.find({
2010
+ where: {
2011
+ userId: user.id,
2012
+ type: mfa_method_enum_1.MFAMethod.SMS,
2013
+ isActive: true,
2014
+ },
2015
+ }));
2016
+ if (smsDevices.length > 0) {
2017
+ this.logger?.log?.(`Deleting ${smsDevices.length} SMS MFA device(s) for user ${user.sub} due to phone number change (old: ${oldPhone}, new: ${dto.phone})`);
2018
+ for (const device of smsDevices) {
2019
+ const deviceId = device.id;
2020
+ await this.mfaDeviceRepository.delete(deviceId);
2021
+ }
2022
+ if (this.auditService) {
2023
+ try {
2024
+ await this.auditService.recordEvent({
2025
+ userId: user.id,
2026
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.MFA_DEVICE_REMOVED,
2027
+ eventStatus: 'INFO',
2028
+ reason: 'phone_changed',
2029
+ description: `SMS MFA device(s) removed due to phone number change (${oldPhone} → ${dto.phone})`,
2030
+ metadata: {
2031
+ method: mfa_method_enum_1.MFAMethod.SMS,
2032
+ deletedCount: smsDevices.length,
2033
+ oldPhone,
2034
+ newPhone: dto.phone,
2035
+ reason: 'phone_number_changed_requires_reverification',
2036
+ },
2037
+ });
2038
+ }
2039
+ catch (auditError) {
2040
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
2041
+ this.logger?.error?.(`Failed to record MFA_DEVICE_REMOVED audit event for phone change: ${errorMessage}`, { error: auditError, userId: user.id });
2042
+ }
2043
+ }
2044
+ const allActiveDevices = (await this.mfaDeviceRepository.find({
2045
+ where: {
2046
+ userId: user.id,
2047
+ isActive: true,
2048
+ },
2049
+ }));
2050
+ if (allActiveDevices.length === 0 && user.mfaEnabled) {
2051
+ updateFields.mfaEnabled = false;
2052
+ updateFields.mfaMethods = [];
2053
+ updateFields.preferredMfaMethod = null;
2054
+ this.logger?.log?.(`MFA disabled for user ${user.sub} - no active MFA devices remaining after phone change`);
2055
+ }
2056
+ else {
2057
+ this.logger?.log?.(`User ${user.sub} still has ${allActiveDevices.length} active MFA device(s) - MFA remains enabled`);
2058
+ }
2059
+ }
2060
+ }
2061
+ catch (error) {
2062
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
2063
+ this.logger?.warn?.(`Failed to handle MFA device deactivation during phone change for user ${user.sub}: ${errorMessage}`);
2064
+ }
2065
+ }
2066
+ }
2067
+ }
2068
+ if (dto.preferredMfaMethod !== undefined) {
2069
+ updateFields.preferredMfaMethod = dto.preferredMfaMethod;
2070
+ }
2071
+ if (dto.metadata !== undefined) {
2072
+ const existingMetadata = user.metadata || {};
2073
+ updateFields.metadata = { ...existingMetadata, ...dto.metadata };
2074
+ }
2075
+ await this.userRepository.update(user.id, updateFields);
2076
+ const updatedUser = (await this.userRepository.findOne({ where: { id: user.id } }));
2077
+ if (!updatedUser) {
2078
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found after update');
2079
+ }
2080
+ try {
2081
+ const updatedFieldNames = Object.keys(updateFields);
2082
+ const fieldChanges = {};
2083
+ if (dto.firstName !== undefined && dto.firstName !== user.firstName) {
2084
+ fieldChanges.firstName = {
2085
+ before: user.firstName ?? null,
2086
+ after: dto.firstName ?? null,
2087
+ };
2088
+ }
2089
+ if (dto.lastName !== undefined && dto.lastName !== user.lastName) {
2090
+ fieldChanges.lastName = {
2091
+ before: user.lastName ?? null,
2092
+ after: dto.lastName ?? null,
2093
+ };
2094
+ }
2095
+ if (dto.username !== undefined && dto.username !== user.username) {
2096
+ fieldChanges.username = {
2097
+ before: user.username ?? null,
2098
+ after: dto.username ?? null,
2099
+ };
2100
+ }
2101
+ if (dto.email !== undefined && dto.email !== user.email) {
2102
+ fieldChanges.email = {
2103
+ before: user.email ?? null,
2104
+ after: dto.email ?? null,
2105
+ };
2106
+ }
2107
+ if (dto.phone !== undefined && dto.phone !== user.phone) {
2108
+ fieldChanges.phone = {
2109
+ before: user.phone ?? null,
2110
+ after: dto.phone ?? null,
2111
+ };
2112
+ }
2113
+ if (dto.preferredMfaMethod !== undefined && dto.preferredMfaMethod !== user.preferredMfaMethod) {
2114
+ fieldChanges.preferredMfaMethod = {
2115
+ before: user.preferredMfaMethod ?? null,
2116
+ after: dto.preferredMfaMethod ?? null,
2117
+ };
2118
+ }
2119
+ if (dto.metadata !== undefined) {
2120
+ const oldMetadata = user.metadata || {};
2121
+ const newMetadata = { ...oldMetadata, ...dto.metadata };
2122
+ const metadataChanges = {};
2123
+ const allKeys = new Set([...Object.keys(oldMetadata), ...Object.keys(dto.metadata)]);
2124
+ for (const key of allKeys) {
2125
+ const oldValue = oldMetadata[key];
2126
+ const newValue = newMetadata[key];
2127
+ if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
2128
+ metadataChanges[key] = {
2129
+ before: oldValue ?? null,
2130
+ after: newValue ?? null,
2131
+ };
2132
+ }
2133
+ }
2134
+ if (Object.keys(metadataChanges).length > 0) {
2135
+ fieldChanges.metadata = metadataChanges;
2136
+ }
2137
+ }
2138
+ if (dto.email !== undefined && dto.email !== user.email) {
2139
+ const emailVerificationChanged = !dto.retainVerification && updateFields.isEmailVerified === false;
2140
+ if (emailVerificationChanged) {
2141
+ fieldChanges.isEmailVerified = {
2142
+ before: user.isEmailVerified,
2143
+ after: false,
2144
+ };
2145
+ }
2146
+ }
2147
+ if (dto.phone !== undefined && dto.phone !== user.phone) {
2148
+ const phoneVerificationChanged = !dto.retainVerification && updateFields.isPhoneVerified === false;
2149
+ if (phoneVerificationChanged) {
2150
+ fieldChanges.isPhoneVerified = {
2151
+ before: user.isPhoneVerified,
2152
+ after: false,
2153
+ };
2154
+ }
2155
+ }
2156
+ await this.auditService?.recordEvent({
2157
+ userId: user.id,
2158
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.PROFILE_UPDATED,
2159
+ eventStatus: 'INFO',
2160
+ metadata: {
2161
+ updatedFields: updatedFieldNames,
2162
+ fieldChanges: Object.keys(fieldChanges).length > 0 ? fieldChanges : undefined,
2163
+ },
2164
+ });
2165
+ if (dto.email !== undefined && dto.email !== user.email) {
2166
+ await this.auditService?.recordEvent({
2167
+ userId: user.id,
2168
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.EMAIL_CHANGED,
2169
+ eventStatus: 'INFO',
2170
+ metadata: {
2171
+ oldEmail: user.email,
2172
+ newEmail: dto.email,
2173
+ retainVerification: dto.retainVerification || false,
2174
+ },
2175
+ });
2176
+ }
2177
+ if (dto.phone !== undefined && dto.phone !== user.phone) {
2178
+ await this.auditService?.recordEvent({
2179
+ userId: user.id,
2180
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.PHONE_CHANGED,
2181
+ eventStatus: 'INFO',
2182
+ metadata: {
2183
+ oldPhone: user.phone,
2184
+ newPhone: dto.phone,
2185
+ retainVerification: dto.retainVerification || false,
2186
+ },
2187
+ });
2188
+ }
2189
+ if (dto.username !== undefined && dto.username !== user.username) {
2190
+ await this.auditService?.recordEvent({
2191
+ userId: user.id,
2192
+ eventType: auth_audit_event_type_enum_1.AuthAuditEventType.USERNAME_CHANGED,
2193
+ eventStatus: 'INFO',
2194
+ metadata: {
2195
+ oldUsername: user.username,
2196
+ newUsername: dto.username,
2197
+ },
2198
+ });
2199
+ }
2200
+ }
2201
+ catch (auditError) {
2202
+ const errorMessage = auditError instanceof Error ? auditError.message : 'Unknown error';
2203
+ this.logger?.error?.(`Failed to record profile update audit events: ${errorMessage}`, {
2204
+ error: auditError,
2205
+ userId: user.id,
2206
+ });
2207
+ }
2208
+ return user_response_dto_1.UserResponseDto.fromEntity(updatedUser);
2209
+ }
2210
+ async validateUniquenessConstraints(userId, updateData) {
2211
+ const conflicts = [];
2212
+ if (updateData.email) {
2213
+ const existingUser = await this.userRepository.findOne({
2214
+ where: { email: updateData.email },
2215
+ });
2216
+ if (existingUser && existingUser.id !== userId) {
2217
+ conflicts.push('Email already exists');
2218
+ }
2219
+ }
2220
+ if (updateData.phone) {
2221
+ const existingUser = await this.userRepository.findOne({
2222
+ where: { phone: updateData.phone },
2223
+ });
2224
+ if (existingUser && existingUser.id !== userId) {
2225
+ conflicts.push('Phone number already exists');
2226
+ }
2227
+ }
2228
+ if (updateData.username) {
2229
+ const existingUser = await this.userRepository.findOne({
2230
+ where: { username: updateData.username },
2231
+ });
2232
+ if (existingUser && existingUser.id !== userId) {
2233
+ conflicts.push('Username already exists');
2234
+ }
2235
+ }
2236
+ if (conflicts.length > 0) {
2237
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.VALIDATION_FAILED, conflicts.join(', '), {
2238
+ conflicts,
2239
+ });
2240
+ }
2241
+ }
2242
+ validateIdentifierType(identifier, allowedType) {
2243
+ const isEmail = identifier.includes('@');
2244
+ const isPhone = /^\+[1-9]\d{1,14}$/.test(identifier.trim());
2245
+ const isUsername = !isEmail && !isPhone;
2246
+ switch (allowedType) {
2247
+ case 'email':
2248
+ return isEmail;
2249
+ case 'username':
2250
+ return isUsername;
2251
+ case 'phone':
2252
+ return isPhone;
2253
+ case 'email_or_username':
2254
+ return isEmail || isUsername;
2255
+ default:
2256
+ return true;
2257
+ }
2258
+ }
2259
+ async findUserByIdentifier(identifier, identifierType) {
2260
+ const queryBuilder = this.userRepository.createQueryBuilder('user');
2261
+ if (!identifierType) {
2262
+ queryBuilder
2263
+ .where('user.email = :identifier', { identifier })
2264
+ .orWhere('user.username = :identifier', { identifier })
2265
+ .orWhere('user.phone = :identifier', { identifier });
2266
+ }
2267
+ else {
2268
+ switch (identifierType) {
2269
+ case 'email':
2270
+ queryBuilder.where('user.email = :identifier', { identifier });
2271
+ break;
2272
+ case 'username':
2273
+ queryBuilder.where('user.username = :identifier', { identifier });
2274
+ break;
2275
+ case 'phone':
2276
+ queryBuilder.where('user.phone = :identifier', { identifier });
2277
+ break;
2278
+ case 'email_or_username':
2279
+ queryBuilder
2280
+ .where('user.email = :identifier', { identifier })
2281
+ .orWhere('user.username = :identifier', { identifier });
2282
+ break;
2283
+ }
2284
+ }
2285
+ queryBuilder.select([
2286
+ 'user.id',
2287
+ 'user.sub',
2288
+ 'user.email',
2289
+ 'user.firstName',
2290
+ 'user.lastName',
2291
+ 'user.username',
2292
+ 'user.phone',
2293
+ 'user.passwordHash',
2294
+ 'user.passwordChangedAt',
2295
+ 'user.mustChangePassword',
2296
+ 'user.isActive',
2297
+ 'user.mfaEnabled',
2298
+ 'user.preferredMfaMethod',
2299
+ 'user.isEmailVerified',
2300
+ 'user.isPhoneVerified',
2301
+ 'user.mfaExempt',
2302
+ 'user.socialProviders',
2303
+ 'user.backupCodes',
2304
+ ]);
2305
+ return (await queryBuilder.getOne());
2306
+ }
2307
+ async handleFailedLogin(identifier, reason) {
2308
+ const clientInfo = this.clientInfoService.get();
2309
+ const ipAddress = clientInfo.ipAddress;
2310
+ await this.recordLoginAttempt(identifier, false, reason);
2311
+ if (this.config.lockout?.enabled && ipAddress) {
2312
+ const attempts = await this.accountLockoutStorage.recordFailedAttempt(ipAddress);
2313
+ if (attempts >= (this.config.lockout.maxAttempts || 5)) {
2314
+ await this.accountLockoutStorage.blockIpAdresss(ipAddress, this.config.lockout.duration || 900, 'Too many failed login attempts from this IP');
2315
+ }
2316
+ }
2317
+ }
2318
+ async recordLoginAttempt(email, success, failureReason, userId) {
2319
+ const clientInfo = this.clientInfoService.get();
2320
+ const attempt = this.loginAttemptRepository.create({
2321
+ email,
2322
+ userId,
2323
+ ipAddress: clientInfo.ipAddress,
2324
+ userAgent: clientInfo.userAgent,
2325
+ success,
2326
+ failureReason,
2327
+ });
2328
+ await this.loginAttemptRepository.save(attempt);
2329
+ }
2330
+ async getUserById(dto) {
2331
+ const user = (await this.userRepository.findOne({ where: { sub: dto.sub } }));
2332
+ return user ? user_response_dto_1.UserResponseDto.fromEntity(user) : null;
2333
+ }
2334
+ async getUserByEmail(dto) {
2335
+ const where = dto.requireEmailVerified
2336
+ ? { email: dto.email, isEmailVerified: true }
2337
+ : { email: dto.email };
2338
+ const user = (await this.userRepository.findOne({ where }));
2339
+ return user ? user_response_dto_1.UserResponseDto.fromEntity(user) : null;
2340
+ }
2341
+ async setMustChangePassword(dto) {
2342
+ const user = await this.userRepository.findOne({ where: { sub: dto.userId } });
2343
+ if (!user) {
2344
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.NOT_FOUND, 'User not found');
2345
+ }
2346
+ if (!user.passwordHash) {
2347
+ this.logger?.warn?.(`Cannot force password change for user ${dto.userId} - user doesn't have a password (pure social signup)`);
2348
+ throw new nauth_exception_1.NAuthException(error_codes_enum_1.AuthErrorCode.PASSWORD_CHANGE_NOT_ALLOWED, 'Password change not available. This account uses social authentication only and has no password.');
2349
+ }
2350
+ await this.userRepository.update({ sub: dto.userId }, { mustChangePassword: true });
2351
+ this.logger?.log?.(`Must-change-password flag set for user: ${dto.userId}`);
2352
+ return { success: true };
2353
+ }
2354
+ }
2355
+ exports.AuthService = AuthService;
2356
+ //# sourceMappingURL=auth.service.js.map