@digitaldefiance/node-express-suite 1.0.23 → 1.0.25

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 (632) hide show
  1. package/README.md +8 -0
  2. package/package.json +8 -7
  3. package/src/application-base.d.ts +112 -0
  4. package/src/application-base.d.ts.map +1 -0
  5. package/src/application-base.js +335 -0
  6. package/src/application-base.js.map +1 -0
  7. package/src/application.d.ts +20 -0
  8. package/src/application.d.ts.map +1 -0
  9. package/src/application.js +124 -0
  10. package/src/application.js.map +1 -0
  11. package/src/backup-code.d.ts +67 -0
  12. package/src/backup-code.d.ts.map +1 -0
  13. package/src/backup-code.js +238 -0
  14. package/src/backup-code.js.map +1 -0
  15. package/src/constants.d.ts +16 -0
  16. package/src/constants.d.ts.map +1 -0
  17. package/src/constants.js +54 -0
  18. package/src/constants.js.map +1 -0
  19. package/src/controllers/base.d.ts +63 -0
  20. package/src/controllers/base.d.ts.map +1 -0
  21. package/src/controllers/base.js +272 -0
  22. package/src/controllers/base.js.map +1 -0
  23. package/src/controllers/{index.ts → index.d.ts} +1 -0
  24. package/src/controllers/index.d.ts.map +1 -0
  25. package/src/controllers/index.js +6 -0
  26. package/src/controllers/index.js.map +1 -0
  27. package/src/controllers/user.d.ts +45 -0
  28. package/src/controllers/user.d.ts.map +1 -0
  29. package/src/controllers/user.js +748 -0
  30. package/src/controllers/user.js.map +1 -0
  31. package/src/decorators/base-controller.d.ts +14 -0
  32. package/src/decorators/base-controller.d.ts.map +1 -0
  33. package/src/decorators/base-controller.js +49 -0
  34. package/src/decorators/base-controller.js.map +1 -0
  35. package/src/decorators/controller.d.ts +32 -0
  36. package/src/decorators/controller.d.ts.map +1 -0
  37. package/src/decorators/controller.js +67 -0
  38. package/src/decorators/controller.js.map +1 -0
  39. package/src/decorators/{index.ts → index.d.ts} +1 -0
  40. package/src/decorators/index.d.ts.map +1 -0
  41. package/src/decorators/index.js +7 -0
  42. package/src/decorators/index.js.map +1 -0
  43. package/src/decorators/zod-validation.d.ts +5 -0
  44. package/src/decorators/zod-validation.d.ts.map +1 -0
  45. package/src/decorators/zod-validation.js +47 -0
  46. package/src/decorators/zod-validation.js.map +1 -0
  47. package/src/defaults.d.ts +7 -0
  48. package/src/defaults.d.ts.map +1 -0
  49. package/src/defaults.js +83 -0
  50. package/src/defaults.js.map +1 -0
  51. package/src/documents/base.d.ts +3 -0
  52. package/src/documents/base.d.ts.map +1 -0
  53. package/src/documents/base.js +3 -0
  54. package/src/documents/base.js.map +1 -0
  55. package/src/documents/email-token.d.ts +8 -0
  56. package/src/documents/email-token.d.ts.map +1 -0
  57. package/src/documents/email-token.js +3 -0
  58. package/src/documents/email-token.js.map +1 -0
  59. package/src/documents/{index.ts → index.d.ts} +1 -0
  60. package/src/documents/index.d.ts.map +1 -0
  61. package/src/documents/index.js +3 -0
  62. package/src/documents/index.js.map +1 -0
  63. package/src/documents/{mnemonic.ts → mnemonic.d.ts} +2 -5
  64. package/src/documents/mnemonic.d.ts.map +1 -0
  65. package/src/documents/mnemonic.js +3 -0
  66. package/src/documents/mnemonic.js.map +1 -0
  67. package/src/documents/{role.ts → role.d.ts} +2 -5
  68. package/src/documents/role.d.ts.map +1 -0
  69. package/src/documents/role.js +3 -0
  70. package/src/documents/role.js.map +1 -0
  71. package/src/documents/used-direct-login-token.d.ts +5 -0
  72. package/src/documents/used-direct-login-token.d.ts.map +1 -0
  73. package/src/documents/used-direct-login-token.js +3 -0
  74. package/src/documents/used-direct-login-token.js.map +1 -0
  75. package/src/documents/{user-role.ts → user-role.d.ts} +2 -5
  76. package/src/documents/user-role.d.ts.map +1 -0
  77. package/src/documents/user-role.js +3 -0
  78. package/src/documents/user-role.js.map +1 -0
  79. package/src/documents/{user.ts → user.d.ts} +2 -4
  80. package/src/documents/user.d.ts.map +1 -0
  81. package/src/documents/user.js +3 -0
  82. package/src/documents/user.js.map +1 -0
  83. package/src/enumerations/base-model-name.d.ts +38 -0
  84. package/src/enumerations/base-model-name.d.ts.map +1 -0
  85. package/src/enumerations/base-model-name.js +34 -0
  86. package/src/enumerations/base-model-name.js.map +1 -0
  87. package/src/enumerations/{index.ts → index.d.ts} +1 -0
  88. package/src/enumerations/index.d.ts.map +1 -0
  89. package/src/enumerations/index.js +8 -0
  90. package/src/enumerations/index.js.map +1 -0
  91. package/src/enumerations/length-encoding-type.d.ts +7 -0
  92. package/src/enumerations/length-encoding-type.d.ts.map +1 -0
  93. package/src/enumerations/length-encoding-type.js +11 -0
  94. package/src/enumerations/length-encoding-type.js.map +1 -0
  95. package/src/enumerations/schema-collection.d.ts +34 -0
  96. package/src/enumerations/schema-collection.d.ts.map +1 -0
  97. package/src/enumerations/schema-collection.js +38 -0
  98. package/src/enumerations/schema-collection.js.map +1 -0
  99. package/src/enumerations/symmetric-error-type.d.ts +5 -0
  100. package/src/enumerations/symmetric-error-type.d.ts.map +1 -0
  101. package/src/enumerations/symmetric-error-type.js +9 -0
  102. package/src/enumerations/symmetric-error-type.js.map +1 -0
  103. package/src/environment.d.ts +189 -0
  104. package/src/environment.d.ts.map +1 -0
  105. package/src/environment.js +620 -0
  106. package/src/environment.js.map +1 -0
  107. package/src/errors/express-validation.d.ts +9 -0
  108. package/src/errors/express-validation.d.ts.map +1 -0
  109. package/src/errors/express-validation.js +18 -0
  110. package/src/errors/express-validation.js.map +1 -0
  111. package/src/errors/{index.ts → index.d.ts} +1 -0
  112. package/src/errors/index.d.ts.map +1 -0
  113. package/src/errors/index.js +16 -0
  114. package/src/errors/index.js.map +1 -0
  115. package/src/errors/invalid-backup-code-version.d.ts +6 -0
  116. package/src/errors/invalid-backup-code-version.d.ts.map +1 -0
  117. package/src/errors/invalid-backup-code-version.js +15 -0
  118. package/src/errors/invalid-backup-code-version.js.map +1 -0
  119. package/src/errors/invalid-jwt-token.d.ts +5 -0
  120. package/src/errors/invalid-jwt-token.d.ts.map +1 -0
  121. package/src/errors/invalid-jwt-token.js +11 -0
  122. package/src/errors/invalid-jwt-token.js.map +1 -0
  123. package/src/errors/invalid-model.d.ts +6 -0
  124. package/src/errors/invalid-model.d.ts.map +1 -0
  125. package/src/errors/invalid-model.js +14 -0
  126. package/src/errors/invalid-model.js.map +1 -0
  127. package/src/errors/invalid-new-password.d.ts +5 -0
  128. package/src/errors/invalid-new-password.d.ts.map +1 -0
  129. package/src/errors/invalid-new-password.js +14 -0
  130. package/src/errors/invalid-new-password.js.map +1 -0
  131. package/src/errors/invalid-password.d.ts +5 -0
  132. package/src/errors/invalid-password.d.ts.map +1 -0
  133. package/src/errors/invalid-password.js +14 -0
  134. package/src/errors/invalid-password.js.map +1 -0
  135. package/src/errors/missing-validated-data.d.ts +7 -0
  136. package/src/errors/missing-validated-data.d.ts.map +1 -0
  137. package/src/errors/missing-validated-data.js +36 -0
  138. package/src/errors/missing-validated-data.js.map +1 -0
  139. package/src/errors/mnemonic-or-password-required.d.ts +5 -0
  140. package/src/errors/mnemonic-or-password-required.d.ts.map +1 -0
  141. package/src/errors/mnemonic-or-password-required.js +13 -0
  142. package/src/errors/mnemonic-or-password-required.js.map +1 -0
  143. package/src/errors/model-not-registered.d.ts +5 -0
  144. package/src/errors/model-not-registered.d.ts.map +1 -0
  145. package/src/errors/model-not-registered.js +12 -0
  146. package/src/errors/model-not-registered.js.map +1 -0
  147. package/src/errors/mongoose-validation.d.ts +11 -0
  148. package/src/errors/mongoose-validation.d.ts.map +1 -0
  149. package/src/errors/mongoose-validation.js +17 -0
  150. package/src/errors/mongoose-validation.js.map +1 -0
  151. package/src/errors/symmetric.d.ts +8 -0
  152. package/src/errors/symmetric.d.ts.map +1 -0
  153. package/src/errors/symmetric.js +23 -0
  154. package/src/errors/symmetric.js.map +1 -0
  155. package/src/errors/token-expired.d.ts +5 -0
  156. package/src/errors/token-expired.d.ts.map +1 -0
  157. package/src/errors/token-expired.js +11 -0
  158. package/src/errors/token-expired.js.map +1 -0
  159. package/src/get-language.d.ts +2 -0
  160. package/src/get-language.d.ts.map +1 -0
  161. package/src/get-language.js +30 -0
  162. package/src/get-language.js.map +1 -0
  163. package/src/get-timezone.d.ts +3 -0
  164. package/src/get-timezone.d.ts.map +1 -0
  165. package/src/get-timezone.js +31 -0
  166. package/src/get-timezone.js.map +1 -0
  167. package/src/{index.ts → index.d.ts} +1 -1
  168. package/src/index.d.ts.map +1 -0
  169. package/src/index.js +28 -0
  170. package/src/index.js.map +1 -0
  171. package/src/interfaces/{api-error-response.ts → api-error-response.d.ts} +2 -2
  172. package/src/interfaces/api-error-response.d.ts.map +1 -0
  173. package/src/interfaces/api-error-response.js +3 -0
  174. package/src/interfaces/api-error-response.js.map +1 -0
  175. package/src/interfaces/api-express-validation-error-response.d.ts +7 -0
  176. package/src/interfaces/api-express-validation-error-response.d.ts.map +1 -0
  177. package/src/interfaces/api-express-validation-error-response.js +3 -0
  178. package/src/interfaces/api-express-validation-error-response.js.map +1 -0
  179. package/src/interfaces/api-message-response.d.ts +4 -0
  180. package/src/interfaces/api-message-response.d.ts.map +1 -0
  181. package/src/interfaces/api-message-response.js +3 -0
  182. package/src/interfaces/api-message-response.js.map +1 -0
  183. package/src/interfaces/{api-mongo-validation-error-response.ts → api-mongo-validation-error-response.d.ts} +2 -2
  184. package/src/interfaces/api-mongo-validation-error-response.d.ts.map +1 -0
  185. package/src/interfaces/api-mongo-validation-error-response.js +3 -0
  186. package/src/interfaces/api-mongo-validation-error-response.js.map +1 -0
  187. package/src/interfaces/api-responses/{backup-codes-response.ts → backup-codes-response.d.ts} +2 -2
  188. package/src/interfaces/api-responses/backup-codes-response.d.ts.map +1 -0
  189. package/src/interfaces/api-responses/backup-codes-response.js +3 -0
  190. package/src/interfaces/api-responses/backup-codes-response.js.map +1 -0
  191. package/src/interfaces/api-responses/{challenge-response.ts → challenge-response.d.ts} +3 -3
  192. package/src/interfaces/api-responses/challenge-response.d.ts.map +1 -0
  193. package/src/interfaces/api-responses/challenge-response.js +3 -0
  194. package/src/interfaces/api-responses/challenge-response.js.map +1 -0
  195. package/src/interfaces/api-responses/{code-count-response.ts → code-count-response.d.ts} +2 -2
  196. package/src/interfaces/api-responses/code-count-response.d.ts.map +1 -0
  197. package/src/interfaces/api-responses/code-count-response.js +3 -0
  198. package/src/interfaces/api-responses/code-count-response.js.map +1 -0
  199. package/src/interfaces/api-responses/{index.ts → index.d.ts} +1 -0
  200. package/src/interfaces/api-responses/index.d.ts.map +1 -0
  201. package/src/interfaces/api-responses/index.js +11 -0
  202. package/src/interfaces/api-responses/index.js.map +1 -0
  203. package/src/interfaces/api-responses/{login-response.ts → login-response.d.ts} +4 -4
  204. package/src/interfaces/api-responses/login-response.d.ts.map +1 -0
  205. package/src/interfaces/api-responses/login-response.js +3 -0
  206. package/src/interfaces/api-responses/login-response.js.map +1 -0
  207. package/src/interfaces/api-responses/{mnemonic-response.ts → mnemonic-response.d.ts} +2 -2
  208. package/src/interfaces/api-responses/mnemonic-response.d.ts.map +1 -0
  209. package/src/interfaces/api-responses/mnemonic-response.js +3 -0
  210. package/src/interfaces/api-responses/mnemonic-response.js.map +1 -0
  211. package/src/interfaces/api-responses/{registration-response.ts → registration-response.d.ts} +3 -3
  212. package/src/interfaces/api-responses/registration-response.d.ts.map +1 -0
  213. package/src/interfaces/api-responses/registration-response.js +3 -0
  214. package/src/interfaces/api-responses/registration-response.js.map +1 -0
  215. package/src/interfaces/api-responses/{request-user-response.ts → request-user-response.d.ts} +2 -2
  216. package/src/interfaces/api-responses/request-user-response.d.ts.map +1 -0
  217. package/src/interfaces/api-responses/request-user-response.js +3 -0
  218. package/src/interfaces/api-responses/request-user-response.js.map +1 -0
  219. package/src/interfaces/{application.ts → application.d.ts} +7 -7
  220. package/src/interfaces/application.d.ts.map +1 -0
  221. package/src/interfaces/application.js +3 -0
  222. package/src/interfaces/application.js.map +1 -0
  223. package/src/interfaces/backend-objects/email-token.d.ts +4 -0
  224. package/src/interfaces/backend-objects/email-token.d.ts.map +1 -0
  225. package/src/interfaces/backend-objects/email-token.js +3 -0
  226. package/src/interfaces/backend-objects/email-token.js.map +1 -0
  227. package/src/interfaces/backend-objects/{index.ts → index.d.ts} +1 -0
  228. package/src/interfaces/backend-objects/index.d.ts.map +1 -0
  229. package/src/interfaces/backend-objects/index.js +8 -0
  230. package/src/interfaces/backend-objects/index.js.map +1 -0
  231. package/src/interfaces/backend-objects/{request-user.ts → request-user.d.ts} +2 -7
  232. package/src/interfaces/backend-objects/request-user.d.ts.map +1 -0
  233. package/src/interfaces/backend-objects/request-user.js +3 -0
  234. package/src/interfaces/backend-objects/request-user.js.map +1 -0
  235. package/src/interfaces/backend-objects/{role.ts → role.d.ts} +1 -1
  236. package/src/interfaces/backend-objects/role.d.ts.map +1 -0
  237. package/src/interfaces/backend-objects/role.js +3 -0
  238. package/src/interfaces/backend-objects/role.js.map +1 -0
  239. package/src/interfaces/backend-objects/user.d.ts +4 -0
  240. package/src/interfaces/backend-objects/user.d.ts.map +1 -0
  241. package/src/interfaces/backend-objects/user.js +3 -0
  242. package/src/interfaces/backend-objects/user.js.map +1 -0
  243. package/src/interfaces/checksum-config.d.ts +5 -0
  244. package/src/interfaces/checksum-config.d.ts.map +1 -0
  245. package/src/interfaces/checksum-config.js +3 -0
  246. package/src/interfaces/checksum-config.js.map +1 -0
  247. package/src/interfaces/checksum-consts.d.ts +11 -0
  248. package/src/interfaces/checksum-consts.d.ts.map +1 -0
  249. package/src/interfaces/checksum-consts.js +3 -0
  250. package/src/interfaces/checksum-consts.js.map +1 -0
  251. package/src/interfaces/{constants.ts → constants.d.ts} +5 -5
  252. package/src/interfaces/constants.d.ts.map +1 -0
  253. package/src/interfaces/constants.js +3 -0
  254. package/src/interfaces/constants.js.map +1 -0
  255. package/src/interfaces/create-user-basics.d.ts +18 -0
  256. package/src/interfaces/create-user-basics.d.ts.map +1 -0
  257. package/src/interfaces/create-user-basics.js +3 -0
  258. package/src/interfaces/create-user-basics.js.map +1 -0
  259. package/src/interfaces/csp-config.d.ts +14 -0
  260. package/src/interfaces/csp-config.d.ts.map +1 -0
  261. package/src/interfaces/csp-config.js +3 -0
  262. package/src/interfaces/csp-config.js.map +1 -0
  263. package/src/interfaces/deep-partial.d.ts +4 -0
  264. package/src/interfaces/deep-partial.d.ts.map +1 -0
  265. package/src/interfaces/deep-partial.js +3 -0
  266. package/src/interfaces/deep-partial.js.map +1 -0
  267. package/src/interfaces/{discriminator-collections.ts → discriminator-collections.d.ts} +3 -3
  268. package/src/interfaces/discriminator-collections.d.ts.map +1 -0
  269. package/src/interfaces/discriminator-collections.js +3 -0
  270. package/src/interfaces/discriminator-collections.js.map +1 -0
  271. package/src/interfaces/email-service.d.ts +4 -0
  272. package/src/interfaces/email-service.d.ts.map +1 -0
  273. package/src/interfaces/email-service.js +3 -0
  274. package/src/interfaces/email-service.js.map +1 -0
  275. package/src/interfaces/environment-mongo.d.ts +76 -0
  276. package/src/interfaces/environment-mongo.d.ts.map +1 -0
  277. package/src/interfaces/environment-mongo.js +3 -0
  278. package/src/interfaces/environment-mongo.js.map +1 -0
  279. package/src/interfaces/environment.d.ts +181 -0
  280. package/src/interfaces/environment.d.ts.map +1 -0
  281. package/src/interfaces/environment.js +3 -0
  282. package/src/interfaces/environment.js.map +1 -0
  283. package/src/interfaces/failable-result.d.ts +7 -0
  284. package/src/interfaces/failable-result.d.ts.map +1 -0
  285. package/src/interfaces/failable-result.js +3 -0
  286. package/src/interfaces/failable-result.js.map +1 -0
  287. package/src/interfaces/fec-consts.d.ts +5 -0
  288. package/src/interfaces/fec-consts.d.ts.map +1 -0
  289. package/src/interfaces/fec-consts.js +3 -0
  290. package/src/interfaces/fec-consts.js.map +1 -0
  291. package/src/interfaces/handleable-error-options.d.ts +7 -0
  292. package/src/interfaces/handleable-error-options.d.ts.map +1 -0
  293. package/src/interfaces/handleable-error-options.js +3 -0
  294. package/src/interfaces/handleable-error-options.js.map +1 -0
  295. package/src/interfaces/{index.ts → index.d.ts} +1 -0
  296. package/src/interfaces/index.d.ts.map +1 -0
  297. package/src/interfaces/index.js +33 -0
  298. package/src/interfaces/index.js.map +1 -0
  299. package/src/interfaces/jwt-consts.d.ts +11 -0
  300. package/src/interfaces/jwt-consts.d.ts.map +1 -0
  301. package/src/interfaces/jwt-consts.js +3 -0
  302. package/src/interfaces/jwt-consts.js.map +1 -0
  303. package/src/interfaces/jwt-sign-response.d.ts +11 -0
  304. package/src/interfaces/jwt-sign-response.d.ts.map +1 -0
  305. package/src/interfaces/jwt-sign-response.js +3 -0
  306. package/src/interfaces/jwt-sign-response.js.map +1 -0
  307. package/src/interfaces/mongo-errors.d.ts +5 -0
  308. package/src/interfaces/mongo-errors.d.ts.map +1 -0
  309. package/src/interfaces/mongo-errors.js +3 -0
  310. package/src/interfaces/mongo-errors.js.map +1 -0
  311. package/src/interfaces/request-user.d.ts +42 -0
  312. package/src/interfaces/request-user.d.ts.map +1 -0
  313. package/src/interfaces/request-user.js +3 -0
  314. package/src/interfaces/request-user.js.map +1 -0
  315. package/src/interfaces/required-string-keys.d.ts +22 -0
  316. package/src/interfaces/required-string-keys.d.ts.map +1 -0
  317. package/src/interfaces/required-string-keys.js +3 -0
  318. package/src/interfaces/required-string-keys.js.map +1 -0
  319. package/src/interfaces/schema.d.ts +29 -0
  320. package/src/interfaces/schema.d.ts.map +1 -0
  321. package/src/interfaces/schema.js +3 -0
  322. package/src/interfaces/schema.js.map +1 -0
  323. package/src/interfaces/server-init-result.d.ts +35 -0
  324. package/src/interfaces/server-init-result.d.ts.map +1 -0
  325. package/src/interfaces/server-init-result.js +3 -0
  326. package/src/interfaces/server-init-result.js.map +1 -0
  327. package/src/interfaces/status-code-response.d.ts +7 -0
  328. package/src/interfaces/status-code-response.d.ts.map +1 -0
  329. package/src/interfaces/status-code-response.js +3 -0
  330. package/src/interfaces/status-code-response.js.map +1 -0
  331. package/src/interfaces/symmetric-encryption-results.d.ts +3 -3
  332. package/src/interfaces/symmetric-encryption-results.d.ts.map +1 -1
  333. package/src/interfaces/symmetric-encryption-results.js.map +1 -1
  334. package/src/interfaces/{token-response.ts → token-response.d.ts} +2 -2
  335. package/src/interfaces/token-response.d.ts.map +1 -0
  336. package/src/interfaces/token-response.js +3 -0
  337. package/src/interfaces/token-response.js.map +1 -0
  338. package/src/middlewares/authenticate-crypto.d.ts +13 -0
  339. package/src/middlewares/authenticate-crypto.d.ts.map +1 -0
  340. package/src/middlewares/authenticate-crypto.js +146 -0
  341. package/src/middlewares/authenticate-crypto.js.map +1 -0
  342. package/src/middlewares/authenticate-token.d.ts +24 -0
  343. package/src/middlewares/authenticate-token.d.ts.map +1 -0
  344. package/src/middlewares/authenticate-token.js +102 -0
  345. package/src/middlewares/authenticate-token.js.map +1 -0
  346. package/src/middlewares/cleanup-crypto.d.ts +7 -0
  347. package/src/middlewares/cleanup-crypto.d.ts.map +1 -0
  348. package/src/middlewares/cleanup-crypto.js +32 -0
  349. package/src/middlewares/cleanup-crypto.js.map +1 -0
  350. package/src/middlewares/{index.ts → index.d.ts} +1 -0
  351. package/src/middlewares/index.d.ts.map +1 -0
  352. package/src/middlewares/index.js +8 -0
  353. package/src/middlewares/index.js.map +1 -0
  354. package/src/middlewares/set-global-context-language.d.ts +3 -0
  355. package/src/middlewares/set-global-context-language.d.ts.map +1 -0
  356. package/src/middlewares/set-global-context-language.js +14 -0
  357. package/src/middlewares/set-global-context-language.js.map +1 -0
  358. package/src/middlewares.d.ts +18 -0
  359. package/src/middlewares.d.ts.map +1 -0
  360. package/src/middlewares.js +74 -0
  361. package/src/middlewares.js.map +1 -0
  362. package/src/model-registry.d.ts +23 -0
  363. package/src/model-registry.d.ts.map +1 -0
  364. package/src/model-registry.js +47 -0
  365. package/src/model-registry.js.map +1 -0
  366. package/src/models/email-token.d.ts +11 -0
  367. package/src/models/email-token.d.ts.map +1 -0
  368. package/src/models/email-token.js +11 -0
  369. package/src/models/email-token.js.map +1 -0
  370. package/src/models/{index.ts → index.d.ts} +1 -0
  371. package/src/models/index.d.ts.map +1 -0
  372. package/src/models/index.js +10 -0
  373. package/src/models/index.js.map +1 -0
  374. package/src/models/mnemonic.d.ts +11 -0
  375. package/src/models/mnemonic.d.ts.map +1 -0
  376. package/src/models/mnemonic.js +11 -0
  377. package/src/models/mnemonic.js.map +1 -0
  378. package/src/models/role.d.ts +11 -0
  379. package/src/models/role.d.ts.map +1 -0
  380. package/src/models/role.js +11 -0
  381. package/src/models/role.js.map +1 -0
  382. package/src/models/used-direct-login-token.d.ts +11 -0
  383. package/src/models/used-direct-login-token.d.ts.map +1 -0
  384. package/src/models/used-direct-login-token.js +11 -0
  385. package/src/models/used-direct-login-token.js.map +1 -0
  386. package/src/models/user-role.d.ts +6 -0
  387. package/src/models/user-role.d.ts.map +1 -0
  388. package/src/models/user-role.js +10 -0
  389. package/src/models/user-role.js.map +1 -0
  390. package/src/models/user.d.ts +7 -0
  391. package/src/models/user.d.ts.map +1 -0
  392. package/src/models/user.js +11 -0
  393. package/src/models/user.js.map +1 -0
  394. package/src/registry/email-service-registry.d.ts +9 -0
  395. package/src/registry/email-service-registry.d.ts.map +1 -0
  396. package/src/registry/email-service-registry.js +18 -0
  397. package/src/registry/email-service-registry.js.map +1 -0
  398. package/src/registry/{index.ts → index.d.ts} +1 -0
  399. package/src/registry/index.d.ts.map +1 -0
  400. package/src/registry/index.js +6 -0
  401. package/src/registry/index.js.map +1 -0
  402. package/src/routers/api.d.ts +27 -0
  403. package/src/routers/api.d.ts.map +1 -0
  404. package/src/routers/api.js +52 -0
  405. package/src/routers/api.js.map +1 -0
  406. package/src/routers/app.d.ts +28 -0
  407. package/src/routers/app.d.ts.map +1 -0
  408. package/src/routers/app.js +186 -0
  409. package/src/routers/app.js.map +1 -0
  410. package/src/routers/base.d.ts +12 -0
  411. package/src/routers/base.d.ts.map +1 -0
  412. package/src/routers/base.js +14 -0
  413. package/src/routers/base.js.map +1 -0
  414. package/src/routers/{index.ts → index.d.ts} +1 -0
  415. package/src/routers/index.d.ts.map +1 -0
  416. package/src/routers/index.js +7 -0
  417. package/src/routers/index.js.map +1 -0
  418. package/src/schemas/email-token.d.ts +38 -0
  419. package/src/schemas/email-token.d.ts.map +1 -0
  420. package/src/schemas/email-token.js +54 -0
  421. package/src/schemas/email-token.js.map +1 -0
  422. package/src/schemas/{index.ts → index.d.ts} +2 -1
  423. package/src/schemas/index.d.ts.map +1 -0
  424. package/src/schemas/index.js +11 -0
  425. package/src/schemas/index.js.map +1 -0
  426. package/src/schemas/mnemonic.d.ts +20 -0
  427. package/src/schemas/mnemonic.d.ts.map +1 -0
  428. package/src/schemas/mnemonic.js +30 -0
  429. package/src/schemas/mnemonic.js.map +1 -0
  430. package/src/schemas/role.d.ts +32 -0
  431. package/src/schemas/role.d.ts.map +1 -0
  432. package/src/schemas/role.js +86 -0
  433. package/src/schemas/role.js.map +1 -0
  434. package/src/schemas/schema.d.ts +40 -0
  435. package/src/schemas/schema.d.ts.map +1 -0
  436. package/src/schemas/schema.js +62 -0
  437. package/src/schemas/schema.js.map +1 -0
  438. package/src/schemas/used-direct-login-token.d.ts +27 -0
  439. package/src/schemas/used-direct-login-token.d.ts.map +1 -0
  440. package/src/schemas/used-direct-login-token.js +23 -0
  441. package/src/schemas/used-direct-login-token.js.map +1 -0
  442. package/src/schemas/user-role.d.ts +29 -0
  443. package/src/schemas/user-role.d.ts.map +1 -0
  444. package/src/schemas/user-role.js +54 -0
  445. package/src/schemas/user-role.js.map +1 -0
  446. package/src/schemas/user.d.ts +21 -0
  447. package/src/schemas/user.d.ts.map +1 -0
  448. package/src/schemas/user.js +176 -0
  449. package/src/schemas/user.js.map +1 -0
  450. package/src/services/backup-code.d.ts +78 -0
  451. package/src/services/backup-code.d.ts.map +1 -0
  452. package/src/services/backup-code.js +184 -0
  453. package/src/services/backup-code.js.map +1 -0
  454. package/src/services/base.d.ts +13 -0
  455. package/src/services/base.d.ts.map +1 -0
  456. package/src/services/base.js +15 -0
  457. package/src/services/base.js.map +1 -0
  458. package/src/services/checksum.d.ts +67 -0
  459. package/src/services/checksum.d.ts.map +1 -0
  460. package/src/services/checksum.js +143 -0
  461. package/src/services/checksum.js.map +1 -0
  462. package/src/services/crc.d.ts +87 -0
  463. package/src/services/crc.d.ts.map +1 -0
  464. package/src/services/crc.js +198 -0
  465. package/src/services/crc.js.map +1 -0
  466. package/src/services/database-initialization.d.ts +105 -0
  467. package/src/services/database-initialization.d.ts.map +1 -0
  468. package/src/services/database-initialization.js +782 -0
  469. package/src/services/database-initialization.js.map +1 -0
  470. package/src/services/db-init-cache.d.ts +7 -13
  471. package/src/services/db-init-cache.d.ts.map +1 -0
  472. package/src/services/db-init-cache.js +3 -0
  473. package/src/services/db-init-cache.js.map +1 -0
  474. package/src/services/direct-login-token.d.ts +9 -0
  475. package/src/services/direct-login-token.d.ts.map +1 -0
  476. package/src/services/direct-login-token.js +41 -0
  477. package/src/services/direct-login-token.js.map +1 -0
  478. package/src/services/fec-usage-example.d.ts +38 -0
  479. package/src/services/fec-usage-example.d.ts.map +1 -0
  480. package/src/services/fec-usage-example.js +75 -0
  481. package/src/services/fec-usage-example.js.map +1 -0
  482. package/src/services/fec.d.ts +46 -0
  483. package/src/services/fec.d.ts.map +1 -0
  484. package/src/services/fec.js +192 -0
  485. package/src/services/fec.js.map +1 -0
  486. package/src/services/{index.ts → index.d.ts} +1 -0
  487. package/src/services/index.d.ts.map +1 -0
  488. package/src/services/index.js +22 -0
  489. package/src/services/index.js.map +1 -0
  490. package/src/services/jwt.d.ts +33 -0
  491. package/src/services/jwt.d.ts.map +1 -0
  492. package/src/services/jwt.js +91 -0
  493. package/src/services/jwt.js.map +1 -0
  494. package/src/services/key-wrapping.d.ts +60 -0
  495. package/src/services/key-wrapping.d.ts.map +1 -0
  496. package/src/services/key-wrapping.js +311 -0
  497. package/src/services/key-wrapping.js.map +1 -0
  498. package/src/services/mnemonic.d.ts +61 -0
  499. package/src/services/mnemonic.d.ts.map +1 -0
  500. package/src/services/mnemonic.js +115 -0
  501. package/src/services/mnemonic.js.map +1 -0
  502. package/src/services/request-user.d.ts +20 -0
  503. package/src/services/request-user.d.ts.map +1 -0
  504. package/src/services/request-user.js +50 -0
  505. package/src/services/request-user.js.map +1 -0
  506. package/src/services/role.d.ts +88 -0
  507. package/src/services/role.d.ts.map +1 -0
  508. package/src/services/role.js +263 -0
  509. package/src/services/role.js.map +1 -0
  510. package/src/services/symmetric.d.ts +42 -0
  511. package/src/services/symmetric.d.ts.map +1 -0
  512. package/src/services/symmetric.js +101 -0
  513. package/src/services/symmetric.js.map +1 -0
  514. package/src/services/system-user.d.ts +17 -0
  515. package/src/services/system-user.d.ts.map +1 -0
  516. package/src/services/system-user.js +46 -0
  517. package/src/services/system-user.js.map +1 -0
  518. package/src/services/user.d.ts +320 -0
  519. package/src/services/user.d.ts.map +1 -0
  520. package/src/services/user.js +1378 -0
  521. package/src/services/user.js.map +1 -0
  522. package/src/services/xor.d.ts +24 -0
  523. package/src/services/xor.d.ts.map +1 -0
  524. package/src/services/xor.js +37 -0
  525. package/src/services/xor.js.map +1 -0
  526. package/src/types.d.ts +66 -40
  527. package/src/types.d.ts.map +1 -0
  528. package/src/types.js +14 -0
  529. package/src/types.js.map +1 -0
  530. package/src/utils.d.ts +202 -0
  531. package/src/utils.d.ts.map +1 -0
  532. package/src/utils.js +784 -0
  533. package/src/utils.js.map +1 -0
  534. package/LICENSE +0 -21
  535. package/src/application-base.ts +0 -492
  536. package/src/application.ts +0 -254
  537. package/src/backup-code.ts +0 -336
  538. package/src/constants.ts +0 -69
  539. package/src/controllers/base.ts +0 -440
  540. package/src/controllers/user.ts +0 -1451
  541. package/src/decorators/base-controller.ts +0 -61
  542. package/src/decorators/controller.ts +0 -109
  543. package/src/decorators/zod-validation.ts +0 -57
  544. package/src/defaults.ts +0 -94
  545. package/src/documents/base.ts +0 -7
  546. package/src/documents/email-token.ts +0 -14
  547. package/src/documents/used-direct-login-token.ts +0 -7
  548. package/src/enumerations/base-model-name.ts +0 -41
  549. package/src/enumerations/length-encoding-type.ts +0 -6
  550. package/src/enumerations/schema-collection.ts +0 -33
  551. package/src/enumerations/symmetric-error-type.ts +0 -4
  552. package/src/environment.ts +0 -770
  553. package/src/errors/express-validation.ts +0 -21
  554. package/src/errors/invalid-backup-code-version.ts +0 -14
  555. package/src/errors/invalid-jwt-token.ts +0 -10
  556. package/src/errors/invalid-model.ts +0 -11
  557. package/src/errors/invalid-new-password.ts +0 -18
  558. package/src/errors/invalid-password.ts +0 -13
  559. package/src/errors/missing-validated-data.ts +0 -36
  560. package/src/errors/mnemonic-or-password-required.ts +0 -12
  561. package/src/errors/model-not-registered.ts +0 -11
  562. package/src/errors/mongoose-validation.ts +0 -34
  563. package/src/errors/symmetric.ts +0 -41
  564. package/src/errors/token-expired.ts +0 -10
  565. package/src/get-language.ts +0 -53
  566. package/src/get-timezone.ts +0 -45
  567. package/src/interfaces/api-express-validation-error-response.ts +0 -8
  568. package/src/interfaces/api-message-response.ts +0 -3
  569. package/src/interfaces/backend-objects/email-token.ts +0 -11
  570. package/src/interfaces/backend-objects/user.ts +0 -9
  571. package/src/interfaces/checksum-config.ts +0 -4
  572. package/src/interfaces/checksum-consts.ts +0 -13
  573. package/src/interfaces/create-user-basics.ts +0 -17
  574. package/src/interfaces/csp-config.ts +0 -35
  575. package/src/interfaces/deep-partial.ts +0 -3
  576. package/src/interfaces/email-service.ts +0 -8
  577. package/src/interfaces/environment-mongo.ts +0 -76
  578. package/src/interfaces/environment.ts +0 -181
  579. package/src/interfaces/failable-result.ts +0 -6
  580. package/src/interfaces/fec-consts.ts +0 -4
  581. package/src/interfaces/handleable-error-options.ts +0 -6
  582. package/src/interfaces/jwt-consts.ts +0 -23
  583. package/src/interfaces/jwt-sign-response.ts +0 -19
  584. package/src/interfaces/mongo-errors.ts +0 -5
  585. package/src/interfaces/request-user.ts +0 -50
  586. package/src/interfaces/required-string-keys.ts +0 -26
  587. package/src/interfaces/schema.ts +0 -31
  588. package/src/interfaces/server-init-result.ts +0 -37
  589. package/src/interfaces/status-code-response.ts +0 -7
  590. package/src/interfaces/symmetric-encryption-results.ts +0 -4
  591. package/src/middlewares/authenticate-crypto.ts +0 -243
  592. package/src/middlewares/authenticate-token.ts +0 -152
  593. package/src/middlewares/cleanup-crypto.ts +0 -40
  594. package/src/middlewares/set-global-context-language.ts +0 -24
  595. package/src/middlewares.ts +0 -120
  596. package/src/model-registry.ts +0 -75
  597. package/src/models/email-token.ts +0 -19
  598. package/src/models/mnemonic.ts +0 -19
  599. package/src/models/role.ts +0 -19
  600. package/src/models/used-direct-login-token.ts +0 -23
  601. package/src/models/user-role.ts +0 -17
  602. package/src/models/user.ts +0 -19
  603. package/src/registry/email-service-registry.ts +0 -24
  604. package/src/routers/api.ts +0 -151
  605. package/src/routers/app.ts +0 -258
  606. package/src/routers/base.ts +0 -17
  607. package/src/schemas/email-token.ts +0 -91
  608. package/src/schemas/mnemonic.ts +0 -37
  609. package/src/schemas/role.ts +0 -127
  610. package/src/schemas/schema.ts +0 -140
  611. package/src/schemas/used-direct-login-token.ts +0 -38
  612. package/src/schemas/user-role.ts +0 -75
  613. package/src/schemas/user.ts +0 -202
  614. package/src/services/backup-code.ts +0 -316
  615. package/src/services/base.ts +0 -33
  616. package/src/services/checksum.ts +0 -161
  617. package/src/services/crc.ts +0 -213
  618. package/src/services/database-initialization.ts +0 -1479
  619. package/src/services/direct-login-token.ts +0 -62
  620. package/src/services/fec-usage-example.ts +0 -102
  621. package/src/services/fec.ts +0 -296
  622. package/src/services/jwt.ts +0 -134
  623. package/src/services/key-wrapping.ts +0 -434
  624. package/src/services/mnemonic.ts +0 -167
  625. package/src/services/request-user.ts +0 -62
  626. package/src/services/role.ts +0 -396
  627. package/src/services/symmetric.ts +0 -139
  628. package/src/services/system-user.ts +0 -82
  629. package/src/services/user.ts +0 -2137
  630. package/src/services/xor.ts +0 -34
  631. package/src/types.ts +0 -128
  632. package/src/utils.ts +0 -1022
@@ -0,0 +1,1378 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserService = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const ecies_lib_1 = require("@digitaldefiance/ecies-lib");
6
+ const node_ecies_lib_1 = require("@digitaldefiance/node-ecies-lib");
7
+ const suite_core_lib_1 = require("@digitaldefiance/suite-core-lib");
8
+ const crypto_1 = require("crypto");
9
+ const mongodb_1 = require("mongodb");
10
+ const mongoose_1 = require("mongoose");
11
+ const validator_1 = tslib_1.__importDefault(require("validator"));
12
+ const backup_code_1 = require("../backup-code");
13
+ const base_model_name_1 = require("../enumerations/base-model-name");
14
+ const errors_1 = require("../errors");
15
+ const mongoose_validation_1 = require("../errors/mongoose-validation");
16
+ const model_registry_1 = require("../model-registry");
17
+ const utils_1 = require("../utils");
18
+ const base_1 = require("./base");
19
+ const direct_login_token_1 = require("./direct-login-token");
20
+ const mnemonic_1 = require("./mnemonic");
21
+ const request_user_1 = require("./request-user");
22
+ const system_user_1 = require("./system-user");
23
+ class UserService extends base_1.BaseService {
24
+ roleService;
25
+ eciesService;
26
+ keyWrappingService;
27
+ mnemonicService;
28
+ emailService;
29
+ backupCodeService;
30
+ serverUrl;
31
+ disableEmailSend;
32
+ constructor(application, roleService, emailService, keyWrappingService, backupCodeService) {
33
+ super(application);
34
+ this.roleService = roleService;
35
+ this.emailService = emailService;
36
+ this.keyWrappingService = keyWrappingService;
37
+ this.backupCodeService = backupCodeService;
38
+ this.serverUrl = application.environment.serverUrl;
39
+ this.disableEmailSend = application.environment.disableEmailSend;
40
+ const config = {
41
+ curveName: this.application.constants.ECIES.CURVE_NAME,
42
+ primaryKeyDerivationPath: this.application.constants.ECIES.PRIMARY_KEY_DERIVATION_PATH,
43
+ mnemonicStrength: this.application.constants.ECIES.MNEMONIC_STRENGTH,
44
+ symmetricAlgorithm: this.application.constants.ECIES.SYMMETRIC_ALGORITHM_CONFIGURATION,
45
+ symmetricKeyBits: this.application.constants.ECIES.SYMMETRIC.KEY_BITS,
46
+ symmetricKeyMode: this.application.constants.ECIES.SYMMETRIC.MODE,
47
+ };
48
+ this.eciesService = new node_ecies_lib_1.ECIESService(config);
49
+ const mnemonicModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.Mnemonic);
50
+ this.mnemonicService = new mnemonic_1.MnemonicService(mnemonicModel, application.environment.mnemonicHmacSecret, this.keyWrappingService);
51
+ }
52
+ /**
53
+ * Given a User Document, make a User DTO
54
+ * @param user a User Document
55
+ * @returns An IUserDTO
56
+ */
57
+ static userToUserDTO(user) {
58
+ return {
59
+ ...(user instanceof mongoose_1.Document ? user.toObject() : user),
60
+ _id: (user._id instanceof mongoose_1.Types.ObjectId
61
+ ? user._id.toString()
62
+ : user._id),
63
+ createdBy: (user.createdBy instanceof Date
64
+ ? user.createdBy.toString()
65
+ : user.createdBy),
66
+ updatedBy: (user.updatedBy instanceof Date
67
+ ? user.updatedBy.toString()
68
+ : user.updatedBy),
69
+ ...(user.lastLogin
70
+ ? {
71
+ lastLogin: (user.lastLogin instanceof Date
72
+ ? user.lastLogin.toString()
73
+ : user.lastLogin),
74
+ }
75
+ : {}),
76
+ ...(user.deletedBy
77
+ ? {
78
+ deletedBy: (user.deletedBy instanceof Date
79
+ ? user.deletedBy.toString()
80
+ : user.deletedBy),
81
+ }
82
+ : {}),
83
+ };
84
+ }
85
+ /**
86
+ * Given a User DTO, reconstitute ids and dates
87
+ * @param user a User DTO
88
+ * @returns An IUserBackendObject
89
+ */
90
+ hydrateUserDTOToBackend(user) {
91
+ return {
92
+ ...user,
93
+ _id: new mongodb_1.ObjectId(user._id),
94
+ ...(user.lastLogin ? { lastLogin: new Date(user.lastLogin) } : {}),
95
+ createdAt: new Date(user.createdAt),
96
+ createdBy: new mongodb_1.ObjectId(user.createdBy),
97
+ updatedAt: new Date(user.updatedAt),
98
+ updatedBy: new mongodb_1.ObjectId(user.updatedBy),
99
+ ...(user.deletedAt ? { deletedAt: new Date(user.deletedAt) } : {}),
100
+ ...(user.deletedBy
101
+ ? {
102
+ deletedBy: new mongodb_1.ObjectId(user.deletedBy),
103
+ }
104
+ : {}),
105
+ ...(user.mnemonicId ? { mnemonicId: new mongodb_1.ObjectId(user.mnemonicId) } : {}),
106
+ };
107
+ }
108
+ /**
109
+ * Create a new email token to send to the user for email verification
110
+ * @param userDoc The user to create the email token for
111
+ * @param type The type of email token to create
112
+ * @param session The session to use for the query
113
+ * @returns The email token document
114
+ */
115
+ async createEmailToken(userDoc, type, session) {
116
+ const EmailTokenModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.EmailToken);
117
+ // If we already have a session, use it directly to avoid nested transactions
118
+ if (session) {
119
+ const now = new Date();
120
+ const tokenData = {
121
+ userId: userDoc._id,
122
+ type: type,
123
+ email: userDoc.email,
124
+ token: (0, crypto_1.randomBytes)(this.application.constants.EmailTokenLength).toString('hex'),
125
+ createdAt: now,
126
+ updatedAt: now,
127
+ expiresAt: new Date(now.getTime() + this.application.constants.EmailTokenExpiration),
128
+ };
129
+ // Use findOneAndUpdate with upsert to avoid duplicate key errors
130
+ const emailToken = await EmailTokenModel.findOneAndUpdate({
131
+ userId: userDoc._id,
132
+ email: userDoc.email,
133
+ type: type,
134
+ }, tokenData, {
135
+ upsert: true,
136
+ new: true,
137
+ session,
138
+ });
139
+ if (!emailToken) {
140
+ throw new suite_core_lib_1.TranslatableSuiteError(suite_core_lib_1.SuiteCoreStringKey.Error_FailedToCreateEmailToken);
141
+ }
142
+ return emailToken;
143
+ }
144
+ // Only create a new transaction if no session is provided
145
+ return await this.withTransaction(async (sess) => {
146
+ const now = new Date();
147
+ const tokenData = {
148
+ userId: userDoc._id,
149
+ type: type,
150
+ email: userDoc.email,
151
+ token: (0, crypto_1.randomBytes)(this.application.constants.EmailTokenLength).toString('hex'),
152
+ createdAt: now,
153
+ updatedAt: now,
154
+ expiresAt: new Date(now.getTime() + this.application.constants.EmailTokenExpiration),
155
+ };
156
+ // Use findOneAndUpdate with upsert to avoid duplicate key errors
157
+ const emailToken = await EmailTokenModel.findOneAndUpdate({
158
+ userId: userDoc._id,
159
+ email: userDoc.email,
160
+ type: type,
161
+ }, tokenData, {
162
+ upsert: true,
163
+ new: true,
164
+ session: sess,
165
+ });
166
+ if (!emailToken) {
167
+ throw new suite_core_lib_1.TranslatableSuiteError(suite_core_lib_1.SuiteCoreStringKey.Error_FailedToCreateEmailToken);
168
+ }
169
+ return emailToken;
170
+ }, undefined, {
171
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
172
+ retryAttempts: 2,
173
+ });
174
+ }
175
+ /**
176
+ * Create and send an email token to the user for email verification
177
+ * @param user The user to send the email token to
178
+ * @param type The type of email token to send
179
+ * @param session The session to use for the query
180
+ * @returns The email token document
181
+ */
182
+ async createAndSendEmailToken(user, type = suite_core_lib_1.EmailTokenType.AccountVerification, session, debug = false) {
183
+ const emailToken = await this.createEmailToken(user, type, session);
184
+ try {
185
+ await this.sendEmailToken(emailToken, session, debug);
186
+ }
187
+ catch (error) {
188
+ // keep parity with previous behavior: continue returning token even if email send fails
189
+ }
190
+ return emailToken;
191
+ }
192
+ /**
193
+ * Create and send an email token directly within an existing transaction
194
+ * @param user The user to send the email token to
195
+ * @param type The type of email token to send
196
+ * @param session The session to use for the query (required)
197
+ * @param debug Whether to enable debug logging
198
+ * @returns The email token document
199
+ */
200
+ async createAndSendEmailTokenDirect(user, type = suite_core_lib_1.EmailTokenType.AccountVerification, session, debug = false) {
201
+ const EmailTokenModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.EmailToken);
202
+ // Create token directly within the existing session using upsert
203
+ const now = new Date();
204
+ const tokenData = {
205
+ userId: user._id,
206
+ type: type,
207
+ email: user.email,
208
+ token: (0, crypto_1.randomBytes)(this.application.constants.EmailTokenLength).toString('hex'),
209
+ createdAt: now,
210
+ updatedAt: now,
211
+ expiresAt: new Date(now.getTime() + this.application.constants.EmailTokenExpiration),
212
+ };
213
+ // Use findOneAndUpdate with upsert to avoid duplicate key errors
214
+ const emailToken = await EmailTokenModel.findOneAndUpdate({
215
+ userId: user._id,
216
+ email: user.email,
217
+ type: type,
218
+ }, tokenData, {
219
+ upsert: true,
220
+ new: true,
221
+ session,
222
+ });
223
+ if (!emailToken) {
224
+ throw new suite_core_lib_1.TranslatableSuiteError(suite_core_lib_1.SuiteCoreStringKey.Error_FailedToCreateEmailToken);
225
+ }
226
+ try {
227
+ await this.sendEmailToken(emailToken, session, debug);
228
+ }
229
+ catch (error) {
230
+ // Ignore email send errors in direct token creation
231
+ }
232
+ return emailToken;
233
+ }
234
+ /**
235
+ * Send an email token to the user for email verification
236
+ * @param emailToken The email token to send
237
+ * @param session The session to use for the query
238
+ * @returns void
239
+ */
240
+ async sendEmailToken(emailToken, session, debug = false) {
241
+ if (this.disableEmailSend) {
242
+ (0, utils_1.debugLog)(debug, 'log', 'Email sending disabled for testing');
243
+ // Still update lastSent and expiration to keep token valid during tests
244
+ emailToken.lastSent = new Date();
245
+ emailToken.expiresAt = new Date(Date.now() + this.application.constants.EmailTokenExpiration);
246
+ await emailToken.save({ session });
247
+ return;
248
+ }
249
+ if (emailToken.lastSent &&
250
+ emailToken.lastSent.getTime() +
251
+ this.application.constants.EmailTokenResendInterval >
252
+ Date.now()) {
253
+ throw new suite_core_lib_1.EmailTokenSentTooRecentlyError(emailToken.lastSent);
254
+ }
255
+ let subjectString;
256
+ let bodyString;
257
+ let url;
258
+ switch (emailToken.type) {
259
+ case suite_core_lib_1.EmailTokenType.AccountVerification:
260
+ subjectString = suite_core_lib_1.SuiteCoreStringKey.Email_ConfirmationSubjectTemplate;
261
+ bodyString = suite_core_lib_1.SuiteCoreStringKey.Email_ConfirmationBody;
262
+ url = `${this.serverUrl}/verify-email?token=${emailToken.token}`;
263
+ break;
264
+ case suite_core_lib_1.EmailTokenType.PasswordReset:
265
+ subjectString = suite_core_lib_1.SuiteCoreStringKey.Email_ResetPasswordSubjectTemplate;
266
+ bodyString = suite_core_lib_1.SuiteCoreStringKey.Email_ResetPasswordBody;
267
+ url = `${this.serverUrl}/forgot-password?token=${emailToken.token}`;
268
+ break;
269
+ case suite_core_lib_1.EmailTokenType.LoginRequest:
270
+ subjectString = suite_core_lib_1.SuiteCoreStringKey.Email_LoginRequestSubjectTemplate;
271
+ bodyString = suite_core_lib_1.SuiteCoreStringKey.Email_LoginRequestBody;
272
+ url = `${this.serverUrl}/challenge?token=${emailToken.token}`;
273
+ break;
274
+ case suite_core_lib_1.EmailTokenType.MnemonicRecoveryRequest:
275
+ case suite_core_lib_1.EmailTokenType.PrivateKeyRequest:
276
+ default:
277
+ throw new Error('Invalid email token type');
278
+ }
279
+ const emailSubject = (0, suite_core_lib_1.getSuiteCoreTranslation)(subjectString);
280
+ const emailText = `${(0, suite_core_lib_1.getSuiteCoreTranslation)(bodyString)}\r\n\r\n${url}`;
281
+ const emailHtml = `<p>${(0, suite_core_lib_1.getSuiteCoreTranslation)(bodyString)}</p><br/><p><a href="${url}">${url}</a></p><p>${(0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Email_LinkExpiresInTemplate)}</p>`;
282
+ try {
283
+ // Use the EmailService to send the email
284
+ await this.emailService.sendEmail(emailToken.email, emailSubject, emailText, emailHtml);
285
+ // update last sent/expiration
286
+ emailToken.lastSent = new Date();
287
+ emailToken.expiresAt = new Date(Date.now() + this.application.constants.EmailTokenExpiration);
288
+ await emailToken.save({ session });
289
+ }
290
+ catch (error) {
291
+ throw new suite_core_lib_1.EmailTokenFailedToSendError(emailToken.type);
292
+ }
293
+ }
294
+ /**
295
+ * Find a user by email or username and enforce account status checks
296
+ * @param email Optional email
297
+ * @param username Optional username
298
+ * @param session Optional mongoose session
299
+ * @throws UsernameOrEmailRequiredError if neither provided
300
+ * @throws InvalidCredentialsError if not found or deleted
301
+ * @throws AccountLockedError | PendingEmailVerificationError | AccountStatusError per status
302
+ */
303
+ async findUser(email, username, session) {
304
+ if (!email && !username) {
305
+ throw new suite_core_lib_1.UsernameOrEmailRequiredError();
306
+ }
307
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
308
+ let userDoc = null;
309
+ try {
310
+ if (email) {
311
+ userDoc = await UserModel.findOne({
312
+ email: email.toLowerCase(),
313
+ })
314
+ .session(session ?? null)
315
+ .exec();
316
+ }
317
+ else if (username) {
318
+ userDoc = await UserModel.findOne({ username })
319
+ .collation({ locale: 'en', strength: 2 })
320
+ .session(session ?? null)
321
+ .exec();
322
+ }
323
+ }
324
+ catch (error) {
325
+ // Database error in findUser - convert to InvalidCredentialsError for security
326
+ throw new suite_core_lib_1.InvalidCredentialsError();
327
+ }
328
+ if (!userDoc || userDoc.deletedAt) {
329
+ if (email) {
330
+ const engine = (0, ecies_lib_1.getEciesI18nEngine)();
331
+ throw new ecies_lib_1.InvalidEmailError(ecies_lib_1.InvalidEmailErrorType.Missing, engine);
332
+ }
333
+ throw new suite_core_lib_1.InvalidUsernameError();
334
+ }
335
+ switch (userDoc.accountStatus) {
336
+ case suite_core_lib_1.AccountStatus.Active:
337
+ break;
338
+ case suite_core_lib_1.AccountStatus.AdminLock:
339
+ throw new suite_core_lib_1.AccountLockedError();
340
+ case suite_core_lib_1.AccountStatus.PendingEmailVerification:
341
+ throw new suite_core_lib_1.PendingEmailVerificationError();
342
+ default:
343
+ throw new suite_core_lib_1.AccountStatusError(userDoc.accountStatus);
344
+ }
345
+ return userDoc;
346
+ }
347
+ /**
348
+ * Finds a user record by ID
349
+ * @param userId The user ID
350
+ * @param throwIfNotActive Whether to throw if the user is inactive
351
+ * @param session The active session, if present
352
+ * @returns The user document
353
+ */
354
+ async findUserById(userId, throwIfNotActive, session, select) {
355
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
356
+ const baseQuery = UserModel.findById(userId).session(session ?? null);
357
+ if (select) {
358
+ // Always include fields needed for status checks
359
+ const merged = this.ensureRequiredFieldsInProjection(select, [
360
+ 'deletedAt',
361
+ 'accountStatus',
362
+ ]);
363
+ baseQuery.select(merged);
364
+ }
365
+ const userDoc = (await baseQuery.exec());
366
+ if (!userDoc || userDoc.deletedAt) {
367
+ throw new suite_core_lib_1.UserNotFoundError();
368
+ }
369
+ if (throwIfNotActive) {
370
+ switch (userDoc.accountStatus) {
371
+ case suite_core_lib_1.AccountStatus.Active:
372
+ break;
373
+ case suite_core_lib_1.AccountStatus.AdminLock:
374
+ throw new suite_core_lib_1.AccountLockedError();
375
+ case suite_core_lib_1.AccountStatus.PendingEmailVerification:
376
+ throw new suite_core_lib_1.PendingEmailVerificationError();
377
+ default:
378
+ throw new suite_core_lib_1.AccountStatusError(userDoc.accountStatus);
379
+ }
380
+ }
381
+ return userDoc;
382
+ }
383
+ /**
384
+ * Ensure required fields are present in a projection for queries that rely on them.
385
+ * Supports string and object-style projections. For inclusion projections, adds fields.
386
+ * For exclusion projections, ensures required fields are not excluded.
387
+ */
388
+ ensureRequiredFieldsInProjection(select, required) {
389
+ if (typeof select === 'string') {
390
+ const parts = select
391
+ .split(/\s+/)
392
+ .map((s) => s.trim())
393
+ .filter(Boolean);
394
+ const exclusions = new Set(parts.filter((p) => p.startsWith('-')).map((p) => p.slice(1)));
395
+ // Remove exclusions on required fields
396
+ for (const r of required) {
397
+ exclusions.delete(r);
398
+ }
399
+ const cleaned = parts.filter((p) => !p.startsWith('-'));
400
+ for (const r of required) {
401
+ if (!cleaned.includes(r))
402
+ cleaned.push(r);
403
+ }
404
+ const result = [...cleaned, ...[...exclusions].map((r) => `-${r}`)];
405
+ return result.join(' ');
406
+ }
407
+ if (select && typeof select === 'object') {
408
+ const proj = { ...select };
409
+ const values = Object.values(proj);
410
+ const hasInclusions = values.some((v) => v === 1 || v === true);
411
+ if (hasInclusions) {
412
+ for (const r of required) {
413
+ proj[r] = 1;
414
+ }
415
+ }
416
+ else {
417
+ const keysToRemove = required.filter((r) => proj[r] === 0 || proj[r] === false || proj[r] === -1);
418
+ keysToRemove.forEach((key) => delete proj[key]);
419
+ }
420
+ return proj;
421
+ }
422
+ return select;
423
+ }
424
+ /**
425
+ * Fill in the default values to a user object
426
+ * @param newUser The user object to fill in
427
+ * @param createdBy The user ID of the user creating the new user
428
+ * @returns The filled in user
429
+ */
430
+ fillUserDefaults(newUser, createdBy, backupCodes, encryptedMnemonic, userId) {
431
+ return {
432
+ ...(userId ? { _id: userId } : {}),
433
+ timezone: 'UTC',
434
+ ...newUser,
435
+ email: newUser.email.toLowerCase(),
436
+ emailVerified: false,
437
+ accountStatus: suite_core_lib_1.AccountStatus.PendingEmailVerification,
438
+ duressPasswords: [],
439
+ siteLanguage: suite_core_lib_1.DefaultLanguageCode,
440
+ publicKey: '',
441
+ backupCodes,
442
+ mnemonicRecovery: encryptedMnemonic,
443
+ directChallenge: false,
444
+ createdAt: new Date(),
445
+ createdBy: createdBy,
446
+ updatedAt: new Date(),
447
+ updatedBy: createdBy,
448
+ };
449
+ }
450
+ /**
451
+ * Create a new user document from an IUser and unhashed password
452
+ * @param newUser The user object
453
+ * @returns The new user document
454
+ */
455
+ async makeUserDoc(newUser) {
456
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
457
+ const newUserDoc = new UserModel(newUser);
458
+ const validationError = newUserDoc.validateSync();
459
+ if (validationError) {
460
+ throw new mongoose_validation_1.MongooseValidationError(validationError.errors);
461
+ }
462
+ return newUserDoc;
463
+ }
464
+ /**
465
+ * Create a new user.
466
+ * Do not set createdBy to a new (non-existing) ObjectId unless you also set newUserId to it.
467
+ * If newUserId is not set, one will be generated.
468
+ * @param systemUser The system user performing the operation
469
+ * @param userData Username, email, password in a ICreateUserBasics object
470
+ * @param createdBy The user id of the user creating the user
471
+ * @param newUserId the user id of the new user object- usually the createdBy user id.
472
+ * @param session The session to use for the query
473
+ * @param debug Whether to log debug information
474
+ * @param password The password to use for the new user (optional, if not provided, mnemonic will be used)
475
+ * @returns The new user document
476
+ */
477
+ async newUser(systemUser, userData, createdBy, newUserId, session, debug = false, password) {
478
+ const _newUserId = newUserId ?? new mongoose_1.Types.ObjectId();
479
+ if (!this.application.constants.UsernameRegex.test(userData.username)) {
480
+ throw new suite_core_lib_1.InvalidUsernameError();
481
+ }
482
+ if (password && !this.application.constants.PasswordRegex.test(password)) {
483
+ throw new errors_1.InvalidNewPasswordError();
484
+ }
485
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
486
+ return await this.withTransaction(async (sess) => {
487
+ const existingEmail = await UserModel.findOne({
488
+ email: userData.email.toLowerCase(),
489
+ }).session(sess ?? null);
490
+ if (existingEmail) {
491
+ throw new suite_core_lib_1.EmailInUseError();
492
+ }
493
+ const existingUsername = await UserModel.findOne({
494
+ username: { $regex: new RegExp(`^${userData.username}$`, 'i') },
495
+ }).session(sess ?? null);
496
+ if (existingUsername) {
497
+ throw new suite_core_lib_1.UsernameInUseError();
498
+ }
499
+ let mnemonic;
500
+ let member;
501
+ while (!mnemonic || !member) {
502
+ try {
503
+ const { member: newMember, mnemonic: newMnemonic } = node_ecies_lib_1.Member.newMember(this.eciesService, ecies_lib_1.MemberType.User, userData.username, new ecies_lib_1.EmailString(userData.email), undefined, createdBy);
504
+ // make sure the new mnemonic is not already in the database
505
+ const mnemonicExists = await this.mnemonicService.mnemonicExists(newMnemonic, sess);
506
+ if (!mnemonicExists) {
507
+ member = newMember;
508
+ mnemonic = newMnemonic;
509
+ }
510
+ }
511
+ catch {
512
+ // If we fail to create a new member, we will retry until we succeed.
513
+ // This is to ensure that we do not end up with duplicate mnemonics.
514
+ (0, utils_1.debugLog)(debug, 'warn', 'Failed to create a new member, retrying...');
515
+ }
516
+ }
517
+ const backupCodes = backup_code_1.BackupCode.generateBackupCodes();
518
+ const encryptedBackupCodes = await backup_code_1.BackupCode.encryptBackupCodes(member, systemUser, backupCodes);
519
+ const encryptedMnemonic = member
520
+ .encryptData(Buffer.from(mnemonic.value ?? '', 'utf-8'))
521
+ .toString('hex');
522
+ const newUserDoc = new UserModel({
523
+ ...this.fillUserDefaults(userData, createdBy ?? _newUserId, encryptedBackupCodes, encryptedMnemonic, _newUserId),
524
+ publicKey: member.publicKey.toString('hex'),
525
+ });
526
+ const validationError = newUserDoc.validateSync();
527
+ if (validationError) {
528
+ throw new mongoose_validation_1.MongooseValidationError(validationError.errors);
529
+ }
530
+ // Always add HMAC-only mnemonic doc
531
+ const newMnemonicDoc = await this.mnemonicService.addMnemonic(mnemonic, sess);
532
+ if (newMnemonicDoc) {
533
+ newUserDoc.mnemonicId = newMnemonicDoc._id;
534
+ }
535
+ // If password provided, wrap the ECIES private key with the password (Option B)
536
+ if (password) {
537
+ const passwordSecure = new ecies_lib_1.SecureString(password);
538
+ try {
539
+ const priv = new ecies_lib_1.SecureBuffer(member.privateKey.value);
540
+ try {
541
+ const wrapped = this.keyWrappingService.wrapSecret(priv, passwordSecure);
542
+ newUserDoc.passwordWrappedPrivateKey = wrapped;
543
+ }
544
+ finally {
545
+ priv.dispose();
546
+ }
547
+ }
548
+ finally {
549
+ passwordSecure.dispose();
550
+ }
551
+ }
552
+ const savedUserDoc = await newUserDoc.save({ session: sess });
553
+ const memberRoleId = await this.roleService.getRoleIdByName(this.application.constants.MemberRole, sess);
554
+ if (!memberRoleId) {
555
+ throw new suite_core_lib_1.TranslatableSuiteError(suite_core_lib_1.SuiteCoreStringKey.Error_FailedToLookupRoleTemplate, {
556
+ ROLE: (0, suite_core_lib_1.getSuiteCoreTranslation)(suite_core_lib_1.SuiteCoreStringKey.Common_Member),
557
+ });
558
+ }
559
+ await this.roleService.addUserToRole(memberRoleId, savedUserDoc._id, _newUserId, sess);
560
+ return {
561
+ user: savedUserDoc,
562
+ mnemonic: mnemonic.value ?? '',
563
+ backupCodes: backupCodes.map((code) => code.value ?? ''),
564
+ ...(password ? { password } : {}),
565
+ };
566
+ }, session, {
567
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 10,
568
+ });
569
+ }
570
+ /**
571
+ * Get the backup codes for a user.
572
+ * Requires the user not be deleted or inactive
573
+ */
574
+ async getEncryptedUserBackupCodes(userId, session) {
575
+ const userWithCodes = await this.findUserById(userId, true, session, {
576
+ backupCodes: 1,
577
+ });
578
+ return userWithCodes.backupCodes;
579
+ }
580
+ /**
581
+ * Resets the given user's backup codes
582
+ * @param backupUser The user to generate codes for
583
+ * @param session The current session, if any
584
+ * @returns A promise of an array of backup codes
585
+ */
586
+ async resetUserBackupCodes(backupUser, systemUser, session) {
587
+ if (!backupUser.hasPrivateKey) {
588
+ throw new suite_core_lib_1.PrivateKeyRequiredError();
589
+ }
590
+ const backupCodes = backup_code_1.BackupCode.generateBackupCodes();
591
+ const encryptedBackupCodes = await backup_code_1.BackupCode.encryptBackupCodes(backupUser, systemUser, backupCodes);
592
+ const UserModel = model_registry_1.ModelRegistry.instance.get('User')?.model;
593
+ return await this.withTransaction(async (sess) => {
594
+ await UserModel.updateOne({ _id: backupUser.id }, { $set: { backupCodes: encryptedBackupCodes } }, { session: sess });
595
+ return backupCodes;
596
+ }, session, {
597
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
598
+ });
599
+ }
600
+ /**
601
+ * Recover a user's mnemonic from an encrypted mnemonic
602
+ * @param user The user whose mnemonic to recover
603
+ * @param encryptedMnemonic The encrypted mnemonic
604
+ * @returns The recovered mnemonic
605
+ */
606
+ recoverMnemonic(user, encryptedMnemonic) {
607
+ if (!encryptedMnemonic) {
608
+ throw new suite_core_lib_1.TranslatableSuiteHandleableError(suite_core_lib_1.SuiteCoreStringKey.MnemonicRecovery_Missing, undefined, undefined, {
609
+ statusCode: 400,
610
+ });
611
+ }
612
+ return new ecies_lib_1.SecureString(user.decryptData(Buffer.from(encryptedMnemonic, 'hex')).toString('utf-8'));
613
+ }
614
+ /**
615
+ * Make a Member from a user document and optional private key
616
+ * @param userDoc The user document
617
+ * @param privateKey Optional private key to load the wallet
618
+ * @param publicKey Optional public key to override the userDoc public key
619
+ * @param session The current session, if any
620
+ * @returns A promise containing the created Member
621
+ */
622
+ async makeUserFromUserDoc(userDoc, privateKey, publicKey, mnemonic, wallet, session) {
623
+ const memberType = await this.roleService.getMemberType(userDoc, session);
624
+ const user = new node_ecies_lib_1.Member(this.eciesService, memberType, userDoc.username, new ecies_lib_1.EmailString(userDoc.email), publicKey ?? Buffer.from(userDoc.publicKey, 'hex'), privateKey, wallet, userDoc._id, new Date(userDoc.createdAt), new Date(userDoc.updatedAt), userDoc.createdBy);
625
+ if ((privateKey?.originalLength ?? -1) > 0 && user.hasPrivateKey) {
626
+ user.loadWallet(mnemonic ?? this.recoverMnemonic(user, userDoc.mnemonicRecovery));
627
+ }
628
+ return user;
629
+ }
630
+ /**
631
+ * Challenges a given userDoc with a given mnemonic, returns a system and user Member
632
+ * @param userDoc The userDoc in question
633
+ * @param mnemonic The mnemonic to challenge against
634
+ * @returns A promise containing the user and system Members
635
+ * @throws InvalidCredentialsError if the challenge fails
636
+ * @throws AccountLockedError if the account is locked
637
+ * @throws PendingEmailVerificationError if the email is not verified
638
+ * @throws AccountStatusError if the account status is invalid
639
+ */
640
+ async challengeUserWithMnemonic(userDoc, mnemonic, session) {
641
+ try {
642
+ // Verify provided mnemonic corresponds to the stored mnemonic HMAC (no password required)
643
+ // This prevents any valid mnemonic from authenticating as another user.
644
+ const MnemonicModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.Mnemonic);
645
+ if (!userDoc.mnemonicId) {
646
+ throw new suite_core_lib_1.InvalidCredentialsError();
647
+ }
648
+ const mnemonicDoc = await MnemonicModel.findById(userDoc.mnemonicId)
649
+ .select('hmac')
650
+ .session(session ?? null)
651
+ .lean()
652
+ .exec();
653
+ if (!mnemonicDoc) {
654
+ throw new suite_core_lib_1.InvalidCredentialsError();
655
+ }
656
+ const computedHmac = this.mnemonicService.getMnemonicHmac(mnemonic);
657
+ console.log('Debug mnemonic auth:', {
658
+ userDocId: userDoc._id.toString(),
659
+ userDocEmail: userDoc.email,
660
+ userDocUsername: userDoc.username,
661
+ mnemonicDocId: mnemonicDoc._id?.toString(),
662
+ storedHmac: mnemonicDoc.hmac,
663
+ computedHmac,
664
+ mnemonicsMatch: computedHmac === mnemonicDoc.hmac,
665
+ });
666
+ if (computedHmac !== mnemonicDoc.hmac) {
667
+ throw new suite_core_lib_1.InvalidCredentialsError();
668
+ }
669
+ // Create a Member from the provided mnemonic to get the keys
670
+ const { wallet } = this.eciesService.walletAndSeedFromMnemonic(mnemonic);
671
+ const privateKey = wallet.getPrivateKey();
672
+ const publicKey = wallet.getPublicKey();
673
+ const publicKeyWithPrefix = Buffer.concat([
674
+ Buffer.from([this.application.constants.ECIES.PUBLIC_KEY_MAGIC]),
675
+ publicKey,
676
+ ]);
677
+ const userMember = await this.makeUserFromUserDoc(userDoc, new ecies_lib_1.SecureBuffer(privateKey), publicKeyWithPrefix, mnemonic, wallet, session);
678
+ // Verify the public key matches the stored userDoc public key
679
+ if (userMember.publicKey.toString('hex') !== userDoc.publicKey) {
680
+ throw new suite_core_lib_1.InvalidCredentialsError();
681
+ }
682
+ // Generate a nonce challenge to verify they can decrypt with their key
683
+ const adminMember = system_user_1.SystemUserService.getSystemUser(this.application.environment);
684
+ const nonce = (0, crypto_1.randomBytes)(32);
685
+ const signature = adminMember.sign(nonce);
686
+ const payload = Buffer.concat([nonce, signature]);
687
+ const encryptedPayload = userMember.encryptData(payload);
688
+ const decryptedPayload = userMember.decryptData(encryptedPayload);
689
+ // Verify the server's signature on the nonce
690
+ const decryptedNonce = decryptedPayload.subarray(0, 32);
691
+ const decryptedSignature = decryptedPayload.subarray(32);
692
+ const isSignatureValid = adminMember.verify(decryptedSignature, decryptedNonce);
693
+ if (!isSignatureValid || !nonce.equals(decryptedNonce)) {
694
+ throw new suite_core_lib_1.InvalidCredentialsError();
695
+ }
696
+ return {
697
+ userMember,
698
+ adminMember: adminMember,
699
+ };
700
+ }
701
+ catch (error) {
702
+ if (error instanceof suite_core_lib_1.InvalidCredentialsError ||
703
+ error instanceof suite_core_lib_1.AccountLockedError ||
704
+ error instanceof suite_core_lib_1.PendingEmailVerificationError ||
705
+ error instanceof suite_core_lib_1.AccountStatusError) {
706
+ throw error;
707
+ }
708
+ throw new suite_core_lib_1.InvalidCredentialsError();
709
+ }
710
+ }
711
+ /**
712
+ * Validates a login challenge response
713
+ * @param challengeResponse The challenge response bytes in hex
714
+ * @param email The email address of the user
715
+ * @param username The username of the user
716
+ * @param session The mongo session for the query
717
+ * @returns A promise that resolves to the user document, user member, and system member
718
+ */
719
+ async loginWithChallengeResponse(challengeResponse, email, username, session) {
720
+ const challengeBuffer = Buffer.from(challengeResponse, 'hex');
721
+ // validate the expected challenge response length (8 + 32 + 64 = 104 bytes)
722
+ if (challengeBuffer.length !=
723
+ this.application.constants.DirectLoginChallengeLength) {
724
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
725
+ }
726
+ // disassemble the challengeResponse into time, nonce, signature
727
+ const time = challengeBuffer.subarray(0, 8); // 16 hex characters
728
+ const nonce = challengeBuffer.subarray(8, 40); // 64 hex characters
729
+ const signature = challengeBuffer.subarray(40); // 65 * 2 hex characters
730
+ const timeMs = parseInt(time.toString('hex'), 16);
731
+ if (new Date().getTime() - timeMs >
732
+ this.application.constants.LoginChallengeExpiration) {
733
+ throw new suite_core_lib_1.LoginChallengeExpiredError();
734
+ }
735
+ const userDoc = await this.findUser(email, username, session);
736
+ if (!userDoc && email) {
737
+ const engine = (0, ecies_lib_1.getEciesI18nEngine)();
738
+ throw new ecies_lib_1.InvalidEmailError(ecies_lib_1.InvalidEmailErrorType.Missing, engine);
739
+ }
740
+ else if (!userDoc) {
741
+ throw new suite_core_lib_1.InvalidUsernameError();
742
+ }
743
+ // re-sign the time + nonce and check if the signature matches
744
+ const adminMember = system_user_1.SystemUserService.getSystemUser(this.application.environment);
745
+ const timeAndNonce = Buffer.concat([time, nonce]);
746
+ const expectedSignature = adminMember.sign(timeAndNonce);
747
+ if (expectedSignature.toString('hex') !== signature.toString('hex')) {
748
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
749
+ }
750
+ const userMember = await this.makeUserFromUserDoc(userDoc, undefined, undefined, undefined, undefined, session);
751
+ return {
752
+ userDoc,
753
+ userMember,
754
+ adminMember: adminMember,
755
+ };
756
+ }
757
+ /**
758
+ * Authenticate a user with client-verified challenge (skips server-side challenge)
759
+ * @returns The authenticated user document.
760
+ */
761
+ async loginWithClientVerifiedChallenge(usernameOrEmail, mnemonic, session) {
762
+ const UserModel = this.application.getModel(base_model_name_1.BaseModelName.User);
763
+ const userQuery = validator_1.default.isEmail(usernameOrEmail)
764
+ ? UserModel.findOne({ email: usernameOrEmail.toLowerCase() }).select('_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey')
765
+ : UserModel.findOne({ username: usernameOrEmail })
766
+ .collation({ locale: 'en', strength: 2 })
767
+ .select('_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey');
768
+ const userDoc = await userQuery.session(session ?? null);
769
+ if (!userDoc || userDoc.deletedAt) {
770
+ throw new suite_core_lib_1.InvalidCredentialsError();
771
+ }
772
+ // Check account status
773
+ switch (userDoc.accountStatus) {
774
+ case suite_core_lib_1.AccountStatus.Active:
775
+ break;
776
+ case suite_core_lib_1.AccountStatus.AdminLock:
777
+ throw new suite_core_lib_1.AccountLockedError();
778
+ case suite_core_lib_1.AccountStatus.PendingEmailVerification:
779
+ throw new suite_core_lib_1.PendingEmailVerificationError();
780
+ default:
781
+ throw new suite_core_lib_1.AccountStatusError(userDoc.accountStatus);
782
+ }
783
+ // Verify mnemonic matches user (simplified verification)
784
+ try {
785
+ const MnemonicModel = this.application.getModel(base_model_name_1.BaseModelName.Mnemonic);
786
+ if (!userDoc.mnemonicId) {
787
+ throw new suite_core_lib_1.InvalidCredentialsError();
788
+ }
789
+ const mnemonicDoc = await MnemonicModel.findById(userDoc.mnemonicId)
790
+ .select('hmac')
791
+ .session(session ?? null)
792
+ .lean()
793
+ .exec();
794
+ if (!mnemonicDoc) {
795
+ throw new suite_core_lib_1.InvalidCredentialsError();
796
+ }
797
+ const computedHmac = this.mnemonicService.getMnemonicHmac(mnemonic);
798
+ if (computedHmac !== mnemonicDoc.hmac) {
799
+ throw new suite_core_lib_1.InvalidCredentialsError();
800
+ }
801
+ // Create Member from mnemonic
802
+ const { wallet } = this.eciesService.walletAndSeedFromMnemonic(mnemonic);
803
+ const privateKey = wallet.getPrivateKey();
804
+ const publicKey = wallet.getPublicKey();
805
+ const publicKeyWithPrefix = Buffer.concat([
806
+ Buffer.from([this.application.constants.ECIES.PUBLIC_KEY_MAGIC]),
807
+ publicKey,
808
+ ]);
809
+ const userMember = await this.makeUserFromUserDoc(userDoc, new ecies_lib_1.SecureBuffer(privateKey), publicKeyWithPrefix, mnemonic, wallet, session);
810
+ // Verify public key matches
811
+ if (userMember.publicKey.toString('hex') !== userDoc.publicKey) {
812
+ throw new suite_core_lib_1.InvalidCredentialsError();
813
+ }
814
+ const adminMember = system_user_1.SystemUserService.getSystemUser(this.application.environment);
815
+ return {
816
+ userMember,
817
+ adminMember,
818
+ userDoc,
819
+ };
820
+ }
821
+ catch (error) {
822
+ if (error instanceof suite_core_lib_1.InvalidCredentialsError ||
823
+ error instanceof suite_core_lib_1.AccountLockedError ||
824
+ error instanceof suite_core_lib_1.PendingEmailVerificationError ||
825
+ error instanceof suite_core_lib_1.AccountStatusError) {
826
+ throw error;
827
+ }
828
+ throw new suite_core_lib_1.InvalidCredentialsError();
829
+ }
830
+ }
831
+ /**
832
+ * Authenticate a user with their mnemonic.
833
+ * @returns The authenticated user document.
834
+ */
835
+ async loginWithMnemonic(usernameOrEmail, mnemonic, session) {
836
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
837
+ const userQuery = validator_1.default.isEmail(usernameOrEmail)
838
+ ? UserModel.findOne({ email: usernameOrEmail.toLowerCase() }).select('_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey')
839
+ : UserModel.findOne({ username: usernameOrEmail })
840
+ .collation({ locale: 'en', strength: 2 })
841
+ .select('_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey');
842
+ const userDoc = await userQuery.session(session ?? null);
843
+ if (!userDoc || userDoc.deletedAt) {
844
+ throw new suite_core_lib_1.InvalidCredentialsError();
845
+ }
846
+ // Check account status
847
+ switch (userDoc.accountStatus) {
848
+ case suite_core_lib_1.AccountStatus.Active:
849
+ break;
850
+ case suite_core_lib_1.AccountStatus.AdminLock:
851
+ throw new suite_core_lib_1.AccountLockedError();
852
+ case suite_core_lib_1.AccountStatus.PendingEmailVerification:
853
+ throw new suite_core_lib_1.PendingEmailVerificationError();
854
+ default:
855
+ throw new suite_core_lib_1.AccountStatusError(userDoc.accountStatus);
856
+ }
857
+ const challengeResponse = await this.challengeUserWithMnemonic(userDoc, mnemonic, session);
858
+ return { ...challengeResponse, userDoc };
859
+ }
860
+ /**
861
+ * Authenticate a user with their password (for key-wrapped accounts).
862
+ * @returns The authenticated user document.
863
+ */
864
+ async loginWithPassword(usernameOrEmail, password, session) {
865
+ const UserModel = this.application.getModel(base_model_name_1.BaseModelName.User);
866
+ const query = validator_1.default.isEmail(usernameOrEmail)
867
+ ? UserModel.findOne({ email: usernameOrEmail.toLowerCase() })
868
+ : UserModel.findOne({ username: usernameOrEmail }).collation({
869
+ locale: 'en',
870
+ strength: 2,
871
+ });
872
+ const userDoc = await query
873
+ .session(session ?? null)
874
+ .exec();
875
+ if (!userDoc || userDoc.deletedAt) {
876
+ throw new suite_core_lib_1.InvalidCredentialsError();
877
+ }
878
+ // Check account status
879
+ switch (userDoc.accountStatus) {
880
+ case suite_core_lib_1.AccountStatus.Active:
881
+ break;
882
+ case suite_core_lib_1.AccountStatus.AdminLock:
883
+ throw new suite_core_lib_1.AccountLockedError();
884
+ case suite_core_lib_1.AccountStatus.PendingEmailVerification:
885
+ throw new suite_core_lib_1.PendingEmailVerificationError();
886
+ default:
887
+ throw new suite_core_lib_1.AccountStatusError(userDoc.accountStatus);
888
+ }
889
+ // Check if user has password-based authentication set up (Option B requires passwordWrappedPrivateKey)
890
+ if (!userDoc.passwordWrappedPrivateKey || !userDoc.mnemonicId) {
891
+ throw new suite_core_lib_1.InvalidCredentialsError();
892
+ }
893
+ // Unwrap password-wrapped private key and complete challenge with possession of private key
894
+ const unwrapped = await this.keyWrappingService.unwrapSecretAsync(userDoc.passwordWrappedPrivateKey, password);
895
+ // Build user member with unwrapped private key to decrypt challenge
896
+ // Note: userMember now owns the unwrapped SecureBuffer, so we don't dispose it here
897
+ const userMember = await this.makeUserFromUserDoc(userDoc, unwrapped, undefined, undefined, undefined, session);
898
+ // Generate a nonce challenge signed by system
899
+ const adminMember = system_user_1.SystemUserService.getSystemUser(this.application.environment);
900
+ const nonce = (0, crypto_1.randomBytes)(32);
901
+ const signature = adminMember.sign(nonce);
902
+ const payload = Buffer.concat([nonce, signature]);
903
+ const encryptedPayload = userMember.encryptData(payload);
904
+ const decryptedPayload = userMember.decryptData(encryptedPayload);
905
+ const decryptedNonce = decryptedPayload.subarray(0, 32);
906
+ const decryptedSignature = decryptedPayload.subarray(32);
907
+ const isSignatureValid = adminMember.verify(decryptedSignature, decryptedNonce);
908
+ if (!isSignatureValid || !nonce.equals(decryptedNonce)) {
909
+ throw new suite_core_lib_1.InvalidCredentialsError();
910
+ }
911
+ return { userDoc, userMember, adminMember: adminMember };
912
+ }
913
+ /**
914
+ * Re-send a previously sent email token
915
+ * @param userId The user id
916
+ * @param session The session to use for the query
917
+ * @returns void
918
+ * @throws EmailTokenUsedOrInvalidError
919
+ */
920
+ async resendEmailToken(userId, type, session, debug = false) {
921
+ const EmailTokenModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.EmailToken);
922
+ return await this.withTransaction(async (sess) => {
923
+ // look up the most recent email token for a given user, then send it
924
+ const emailToken = await EmailTokenModel.findOne({
925
+ userId,
926
+ type,
927
+ expiresAt: { $gt: new Date() },
928
+ })
929
+ .session(sess ?? null)
930
+ .sort({ createdAt: -1 })
931
+ .limit(1);
932
+ if (!emailToken) {
933
+ throw new suite_core_lib_1.EmailTokenUsedOrInvalidError();
934
+ }
935
+ await this.sendEmailToken(emailToken, sess, debug);
936
+ }, session, {
937
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
938
+ });
939
+ }
940
+ /**
941
+ * Verify the email token and update the user's account status
942
+ * @param emailToken The email token to verify
943
+ * @param session The session to use for the query
944
+ * @returns void
945
+ * @throws EmailTokenUsedOrInvalidError
946
+ * @throws EmailTokenExpiredError
947
+ * @throws EmailVerifiedError
948
+ * @throws UserNotFoundError
949
+ */
950
+ async verifyAccountTokenAndComplete(emailToken, session) {
951
+ let alreadyVerified = false;
952
+ await this.withTransaction(async (sess) => {
953
+ const EmailTokenModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.EmailToken);
954
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
955
+ const token = await this.findEmailToken(emailToken, suite_core_lib_1.EmailTokenType.AccountVerification, sess);
956
+ if (!token) {
957
+ throw new suite_core_lib_1.EmailTokenUsedOrInvalidError();
958
+ }
959
+ if (token.expiresAt < new Date()) {
960
+ await EmailTokenModel.deleteOne({ _id: token._id }).session(sess ?? null);
961
+ throw new suite_core_lib_1.EmailTokenExpiredError();
962
+ }
963
+ const user = await UserModel.findById(token.userId).session(sess ?? null);
964
+ if (!user || user.deletedAt) {
965
+ throw new suite_core_lib_1.UserNotFoundError();
966
+ }
967
+ if (user.emailVerified) {
968
+ // Delete the token and mark to throw error after transaction commits
969
+ await EmailTokenModel.deleteOne({ _id: token._id }).session(sess ?? null);
970
+ alreadyVerified = true;
971
+ return;
972
+ }
973
+ // set user email to token email and mark as verified
974
+ user.email = token.email;
975
+ user.emailVerified = true;
976
+ user.accountStatus = suite_core_lib_1.AccountStatus.Active;
977
+ user.updatedBy = user._id;
978
+ await user.save({ session: sess });
979
+ // Delete the token after successful verification
980
+ await EmailTokenModel.deleteOne({ _id: token._id }).session(sess ?? null);
981
+ // add the user to the member role
982
+ const memberRoleId = await this.roleService.getRoleIdByName(this.application.constants.MemberRole, sess);
983
+ if (memberRoleId) {
984
+ await this.roleService.addUserToRole(memberRoleId, user._id, user._id, sess);
985
+ }
986
+ else {
987
+ throw new Error('Member role not found');
988
+ }
989
+ }, session, {
990
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
991
+ });
992
+ if (alreadyVerified) {
993
+ throw new suite_core_lib_1.EmailVerifiedError(409);
994
+ }
995
+ }
996
+ /**
997
+ * Validate the email token
998
+ * @param token The token to validate
999
+ * @param restrictType The type of email token to validate (or throw)
1000
+ * @param session The session to use for the query
1001
+ * @returns void
1002
+ * @throws EmailTokenUsedOrInvalidError
1003
+ */
1004
+ async validateEmailToken(token, restrictType, session) {
1005
+ return await this.withTransaction(async (ses) => {
1006
+ const EmailTokenModel = this.application.getModel(base_model_name_1.BaseModelName.EmailToken);
1007
+ const emailToken = await EmailTokenModel.findOne({
1008
+ token,
1009
+ ...(restrictType ? { type: suite_core_lib_1.EmailTokenType.PasswordReset } : {}),
1010
+ }).session(ses ?? null);
1011
+ if (!emailToken) {
1012
+ throw new suite_core_lib_1.EmailTokenUsedOrInvalidError();
1013
+ }
1014
+ else if (emailToken.expiresAt < new Date()) {
1015
+ await EmailTokenModel.deleteOne({ _id: emailToken._id }).session(ses ?? null);
1016
+ throw new suite_core_lib_1.EmailTokenExpiredError();
1017
+ }
1018
+ // nothing else to do here, token is valid
1019
+ }, session, {
1020
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1021
+ });
1022
+ }
1023
+ /**
1024
+ * Updates the user's language
1025
+ * @param userId - The ID of the user
1026
+ * @param newLanguage - The new language
1027
+ * @param session - The session to use for the query
1028
+ * @returns The updated user
1029
+ */
1030
+ async updateSiteLanguage(userId, newLanguage, session) {
1031
+ return await this.withTransaction(async (sess) => {
1032
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
1033
+ const userDoc = await UserModel.findByIdAndUpdate(new mongoose_1.Types.ObjectId(userId), {
1034
+ siteLanguage: newLanguage,
1035
+ }, { new: true }).session(sess ?? null);
1036
+ if (!userDoc) {
1037
+ throw new suite_core_lib_1.UserNotFoundError();
1038
+ }
1039
+ const roles = await this.roleService.getUserRoles(userDoc._id);
1040
+ const tokenRoles = this.roleService.rolesToTokenRoles(roles);
1041
+ return request_user_1.RequestUserService.makeRequestUserDTO(userDoc, tokenRoles);
1042
+ }, session, {
1043
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1044
+ });
1045
+ }
1046
+ /**
1047
+ * Changes the user's password by re-wrapping their master key
1048
+ * @param userId - The ID of the user
1049
+ * @param currentPassword - The current password
1050
+ * @param newPassword - The new password
1051
+ * @param session - The session to use for the query
1052
+ * @returns void
1053
+ */
1054
+ async changePassword(userId, currentPassword, newPassword, session) {
1055
+ return await this.withTransaction(async (sess) => {
1056
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
1057
+ const userDoc = await UserModel.findById(userId).session(sess ?? null);
1058
+ if (!userDoc || !userDoc.passwordWrappedPrivateKey) {
1059
+ throw new suite_core_lib_1.UserNotFoundError();
1060
+ }
1061
+ if (!ecies_lib_1.Constants.PasswordRegex.test(newPassword)) {
1062
+ throw new errors_1.InvalidNewPasswordError();
1063
+ }
1064
+ const currentPasswordSecure = new ecies_lib_1.SecureString(currentPassword);
1065
+ const newPasswordSecure = new ecies_lib_1.SecureString(newPassword);
1066
+ try {
1067
+ // Unwrap existing private key and rewrap under new password
1068
+ const priv = this.keyWrappingService.unwrapSecret(userDoc.passwordWrappedPrivateKey, currentPasswordSecure);
1069
+ try {
1070
+ const wrapped = this.keyWrappingService.wrapSecret(priv, newPasswordSecure);
1071
+ userDoc.passwordWrappedPrivateKey = wrapped;
1072
+ await userDoc.save({ session: sess });
1073
+ }
1074
+ finally {
1075
+ priv.dispose();
1076
+ }
1077
+ }
1078
+ catch (error) {
1079
+ // Re-throw original error so controller can map it properly
1080
+ // Re-throw original error so controller can map it properly
1081
+ throw error;
1082
+ }
1083
+ finally {
1084
+ currentPasswordSecure.dispose();
1085
+ newPasswordSecure.dispose();
1086
+ }
1087
+ }, session, {
1088
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1089
+ });
1090
+ }
1091
+ /**
1092
+ * Retrieve an email token by its token string and type
1093
+ * @param token - The token string
1094
+ * @param type - The type of the email token
1095
+ * @param session - The session to use for the query
1096
+ * @returns The email token document or null if not found
1097
+ */
1098
+ async findEmailToken(token, type, session) {
1099
+ const EmailTokenModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.EmailToken);
1100
+ return await EmailTokenModel.findOne({
1101
+ token: token.toLowerCase().trim(),
1102
+ ...(type ? { type } : {}),
1103
+ expiresAt: { $gt: new Date() },
1104
+ }).session(session ?? null);
1105
+ }
1106
+ /**
1107
+ * Verify email token is valid
1108
+ * @param token - The email token
1109
+ * @param session - The session to use for the query
1110
+ * @returns void
1111
+ */
1112
+ async verifyEmailToken(token, type, session) {
1113
+ return await this.withTransaction(async (sess) => {
1114
+ // Find and validate the token
1115
+ const emailToken = await this.findEmailToken(token, type, sess);
1116
+ if (!emailToken) {
1117
+ throw new suite_core_lib_1.EmailTokenUsedOrInvalidError();
1118
+ }
1119
+ }, session, {
1120
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1121
+ });
1122
+ }
1123
+ /**
1124
+ * Reset password using email token
1125
+ * @param token - The email token
1126
+ * @param newPassword - The new password
1127
+ * @param session - The session to use for the query
1128
+ * @returns void
1129
+ */
1130
+ async resetPasswordWithToken(token, newPassword, credential, // either mnemonic or current password; required
1131
+ session) {
1132
+ if (!ecies_lib_1.Constants.PasswordRegex.test(newPassword)) {
1133
+ throw new errors_1.InvalidNewPasswordError();
1134
+ }
1135
+ if (!credential) {
1136
+ throw new suite_core_lib_1.EmailTokenUsedOrInvalidError();
1137
+ }
1138
+ return await this.withTransaction(async (sess) => {
1139
+ const EmailTokenModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.EmailToken);
1140
+ const UserModel = model_registry_1.ModelRegistry.instance.getTypedModel(base_model_name_1.BaseModelName.User);
1141
+ // Find and validate the token
1142
+ const emailToken = await this.findEmailToken(token, suite_core_lib_1.EmailTokenType.PasswordReset, sess);
1143
+ if (!emailToken) {
1144
+ throw new suite_core_lib_1.EmailTokenUsedOrInvalidError();
1145
+ }
1146
+ // Find the user
1147
+ const userDoc = await UserModel.findById(emailToken.userId).session(sess ?? null);
1148
+ if (!userDoc) {
1149
+ throw new suite_core_lib_1.UserNotFoundError();
1150
+ }
1151
+ // Update password-wrapped secrets based on credential type (Option B)
1152
+ const newPasswordSecure = new ecies_lib_1.SecureString(newPassword);
1153
+ try {
1154
+ if (ecies_lib_1.Constants.MnemonicRegex.test(credential)) {
1155
+ // Credential is mnemonic: verify it belongs to this user via public key
1156
+ const providedMnemonic = new ecies_lib_1.SecureString(credential);
1157
+ try {
1158
+ const { wallet } = this.eciesService.walletAndSeedFromMnemonic(providedMnemonic);
1159
+ const pub = Buffer.concat([
1160
+ Buffer.from([
1161
+ this.application.constants.ECIES.PUBLIC_KEY_MAGIC,
1162
+ ]),
1163
+ wallet.getPublicKey(),
1164
+ ]);
1165
+ if (pub.toString('hex') !== userDoc.publicKey) {
1166
+ throw new suite_core_lib_1.InvalidCredentialsError();
1167
+ }
1168
+ // Derive private key from mnemonic and wrap it with new password
1169
+ const privateKey = wallet.getPrivateKey();
1170
+ const priv = new ecies_lib_1.SecureBuffer(privateKey);
1171
+ try {
1172
+ const wrappedPriv = this.keyWrappingService.wrapSecret(priv, newPasswordSecure);
1173
+ userDoc.passwordWrappedPrivateKey = wrappedPriv;
1174
+ await userDoc.save({ session: sess });
1175
+ }
1176
+ finally {
1177
+ priv.dispose();
1178
+ }
1179
+ }
1180
+ finally {
1181
+ providedMnemonic.dispose();
1182
+ }
1183
+ }
1184
+ else {
1185
+ // Credential is current password: unwrap existing master key
1186
+ if (!userDoc.passwordWrappedPrivateKey) {
1187
+ throw new suite_core_lib_1.InvalidCredentialsError();
1188
+ }
1189
+ const privateKeyBuf = await this.keyWrappingService.unwrapSecretAsync(userDoc.passwordWrappedPrivateKey, credential);
1190
+ try {
1191
+ // Re-wrap the existing private key under the new password
1192
+ const wrappedPriv = this.keyWrappingService.wrapSecret(privateKeyBuf, newPasswordSecure);
1193
+ userDoc.passwordWrappedPrivateKey = wrappedPriv;
1194
+ await userDoc.save({ session: sess });
1195
+ }
1196
+ finally {
1197
+ privateKeyBuf.dispose();
1198
+ }
1199
+ }
1200
+ // Delete the used token
1201
+ await EmailTokenModel.deleteOne({ _id: emailToken._id }).session(sess ?? null);
1202
+ // Dispose temporary master key
1203
+ }
1204
+ finally {
1205
+ newPasswordSecure.dispose();
1206
+ }
1207
+ }, session, {
1208
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1209
+ });
1210
+ }
1211
+ /**
1212
+ * Generate a login challenge for the client to sign
1213
+ * @returns The login challenge in hex
1214
+ */
1215
+ generateDirectLoginChallenge() {
1216
+ const adminMember = system_user_1.SystemUserService.getSystemUser(this.application.environment);
1217
+ const time = Buffer.alloc(8);
1218
+ time.writeBigUInt64BE(BigInt(new Date().getTime()));
1219
+ const nonce = (0, crypto_1.randomBytes)(32);
1220
+ const signature = adminMember.sign(Buffer.concat([time, nonce]));
1221
+ return Buffer.concat([time, nonce, signature]).toString('hex');
1222
+ }
1223
+ /**
1224
+ * Verifies a direct login challenge response
1225
+ * @param serverSignedRequest The login challenge response in hex
1226
+ * @param session The mongoose session, if provided
1227
+ * @returns A promise with the user document and user member object
1228
+ */
1229
+ async verifyDirectLoginChallenge(serverSignedRequest, signature, username, email, session) {
1230
+ return await this.withTransaction(async (sess) => {
1231
+ // serverSignedRequest is:
1232
+ // time (8) +
1233
+ // nonce (32) +
1234
+ // server signature (64) +
1235
+ // signature (64)
1236
+ if (serverSignedRequest.length <
1237
+ (8 + 32 + this.application.constants.ECIES.SIGNATURE_SIZE) * 2) {
1238
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
1239
+ }
1240
+ // get signed request into a buffer
1241
+ const requestBuffer = Buffer.from(serverSignedRequest, 'hex');
1242
+ // start tracking offset
1243
+ let offset = 0;
1244
+ // get the time
1245
+ const time = requestBuffer.subarray(offset, 8);
1246
+ offset += 8;
1247
+ // get the nonce
1248
+ const nonce = requestBuffer.subarray(offset, 40);
1249
+ offset += 32;
1250
+ // get the server signature
1251
+ const serverSignature = requestBuffer.subarray(offset, this.application.constants.ECIES.SIGNATURE_SIZE + 40);
1252
+ offset += this.application.constants.ECIES.SIGNATURE_SIZE;
1253
+ const signedDataLength = offset;
1254
+ if (offset !== requestBuffer.length) {
1255
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
1256
+ }
1257
+ // validate time is within acceptable range
1258
+ const timeMs = time.readBigUInt64BE();
1259
+ if (new Date().getTime() - Number(timeMs) >
1260
+ this.application.constants.LoginChallengeExpiration) {
1261
+ throw new suite_core_lib_1.LoginChallengeExpiredError();
1262
+ }
1263
+ // validate the server's signature on the time + nonce portion
1264
+ const adminMember = system_user_1.SystemUserService.getSystemUser(this.application.environment);
1265
+ if (!adminMember.verify(serverSignature, Buffer.concat([time, nonce]))) {
1266
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
1267
+ }
1268
+ // locate the user by email or username
1269
+ const userDoc = await this.findUser(email, username, sess);
1270
+ if (!userDoc) {
1271
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
1272
+ }
1273
+ // get the user's member object
1274
+ const user = await this.makeUserFromUserDoc(userDoc, undefined, undefined, undefined, undefined, sess);
1275
+ // get the signed portion of the response
1276
+ const signedData = requestBuffer.subarray(0, signedDataLength);
1277
+ // verify the user's signature on the signed portion
1278
+ if (!user.verify(Buffer.from(signature, 'hex'), signedData)) {
1279
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
1280
+ }
1281
+ if (userDoc.directChallenge !== true) {
1282
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
1283
+ }
1284
+ // if the user is valid, try to use the token (prevents replay attacks)
1285
+ await direct_login_token_1.DirectLoginTokenService.useToken(this.application, userDoc._id, nonce.toString('hex'));
1286
+ // if successful, update lastLogin
1287
+ await this.updateLastLogin(userDoc._id);
1288
+ // return the user document and member object
1289
+ return { userDoc, userMember: user };
1290
+ }, session, { timeoutMs: this.application.environment.mongo.transactionTimeout });
1291
+ }
1292
+ /**
1293
+ * Request a login link via email
1294
+ * @param email Email address
1295
+ * @param username Username
1296
+ * @param session Existing session, if any
1297
+ * @returns void
1298
+ */
1299
+ async requestEmailLogin(email, username, session) {
1300
+ return this.withTransaction(async (sess) => {
1301
+ const userDoc = await this.findUser(email, username, sess);
1302
+ if (!userDoc) {
1303
+ return;
1304
+ }
1305
+ await this.createAndSendEmailToken(userDoc, suite_core_lib_1.EmailTokenType.LoginRequest, sess, this.application.environment.debug);
1306
+ }, session, {
1307
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
1308
+ });
1309
+ }
1310
+ /**
1311
+ * Validate an email login token challenge
1312
+ * @param token The token to challenge
1313
+ * @param signature The signature of the token by the user's private key
1314
+ * @param session The session to use for the query
1315
+ * @returns The user document if the challenge is valid
1316
+ */
1317
+ async validateEmailLoginTokenChallenge(token, signature, session) {
1318
+ return this.withTransaction(async (sess) => {
1319
+ const emailToken = await this.findEmailToken(token, suite_core_lib_1.EmailTokenType.LoginRequest, sess);
1320
+ if (!emailToken) {
1321
+ throw new suite_core_lib_1.EmailTokenUsedOrInvalidError();
1322
+ }
1323
+ const userDoc = await this.findUser(emailToken.email, undefined, sess);
1324
+ if (!userDoc) {
1325
+ throw new suite_core_lib_1.UserNotFoundError();
1326
+ }
1327
+ const user = await this.makeUserFromUserDoc(userDoc, undefined, undefined, undefined, undefined, sess);
1328
+ const result = user.verify(Buffer.from(signature, 'hex'), Buffer.from(token, 'hex'));
1329
+ if (!result) {
1330
+ throw new suite_core_lib_1.InvalidChallengeResponseError();
1331
+ }
1332
+ await emailToken.deleteOne({ session: sess ?? null });
1333
+ await this.updateLastLogin(userDoc._id);
1334
+ return userDoc;
1335
+ }, session, {
1336
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
1337
+ });
1338
+ }
1339
+ /**
1340
+ * Updates the user's last login time atomically
1341
+ * @param userId - The ID of the user
1342
+ * @returns void
1343
+ */
1344
+ async updateLastLogin(userId) {
1345
+ const UserModel = model_registry_1.ModelRegistry.instance.get('User')?.model;
1346
+ try {
1347
+ // Check if the database connection is still open
1348
+ const connection = this.application.db.connection;
1349
+ if (connection.readyState !== 1) {
1350
+ // Connection is not open (0 = disconnected, 1 = connected, 2 = connecting, 3 = disconnecting)
1351
+ return; // Silently return if connection is not available
1352
+ }
1353
+ // Use atomic update to avoid conflicts and ensure we only update lastLogin
1354
+ // Use a separate session to avoid interfering with any ongoing transactions
1355
+ await UserModel.updateOne({ _id: userId }, {
1356
+ $set: { lastLogin: new Date() },
1357
+ $setOnInsert: {}, // Prevent any unintended document creation
1358
+ }, {
1359
+ upsert: false, // Never create a new document
1360
+ runValidators: false, // Skip validation for performance since we're only updating lastLogin
1361
+ // Don't use any session to avoid transaction conflicts
1362
+ });
1363
+ }
1364
+ catch (error) {
1365
+ // Check if the error is due to client being closed
1366
+ if (error instanceof Error &&
1367
+ (error.message.includes('client was closed') ||
1368
+ error.message.includes('MongoClientClosedError') ||
1369
+ error.name === 'MongoClientClosedError')) {
1370
+ // This is expected during shutdown, don't log it as an error
1371
+ return;
1372
+ }
1373
+ // If this fails, it's not critical for login functionality. Ignore and move on.
1374
+ }
1375
+ }
1376
+ }
1377
+ exports.UserService = UserService;
1378
+ //# sourceMappingURL=user.js.map