@digitaldefiance/node-express-suite 3.7.3 → 3.7.5

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 (882) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +4 -5
  3. package/src/__tests__/fixtures/{index.d.ts → index.ts} +0 -1
  4. package/src/__tests__/fixtures/model-mocks.mock.ts +164 -0
  5. package/src/__tests__/helpers/application.mock.ts +89 -0
  6. package/src/__tests__/helpers/{index.d.ts → index.ts} +0 -1
  7. package/src/__tests__/helpers/setup-test-env.ts +202 -0
  8. package/src/__tests__/{index.d.ts → index.ts} +0 -1
  9. package/src/application-base.ts +548 -0
  10. package/src/application-concrete.ts +62 -0
  11. package/src/application.ts +330 -0
  12. package/src/backup-code.ts +348 -0
  13. package/src/builders/application-builder.ts +147 -0
  14. package/src/builders/{index.d.ts → index.ts} +0 -1
  15. package/src/constants.ts +89 -0
  16. package/src/container/{index.d.ts → index.ts} +0 -1
  17. package/src/container/service-container.ts +85 -0
  18. package/src/container/service-definitions.ts +23 -0
  19. package/src/controllers/base.ts +512 -0
  20. package/src/controllers/{index.d.ts → index.ts} +0 -1
  21. package/src/controllers/user.ts +1734 -0
  22. package/src/database/database-initializer.ts +13 -0
  23. package/src/database/{index.d.ts → index.ts} +0 -1
  24. package/src/decorators/base-controller.ts +91 -0
  25. package/src/decorators/controller.ts +152 -0
  26. package/src/decorators/{index.d.ts → index.ts} +0 -1
  27. package/src/decorators/zod-validation.ts +64 -0
  28. package/src/defaults.ts +259 -0
  29. package/src/documents/base.ts +17 -0
  30. package/src/documents/email-token.ts +20 -0
  31. package/src/documents/{index.d.ts → index.ts} +0 -1
  32. package/src/documents/mnemonic.ts +20 -0
  33. package/src/documents/role.ts +19 -0
  34. package/src/documents/used-direct-login-token.ts +18 -0
  35. package/src/documents/user-role.ts +20 -0
  36. package/src/documents/user.ts +20 -0
  37. package/src/enumerations/base-model-name.ts +47 -0
  38. package/src/enumerations/{index.d.ts → index.ts} +0 -1
  39. package/src/enumerations/length-encoding-type.ts +16 -0
  40. package/src/enumerations/schema-collection.ts +39 -0
  41. package/src/enumerations/symmetric-error-type.ts +13 -0
  42. package/src/environment.ts +859 -0
  43. package/src/errors/express-validation.ts +38 -0
  44. package/src/errors/{index.d.ts → index.ts} +0 -1
  45. package/src/errors/invalid-backup-code-version.ts +30 -0
  46. package/src/errors/invalid-jwt-token.ts +24 -0
  47. package/src/errors/invalid-model.ts +24 -0
  48. package/src/errors/invalid-new-password.ts +33 -0
  49. package/src/errors/invalid-password.ts +28 -0
  50. package/src/errors/missing-validated-data.ts +55 -0
  51. package/src/errors/mnemonic-or-password-required.ts +26 -0
  52. package/src/errors/model-not-registered.ts +24 -0
  53. package/src/errors/mongoose-validation.ts +56 -0
  54. package/src/errors/symmetric.ts +53 -0
  55. package/src/errors/token-expired.ts +24 -0
  56. package/src/get-language.ts +64 -0
  57. package/src/get-timezone.ts +76 -0
  58. package/src/{index.d.ts → index.ts} +44 -2
  59. package/src/interfaces/api-error-response.ts +15 -0
  60. package/src/interfaces/api-express-validation-error-response.ts +17 -0
  61. package/src/interfaces/api-message-response.ts +12 -0
  62. package/src/interfaces/api-mongo-validation-error-response.ts +17 -0
  63. package/src/interfaces/api-responses/backup-codes-response.ts +15 -0
  64. package/src/interfaces/api-responses/challenge-response.ts +17 -0
  65. package/src/interfaces/api-responses/code-count-response.ts +12 -0
  66. package/src/interfaces/api-responses/{index.d.ts → index.ts} +0 -1
  67. package/src/interfaces/api-responses/login-response.ts +18 -0
  68. package/src/interfaces/api-responses/mnemonic-response.ts +15 -0
  69. package/src/interfaces/api-responses/registration-response.ts +17 -0
  70. package/src/interfaces/api-responses/request-user-response.ts +16 -0
  71. package/src/interfaces/api-responses/user-settings-response.ts +19 -0
  72. package/src/interfaces/application.ts +40 -0
  73. package/src/interfaces/backend-objects/email-token.ts +18 -0
  74. package/src/interfaces/backend-objects/{index.d.ts → index.ts} +0 -1
  75. package/src/interfaces/backend-objects/request-user.ts +19 -0
  76. package/src/interfaces/backend-objects/role.ts +18 -0
  77. package/src/interfaces/backend-objects/user.ts +18 -0
  78. package/src/interfaces/checksum-config.ts +15 -0
  79. package/src/interfaces/checksum-consts.ts +23 -0
  80. package/src/interfaces/constants.ts +114 -0
  81. package/src/interfaces/controller-config.ts +54 -0
  82. package/src/interfaces/create-user-basics.ts +24 -0
  83. package/src/interfaces/csp-config.ts +32 -0
  84. package/src/interfaces/csp-definition.ts +71 -0
  85. package/src/interfaces/db-init-result.ts +17 -0
  86. package/src/interfaces/deep-partial.ts +14 -0
  87. package/src/interfaces/discriminator-collections.ts +21 -0
  88. package/src/interfaces/email-service.ts +26 -0
  89. package/src/interfaces/environment-mongo.ts +86 -0
  90. package/src/interfaces/environment.ts +191 -0
  91. package/src/interfaces/failable-result.ts +20 -0
  92. package/src/interfaces/fec-consts.ts +14 -0
  93. package/src/interfaces/flexible-csp.ts +35 -0
  94. package/src/interfaces/handleable-error-options.ts +19 -0
  95. package/src/interfaces/{index.d.ts → index.ts} +0 -1
  96. package/src/interfaces/jwt-consts.ts +33 -0
  97. package/src/interfaces/jwt-sign-response.ts +31 -0
  98. package/src/interfaces/models/email-token.ts +13 -0
  99. package/src/interfaces/models/{index.d.ts → index.ts} +0 -1
  100. package/src/interfaces/models/mnemonic.ts +14 -0
  101. package/src/interfaces/models/role.ts +13 -0
  102. package/src/interfaces/models/token-role.ts +23 -0
  103. package/src/interfaces/models/used-direct-login-token.ts +21 -0
  104. package/src/interfaces/models/user-role.ts +23 -0
  105. package/src/interfaces/models/user.ts +30 -0
  106. package/src/interfaces/mongo-errors.ts +14 -0
  107. package/src/interfaces/request-user.ts +80 -0
  108. package/src/interfaces/required-string-keys.ts +33 -0
  109. package/src/interfaces/schema.ts +43 -0
  110. package/src/interfaces/server-init-result.ts +48 -0
  111. package/src/interfaces/status-code-response.ts +20 -0
  112. package/src/interfaces/symmetric-encryption-results.d.ts.map +1 -1
  113. package/src/interfaces/symmetric-encryption-results.js.map +1 -1
  114. package/src/interfaces/symmetric-encryption-results.ts +15 -0
  115. package/src/interfaces/test-environment.ts +23 -0
  116. package/src/interfaces/token-response.ts +16 -0
  117. package/src/middleware-utils.ts +138 -0
  118. package/src/middlewares/authenticate-crypto.ts +237 -0
  119. package/src/middlewares/authenticate-token.ts +165 -0
  120. package/src/middlewares/cleanup-crypto.ts +47 -0
  121. package/src/middlewares/{index.d.ts → index.ts} +0 -1
  122. package/src/middlewares/set-global-context-language.ts +38 -0
  123. package/src/model-registry.ts +142 -0
  124. package/src/models/email-token.ts +49 -0
  125. package/src/models/{index.d.ts → index.ts} +0 -1
  126. package/src/models/mnemonic.ts +42 -0
  127. package/src/models/role.ts +38 -0
  128. package/src/models/used-direct-login-token.ts +49 -0
  129. package/src/models/user-role.ts +40 -0
  130. package/src/models/user.ts +42 -0
  131. package/src/pipeline/{index.d.ts → index.ts} +0 -1
  132. package/src/pipeline/pipeline-builder.ts +27 -0
  133. package/src/plugins/{index.d.ts → index.ts} +0 -1
  134. package/src/plugins/plugin-interface.ts +19 -0
  135. package/src/plugins/plugin-manager.ts +53 -0
  136. package/src/registry/email-service-registry.ts +76 -0
  137. package/src/registry/{index.d.ts → index.ts} +0 -1
  138. package/src/responses/{index.d.ts → index.ts} +0 -1
  139. package/src/responses/response-builder.ts +166 -0
  140. package/src/routers/api.ts +233 -0
  141. package/src/routers/app.ts +395 -0
  142. package/src/routers/base.ts +34 -0
  143. package/src/routers/{index.d.ts → index.ts} +0 -1
  144. package/src/routers/router-config.ts +34 -0
  145. package/src/routing/index.ts +1 -0
  146. package/src/routing/route-builder.ts +214 -0
  147. package/src/schemas/email-token.ts +112 -0
  148. package/src/schemas/{index.d.ts → index.ts} +0 -1
  149. package/src/schemas/mnemonic.ts +48 -0
  150. package/src/schemas/role.ts +153 -0
  151. package/src/schemas/schema.ts +185 -0
  152. package/src/schemas/used-direct-login-token.ts +58 -0
  153. package/src/schemas/user-role.ts +93 -0
  154. package/src/schemas/user.ts +244 -0
  155. package/src/services/backup-code.ts +327 -0
  156. package/src/services/base.ts +46 -0
  157. package/src/services/checksum.ts +189 -0
  158. package/src/services/database-initialization.ts +1653 -0
  159. package/src/services/db-init-cache.ts +28 -0
  160. package/src/services/direct-login-token.ts +83 -0
  161. package/src/services/dummy-email-service.ts +43 -0
  162. package/src/services/fec-usage-example.ts +123 -0
  163. package/src/services/fec.ts +399 -0
  164. package/src/services/{index.d.ts → index.ts} +0 -2
  165. package/src/services/jwt.ts +146 -0
  166. package/src/services/key-wrapping.ts +528 -0
  167. package/src/services/mnemonic.ts +174 -0
  168. package/src/services/request-user.ts +127 -0
  169. package/src/services/role.ts +417 -0
  170. package/src/services/symmetric.ts +164 -0
  171. package/src/services/system-user.ts +87 -0
  172. package/src/services/user.ts +2324 -0
  173. package/src/services/xor.ts +39 -0
  174. package/src/testing.ts +9 -0
  175. package/src/transactions/{index.d.ts → index.ts} +0 -1
  176. package/src/transactions/transaction-manager.ts +63 -0
  177. package/src/types/app-config.ts +36 -0
  178. package/src/types/controller-config.ts +28 -0
  179. package/src/types/{environment-variables.d.ts → environment-variables.ts} +32 -5
  180. package/src/types/{index.d.ts → index.ts} +0 -1
  181. package/src/types/{mongoose-helpers.d.ts → mongoose-helpers.ts} +8 -2
  182. package/src/types/mongoose-override.d.ts +1 -0
  183. package/src/types/mongoose.d.ts +1 -0
  184. package/src/types.ts +189 -0
  185. package/src/utils.ts +1116 -0
  186. package/src/validation/{index.d.ts → index.ts} +0 -1
  187. package/src/validation/validation-builder.ts +155 -0
  188. package/src/__tests__/fixtures/index.d.ts.map +0 -1
  189. package/src/__tests__/fixtures/index.js +0 -5
  190. package/src/__tests__/fixtures/index.js.map +0 -1
  191. package/src/__tests__/fixtures/model-mocks.mock.d.ts +0 -12
  192. package/src/__tests__/fixtures/model-mocks.mock.d.ts.map +0 -1
  193. package/src/__tests__/fixtures/model-mocks.mock.js +0 -102
  194. package/src/__tests__/fixtures/model-mocks.mock.js.map +0 -1
  195. package/src/__tests__/helpers/application.mock.d.ts +0 -8
  196. package/src/__tests__/helpers/application.mock.d.ts.map +0 -1
  197. package/src/__tests__/helpers/application.mock.js +0 -77
  198. package/src/__tests__/helpers/application.mock.js.map +0 -1
  199. package/src/__tests__/helpers/index.d.ts.map +0 -1
  200. package/src/__tests__/helpers/index.js +0 -7
  201. package/src/__tests__/helpers/index.js.map +0 -1
  202. package/src/__tests__/helpers/setup-test-env.d.ts +0 -12
  203. package/src/__tests__/helpers/setup-test-env.d.ts.map +0 -1
  204. package/src/__tests__/helpers/setup-test-env.js +0 -121
  205. package/src/__tests__/helpers/setup-test-env.js.map +0 -1
  206. package/src/__tests__/index.d.ts.map +0 -1
  207. package/src/__tests__/index.js +0 -6
  208. package/src/__tests__/index.js.map +0 -1
  209. package/src/application-base.d.ts +0 -123
  210. package/src/application-base.d.ts.map +0 -1
  211. package/src/application-base.js +0 -359
  212. package/src/application-base.js.map +0 -1
  213. package/src/application-concrete.d.ts +0 -13
  214. package/src/application-concrete.d.ts.map +0 -1
  215. package/src/application-concrete.js +0 -21
  216. package/src/application-concrete.js.map +0 -1
  217. package/src/application.d.ts +0 -29
  218. package/src/application.d.ts.map +0 -1
  219. package/src/application.js +0 -167
  220. package/src/application.js.map +0 -1
  221. package/src/backup-code.d.ts +0 -67
  222. package/src/backup-code.d.ts.map +0 -1
  223. package/src/backup-code.js +0 -238
  224. package/src/backup-code.js.map +0 -1
  225. package/src/builders/application-builder.d.ts +0 -35
  226. package/src/builders/application-builder.d.ts.map +0 -1
  227. package/src/builders/application-builder.js +0 -64
  228. package/src/builders/application-builder.js.map +0 -1
  229. package/src/builders/index.d.ts.map +0 -1
  230. package/src/builders/index.js +0 -5
  231. package/src/builders/index.js.map +0 -1
  232. package/src/constants.d.ts +0 -16
  233. package/src/constants.d.ts.map +0 -1
  234. package/src/constants.js +0 -58
  235. package/src/constants.js.map +0 -1
  236. package/src/container/index.d.ts.map +0 -1
  237. package/src/container/index.js +0 -6
  238. package/src/container/index.js.map +0 -1
  239. package/src/container/service-container.d.ts +0 -11
  240. package/src/container/service-container.d.ts.map +0 -1
  241. package/src/container/service-container.js +0 -38
  242. package/src/container/service-container.js.map +0 -1
  243. package/src/container/service-definitions.d.ts +0 -11
  244. package/src/container/service-definitions.d.ts.map +0 -1
  245. package/src/container/service-definitions.js +0 -13
  246. package/src/container/service-definitions.js.map +0 -1
  247. package/src/controllers/base.d.ts +0 -67
  248. package/src/controllers/base.d.ts.map +0 -1
  249. package/src/controllers/base.js +0 -305
  250. package/src/controllers/base.js.map +0 -1
  251. package/src/controllers/index.d.ts.map +0 -1
  252. package/src/controllers/index.js +0 -6
  253. package/src/controllers/index.js.map +0 -1
  254. package/src/controllers/user.d.ts +0 -49
  255. package/src/controllers/user.d.ts.map +0 -1
  256. package/src/controllers/user.js +0 -919
  257. package/src/controllers/user.js.map +0 -1
  258. package/src/database/database-initializer.d.ts +0 -7
  259. package/src/database/database-initializer.d.ts.map +0 -1
  260. package/src/database/database-initializer.js +0 -3
  261. package/src/database/database-initializer.js.map +0 -1
  262. package/src/database/index.d.ts.map +0 -1
  263. package/src/database/index.js +0 -5
  264. package/src/database/index.js.map +0 -1
  265. package/src/decorators/base-controller.d.ts +0 -11
  266. package/src/decorators/base-controller.d.ts.map +0 -1
  267. package/src/decorators/base-controller.js +0 -60
  268. package/src/decorators/base-controller.js.map +0 -1
  269. package/src/decorators/controller.d.ts +0 -38
  270. package/src/decorators/controller.d.ts.map +0 -1
  271. package/src/decorators/controller.js +0 -68
  272. package/src/decorators/controller.js.map +0 -1
  273. package/src/decorators/index.d.ts.map +0 -1
  274. package/src/decorators/index.js +0 -7
  275. package/src/decorators/index.js.map +0 -1
  276. package/src/decorators/zod-validation.d.ts +0 -5
  277. package/src/decorators/zod-validation.d.ts.map +0 -1
  278. package/src/decorators/zod-validation.js +0 -48
  279. package/src/decorators/zod-validation.js.map +0 -1
  280. package/src/defaults.d.ts +0 -7
  281. package/src/defaults.d.ts.map +0 -1
  282. package/src/defaults.js +0 -205
  283. package/src/defaults.js.map +0 -1
  284. package/src/documents/base.d.ts +0 -4
  285. package/src/documents/base.d.ts.map +0 -1
  286. package/src/documents/base.js +0 -3
  287. package/src/documents/base.js.map +0 -1
  288. package/src/documents/email-token.d.ts +0 -8
  289. package/src/documents/email-token.d.ts.map +0 -1
  290. package/src/documents/email-token.js +0 -3
  291. package/src/documents/email-token.js.map +0 -1
  292. package/src/documents/index.d.ts.map +0 -1
  293. package/src/documents/index.js +0 -3
  294. package/src/documents/index.js.map +0 -1
  295. package/src/documents/mnemonic.d.ts +0 -8
  296. package/src/documents/mnemonic.d.ts.map +0 -1
  297. package/src/documents/mnemonic.js +0 -3
  298. package/src/documents/mnemonic.js.map +0 -1
  299. package/src/documents/role.d.ts +0 -8
  300. package/src/documents/role.d.ts.map +0 -1
  301. package/src/documents/role.js +0 -3
  302. package/src/documents/role.js.map +0 -1
  303. package/src/documents/used-direct-login-token.d.ts +0 -5
  304. package/src/documents/used-direct-login-token.d.ts.map +0 -1
  305. package/src/documents/used-direct-login-token.js +0 -3
  306. package/src/documents/used-direct-login-token.js.map +0 -1
  307. package/src/documents/user-role.d.ts +0 -8
  308. package/src/documents/user-role.d.ts.map +0 -1
  309. package/src/documents/user-role.js +0 -3
  310. package/src/documents/user-role.js.map +0 -1
  311. package/src/documents/user.d.ts +0 -8
  312. package/src/documents/user.d.ts.map +0 -1
  313. package/src/documents/user.js +0 -3
  314. package/src/documents/user.js.map +0 -1
  315. package/src/enumerations/base-model-name.d.ts +0 -38
  316. package/src/enumerations/base-model-name.d.ts.map +0 -1
  317. package/src/enumerations/base-model-name.js +0 -34
  318. package/src/enumerations/base-model-name.js.map +0 -1
  319. package/src/enumerations/index.d.ts.map +0 -1
  320. package/src/enumerations/index.js +0 -8
  321. package/src/enumerations/index.js.map +0 -1
  322. package/src/enumerations/length-encoding-type.d.ts +0 -7
  323. package/src/enumerations/length-encoding-type.d.ts.map +0 -1
  324. package/src/enumerations/length-encoding-type.js +0 -11
  325. package/src/enumerations/length-encoding-type.js.map +0 -1
  326. package/src/enumerations/schema-collection.d.ts +0 -34
  327. package/src/enumerations/schema-collection.d.ts.map +0 -1
  328. package/src/enumerations/schema-collection.js +0 -38
  329. package/src/enumerations/schema-collection.js.map +0 -1
  330. package/src/enumerations/symmetric-error-type.d.ts +0 -5
  331. package/src/enumerations/symmetric-error-type.d.ts.map +0 -1
  332. package/src/enumerations/symmetric-error-type.js +0 -9
  333. package/src/enumerations/symmetric-error-type.js.map +0 -1
  334. package/src/environment.d.ts +0 -189
  335. package/src/environment.d.ts.map +0 -1
  336. package/src/environment.js +0 -641
  337. package/src/environment.js.map +0 -1
  338. package/src/errors/express-validation.d.ts +0 -9
  339. package/src/errors/express-validation.d.ts.map +0 -1
  340. package/src/errors/express-validation.js +0 -18
  341. package/src/errors/express-validation.js.map +0 -1
  342. package/src/errors/index.d.ts.map +0 -1
  343. package/src/errors/index.js +0 -16
  344. package/src/errors/index.js.map +0 -1
  345. package/src/errors/invalid-backup-code-version.d.ts +0 -6
  346. package/src/errors/invalid-backup-code-version.d.ts.map +0 -1
  347. package/src/errors/invalid-backup-code-version.js +0 -16
  348. package/src/errors/invalid-backup-code-version.js.map +0 -1
  349. package/src/errors/invalid-jwt-token.d.ts +0 -5
  350. package/src/errors/invalid-jwt-token.d.ts.map +0 -1
  351. package/src/errors/invalid-jwt-token.js +0 -12
  352. package/src/errors/invalid-jwt-token.js.map +0 -1
  353. package/src/errors/invalid-model.d.ts +0 -6
  354. package/src/errors/invalid-model.d.ts.map +0 -1
  355. package/src/errors/invalid-model.js +0 -14
  356. package/src/errors/invalid-model.js.map +0 -1
  357. package/src/errors/invalid-new-password.d.ts +0 -5
  358. package/src/errors/invalid-new-password.d.ts.map +0 -1
  359. package/src/errors/invalid-new-password.js +0 -14
  360. package/src/errors/invalid-new-password.js.map +0 -1
  361. package/src/errors/invalid-password.d.ts +0 -5
  362. package/src/errors/invalid-password.d.ts.map +0 -1
  363. package/src/errors/invalid-password.js +0 -14
  364. package/src/errors/invalid-password.js.map +0 -1
  365. package/src/errors/missing-validated-data.d.ts +0 -7
  366. package/src/errors/missing-validated-data.d.ts.map +0 -1
  367. package/src/errors/missing-validated-data.js +0 -36
  368. package/src/errors/missing-validated-data.js.map +0 -1
  369. package/src/errors/mnemonic-or-password-required.d.ts +0 -5
  370. package/src/errors/mnemonic-or-password-required.d.ts.map +0 -1
  371. package/src/errors/mnemonic-or-password-required.js +0 -14
  372. package/src/errors/mnemonic-or-password-required.js.map +0 -1
  373. package/src/errors/model-not-registered.d.ts +0 -6
  374. package/src/errors/model-not-registered.d.ts.map +0 -1
  375. package/src/errors/model-not-registered.js +0 -14
  376. package/src/errors/model-not-registered.js.map +0 -1
  377. package/src/errors/mongoose-validation.d.ts +0 -12
  378. package/src/errors/mongoose-validation.d.ts.map +0 -1
  379. package/src/errors/mongoose-validation.js +0 -17
  380. package/src/errors/mongoose-validation.js.map +0 -1
  381. package/src/errors/symmetric.d.ts +0 -8
  382. package/src/errors/symmetric.d.ts.map +0 -1
  383. package/src/errors/symmetric.js +0 -22
  384. package/src/errors/symmetric.js.map +0 -1
  385. package/src/errors/token-expired.d.ts +0 -5
  386. package/src/errors/token-expired.d.ts.map +0 -1
  387. package/src/errors/token-expired.js +0 -12
  388. package/src/errors/token-expired.js.map +0 -1
  389. package/src/get-language.d.ts +0 -2
  390. package/src/get-language.d.ts.map +0 -1
  391. package/src/get-language.js +0 -30
  392. package/src/get-language.js.map +0 -1
  393. package/src/get-timezone.d.ts +0 -2
  394. package/src/get-timezone.d.ts.map +0 -1
  395. package/src/get-timezone.js +0 -39
  396. package/src/get-timezone.js.map +0 -1
  397. package/src/index.d.ts.map +0 -1
  398. package/src/index.js +0 -80
  399. package/src/index.js.map +0 -1
  400. package/src/interfaces/api-error-response.d.ts +0 -5
  401. package/src/interfaces/api-error-response.d.ts.map +0 -1
  402. package/src/interfaces/api-error-response.js +0 -3
  403. package/src/interfaces/api-error-response.js.map +0 -1
  404. package/src/interfaces/api-express-validation-error-response.d.ts +0 -7
  405. package/src/interfaces/api-express-validation-error-response.d.ts.map +0 -1
  406. package/src/interfaces/api-express-validation-error-response.js +0 -3
  407. package/src/interfaces/api-express-validation-error-response.js.map +0 -1
  408. package/src/interfaces/api-message-response.d.ts +0 -4
  409. package/src/interfaces/api-message-response.d.ts.map +0 -1
  410. package/src/interfaces/api-message-response.js +0 -3
  411. package/src/interfaces/api-message-response.js.map +0 -1
  412. package/src/interfaces/api-mongo-validation-error-response.d.ts +0 -6
  413. package/src/interfaces/api-mongo-validation-error-response.d.ts.map +0 -1
  414. package/src/interfaces/api-mongo-validation-error-response.js +0 -3
  415. package/src/interfaces/api-mongo-validation-error-response.js.map +0 -1
  416. package/src/interfaces/api-responses/backup-codes-response.d.ts +0 -5
  417. package/src/interfaces/api-responses/backup-codes-response.d.ts.map +0 -1
  418. package/src/interfaces/api-responses/backup-codes-response.js +0 -3
  419. package/src/interfaces/api-responses/backup-codes-response.js.map +0 -1
  420. package/src/interfaces/api-responses/challenge-response.d.ts +0 -6
  421. package/src/interfaces/api-responses/challenge-response.d.ts.map +0 -1
  422. package/src/interfaces/api-responses/challenge-response.js +0 -3
  423. package/src/interfaces/api-responses/challenge-response.js.map +0 -1
  424. package/src/interfaces/api-responses/code-count-response.d.ts +0 -5
  425. package/src/interfaces/api-responses/code-count-response.d.ts.map +0 -1
  426. package/src/interfaces/api-responses/code-count-response.js +0 -3
  427. package/src/interfaces/api-responses/code-count-response.js.map +0 -1
  428. package/src/interfaces/api-responses/index.d.ts.map +0 -1
  429. package/src/interfaces/api-responses/index.js +0 -12
  430. package/src/interfaces/api-responses/index.js.map +0 -1
  431. package/src/interfaces/api-responses/login-response.d.ts +0 -8
  432. package/src/interfaces/api-responses/login-response.d.ts.map +0 -1
  433. package/src/interfaces/api-responses/login-response.js +0 -3
  434. package/src/interfaces/api-responses/login-response.js.map +0 -1
  435. package/src/interfaces/api-responses/mnemonic-response.d.ts +0 -5
  436. package/src/interfaces/api-responses/mnemonic-response.d.ts.map +0 -1
  437. package/src/interfaces/api-responses/mnemonic-response.js +0 -3
  438. package/src/interfaces/api-responses/mnemonic-response.js.map +0 -1
  439. package/src/interfaces/api-responses/registration-response.d.ts +0 -6
  440. package/src/interfaces/api-responses/registration-response.d.ts.map +0 -1
  441. package/src/interfaces/api-responses/registration-response.js +0 -3
  442. package/src/interfaces/api-responses/registration-response.js.map +0 -1
  443. package/src/interfaces/api-responses/request-user-response.d.ts +0 -6
  444. package/src/interfaces/api-responses/request-user-response.d.ts.map +0 -1
  445. package/src/interfaces/api-responses/request-user-response.js +0 -3
  446. package/src/interfaces/api-responses/request-user-response.js.map +0 -1
  447. package/src/interfaces/api-responses/user-settings-response.d.ts +0 -12
  448. package/src/interfaces/api-responses/user-settings-response.d.ts.map +0 -1
  449. package/src/interfaces/api-responses/user-settings-response.js +0 -3
  450. package/src/interfaces/api-responses/user-settings-response.js.map +0 -1
  451. package/src/interfaces/application.d.ts +0 -17
  452. package/src/interfaces/application.d.ts.map +0 -1
  453. package/src/interfaces/application.js +0 -3
  454. package/src/interfaces/application.js.map +0 -1
  455. package/src/interfaces/backend-objects/email-token.d.ts +0 -4
  456. package/src/interfaces/backend-objects/email-token.d.ts.map +0 -1
  457. package/src/interfaces/backend-objects/email-token.js +0 -3
  458. package/src/interfaces/backend-objects/email-token.js.map +0 -1
  459. package/src/interfaces/backend-objects/index.d.ts.map +0 -1
  460. package/src/interfaces/backend-objects/index.js +0 -8
  461. package/src/interfaces/backend-objects/index.js.map +0 -1
  462. package/src/interfaces/backend-objects/request-user.d.ts +0 -5
  463. package/src/interfaces/backend-objects/request-user.d.ts.map +0 -1
  464. package/src/interfaces/backend-objects/request-user.js +0 -3
  465. package/src/interfaces/backend-objects/request-user.js.map +0 -1
  466. package/src/interfaces/backend-objects/role.d.ts +0 -4
  467. package/src/interfaces/backend-objects/role.d.ts.map +0 -1
  468. package/src/interfaces/backend-objects/role.js +0 -3
  469. package/src/interfaces/backend-objects/role.js.map +0 -1
  470. package/src/interfaces/backend-objects/user.d.ts +0 -4
  471. package/src/interfaces/backend-objects/user.d.ts.map +0 -1
  472. package/src/interfaces/backend-objects/user.js +0 -3
  473. package/src/interfaces/backend-objects/user.js.map +0 -1
  474. package/src/interfaces/checksum-config.d.ts +0 -5
  475. package/src/interfaces/checksum-config.d.ts.map +0 -1
  476. package/src/interfaces/checksum-config.js +0 -3
  477. package/src/interfaces/checksum-config.js.map +0 -1
  478. package/src/interfaces/checksum-consts.d.ts +0 -11
  479. package/src/interfaces/checksum-consts.d.ts.map +0 -1
  480. package/src/interfaces/checksum-consts.js +0 -3
  481. package/src/interfaces/checksum-consts.js.map +0 -1
  482. package/src/interfaces/constants.d.ts +0 -102
  483. package/src/interfaces/constants.d.ts.map +0 -1
  484. package/src/interfaces/constants.js +0 -3
  485. package/src/interfaces/constants.js.map +0 -1
  486. package/src/interfaces/controller-config.d.ts +0 -21
  487. package/src/interfaces/controller-config.d.ts.map +0 -1
  488. package/src/interfaces/controller-config.js +0 -3
  489. package/src/interfaces/controller-config.js.map +0 -1
  490. package/src/interfaces/create-user-basics.d.ts +0 -18
  491. package/src/interfaces/create-user-basics.d.ts.map +0 -1
  492. package/src/interfaces/create-user-basics.js +0 -3
  493. package/src/interfaces/create-user-basics.js.map +0 -1
  494. package/src/interfaces/csp-config.d.ts +0 -7
  495. package/src/interfaces/csp-config.d.ts.map +0 -1
  496. package/src/interfaces/csp-config.js +0 -13
  497. package/src/interfaces/csp-config.js.map +0 -1
  498. package/src/interfaces/csp-definition.d.ts +0 -13
  499. package/src/interfaces/csp-definition.d.ts.map +0 -1
  500. package/src/interfaces/csp-definition.js +0 -22
  501. package/src/interfaces/csp-definition.js.map +0 -1
  502. package/src/interfaces/db-init-result.d.ts +0 -5
  503. package/src/interfaces/db-init-result.d.ts.map +0 -1
  504. package/src/interfaces/db-init-result.js +0 -3
  505. package/src/interfaces/db-init-result.js.map +0 -1
  506. package/src/interfaces/deep-partial.d.ts +0 -4
  507. package/src/interfaces/deep-partial.d.ts.map +0 -1
  508. package/src/interfaces/deep-partial.js +0 -3
  509. package/src/interfaces/deep-partial.js.map +0 -1
  510. package/src/interfaces/discriminator-collections.d.ts +0 -7
  511. package/src/interfaces/discriminator-collections.d.ts.map +0 -1
  512. package/src/interfaces/discriminator-collections.js +0 -3
  513. package/src/interfaces/discriminator-collections.js.map +0 -1
  514. package/src/interfaces/email-service.d.ts +0 -4
  515. package/src/interfaces/email-service.d.ts.map +0 -1
  516. package/src/interfaces/email-service.js +0 -3
  517. package/src/interfaces/email-service.js.map +0 -1
  518. package/src/interfaces/environment-mongo.d.ts +0 -76
  519. package/src/interfaces/environment-mongo.d.ts.map +0 -1
  520. package/src/interfaces/environment-mongo.js +0 -3
  521. package/src/interfaces/environment-mongo.js.map +0 -1
  522. package/src/interfaces/environment.d.ts +0 -180
  523. package/src/interfaces/environment.d.ts.map +0 -1
  524. package/src/interfaces/environment.js +0 -3
  525. package/src/interfaces/environment.js.map +0 -1
  526. package/src/interfaces/failable-result.d.ts +0 -7
  527. package/src/interfaces/failable-result.d.ts.map +0 -1
  528. package/src/interfaces/failable-result.js +0 -3
  529. package/src/interfaces/failable-result.js.map +0 -1
  530. package/src/interfaces/fec-consts.d.ts +0 -5
  531. package/src/interfaces/fec-consts.d.ts.map +0 -1
  532. package/src/interfaces/fec-consts.js +0 -3
  533. package/src/interfaces/fec-consts.js.map +0 -1
  534. package/src/interfaces/flexible-csp.d.ts +0 -8
  535. package/src/interfaces/flexible-csp.d.ts.map +0 -1
  536. package/src/interfaces/flexible-csp.js +0 -14
  537. package/src/interfaces/flexible-csp.js.map +0 -1
  538. package/src/interfaces/handleable-error-options.d.ts +0 -7
  539. package/src/interfaces/handleable-error-options.d.ts.map +0 -1
  540. package/src/interfaces/handleable-error-options.js +0 -3
  541. package/src/interfaces/handleable-error-options.js.map +0 -1
  542. package/src/interfaces/index.d.ts.map +0 -1
  543. package/src/interfaces/index.js +0 -38
  544. package/src/interfaces/index.js.map +0 -1
  545. package/src/interfaces/jwt-consts.d.ts +0 -11
  546. package/src/interfaces/jwt-consts.d.ts.map +0 -1
  547. package/src/interfaces/jwt-consts.js +0 -3
  548. package/src/interfaces/jwt-consts.js.map +0 -1
  549. package/src/interfaces/jwt-sign-response.d.ts +0 -11
  550. package/src/interfaces/jwt-sign-response.d.ts.map +0 -1
  551. package/src/interfaces/jwt-sign-response.js +0 -3
  552. package/src/interfaces/jwt-sign-response.js.map +0 -1
  553. package/src/interfaces/models/email-token.d.ts +0 -6
  554. package/src/interfaces/models/email-token.d.ts.map +0 -1
  555. package/src/interfaces/models/email-token.js +0 -3
  556. package/src/interfaces/models/email-token.js.map +0 -1
  557. package/src/interfaces/models/index.d.ts.map +0 -1
  558. package/src/interfaces/models/index.js +0 -11
  559. package/src/interfaces/models/index.js.map +0 -1
  560. package/src/interfaces/models/mnemonic.d.ts +0 -6
  561. package/src/interfaces/models/mnemonic.d.ts.map +0 -1
  562. package/src/interfaces/models/mnemonic.js +0 -3
  563. package/src/interfaces/models/mnemonic.js.map +0 -1
  564. package/src/interfaces/models/role.d.ts +0 -6
  565. package/src/interfaces/models/role.d.ts.map +0 -1
  566. package/src/interfaces/models/role.js +0 -3
  567. package/src/interfaces/models/role.js.map +0 -1
  568. package/src/interfaces/models/token-role.d.ts +0 -11
  569. package/src/interfaces/models/token-role.d.ts.map +0 -1
  570. package/src/interfaces/models/token-role.js +0 -3
  571. package/src/interfaces/models/token-role.js.map +0 -1
  572. package/src/interfaces/models/used-direct-login-token.d.ts +0 -11
  573. package/src/interfaces/models/used-direct-login-token.d.ts.map +0 -1
  574. package/src/interfaces/models/used-direct-login-token.js +0 -3
  575. package/src/interfaces/models/used-direct-login-token.js.map +0 -1
  576. package/src/interfaces/models/user-role.d.ts +0 -11
  577. package/src/interfaces/models/user-role.d.ts.map +0 -1
  578. package/src/interfaces/models/user-role.js +0 -3
  579. package/src/interfaces/models/user-role.js.map +0 -1
  580. package/src/interfaces/models/user.d.ts +0 -11
  581. package/src/interfaces/models/user.d.ts.map +0 -1
  582. package/src/interfaces/models/user.js +0 -3
  583. package/src/interfaces/models/user.js.map +0 -1
  584. package/src/interfaces/mongo-errors.d.ts +0 -5
  585. package/src/interfaces/mongo-errors.d.ts.map +0 -1
  586. package/src/interfaces/mongo-errors.js +0 -3
  587. package/src/interfaces/mongo-errors.js.map +0 -1
  588. package/src/interfaces/request-user.d.ts +0 -58
  589. package/src/interfaces/request-user.d.ts.map +0 -1
  590. package/src/interfaces/request-user.js +0 -3
  591. package/src/interfaces/request-user.js.map +0 -1
  592. package/src/interfaces/required-string-keys.d.ts +0 -22
  593. package/src/interfaces/required-string-keys.d.ts.map +0 -1
  594. package/src/interfaces/required-string-keys.js +0 -3
  595. package/src/interfaces/required-string-keys.js.map +0 -1
  596. package/src/interfaces/schema.d.ts +0 -29
  597. package/src/interfaces/schema.d.ts.map +0 -1
  598. package/src/interfaces/schema.js +0 -3
  599. package/src/interfaces/schema.js.map +0 -1
  600. package/src/interfaces/server-init-result.d.ts +0 -35
  601. package/src/interfaces/server-init-result.d.ts.map +0 -1
  602. package/src/interfaces/server-init-result.js +0 -3
  603. package/src/interfaces/server-init-result.js.map +0 -1
  604. package/src/interfaces/status-code-response.d.ts +0 -7
  605. package/src/interfaces/status-code-response.d.ts.map +0 -1
  606. package/src/interfaces/status-code-response.js +0 -3
  607. package/src/interfaces/status-code-response.js.map +0 -1
  608. package/src/interfaces/symmetric-encryption-results.d.ts +0 -5
  609. package/src/interfaces/test-environment.d.ts +0 -12
  610. package/src/interfaces/test-environment.d.ts.map +0 -1
  611. package/src/interfaces/test-environment.js +0 -3
  612. package/src/interfaces/test-environment.js.map +0 -1
  613. package/src/interfaces/token-response.d.ts +0 -5
  614. package/src/interfaces/token-response.d.ts.map +0 -1
  615. package/src/interfaces/token-response.js +0 -3
  616. package/src/interfaces/token-response.js.map +0 -1
  617. package/src/middleware-utils.d.ts +0 -8
  618. package/src/middleware-utils.d.ts.map +0 -1
  619. package/src/middleware-utils.js +0 -94
  620. package/src/middleware-utils.js.map +0 -1
  621. package/src/middlewares/authenticate-crypto.d.ts +0 -10
  622. package/src/middlewares/authenticate-crypto.d.ts.map +0 -1
  623. package/src/middlewares/authenticate-crypto.js +0 -126
  624. package/src/middlewares/authenticate-crypto.js.map +0 -1
  625. package/src/middlewares/authenticate-token.d.ts +0 -21
  626. package/src/middlewares/authenticate-token.d.ts.map +0 -1
  627. package/src/middlewares/authenticate-token.js +0 -104
  628. package/src/middlewares/authenticate-token.js.map +0 -1
  629. package/src/middlewares/cleanup-crypto.d.ts +0 -7
  630. package/src/middlewares/cleanup-crypto.d.ts.map +0 -1
  631. package/src/middlewares/cleanup-crypto.js +0 -32
  632. package/src/middlewares/cleanup-crypto.js.map +0 -1
  633. package/src/middlewares/index.d.ts.map +0 -1
  634. package/src/middlewares/index.js +0 -8
  635. package/src/middlewares/index.js.map +0 -1
  636. package/src/middlewares/set-global-context-language.d.ts +0 -3
  637. package/src/middlewares/set-global-context-language.d.ts.map +0 -1
  638. package/src/middlewares/set-global-context-language.js +0 -14
  639. package/src/middlewares/set-global-context-language.js.map +0 -1
  640. package/src/model-registry.d.ts +0 -23
  641. package/src/model-registry.d.ts.map +0 -1
  642. package/src/model-registry.js +0 -47
  643. package/src/model-registry.js.map +0 -1
  644. package/src/models/email-token.d.ts +0 -8
  645. package/src/models/email-token.d.ts.map +0 -1
  646. package/src/models/email-token.js +0 -11
  647. package/src/models/email-token.js.map +0 -1
  648. package/src/models/index.d.ts.map +0 -1
  649. package/src/models/index.js +0 -10
  650. package/src/models/index.js.map +0 -1
  651. package/src/models/mnemonic.d.ts +0 -8
  652. package/src/models/mnemonic.d.ts.map +0 -1
  653. package/src/models/mnemonic.js +0 -11
  654. package/src/models/mnemonic.js.map +0 -1
  655. package/src/models/role.d.ts +0 -8
  656. package/src/models/role.d.ts.map +0 -1
  657. package/src/models/role.js +0 -11
  658. package/src/models/role.js.map +0 -1
  659. package/src/models/used-direct-login-token.d.ts +0 -8
  660. package/src/models/used-direct-login-token.d.ts.map +0 -1
  661. package/src/models/used-direct-login-token.js +0 -11
  662. package/src/models/used-direct-login-token.js.map +0 -1
  663. package/src/models/user-role.d.ts +0 -7
  664. package/src/models/user-role.d.ts.map +0 -1
  665. package/src/models/user-role.js +0 -10
  666. package/src/models/user-role.js.map +0 -1
  667. package/src/models/user.d.ts +0 -8
  668. package/src/models/user.d.ts.map +0 -1
  669. package/src/models/user.js +0 -11
  670. package/src/models/user.js.map +0 -1
  671. package/src/pipeline/index.d.ts.map +0 -1
  672. package/src/pipeline/index.js +0 -5
  673. package/src/pipeline/index.js.map +0 -1
  674. package/src/pipeline/pipeline-builder.d.ts +0 -8
  675. package/src/pipeline/pipeline-builder.d.ts.map +0 -1
  676. package/src/pipeline/pipeline-builder.js +0 -18
  677. package/src/pipeline/pipeline-builder.js.map +0 -1
  678. package/src/plugins/index.d.ts.map +0 -1
  679. package/src/plugins/index.js +0 -6
  680. package/src/plugins/index.js.map +0 -1
  681. package/src/plugins/plugin-interface.d.ts +0 -9
  682. package/src/plugins/plugin-interface.d.ts.map +0 -1
  683. package/src/plugins/plugin-interface.js +0 -3
  684. package/src/plugins/plugin-interface.js.map +0 -1
  685. package/src/plugins/plugin-manager.d.ts +0 -13
  686. package/src/plugins/plugin-manager.d.ts.map +0 -1
  687. package/src/plugins/plugin-manager.js +0 -37
  688. package/src/plugins/plugin-manager.js.map +0 -1
  689. package/src/registry/email-service-registry.d.ts +0 -27
  690. package/src/registry/email-service-registry.d.ts.map +0 -1
  691. package/src/registry/email-service-registry.js +0 -42
  692. package/src/registry/email-service-registry.js.map +0 -1
  693. package/src/registry/index.d.ts.map +0 -1
  694. package/src/registry/index.js +0 -6
  695. package/src/registry/index.js.map +0 -1
  696. package/src/responses/index.d.ts.map +0 -1
  697. package/src/responses/index.js +0 -5
  698. package/src/responses/index.js.map +0 -1
  699. package/src/responses/response-builder.d.ts +0 -24
  700. package/src/responses/response-builder.d.ts.map +0 -1
  701. package/src/responses/response-builder.js +0 -63
  702. package/src/responses/response-builder.js.map +0 -1
  703. package/src/routers/api.d.ts +0 -28
  704. package/src/routers/api.d.ts.map +0 -1
  705. package/src/routers/api.js +0 -80
  706. package/src/routers/api.js.map +0 -1
  707. package/src/routers/app.d.ts +0 -33
  708. package/src/routers/app.d.ts.map +0 -1
  709. package/src/routers/app.js +0 -228
  710. package/src/routers/app.js.map +0 -1
  711. package/src/routers/base.d.ts +0 -9
  712. package/src/routers/base.d.ts.map +0 -1
  713. package/src/routers/base.js +0 -14
  714. package/src/routers/base.js.map +0 -1
  715. package/src/routers/index.d.ts.map +0 -1
  716. package/src/routers/index.js +0 -7
  717. package/src/routers/index.js.map +0 -1
  718. package/src/routers/router-config.d.ts +0 -18
  719. package/src/routers/router-config.d.ts.map +0 -1
  720. package/src/routers/router-config.js +0 -8
  721. package/src/routers/router-config.js.map +0 -1
  722. package/src/routing/index.d.ts +0 -2
  723. package/src/routing/index.d.ts.map +0 -1
  724. package/src/routing/index.js +0 -5
  725. package/src/routing/index.js.map +0 -1
  726. package/src/routing/route-builder.d.ts +0 -36
  727. package/src/routing/route-builder.d.ts.map +0 -1
  728. package/src/routing/route-builder.js +0 -86
  729. package/src/routing/route-builder.js.map +0 -1
  730. package/src/schemas/email-token.d.ts +0 -49
  731. package/src/schemas/email-token.d.ts.map +0 -1
  732. package/src/schemas/email-token.js +0 -55
  733. package/src/schemas/email-token.js.map +0 -1
  734. package/src/schemas/index.d.ts.map +0 -1
  735. package/src/schemas/index.js +0 -11
  736. package/src/schemas/index.js.map +0 -1
  737. package/src/schemas/mnemonic.d.ts +0 -27
  738. package/src/schemas/mnemonic.d.ts.map +0 -1
  739. package/src/schemas/mnemonic.js +0 -31
  740. package/src/schemas/mnemonic.js.map +0 -1
  741. package/src/schemas/role.d.ts +0 -42
  742. package/src/schemas/role.d.ts.map +0 -1
  743. package/src/schemas/role.js +0 -89
  744. package/src/schemas/role.js.map +0 -1
  745. package/src/schemas/schema.d.ts +0 -42
  746. package/src/schemas/schema.d.ts.map +0 -1
  747. package/src/schemas/schema.js +0 -70
  748. package/src/schemas/schema.js.map +0 -1
  749. package/src/schemas/used-direct-login-token.d.ts +0 -37
  750. package/src/schemas/used-direct-login-token.d.ts.map +0 -1
  751. package/src/schemas/used-direct-login-token.js +0 -24
  752. package/src/schemas/used-direct-login-token.js.map +0 -1
  753. package/src/schemas/user-role.d.ts +0 -39
  754. package/src/schemas/user-role.d.ts.map +0 -1
  755. package/src/schemas/user-role.js +0 -55
  756. package/src/schemas/user-role.js.map +0 -1
  757. package/src/schemas/user.d.ts +0 -24
  758. package/src/schemas/user.d.ts.map +0 -1
  759. package/src/schemas/user.js +0 -195
  760. package/src/schemas/user.js.map +0 -1
  761. package/src/services/backup-code.d.ts +0 -76
  762. package/src/services/backup-code.d.ts.map +0 -1
  763. package/src/services/backup-code.js +0 -185
  764. package/src/services/backup-code.js.map +0 -1
  765. package/src/services/base.d.ts +0 -11
  766. package/src/services/base.d.ts.map +0 -1
  767. package/src/services/base.js +0 -15
  768. package/src/services/base.js.map +0 -1
  769. package/src/services/checksum.d.ts +0 -69
  770. package/src/services/checksum.d.ts.map +0 -1
  771. package/src/services/checksum.js +0 -145
  772. package/src/services/checksum.js.map +0 -1
  773. package/src/services/crc.d.ts +0 -87
  774. package/src/services/crc.d.ts.map +0 -1
  775. package/src/services/crc.js +0 -198
  776. package/src/services/crc.js.map +0 -1
  777. package/src/services/database-initialization.d.ts +0 -111
  778. package/src/services/database-initialization.d.ts.map +0 -1
  779. package/src/services/database-initialization.js +0 -878
  780. package/src/services/database-initialization.js.map +0 -1
  781. package/src/services/db-init-cache.d.ts +0 -10
  782. package/src/services/db-init-cache.d.ts.map +0 -1
  783. package/src/services/db-init-cache.js +0 -3
  784. package/src/services/db-init-cache.js.map +0 -1
  785. package/src/services/direct-login-token.d.ts +0 -7
  786. package/src/services/direct-login-token.d.ts.map +0 -1
  787. package/src/services/direct-login-token.js +0 -41
  788. package/src/services/direct-login-token.js.map +0 -1
  789. package/src/services/dummy-email-service.d.ts +0 -11
  790. package/src/services/dummy-email-service.d.ts.map +0 -1
  791. package/src/services/dummy-email-service.js +0 -16
  792. package/src/services/dummy-email-service.js.map +0 -1
  793. package/src/services/fec-usage-example.d.ts +0 -38
  794. package/src/services/fec-usage-example.d.ts.map +0 -1
  795. package/src/services/fec-usage-example.js +0 -75
  796. package/src/services/fec-usage-example.js.map +0 -1
  797. package/src/services/fec.d.ts +0 -46
  798. package/src/services/fec.d.ts.map +0 -1
  799. package/src/services/fec.js +0 -214
  800. package/src/services/fec.js.map +0 -1
  801. package/src/services/index.d.ts.map +0 -1
  802. package/src/services/index.js +0 -23
  803. package/src/services/index.js.map +0 -1
  804. package/src/services/jwt.d.ts +0 -30
  805. package/src/services/jwt.d.ts.map +0 -1
  806. package/src/services/jwt.js +0 -90
  807. package/src/services/jwt.js.map +0 -1
  808. package/src/services/key-wrapping.d.ts +0 -61
  809. package/src/services/key-wrapping.d.ts.map +0 -1
  810. package/src/services/key-wrapping.js +0 -307
  811. package/src/services/key-wrapping.js.map +0 -1
  812. package/src/services/mnemonic.d.ts +0 -62
  813. package/src/services/mnemonic.d.ts.map +0 -1
  814. package/src/services/mnemonic.js +0 -114
  815. package/src/services/mnemonic.js.map +0 -1
  816. package/src/services/request-user.d.ts +0 -23
  817. package/src/services/request-user.d.ts.map +0 -1
  818. package/src/services/request-user.js +0 -68
  819. package/src/services/request-user.js.map +0 -1
  820. package/src/services/role.d.ts +0 -87
  821. package/src/services/role.d.ts.map +0 -1
  822. package/src/services/role.js +0 -279
  823. package/src/services/role.js.map +0 -1
  824. package/src/services/symmetric.d.ts +0 -42
  825. package/src/services/symmetric.d.ts.map +0 -1
  826. package/src/services/symmetric.js +0 -101
  827. package/src/services/symmetric.js.map +0 -1
  828. package/src/services/system-user.d.ts +0 -16
  829. package/src/services/system-user.d.ts.map +0 -1
  830. package/src/services/system-user.js +0 -46
  831. package/src/services/system-user.js.map +0 -1
  832. package/src/services/user.d.ts +0 -345
  833. package/src/services/user.d.ts.map +0 -1
  834. package/src/services/user.js +0 -1447
  835. package/src/services/user.js.map +0 -1
  836. package/src/services/xor.d.ts +0 -24
  837. package/src/services/xor.d.ts.map +0 -1
  838. package/src/services/xor.js +0 -37
  839. package/src/services/xor.js.map +0 -1
  840. package/src/testing.d.ts +0 -3
  841. package/src/testing.d.ts.map +0 -1
  842. package/src/testing.js +0 -7
  843. package/src/testing.js.map +0 -1
  844. package/src/transactions/index.d.ts.map +0 -1
  845. package/src/transactions/index.js +0 -5
  846. package/src/transactions/index.js.map +0 -1
  847. package/src/transactions/transaction-manager.d.ts +0 -12
  848. package/src/transactions/transaction-manager.d.ts.map +0 -1
  849. package/src/transactions/transaction-manager.js +0 -30
  850. package/src/transactions/transaction-manager.js.map +0 -1
  851. package/src/types/app-config.d.ts +0 -16
  852. package/src/types/app-config.d.ts.map +0 -1
  853. package/src/types/app-config.js +0 -3
  854. package/src/types/app-config.js.map +0 -1
  855. package/src/types/controller-config.d.ts +0 -14
  856. package/src/types/controller-config.d.ts.map +0 -1
  857. package/src/types/controller-config.js +0 -3
  858. package/src/types/controller-config.js.map +0 -1
  859. package/src/types/environment-variables.d.ts.map +0 -1
  860. package/src/types/environment-variables.js +0 -39
  861. package/src/types/environment-variables.js.map +0 -1
  862. package/src/types/index.d.ts.map +0 -1
  863. package/src/types/index.js +0 -6
  864. package/src/types/index.js.map +0 -1
  865. package/src/types/mongoose-helpers.d.ts.map +0 -1
  866. package/src/types/mongoose-helpers.js +0 -6
  867. package/src/types/mongoose-helpers.js.map +0 -1
  868. package/src/types.d.ts +0 -104
  869. package/src/types.d.ts.map +0 -1
  870. package/src/types.js +0 -14
  871. package/src/types.js.map +0 -1
  872. package/src/utils.d.ts +0 -211
  873. package/src/utils.d.ts.map +0 -1
  874. package/src/utils.js +0 -818
  875. package/src/utils.js.map +0 -1
  876. package/src/validation/index.d.ts.map +0 -1
  877. package/src/validation/index.js +0 -5
  878. package/src/validation/index.js.map +0 -1
  879. package/src/validation/validation-builder.d.ts +0 -32
  880. package/src/validation/validation-builder.d.ts.map +0 -1
  881. package/src/validation/validation-builder.js +0 -81
  882. package/src/validation/validation-builder.js.map +0 -1
@@ -0,0 +1,2324 @@
1
+ /**
2
+ * @fileoverview Comprehensive user management service.
3
+ * Handles user authentication, registration, password management, email verification,
4
+ * mnemonic recovery, backup codes, and all user-related operations.
5
+ * @module services/user
6
+ */
7
+
8
+ import {
9
+ EmailString,
10
+ IECIESConfig,
11
+ InvalidEmailErrorType,
12
+ MemberType,
13
+ SecureBuffer,
14
+ SecureString,
15
+ } from '@digitaldefiance/ecies-lib';
16
+ import {
17
+ ClientSession,
18
+ Document,
19
+ ProjectionType,
20
+ } from '@digitaldefiance/mongoose-types';
21
+ import {
22
+ Member as BackendMember,
23
+ ECIESService,
24
+ getEnhancedNodeIdProvider,
25
+ PlatformID,
26
+ SignatureBuffer,
27
+ } from '@digitaldefiance/node-ecies-lib';
28
+ import {
29
+ AccountLockedError,
30
+ AccountStatus,
31
+ AccountStatusError,
32
+ DirectChallengeNotEnabledError,
33
+ EmailInUseError,
34
+ EmailTokenExpiredError,
35
+ EmailTokenFailedToSendError,
36
+ EmailTokenSentTooRecentlyError,
37
+ EmailTokenType,
38
+ EmailTokenUsedOrInvalidError,
39
+ EmailVerifiedError,
40
+ getSuiteCoreTranslation,
41
+ IBackupCode,
42
+ InvalidChallengeResponseError,
43
+ InvalidCredentialsError,
44
+ InvalidEmailError,
45
+ InvalidUsernameError,
46
+ IRequestUserDTO,
47
+ ITokenRole,
48
+ IUserBase,
49
+ IUserDTO,
50
+ LoginChallengeExpiredError,
51
+ PasswordLoginNotEnabledError,
52
+ PendingEmailVerificationError,
53
+ PrivateKeyRequiredError,
54
+ Role,
55
+ SuiteCoreStringKey,
56
+ TranslatableSuiteError,
57
+ TranslatableSuiteHandleableError,
58
+ UsernameInUseError,
59
+ UsernameOrEmailRequiredError,
60
+ UserNotFoundError,
61
+ } from '@digitaldefiance/suite-core-lib';
62
+ import { Wallet } from '@ethereumjs/wallet';
63
+ import { randomBytes } from 'crypto';
64
+ import validator from 'validator';
65
+ import { BackupCode } from '../backup-code';
66
+ import { IBaseDocument } from '../documents';
67
+ import { IEmailTokenDocument } from '../documents/email-token';
68
+ import { IMnemonicDocument } from '../documents/mnemonic';
69
+ import { IUserDocument } from '../documents/user';
70
+ import { BaseModelName } from '../enumerations/base-model-name';
71
+ import { Environment } from '../environment';
72
+ import { InvalidNewPasswordError } from '../errors';
73
+ import { MongooseValidationError } from '../errors/mongoose-validation';
74
+ import { ICreateUserBasics } from '../interfaces';
75
+ import { IApplication } from '../interfaces/application';
76
+ import { IUserBackendObject } from '../interfaces/backend-objects/user';
77
+ import { IConstants } from '../interfaces/constants';
78
+ import { IEmailService } from '../interfaces/email-service';
79
+ import { ModelRegistry } from '../model-registry';
80
+ import { debugLog } from '../utils';
81
+ import { BackupCodeService } from './backup-code';
82
+ import { BaseService } from './base';
83
+ import { DirectLoginTokenService } from './direct-login-token';
84
+ import { KeyWrappingService } from './key-wrapping';
85
+ import { MnemonicService } from './mnemonic';
86
+ import { RequestUserService } from './request-user';
87
+ import { RoleService } from './role';
88
+ import { SystemUserService } from './system-user';
89
+
90
+ type ProjectionObject = Record<string, 0 | 1 | -1 | boolean>;
91
+
92
+ /**
93
+ * Comprehensive service for user management and authentication.
94
+ * Provides methods for user creation, authentication (mnemonic/password/challenge),
95
+ * email verification, password reset, backup code recovery, and settings management.
96
+ * @template T - User document type
97
+ * @template TID - Platform ID type
98
+ * @template TDate - Date type
99
+ * @template TLanguage - String type for site language
100
+ * @template TAccountStatus - String type for account status
101
+ * @template _TEnvironment - Environment type
102
+ * @template _TConstants - Constants type
103
+ * @template _TBaseDocument - Base document type
104
+ * @template TUser - User base interface type
105
+ * @template TTokenRole - Token role interface type
106
+ * @template TApplication - Application interface type
107
+ * @extends {BaseService<TID, TApplication>}
108
+ */
109
+ export class UserService<
110
+ T,
111
+ TID extends PlatformID,
112
+ TDate extends Date,
113
+ TLanguage extends string,
114
+ TAccountStatus extends string,
115
+ _TEnvironment extends Environment<TID> = Environment<TID>,
116
+ _TConstants extends IConstants = IConstants,
117
+ _TBaseDocument extends IBaseDocument<T, TID> = IBaseDocument<T, TID>,
118
+ TUser extends IUserBase<TID, TDate, TLanguage, TAccountStatus> = IUserBase<
119
+ TID,
120
+ TDate,
121
+ TLanguage,
122
+ TAccountStatus
123
+ >,
124
+ TTokenRole extends ITokenRole<TID, TDate> = ITokenRole<TID, TDate>,
125
+ TApplication extends IApplication<TID> = IApplication<TID>,
126
+ > extends BaseService<TID, TApplication> {
127
+ protected readonly roleService: RoleService<TID, TDate, TTokenRole>;
128
+ protected readonly eciesService: ECIESService<TID>;
129
+ protected readonly keyWrappingService: KeyWrappingService;
130
+ protected readonly mnemonicService: MnemonicService;
131
+ protected readonly emailService: IEmailService;
132
+ protected readonly backupCodeService: BackupCodeService<
133
+ TID,
134
+ TDate,
135
+ TTokenRole,
136
+ TApplication
137
+ >;
138
+ protected readonly serverUrl: string;
139
+ protected readonly disableEmailSend: boolean;
140
+
141
+ constructor(
142
+ application: TApplication,
143
+ roleService: RoleService<TID, TDate, TTokenRole>,
144
+ emailService: IEmailService,
145
+ keyWrappingService: KeyWrappingService,
146
+ backupCodeService: BackupCodeService<TID, TDate, TTokenRole, TApplication>,
147
+ ) {
148
+ super(application);
149
+ this.roleService = roleService;
150
+ this.emailService = emailService;
151
+ this.keyWrappingService = keyWrappingService;
152
+ this.backupCodeService = backupCodeService;
153
+ this.serverUrl = application.environment.serverUrl;
154
+ this.disableEmailSend = application.environment.disableEmailSend;
155
+ const config: IECIESConfig = {
156
+ curveName: this.application.constants.ECIES.CURVE_NAME,
157
+ primaryKeyDerivationPath:
158
+ this.application.constants.ECIES.PRIMARY_KEY_DERIVATION_PATH,
159
+ mnemonicStrength: this.application.constants.ECIES.MNEMONIC_STRENGTH,
160
+ symmetricAlgorithm:
161
+ this.application.constants.ECIES.SYMMETRIC_ALGORITHM_CONFIGURATION,
162
+ symmetricKeyBits: this.application.constants.ECIES.SYMMETRIC.KEY_BITS,
163
+ symmetricKeyMode: this.application.constants.ECIES.SYMMETRIC.MODE,
164
+ };
165
+ this.eciesService = new ECIESService(config);
166
+ const mnemonicModel =
167
+ ModelRegistry.instance.getTypedModel<IMnemonicDocument>(
168
+ BaseModelName.Mnemonic,
169
+ );
170
+ this.mnemonicService = new MnemonicService(
171
+ mnemonicModel,
172
+ application.environment.mnemonicHmacSecret,
173
+ this.application.constants,
174
+ );
175
+ }
176
+
177
+ /**
178
+ * Given a User Document, make a User DTO
179
+ * @param user a User Document
180
+ * @returns An IUserDTO
181
+ */
182
+ public static userToUserDTO<
183
+ TLanguage extends string,
184
+ TID extends PlatformID = Buffer,
185
+ >(user: IUserDocument<TLanguage, TID> | Record<string, unknown>): IUserDTO {
186
+ const provider = getEnhancedNodeIdProvider<TID>();
187
+ const userId = user._id as TID;
188
+ return {
189
+ ...(user instanceof Document ? user.toObject() : user),
190
+ _id:
191
+ provider.validate(provider.toBytes(userId)) &&
192
+ provider.idToString(userId),
193
+ createdAt: (user.createdAt instanceof Date
194
+ ? user.createdAt.toString()
195
+ : user.createdAt) as string,
196
+ createdBy:
197
+ provider.validate(provider.toBytes(user.createdBy as TID)) &&
198
+ provider.idToString(user.createdBy as TID),
199
+ updatedAt: (user.updatedAt instanceof Date
200
+ ? user.updatedAt.toString()
201
+ : user.updatedAt) as string,
202
+ updatedBy:
203
+ provider.validate(provider.toBytes(user.updatedBy as TID)) &&
204
+ provider.idToString(user.updatedBy as TID),
205
+ ...(user.lastLogin
206
+ ? {
207
+ lastLogin: (user.lastLogin instanceof Date
208
+ ? user.lastLogin.toString()
209
+ : user.lastLogin) as string,
210
+ }
211
+ : {}),
212
+ ...(user.deletedAt
213
+ ? {
214
+ deletedAt: (user.deletedAt instanceof Date
215
+ ? user.deletedAt.toString()
216
+ : user.deletedAt) as string,
217
+ }
218
+ : {}),
219
+ ...(user.deletedBy
220
+ ? {
221
+ deletedBy:
222
+ provider.validate(provider.toBytes(user.deletedBy as TID)) &&
223
+ provider.idToString(user.deletedBy as TID),
224
+ }
225
+ : {}),
226
+ } as IUserDTO;
227
+ }
228
+
229
+ /**
230
+ * Given a User DTO, reconstitute ids and dates
231
+ * @param user a User DTO
232
+ * @returns An IUserBackendObject
233
+ */
234
+ public hydrateUserDTOToBackend(
235
+ user: IUserDTO,
236
+ ): IUserBackendObject<TLanguage, TID> {
237
+ const provider = getEnhancedNodeIdProvider<TID>();
238
+ return {
239
+ ...user,
240
+ _id: provider.idFromString(user._id),
241
+ ...(user.lastLogin ? { lastLogin: new Date(user.lastLogin) } : {}),
242
+ createdAt: new Date(user.createdAt),
243
+ createdBy: provider.idFromString(user.createdBy),
244
+ updatedAt: new Date(user.updatedAt),
245
+ updatedBy: provider.idFromString(user.updatedBy),
246
+ ...(user.deletedAt ? { deletedAt: new Date(user.deletedAt) } : {}),
247
+ ...(user.deletedBy
248
+ ? {
249
+ deletedBy: provider.idFromString(user.deletedBy),
250
+ }
251
+ : {}),
252
+ ...(user.mnemonicId
253
+ ? { mnemonicId: provider.idFromString(user.mnemonicId) }
254
+ : {}),
255
+ } as IUserBackendObject<TLanguage, TID>;
256
+ }
257
+
258
+ /**
259
+ * Create a new email token to send to the user for email verification
260
+ * @param userDoc The user to create the email token for
261
+ * @param type The type of email token to create
262
+ * @param session The session to use for the query
263
+ * @returns The email token document
264
+ */
265
+ public async createEmailToken(
266
+ userDoc: IUserDocument<TLanguage, TID>,
267
+ type: EmailTokenType,
268
+ session?: ClientSession,
269
+ ): Promise<IEmailTokenDocument> {
270
+ const EmailTokenModel =
271
+ ModelRegistry.instance.getTypedModel<IEmailTokenDocument>(
272
+ BaseModelName.EmailToken,
273
+ );
274
+
275
+ // If we already have a session, use it directly to avoid nested transactions
276
+ if (session) {
277
+ const now = new Date();
278
+ const tokenData = {
279
+ userId: userDoc._id,
280
+ type: type,
281
+ email: userDoc.email,
282
+ token: randomBytes(
283
+ this.application.constants.EmailTokenLength,
284
+ ).toString('hex'),
285
+ createdAt: now,
286
+ updatedAt: now,
287
+ expiresAt: new Date(
288
+ now.getTime() + this.application.constants.EmailTokenExpiration,
289
+ ),
290
+ };
291
+
292
+ // Use findOneAndUpdate with upsert to avoid duplicate key errors
293
+ const emailToken = await EmailTokenModel.findOneAndUpdate(
294
+ {
295
+ userId: userDoc._id,
296
+ email: userDoc.email,
297
+ type: type,
298
+ },
299
+ tokenData,
300
+ {
301
+ upsert: true,
302
+ new: true,
303
+ session,
304
+ },
305
+ );
306
+
307
+ if (!emailToken) {
308
+ throw new TranslatableSuiteError(
309
+ SuiteCoreStringKey.Error_FailedToCreateEmailToken,
310
+ );
311
+ }
312
+ return emailToken;
313
+ }
314
+
315
+ // Only create a new transaction if no session is provided
316
+ return await this.withTransaction<IEmailTokenDocument>(
317
+ async (sess: ClientSession | undefined): Promise<IEmailTokenDocument> => {
318
+ const now = new Date();
319
+ const tokenData = {
320
+ userId: userDoc._id,
321
+ type: type,
322
+ email: userDoc.email,
323
+ token: randomBytes(
324
+ this.application.constants.EmailTokenLength,
325
+ ).toString('hex'),
326
+ createdAt: now,
327
+ updatedAt: now,
328
+ expiresAt: new Date(
329
+ now.getTime() + this.application.constants.EmailTokenExpiration,
330
+ ),
331
+ };
332
+
333
+ // Use findOneAndUpdate with upsert to avoid duplicate key errors
334
+ const emailToken = await EmailTokenModel.findOneAndUpdate(
335
+ {
336
+ userId: userDoc._id,
337
+ email: userDoc.email,
338
+ type: type,
339
+ },
340
+ tokenData,
341
+ {
342
+ upsert: true,
343
+ new: true,
344
+ session: sess,
345
+ },
346
+ );
347
+
348
+ if (!emailToken) {
349
+ throw new TranslatableSuiteError(
350
+ SuiteCoreStringKey.Error_FailedToCreateEmailToken,
351
+ );
352
+ }
353
+ return emailToken;
354
+ },
355
+ undefined,
356
+ {
357
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
358
+ retryAttempts: 2,
359
+ },
360
+ );
361
+ }
362
+
363
+ /**
364
+ * Create and send an email token to the user for email verification
365
+ * @param user The user to send the email token to
366
+ * @param type The type of email token to send
367
+ * @param session The session to use for the query
368
+ * @returns The email token document
369
+ */
370
+ public async createAndSendEmailToken(
371
+ user:
372
+ | IUserDocument<TLanguage, TID>
373
+ | (Pick<
374
+ IUserDocument<TLanguage, TID>,
375
+ keyof IUserDocument<TLanguage, TID>
376
+ > & { _id: any }),
377
+ type: EmailTokenType = EmailTokenType.AccountVerification,
378
+ session?: ClientSession,
379
+ debug = false,
380
+ ): Promise<IEmailTokenDocument> {
381
+ const emailToken = await this.createEmailToken(user, type, session);
382
+ try {
383
+ await this.sendEmailToken(emailToken, session, debug);
384
+ } catch {
385
+ // keep parity with previous behavior: continue returning token even if email send fails
386
+ }
387
+ return emailToken;
388
+ }
389
+
390
+ /**
391
+ * Create and send an email token directly within an existing transaction
392
+ * @param user The user to send the email token to
393
+ * @param type The type of email token to send
394
+ * @param session The session to use for the query (required)
395
+ * @param debug Whether to enable debug logging
396
+ * @returns The email token document
397
+ */
398
+ public async createAndSendEmailTokenDirect(
399
+ user: IUserDocument<TLanguage, TID>,
400
+ type: EmailTokenType = EmailTokenType.AccountVerification,
401
+ session: ClientSession,
402
+ debug = false,
403
+ ): Promise<IEmailTokenDocument> {
404
+ const EmailTokenModel =
405
+ ModelRegistry.instance.getTypedModel<IEmailTokenDocument>(
406
+ BaseModelName.EmailToken,
407
+ );
408
+
409
+ // Create token directly within the existing session using upsert
410
+ const now = new Date();
411
+ const tokenData = {
412
+ userId: user._id,
413
+ type: type,
414
+ email: user.email,
415
+ token: randomBytes(this.application.constants.EmailTokenLength).toString(
416
+ 'hex',
417
+ ),
418
+ createdAt: now,
419
+ updatedAt: now,
420
+ expiresAt: new Date(
421
+ now.getTime() + this.application.constants.EmailTokenExpiration,
422
+ ),
423
+ };
424
+
425
+ // Use findOneAndUpdate with upsert to avoid duplicate key errors
426
+ const emailToken = await EmailTokenModel.findOneAndUpdate(
427
+ {
428
+ userId: user._id,
429
+ email: user.email,
430
+ type: type,
431
+ },
432
+ tokenData,
433
+ {
434
+ upsert: true,
435
+ new: true,
436
+ session,
437
+ },
438
+ );
439
+
440
+ if (!emailToken) {
441
+ throw new TranslatableSuiteError(
442
+ SuiteCoreStringKey.Error_FailedToCreateEmailToken,
443
+ );
444
+ }
445
+
446
+ try {
447
+ await this.sendEmailToken(emailToken, session, debug);
448
+ } catch {
449
+ // Ignore email send errors in direct token creation
450
+ }
451
+
452
+ return emailToken;
453
+ }
454
+
455
+ /**
456
+ * Send an email token to the user for email verification
457
+ * @param emailToken The email token to send
458
+ * @param session The session to use for the query
459
+ * @returns void
460
+ */
461
+ public async sendEmailToken(
462
+ emailToken: IEmailTokenDocument,
463
+ session?: ClientSession,
464
+ debug = false,
465
+ ): Promise<void> {
466
+ if (this.disableEmailSend) {
467
+ debugLog(debug, 'log', 'Email sending disabled for testing');
468
+ // Still update lastSent and expiration to keep token valid during tests
469
+ emailToken.lastSent = new Date();
470
+ emailToken.expiresAt = new Date(
471
+ Date.now() + this.application.constants.EmailTokenExpiration,
472
+ );
473
+ await emailToken.save({ session });
474
+ return;
475
+ }
476
+
477
+ if (
478
+ emailToken.lastSent &&
479
+ emailToken.lastSent.getTime() +
480
+ this.application.constants.EmailTokenResendInterval >
481
+ Date.now()
482
+ ) {
483
+ throw new EmailTokenSentTooRecentlyError(emailToken.lastSent);
484
+ }
485
+
486
+ let subjectString: SuiteCoreStringKey;
487
+ let bodyString: SuiteCoreStringKey;
488
+ let url: string;
489
+ switch (emailToken.type) {
490
+ case EmailTokenType.AccountVerification:
491
+ subjectString = SuiteCoreStringKey.Email_ConfirmationSubjectTemplate;
492
+ bodyString = SuiteCoreStringKey.Email_ConfirmationBody;
493
+ url = `${this.serverUrl}/verify-email?token=${emailToken.token}`;
494
+ break;
495
+ case EmailTokenType.PasswordReset:
496
+ subjectString = SuiteCoreStringKey.Email_ResetPasswordSubjectTemplate;
497
+ bodyString = SuiteCoreStringKey.Email_ResetPasswordBody;
498
+ url = `${this.serverUrl}/forgot-password?token=${emailToken.token}`;
499
+ break;
500
+ case EmailTokenType.LoginRequest:
501
+ subjectString = SuiteCoreStringKey.Email_LoginRequestSubjectTemplate;
502
+ bodyString = SuiteCoreStringKey.Email_LoginRequestBody;
503
+ url = `${this.serverUrl}/challenge?token=${emailToken.token}`;
504
+ break;
505
+ case EmailTokenType.MnemonicRecoveryRequest:
506
+ case EmailTokenType.PrivateKeyRequest:
507
+ default:
508
+ throw new TranslatableSuiteError(
509
+ SuiteCoreStringKey.Error_InvalidEmailTokenType,
510
+ );
511
+ }
512
+ const emailSubject = getSuiteCoreTranslation(subjectString);
513
+ const emailText = `${getSuiteCoreTranslation(bodyString)}\r\n\r\n${url}`;
514
+ const emailHtml = `<p>${getSuiteCoreTranslation(
515
+ bodyString,
516
+ )}</p><br/><p><a href="${url}">${url}</a></p><p>${getSuiteCoreTranslation(
517
+ SuiteCoreStringKey.Email_LinkExpiresInTemplate,
518
+ )}</p>`;
519
+
520
+ try {
521
+ // Use the EmailService to send the email
522
+ await this.emailService.sendEmail(
523
+ emailToken.email,
524
+ emailSubject,
525
+ emailText,
526
+ emailHtml,
527
+ );
528
+
529
+ // update last sent/expiration
530
+ emailToken.lastSent = new Date();
531
+ emailToken.expiresAt = new Date(
532
+ Date.now() + this.application.constants.EmailTokenExpiration,
533
+ );
534
+ await emailToken.save({ session });
535
+ } catch {
536
+ throw new EmailTokenFailedToSendError(emailToken.type);
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Find a user by email or username and enforce account status checks
542
+ * @param email Optional email
543
+ * @param username Optional username
544
+ * @param session Optional mongoose session
545
+ * @throws UsernameOrEmailRequiredError if neither provided
546
+ * @throws InvalidCredentialsError if not found or deleted
547
+ * @throws AccountLockedError | PendingEmailVerificationError | AccountStatusError per status
548
+ */
549
+ public async findUser(
550
+ email?: string,
551
+ username?: string,
552
+ session?: ClientSession,
553
+ ): Promise<IUserDocument<TLanguage, TID>> {
554
+ if (!email && !username) {
555
+ throw new UsernameOrEmailRequiredError();
556
+ }
557
+ const UserModel = ModelRegistry.instance.getTypedModel<
558
+ IUserDocument<TLanguage, TID>
559
+ >(BaseModelName.User);
560
+ let userDoc: IUserDocument<TLanguage, TID> | null = null;
561
+
562
+ try {
563
+ if (email) {
564
+ userDoc = await UserModel.findOne({
565
+ email: email.toLowerCase(),
566
+ })
567
+ .session(session ?? null)
568
+ .exec();
569
+ } else if (username) {
570
+ userDoc = await UserModel.findOne({ username })
571
+ .collation({ locale: 'en', strength: 2 })
572
+ .session(session ?? null)
573
+ .exec();
574
+ }
575
+ } catch {
576
+ // Database error in findUser - convert to InvalidCredentialsError for security
577
+ throw new InvalidCredentialsError();
578
+ }
579
+
580
+ if (!userDoc || userDoc.deletedAt) {
581
+ if (email) {
582
+ throw new InvalidEmailError(InvalidEmailErrorType.Missing);
583
+ }
584
+ throw new InvalidUsernameError();
585
+ }
586
+
587
+ switch (userDoc.accountStatus) {
588
+ case AccountStatus.Active:
589
+ break;
590
+ case AccountStatus.AdminLock:
591
+ throw new AccountLockedError();
592
+ case AccountStatus.PendingEmailVerification:
593
+ throw new PendingEmailVerificationError();
594
+ default:
595
+ throw new AccountStatusError(userDoc.accountStatus);
596
+ }
597
+
598
+ return userDoc as IUserDocument<TLanguage, TID>;
599
+ }
600
+
601
+ /**
602
+ * Finds a user record by ID
603
+ * @param userId The user ID
604
+ * @param throwIfNotActive Whether to throw if the user is inactive
605
+ * @param session The active session, if present
606
+ * @returns The user document
607
+ */
608
+ public async findUserById(
609
+ userId: TID,
610
+ throwIfNotActive: boolean,
611
+ session?: ClientSession,
612
+ select?: ProjectionType<IUserDocument<TLanguage, TID>>,
613
+ ): Promise<IUserDocument<TLanguage, TID>> {
614
+ const UserModel = ModelRegistry.instance.getTypedModel<
615
+ IUserDocument<TLanguage, TID>
616
+ >(BaseModelName.User);
617
+ const baseQuery = UserModel.findById(userId).session(session ?? null);
618
+ if (select) {
619
+ // Always include fields needed for status checks
620
+ const merged = this.ensureRequiredFieldsInProjection(select, [
621
+ 'deletedAt',
622
+ 'accountStatus',
623
+ ]);
624
+ baseQuery.select(merged);
625
+ }
626
+ const userDoc = (await baseQuery.exec()) as IUserDocument<
627
+ TLanguage,
628
+ TID
629
+ > | null;
630
+ if (!userDoc || userDoc.deletedAt) {
631
+ throw new UserNotFoundError();
632
+ }
633
+ if (throwIfNotActive) {
634
+ switch (userDoc.accountStatus) {
635
+ case AccountStatus.Active:
636
+ break;
637
+ case AccountStatus.AdminLock:
638
+ throw new AccountLockedError();
639
+ case AccountStatus.PendingEmailVerification:
640
+ throw new PendingEmailVerificationError();
641
+ default:
642
+ throw new AccountStatusError(userDoc.accountStatus);
643
+ }
644
+ }
645
+ return userDoc;
646
+ }
647
+
648
+ /**
649
+ * Ensure required fields are present in a projection for queries that rely on them.
650
+ * Supports string and object-style projections. For inclusion projections, adds fields.
651
+ * For exclusion projections, ensures required fields are not excluded.
652
+ */
653
+ private ensureRequiredFieldsInProjection(
654
+ select: ProjectionType<IUserDocument<TLanguage, TID>>,
655
+ required: string[],
656
+ ): ProjectionType<IUserDocument<TLanguage, TID>> {
657
+ if (typeof select === 'string') {
658
+ const parts = select
659
+ .split(/\s+/)
660
+ .map((s) => s.trim())
661
+ .filter(Boolean);
662
+ const exclusions = new Set(
663
+ parts.filter((p) => p.startsWith('-')).map((p) => p.slice(1)),
664
+ );
665
+ // Remove exclusions on required fields
666
+ for (const r of required) {
667
+ exclusions.delete(r);
668
+ }
669
+ const cleaned = parts.filter((p) => !p.startsWith('-'));
670
+ for (const r of required) {
671
+ if (!cleaned.includes(r)) cleaned.push(r);
672
+ }
673
+ const result = [...cleaned, ...[...exclusions].map((r) => `-${r}`)];
674
+ return result.join(' ');
675
+ }
676
+ if (select && typeof select === 'object') {
677
+ const proj: ProjectionObject = { ...(select as ProjectionObject) };
678
+ const values = Object.values(proj);
679
+ const hasInclusions = values.some((v) => v === 1 || v === true);
680
+ if (hasInclusions) {
681
+ for (const r of required) {
682
+ proj[r] = 1;
683
+ }
684
+ } else {
685
+ const keysToRemove = required.filter(
686
+ (r) => proj[r] === 0 || proj[r] === false || proj[r] === -1,
687
+ );
688
+ keysToRemove.forEach((key) => delete proj[key]);
689
+ }
690
+ return proj as ProjectionType<IUserDocument<TLanguage, TID>>;
691
+ }
692
+ return select;
693
+ }
694
+
695
+ /**
696
+ * Fill in the default values to a user object
697
+ * @param newUser The user object to fill in
698
+ * @param createdBy The user ID of the user creating the new user
699
+ * @returns The filled in user
700
+ */
701
+ public fillUserDefaults(
702
+ newUser: ICreateUserBasics,
703
+ createdBy: TID,
704
+ backupCodes: Array<IBackupCode>,
705
+ encryptedMnemonic: string,
706
+ userId?: TID,
707
+ ): IUserBackendObject<TLanguage, TID> {
708
+ return {
709
+ ...(userId ? { _id: userId } : {}),
710
+ timezone: 'UTC',
711
+ ...newUser,
712
+ email: newUser.email.toLowerCase(),
713
+ emailVerified: false,
714
+ darkMode: false,
715
+ accountStatus: AccountStatus.PendingEmailVerification,
716
+ siteLanguage: 'en-US' as TLanguage,
717
+ duressPasswords: [],
718
+ publicKey: '',
719
+ backupCodes,
720
+ mnemonicRecovery: encryptedMnemonic,
721
+ currency: 'USD',
722
+ directChallenge: true,
723
+ createdAt: new Date(),
724
+ createdBy: createdBy,
725
+ updatedAt: new Date(),
726
+ updatedBy: createdBy,
727
+ } as IUserBackendObject<TLanguage, TID>;
728
+ }
729
+
730
+ /**
731
+ * Create a new user document from an IUser and unhashed password
732
+ * @param newUser The user object
733
+ * @returns The new user document
734
+ */
735
+ public async makeUserDoc(
736
+ newUser: TUser,
737
+ ): Promise<IUserDocument<TLanguage, TID>> {
738
+ const UserModel = ModelRegistry.instance.getTypedModel<
739
+ IUserDocument<TLanguage, TID>
740
+ >(BaseModelName.User);
741
+
742
+ const newUserDoc: IUserDocument<TLanguage, TID> = new UserModel(newUser);
743
+
744
+ const validationError = newUserDoc.validateSync();
745
+ if (validationError) {
746
+ throw new MongooseValidationError(validationError.errors);
747
+ }
748
+
749
+ return newUserDoc;
750
+ }
751
+
752
+ /**
753
+ * Create a new user.
754
+ * Do not set createdBy to a new (non-existing) ObjectId unless you also set newUserId to it.
755
+ * If newUserId is not set, one will be generated.
756
+ * @param systemUser The system user performing the operation
757
+ * @param userData Username, email, password in a ICreateUserBasics object
758
+ * @param createdBy The user id of the user creating the user
759
+ * @param newUserId the user id of the new user object- usually the createdBy user id.
760
+ * @param session The session to use for the query
761
+ * @param debug Whether to log debug information
762
+ * @param password The password to use for the new user (optional, if not provided, mnemonic will be used)
763
+ * @returns The new user document
764
+ */
765
+ public async newUser(
766
+ systemUser: BackendMember<TID>,
767
+ userData: ICreateUserBasics,
768
+ createdBy?: TID,
769
+ newUserId?: TID,
770
+ session?: ClientSession,
771
+ debug = false,
772
+ password?: string,
773
+ ): Promise<{
774
+ user: IUserDocument<TLanguage, TID>;
775
+ mnemonic: string;
776
+ backupCodes: Array<string>;
777
+ password?: string;
778
+ }> {
779
+ const provider = getEnhancedNodeIdProvider<TID>();
780
+ const _newUserId = newUserId ?? provider.generateTyped();
781
+ if (!this.application.constants.UsernameRegex.test(userData.username)) {
782
+ throw new InvalidUsernameError();
783
+ }
784
+ if (password && !this.application.constants.PasswordRegex.test(password)) {
785
+ throw new InvalidNewPasswordError();
786
+ }
787
+
788
+ const UserModel = ModelRegistry.instance.getTypedModel<
789
+ IUserDocument<TLanguage, TID>
790
+ >(BaseModelName.User);
791
+ return await this.withTransaction<{
792
+ user: IUserDocument<TLanguage, TID>;
793
+ backupCodes: Array<string>;
794
+ mnemonic: string;
795
+ }>(
796
+ async (sess: ClientSession | undefined) => {
797
+ const existingEmail: IUserDocument<TLanguage, TID> | null =
798
+ await UserModel.findOne({
799
+ email: userData.email.toLowerCase(),
800
+ }).session(sess ?? null);
801
+ if (existingEmail) {
802
+ throw new EmailInUseError();
803
+ }
804
+ const existingUsername: IUserDocument<TLanguage, TID> | null =
805
+ await UserModel.findOne({
806
+ username: { $regex: new RegExp(`^${userData.username}$`, 'i') },
807
+ }).session(sess ?? null);
808
+ if (existingUsername) {
809
+ throw new UsernameInUseError();
810
+ }
811
+
812
+ let mnemonic: SecureString | undefined;
813
+ let member: BackendMember<TID> | undefined;
814
+ while (!mnemonic || !member) {
815
+ try {
816
+ const { member: newMember, mnemonic: newMnemonic } =
817
+ BackendMember.newMember<TID>(
818
+ this.eciesService,
819
+ MemberType.User,
820
+ userData.username,
821
+ new EmailString(userData.email),
822
+ undefined,
823
+ createdBy,
824
+ );
825
+ // make sure the new mnemonic is not already in the database
826
+
827
+ const mnemonicExists = await this.mnemonicService.mnemonicExists(
828
+ newMnemonic,
829
+ sess,
830
+ );
831
+ if (!mnemonicExists) {
832
+ member = newMember;
833
+ mnemonic = newMnemonic;
834
+ }
835
+ } catch {
836
+ // If we fail to create a new member, we will retry until we succeed.
837
+ // This is to ensure that we do not end up with duplicate mnemonics.
838
+ debugLog(
839
+ debug,
840
+ 'warn',
841
+ 'Failed to create a new member, retrying...',
842
+ );
843
+ }
844
+ }
845
+
846
+ const backupCodes = BackupCode.generateBackupCodes();
847
+ const encryptedBackupCodes = await BackupCode.encryptBackupCodes(
848
+ member,
849
+ systemUser,
850
+ backupCodes,
851
+ );
852
+ const encryptedMnemonic = member
853
+ .encryptData(Buffer.from(mnemonic.value ?? '', 'utf-8'))
854
+ .toString('hex');
855
+
856
+ const newUserDoc = new UserModel({
857
+ ...this.fillUserDefaults(
858
+ userData,
859
+ createdBy ?? _newUserId,
860
+ encryptedBackupCodes,
861
+ encryptedMnemonic,
862
+ _newUserId,
863
+ ),
864
+ publicKey: member.publicKey.toString('hex'),
865
+ });
866
+
867
+ const validationError = newUserDoc.validateSync();
868
+ if (validationError) {
869
+ throw new MongooseValidationError(validationError.errors);
870
+ }
871
+
872
+ // Always add HMAC-only mnemonic doc
873
+ const newMnemonicDoc = await this.mnemonicService.addMnemonic(
874
+ mnemonic,
875
+ sess,
876
+ );
877
+ if (newMnemonicDoc) {
878
+ newUserDoc.mnemonicId = newMnemonicDoc._id as TID;
879
+ }
880
+
881
+ // If password provided, wrap the ECIES private key with the password (Option B)
882
+ if (password) {
883
+ const passwordSecure = new SecureString(password);
884
+ try {
885
+ const priv = new SecureBuffer(member.privateKey!.value);
886
+ try {
887
+ const wrapped = this.keyWrappingService.wrapSecret(
888
+ priv,
889
+ passwordSecure,
890
+ this.application.constants,
891
+ );
892
+ newUserDoc.passwordWrappedPrivateKey = wrapped;
893
+ } finally {
894
+ priv.dispose();
895
+ }
896
+ } finally {
897
+ passwordSecure.dispose();
898
+ }
899
+ }
900
+
901
+ const savedUserDoc = await newUserDoc.save({ session: sess });
902
+
903
+ const memberRoleId = await this.roleService.getRoleIdByName(
904
+ this.application.constants.MemberRole as Role,
905
+ sess,
906
+ );
907
+
908
+ if (!memberRoleId) {
909
+ throw new TranslatableSuiteError(
910
+ SuiteCoreStringKey.Error_FailedToLookupRoleTemplate,
911
+ {
912
+ ROLE: getSuiteCoreTranslation(SuiteCoreStringKey.Common_Member),
913
+ },
914
+ );
915
+ }
916
+
917
+ await this.roleService.addUserToRole(
918
+ memberRoleId,
919
+ savedUserDoc._id,
920
+ _newUserId,
921
+ sess,
922
+ );
923
+
924
+ return {
925
+ user: savedUserDoc,
926
+ mnemonic: mnemonic.value ?? '',
927
+ backupCodes: backupCodes.map((code: BackupCode) => code.value ?? ''),
928
+ ...(password ? { password } : {}),
929
+ };
930
+ },
931
+ session,
932
+ {
933
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 10,
934
+ },
935
+ );
936
+ }
937
+
938
+ /**
939
+ * Get the backup codes for a user.
940
+ * Requires the user not be deleted or inactive
941
+ */
942
+ public async getEncryptedUserBackupCodes(
943
+ userId: TID,
944
+ session?: ClientSession,
945
+ ): Promise<Array<IBackupCode>> {
946
+ const userWithCodes = await this.findUserById(userId, true, session);
947
+ return userWithCodes.backupCodes;
948
+ }
949
+
950
+ /**
951
+ * Resets the given user's backup codes
952
+ * @param backupUser The user to generate codes for
953
+ * @param session The current session, if any
954
+ * @returns A promise of an array of backup codes
955
+ */
956
+ public async resetUserBackupCodes(
957
+ backupUser: BackendMember<TID>,
958
+ systemUser: BackendMember<TID>,
959
+ session?: ClientSession,
960
+ ): Promise<Array<BackupCode>> {
961
+ if (!backupUser.hasPrivateKey) {
962
+ throw new PrivateKeyRequiredError();
963
+ }
964
+ const backupCodes = BackupCode.generateBackupCodes();
965
+ const encryptedBackupCodes = await BackupCode.encryptBackupCodes(
966
+ backupUser,
967
+ systemUser,
968
+ backupCodes,
969
+ );
970
+ const UserModel = ModelRegistry.instance.get('User')?.model;
971
+ return await this.withTransaction<Array<BackupCode>>(
972
+ async (sess: ClientSession | undefined) => {
973
+ await UserModel.updateOne(
974
+ { _id: backupUser.id },
975
+ { $set: { backupCodes: encryptedBackupCodes } },
976
+ { session: sess },
977
+ );
978
+ return backupCodes;
979
+ },
980
+ session,
981
+ {
982
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
983
+ },
984
+ );
985
+ }
986
+
987
+ /**
988
+ * Recover a user's mnemonic from an encrypted mnemonic
989
+ * @param user The user whose mnemonic to recover
990
+ * @param encryptedMnemonic The encrypted mnemonic
991
+ * @returns The recovered mnemonic
992
+ */
993
+ public recoverMnemonic(
994
+ user: BackendMember<any>,
995
+ encryptedMnemonic: string,
996
+ ): SecureString {
997
+ if (!encryptedMnemonic) {
998
+ throw new TranslatableSuiteHandleableError(
999
+ SuiteCoreStringKey.MnemonicRecovery_Missing,
1000
+ undefined,
1001
+ undefined,
1002
+ {
1003
+ statusCode: 400,
1004
+ },
1005
+ );
1006
+ }
1007
+
1008
+ return new SecureString(
1009
+ user.decryptData(Buffer.from(encryptedMnemonic, 'hex')).toString('utf-8'),
1010
+ );
1011
+ }
1012
+
1013
+ /**
1014
+ * Make a Member from a user document and optional private key
1015
+ * @param userDoc The user document
1016
+ * @param privateKey Optional private key to load the wallet
1017
+ * @param publicKey Optional public key to override the userDoc public key
1018
+ * @param session The current session, if any
1019
+ * @returns A promise containing the created Member
1020
+ */
1021
+ public async makeUserFromUserDoc(
1022
+ userDoc: IUserDocument<TLanguage, TID>,
1023
+ privateKey?: SecureBuffer,
1024
+ publicKey?: Buffer,
1025
+ mnemonic?: SecureString,
1026
+ wallet?: Wallet,
1027
+ session?: ClientSession,
1028
+ ): Promise<BackendMember<TID>> {
1029
+ const memberType = await this.roleService.getMemberType(userDoc, session);
1030
+ const user = new BackendMember<TID>(
1031
+ this.eciesService,
1032
+ memberType,
1033
+ userDoc.username,
1034
+ new EmailString(userDoc.email),
1035
+ publicKey ?? Buffer.from(userDoc.publicKey, 'hex'),
1036
+ privateKey,
1037
+ wallet,
1038
+ userDoc._id,
1039
+ new Date(userDoc.createdAt),
1040
+ new Date(userDoc.updatedAt),
1041
+ userDoc.createdBy,
1042
+ );
1043
+ if (
1044
+ (privateKey?.originalLength ?? -1) > 0 &&
1045
+ user.hasPrivateKey &&
1046
+ !wallet
1047
+ ) {
1048
+ user.loadWallet(
1049
+ mnemonic ?? this.recoverMnemonic(user, userDoc.mnemonicRecovery),
1050
+ );
1051
+ }
1052
+ return user;
1053
+ }
1054
+
1055
+ /**
1056
+ * Challenges a given userDoc with a given mnemonic, returns a system and user Member
1057
+ * @param userDoc The userDoc in question
1058
+ * @param mnemonic The mnemonic to challenge against
1059
+ * @returns A promise containing the user and system Members
1060
+ * @throws InvalidCredentialsError if the challenge fails
1061
+ * @throws AccountLockedError if the account is locked
1062
+ * @throws PendingEmailVerificationError if the email is not verified
1063
+ * @throws AccountStatusError if the account status is invalid
1064
+ */
1065
+ public async challengeUserWithMnemonic(
1066
+ userDoc: IUserDocument<TLanguage, TID>,
1067
+ mnemonic: SecureString,
1068
+ session?: ClientSession,
1069
+ ): Promise<{
1070
+ userMember: BackendMember<TID>;
1071
+ adminMember: BackendMember<TID>;
1072
+ }> {
1073
+ try {
1074
+ // Verify provided mnemonic corresponds to the stored mnemonic HMAC (no password required)
1075
+ // This prevents any valid mnemonic from authenticating as another user.
1076
+ const MnemonicModel =
1077
+ ModelRegistry.instance.getTypedModel<IMnemonicDocument>(
1078
+ BaseModelName.Mnemonic,
1079
+ );
1080
+ if (!userDoc.mnemonicId) {
1081
+ throw new InvalidCredentialsError();
1082
+ }
1083
+ const mnemonicDoc = await MnemonicModel.findById(userDoc.mnemonicId)
1084
+ .select('hmac')
1085
+ .session(session ?? null)
1086
+ .lean()
1087
+ .exec();
1088
+ if (!mnemonicDoc) {
1089
+ throw new InvalidCredentialsError();
1090
+ }
1091
+ const computedHmac = this.mnemonicService.getMnemonicHmac(mnemonic);
1092
+ if (computedHmac !== mnemonicDoc.hmac) {
1093
+ throw new InvalidCredentialsError();
1094
+ }
1095
+
1096
+ // Create a Member from the provided mnemonic to get the keys
1097
+ const { wallet } = this.eciesService.walletAndSeedFromMnemonic(mnemonic);
1098
+ const privateKey = wallet.getPrivateKey();
1099
+ // Get compressed public key (already includes prefix)
1100
+ const publicKeyWithPrefix = this.eciesService.getPublicKey(
1101
+ Buffer.from(privateKey),
1102
+ );
1103
+ const userMember = await this.makeUserFromUserDoc(
1104
+ userDoc,
1105
+ new SecureBuffer(privateKey),
1106
+ publicKeyWithPrefix,
1107
+ mnemonic,
1108
+ wallet,
1109
+ session,
1110
+ );
1111
+
1112
+ // Verify the public key matches the stored userDoc public key
1113
+ if (userMember.publicKey.toString('hex') !== userDoc.publicKey) {
1114
+ throw new InvalidCredentialsError();
1115
+ }
1116
+
1117
+ // Generate a nonce challenge to verify they can decrypt with their key
1118
+ const adminMember = SystemUserService.getSystemUser<TID>(
1119
+ this.application.environment,
1120
+ this.application.constants,
1121
+ );
1122
+ const nonce = randomBytes(32);
1123
+ const signature = adminMember.sign(nonce);
1124
+ const payload = Buffer.concat([nonce, signature]);
1125
+
1126
+ const encryptedPayload = userMember.encryptData(payload);
1127
+ const decryptedPayload = userMember.decryptData(encryptedPayload);
1128
+
1129
+ // Verify the server's signature on the nonce
1130
+ const decryptedNonce = decryptedPayload.subarray(0, 32);
1131
+ const decryptedSignature = decryptedPayload.subarray(32);
1132
+
1133
+ const isSignatureValid = adminMember.verify(
1134
+ decryptedSignature as SignatureBuffer,
1135
+ decryptedNonce,
1136
+ );
1137
+
1138
+ if (!isSignatureValid || !nonce.equals(decryptedNonce)) {
1139
+ throw new InvalidCredentialsError();
1140
+ }
1141
+
1142
+ return {
1143
+ userMember,
1144
+ adminMember: adminMember,
1145
+ };
1146
+ } catch (error) {
1147
+ if (
1148
+ error instanceof InvalidCredentialsError ||
1149
+ error instanceof AccountLockedError ||
1150
+ error instanceof PendingEmailVerificationError ||
1151
+ error instanceof AccountStatusError
1152
+ ) {
1153
+ throw error;
1154
+ }
1155
+ throw new InvalidCredentialsError();
1156
+ }
1157
+ }
1158
+
1159
+ /**
1160
+ * Validates a login challenge response
1161
+ * @param challengeResponse The challenge response bytes in hex
1162
+ * @param email The email address of the user
1163
+ * @param username The username of the user
1164
+ * @param session The mongo session for the query
1165
+ * @returns A promise that resolves to the user document, user member, and system member
1166
+ */
1167
+ public async loginWithChallengeResponse(
1168
+ challengeResponse: string,
1169
+ email?: string,
1170
+ username?: string,
1171
+ session?: ClientSession,
1172
+ ): Promise<{
1173
+ userDoc: IUserDocument<TLanguage, TID>;
1174
+ userMember: BackendMember<TID>;
1175
+ adminMember: BackendMember<TID>;
1176
+ }> {
1177
+ const challengeBuffer = Buffer.from(challengeResponse, 'hex');
1178
+ // validate the expected challenge response length (8 + 32 + 64 = 104 bytes)
1179
+ if (
1180
+ challengeBuffer.length !=
1181
+ this.application.constants.DirectLoginChallengeLength
1182
+ ) {
1183
+ throw new InvalidChallengeResponseError();
1184
+ }
1185
+ // disassemble the challengeResponse into time, nonce, signature
1186
+ const time = challengeBuffer.subarray(0, 8); // 16 hex characters
1187
+ const nonce = challengeBuffer.subarray(8, 40); // 64 hex characters
1188
+ const signature = challengeBuffer.subarray(40); // 65 * 2 hex characters
1189
+
1190
+ const timeMs = parseInt(time.toString('hex'), 16);
1191
+ if (
1192
+ new Date().getTime() - timeMs >
1193
+ this.application.constants.LoginChallengeExpiration
1194
+ ) {
1195
+ throw new LoginChallengeExpiredError();
1196
+ }
1197
+
1198
+ const userDoc = await this.findUser(email, username, session);
1199
+ if (!userDoc && email) {
1200
+ throw new InvalidEmailError(InvalidEmailErrorType.Missing);
1201
+ } else if (!userDoc) {
1202
+ throw new InvalidUsernameError();
1203
+ }
1204
+ // re-sign the time + nonce and check if the signature matches
1205
+ const adminMember = SystemUserService.getSystemUser<TID>(
1206
+ this.application.environment,
1207
+ this.application.constants,
1208
+ );
1209
+ const timeAndNonce = Buffer.concat([time, nonce]);
1210
+ const expectedSignature = adminMember.sign(timeAndNonce);
1211
+ if (expectedSignature.toString('hex') !== signature.toString('hex')) {
1212
+ throw new InvalidChallengeResponseError();
1213
+ }
1214
+
1215
+ const userMember = await this.makeUserFromUserDoc(
1216
+ userDoc,
1217
+ undefined,
1218
+ undefined,
1219
+ undefined,
1220
+ undefined,
1221
+ session,
1222
+ );
1223
+
1224
+ return {
1225
+ userDoc,
1226
+ userMember,
1227
+ adminMember: adminMember,
1228
+ };
1229
+ }
1230
+
1231
+ /**
1232
+ * Authenticate a user with client-verified challenge (skips server-side challenge)
1233
+ * @returns The authenticated user document.
1234
+ */
1235
+ public async loginWithClientVerifiedChallenge(
1236
+ usernameOrEmail: string,
1237
+ mnemonic: SecureString,
1238
+ session?: ClientSession,
1239
+ ): Promise<{
1240
+ userDoc: IUserDocument<TLanguage, TID>;
1241
+ userMember: BackendMember<TID>;
1242
+ adminMember: BackendMember<TID>;
1243
+ }> {
1244
+ const UserModel = this.application.getModel<IUserDocument<TLanguage, TID>>(
1245
+ BaseModelName.User,
1246
+ );
1247
+ const userQuery = validator.isEmail(usernameOrEmail)
1248
+ ? UserModel.findOne({ email: usernameOrEmail.toLowerCase() }).select(
1249
+ '_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey',
1250
+ )
1251
+ : UserModel.findOne({ username: usernameOrEmail })
1252
+ .collation({ locale: 'en', strength: 2 })
1253
+ .select(
1254
+ '_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey',
1255
+ );
1256
+ const userDoc = await userQuery.session(session ?? null);
1257
+
1258
+ if (!userDoc || userDoc.deletedAt) {
1259
+ throw new InvalidCredentialsError();
1260
+ }
1261
+
1262
+ // Check account status
1263
+ switch (userDoc.accountStatus) {
1264
+ case AccountStatus.Active:
1265
+ break;
1266
+ case AccountStatus.AdminLock:
1267
+ throw new AccountLockedError();
1268
+ case AccountStatus.PendingEmailVerification:
1269
+ throw new PendingEmailVerificationError();
1270
+ default:
1271
+ throw new AccountStatusError(userDoc.accountStatus);
1272
+ }
1273
+
1274
+ // Verify mnemonic matches user (simplified verification)
1275
+ try {
1276
+ const MnemonicModel = this.application.getModel<IMnemonicDocument>(
1277
+ BaseModelName.Mnemonic,
1278
+ );
1279
+ if (!userDoc.mnemonicId) {
1280
+ throw new InvalidCredentialsError();
1281
+ }
1282
+ const mnemonicDoc = await MnemonicModel.findById(userDoc.mnemonicId)
1283
+ .select('hmac')
1284
+ .session(session ?? null)
1285
+ .lean()
1286
+ .exec();
1287
+ if (!mnemonicDoc) {
1288
+ throw new InvalidCredentialsError();
1289
+ }
1290
+ const computedHmac = this.mnemonicService.getMnemonicHmac(mnemonic);
1291
+ if (computedHmac !== mnemonicDoc.hmac) {
1292
+ throw new InvalidCredentialsError();
1293
+ }
1294
+
1295
+ // Create Member from mnemonic
1296
+ const { wallet } = this.eciesService.walletAndSeedFromMnemonic(mnemonic);
1297
+ const privateKey = wallet.getPrivateKey();
1298
+ // Get compressed public key (already includes prefix)
1299
+ const publicKeyWithPrefix = this.eciesService.getPublicKey(
1300
+ Buffer.from(privateKey),
1301
+ );
1302
+ const userMember = await this.makeUserFromUserDoc(
1303
+ userDoc,
1304
+ new SecureBuffer(privateKey),
1305
+ publicKeyWithPrefix,
1306
+ mnemonic,
1307
+ wallet,
1308
+ session,
1309
+ );
1310
+
1311
+ // Verify public key matches
1312
+ if (userMember.publicKey.toString('hex') !== userDoc.publicKey) {
1313
+ throw new InvalidCredentialsError();
1314
+ }
1315
+
1316
+ const adminMember = SystemUserService.getSystemUser<TID>(
1317
+ this.application.environment,
1318
+ this.application.constants,
1319
+ );
1320
+
1321
+ return {
1322
+ userMember,
1323
+ adminMember,
1324
+ userDoc,
1325
+ };
1326
+ } catch (error) {
1327
+ if (
1328
+ error instanceof InvalidCredentialsError ||
1329
+ error instanceof AccountLockedError ||
1330
+ error instanceof PendingEmailVerificationError ||
1331
+ error instanceof AccountStatusError
1332
+ ) {
1333
+ throw error;
1334
+ }
1335
+ throw new InvalidCredentialsError();
1336
+ }
1337
+ }
1338
+
1339
+ /**
1340
+ * Authenticate a user with their mnemonic.
1341
+ * @returns The authenticated user document.
1342
+ */
1343
+ public async loginWithMnemonic(
1344
+ usernameOrEmail: string,
1345
+ mnemonic: SecureString,
1346
+ session?: ClientSession,
1347
+ ): Promise<{
1348
+ userDoc: IUserDocument<TLanguage, TID>;
1349
+ userMember: BackendMember<TID>;
1350
+ adminMember: BackendMember<TID>;
1351
+ }> {
1352
+ const UserModel = ModelRegistry.instance.getTypedModel<
1353
+ IUserDocument<TLanguage, TID>
1354
+ >(BaseModelName.User);
1355
+ const userQuery = validator.isEmail(usernameOrEmail)
1356
+ ? UserModel.findOne({ email: usernameOrEmail.toLowerCase() }).select(
1357
+ '_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey',
1358
+ )
1359
+ : UserModel.findOne({ username: usernameOrEmail })
1360
+ .collation({ locale: 'en', strength: 2 })
1361
+ .select(
1362
+ '_id username email accountStatus deletedAt mnemonicId publicKey passwordWrappedPrivateKey',
1363
+ );
1364
+ const userDoc = await userQuery.session(session ?? null);
1365
+
1366
+ if (!userDoc || userDoc.deletedAt) {
1367
+ throw new InvalidCredentialsError();
1368
+ }
1369
+
1370
+ // Check account status
1371
+ switch (userDoc.accountStatus) {
1372
+ case AccountStatus.Active:
1373
+ break;
1374
+ case AccountStatus.AdminLock:
1375
+ throw new AccountLockedError();
1376
+ case AccountStatus.PendingEmailVerification:
1377
+ throw new PendingEmailVerificationError();
1378
+ default:
1379
+ throw new AccountStatusError(userDoc.accountStatus);
1380
+ }
1381
+
1382
+ const challengeResponse = await this.challengeUserWithMnemonic(
1383
+ userDoc,
1384
+ mnemonic,
1385
+ session,
1386
+ );
1387
+ return { ...challengeResponse, userDoc };
1388
+ }
1389
+
1390
+ /**
1391
+ * Authenticate a user with their password (for key-wrapped accounts).
1392
+ * @returns The authenticated user document.
1393
+ */
1394
+ public async loginWithPassword(
1395
+ usernameOrEmail: string,
1396
+ password: string,
1397
+ session?: ClientSession,
1398
+ ): Promise<{
1399
+ userDoc: IUserDocument<TLanguage, TID>;
1400
+ userMember: BackendMember<TID>;
1401
+ adminMember: BackendMember<TID>;
1402
+ }> {
1403
+ const UserModel = this.application.getModel<IUserDocument<TLanguage, TID>>(
1404
+ BaseModelName.User,
1405
+ );
1406
+ const query = validator.isEmail(usernameOrEmail)
1407
+ ? UserModel.findOne({ email: usernameOrEmail.toLowerCase() })
1408
+ : UserModel.findOne({ username: usernameOrEmail }).collation({
1409
+ locale: 'en',
1410
+ strength: 2,
1411
+ });
1412
+
1413
+ const userDoc: IUserDocument<TLanguage, TID> | null = await query
1414
+ .session(session ?? null)
1415
+ .exec();
1416
+
1417
+ if (!userDoc || userDoc.deletedAt) {
1418
+ throw new InvalidCredentialsError();
1419
+ }
1420
+
1421
+ // Check account status
1422
+ switch (userDoc.accountStatus) {
1423
+ case AccountStatus.Active:
1424
+ break;
1425
+ case AccountStatus.AdminLock:
1426
+ throw new AccountLockedError();
1427
+ case AccountStatus.PendingEmailVerification:
1428
+ throw new PendingEmailVerificationError();
1429
+ default:
1430
+ throw new AccountStatusError(userDoc.accountStatus);
1431
+ }
1432
+
1433
+ // Check if user has password-based authentication set up (Option B requires passwordWrappedPrivateKey)
1434
+ if (!userDoc.passwordWrappedPrivateKey || !userDoc.mnemonicId) {
1435
+ throw new PasswordLoginNotEnabledError();
1436
+ }
1437
+ // Unwrap password-wrapped private key and complete challenge with possession of private key
1438
+ const unwrapped = await this.keyWrappingService.unwrapSecretAsync(
1439
+ userDoc.passwordWrappedPrivateKey!,
1440
+ password,
1441
+ this.application.constants,
1442
+ );
1443
+
1444
+ // Build user member with unwrapped private key to decrypt challenge
1445
+ // Note: userMember now owns the unwrapped SecureBuffer, so we don't dispose it here
1446
+ const userMember = await this.makeUserFromUserDoc(
1447
+ userDoc,
1448
+ unwrapped,
1449
+ undefined,
1450
+ undefined,
1451
+ undefined,
1452
+ session,
1453
+ );
1454
+
1455
+ // Generate a nonce challenge signed by system
1456
+ const adminMember = SystemUserService.getSystemUser<TID>(
1457
+ this.application.environment,
1458
+ this.application.constants,
1459
+ );
1460
+ const nonce = randomBytes(32);
1461
+ const signature = adminMember.sign(nonce);
1462
+ const payload = Buffer.concat([nonce, signature]);
1463
+
1464
+ const encryptedPayload = userMember.encryptData(payload);
1465
+ const decryptedPayload = userMember.decryptData(encryptedPayload);
1466
+
1467
+ const decryptedNonce = decryptedPayload.subarray(0, 32);
1468
+ const decryptedSignature = decryptedPayload.subarray(32);
1469
+
1470
+ const isSignatureValid = adminMember.verify(
1471
+ decryptedSignature as SignatureBuffer,
1472
+ decryptedNonce,
1473
+ );
1474
+ if (!isSignatureValid || !nonce.equals(decryptedNonce)) {
1475
+ throw new InvalidCredentialsError();
1476
+ }
1477
+ return { userDoc, userMember, adminMember: adminMember };
1478
+ }
1479
+
1480
+ /**
1481
+ * Re-send a previously sent email token
1482
+ * @param userId The user id
1483
+ * @param session The session to use for the query
1484
+ * @returns void
1485
+ * @throws EmailTokenUsedOrInvalidError
1486
+ */
1487
+ public async resendEmailToken(
1488
+ userId: string,
1489
+ type: EmailTokenType,
1490
+ session?: ClientSession,
1491
+ debug = false,
1492
+ ): Promise<void> {
1493
+ const EmailTokenModel =
1494
+ ModelRegistry.instance.getTypedModel<IEmailTokenDocument>(
1495
+ BaseModelName.EmailToken,
1496
+ );
1497
+ return await this.withTransaction<void>(
1498
+ async (sess: ClientSession | undefined) => {
1499
+ // look up the most recent email token for a given user, then send it
1500
+ const emailToken: IEmailTokenDocument | null =
1501
+ await EmailTokenModel.findOne({
1502
+ userId,
1503
+ type,
1504
+ expiresAt: { $gt: new Date() },
1505
+ })
1506
+ .session(sess ?? null)
1507
+ .sort({ createdAt: -1 })
1508
+ .limit(1);
1509
+
1510
+ if (!emailToken) {
1511
+ throw new EmailTokenUsedOrInvalidError();
1512
+ }
1513
+
1514
+ await this.sendEmailToken(emailToken, sess, debug);
1515
+ },
1516
+ session,
1517
+ {
1518
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1519
+ },
1520
+ );
1521
+ }
1522
+
1523
+ /**
1524
+ * Verify the email token and update the user's account status
1525
+ * @param emailToken The email token to verify
1526
+ * @param session The session to use for the query
1527
+ * @returns void
1528
+ * @throws EmailTokenUsedOrInvalidError
1529
+ * @throws EmailTokenExpiredError
1530
+ * @throws EmailVerifiedError
1531
+ * @throws UserNotFoundError
1532
+ */
1533
+ public async verifyAccountTokenAndComplete(
1534
+ emailToken: string,
1535
+ session?: ClientSession,
1536
+ ): Promise<void> {
1537
+ let alreadyVerified = false;
1538
+
1539
+ await this.withTransaction<void>(
1540
+ async (sess: ClientSession | undefined) => {
1541
+ const EmailTokenModel =
1542
+ ModelRegistry.instance.getTypedModel<IEmailTokenDocument>(
1543
+ BaseModelName.EmailToken,
1544
+ );
1545
+ const UserModel = ModelRegistry.instance.getTypedModel<
1546
+ IUserDocument<TLanguage, TID>
1547
+ >(BaseModelName.User);
1548
+ const token: IEmailTokenDocument | null = await this.findEmailToken(
1549
+ emailToken,
1550
+ EmailTokenType.AccountVerification,
1551
+ sess,
1552
+ );
1553
+
1554
+ if (!token) {
1555
+ throw new EmailTokenUsedOrInvalidError();
1556
+ }
1557
+
1558
+ if (token.expiresAt < new Date()) {
1559
+ await EmailTokenModel.deleteOne({ _id: token._id }).session(
1560
+ sess ?? null,
1561
+ );
1562
+ throw new EmailTokenExpiredError();
1563
+ }
1564
+
1565
+ const user: IUserDocument<TLanguage, TID> | null =
1566
+ await UserModel.findById(token.userId).session(sess ?? null);
1567
+
1568
+ if (!user || user.deletedAt) {
1569
+ throw new UserNotFoundError();
1570
+ }
1571
+
1572
+ if (user.emailVerified) {
1573
+ // Delete the token and mark to throw error after transaction commits
1574
+ await EmailTokenModel.deleteOne({ _id: token._id }).session(
1575
+ sess ?? null,
1576
+ );
1577
+ alreadyVerified = true;
1578
+ return;
1579
+ }
1580
+
1581
+ // set user email to token email and mark as verified
1582
+ user.email = token.email;
1583
+ user.emailVerified = true;
1584
+ user.accountStatus = AccountStatus.Active;
1585
+ user.updatedBy = user._id;
1586
+ await user.save({ session: sess });
1587
+
1588
+ // Delete the token after successful verification
1589
+ await EmailTokenModel.deleteOne({ _id: token._id }).session(
1590
+ sess ?? null,
1591
+ );
1592
+
1593
+ // add the user to the member role
1594
+ const memberRoleId = await this.roleService.getRoleIdByName(
1595
+ this.application.constants.MemberRole as Role,
1596
+ sess,
1597
+ );
1598
+ if (memberRoleId) {
1599
+ await this.roleService.addUserToRole(
1600
+ memberRoleId,
1601
+ user._id,
1602
+ user._id,
1603
+ sess,
1604
+ );
1605
+ } else {
1606
+ throw new Error('Member role not found');
1607
+ }
1608
+ },
1609
+ session,
1610
+ {
1611
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1612
+ },
1613
+ );
1614
+
1615
+ if (alreadyVerified) {
1616
+ throw new EmailVerifiedError(409);
1617
+ }
1618
+ }
1619
+
1620
+ /**
1621
+ * Validate the email token
1622
+ * @param token The token to validate
1623
+ * @param restrictType The type of email token to validate (or throw)
1624
+ * @param session The session to use for the query
1625
+ * @returns void
1626
+ * @throws EmailTokenUsedOrInvalidError
1627
+ */
1628
+ public async validateEmailToken(
1629
+ token: string,
1630
+ restrictType?: EmailTokenType,
1631
+ session?: ClientSession,
1632
+ ): Promise<void> {
1633
+ return await this.withTransaction<void>(
1634
+ async (ses: ClientSession | undefined) => {
1635
+ const EmailTokenModel = this.application.getModel<IEmailTokenDocument>(
1636
+ BaseModelName.EmailToken,
1637
+ );
1638
+ const emailToken = await EmailTokenModel.findOne({
1639
+ token,
1640
+ ...(restrictType ? { type: EmailTokenType.PasswordReset } : {}),
1641
+ }).session(ses ?? null);
1642
+
1643
+ if (!emailToken) {
1644
+ throw new EmailTokenUsedOrInvalidError();
1645
+ } else if (emailToken.expiresAt < new Date()) {
1646
+ await EmailTokenModel.deleteOne({ _id: emailToken._id }).session(
1647
+ ses ?? null,
1648
+ );
1649
+ throw new EmailTokenExpiredError();
1650
+ }
1651
+
1652
+ // nothing else to do here, token is valid
1653
+ },
1654
+ session,
1655
+ {
1656
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1657
+ },
1658
+ );
1659
+ }
1660
+
1661
+ /**
1662
+ * Updates the user's language
1663
+ * @param userId - The ID of the user
1664
+ * @param newLanguage - The new language
1665
+ * @param session - The session to use for the query
1666
+ * @returns The updated user
1667
+ */
1668
+ public async updateSiteLanguage(
1669
+ userId: string,
1670
+ newLanguage: string,
1671
+ session?: ClientSession,
1672
+ ): Promise<IRequestUserDTO> {
1673
+ const provider = getEnhancedNodeIdProvider<TID>();
1674
+ return await this.withTransaction<IRequestUserDTO>(
1675
+ async (sess: ClientSession | undefined) => {
1676
+ const UserModel = ModelRegistry.instance.getTypedModel<
1677
+ IUserDocument<TLanguage, TID>
1678
+ >(BaseModelName.User);
1679
+ const userDoc = await UserModel.findByIdAndUpdate(
1680
+ provider.idFromString(userId),
1681
+ {
1682
+ siteLanguage: newLanguage,
1683
+ },
1684
+ { new: true },
1685
+ ).session(sess ?? null);
1686
+ if (!userDoc) {
1687
+ throw new UserNotFoundError();
1688
+ }
1689
+ const roles = await this.roleService.getUserRoles(userDoc._id);
1690
+ const tokenRoles = this.roleService.rolesToTokenRoles(roles);
1691
+ return RequestUserService.makeRequestUserDTO(userDoc, tokenRoles);
1692
+ },
1693
+ session,
1694
+ {
1695
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1696
+ },
1697
+ );
1698
+ }
1699
+
1700
+ /**
1701
+ * Updates the user's Dark Mode preference
1702
+ * @param userId - The ID of the user
1703
+ * @param newDarkMode - The new Dark Mode preference
1704
+ * @param session - The session to use for the query
1705
+ * @returns The updated user
1706
+ */
1707
+ public async updateDarkMode(
1708
+ userId: string,
1709
+ newDarkMode: boolean,
1710
+ session?: ClientSession,
1711
+ ): Promise<IRequestUserDTO> {
1712
+ const provider = getEnhancedNodeIdProvider<TID>();
1713
+ return await this.withTransaction<IRequestUserDTO>(
1714
+ async (sess: ClientSession | undefined) => {
1715
+ const UserModel = ModelRegistry.instance.getTypedModel<
1716
+ IUserDocument<TLanguage, TID>
1717
+ >(BaseModelName.User);
1718
+ const userDoc = await UserModel.findByIdAndUpdate(
1719
+ provider.idFromString(userId),
1720
+ {
1721
+ darkMode: newDarkMode,
1722
+ },
1723
+ { new: true },
1724
+ ).session(sess ?? null);
1725
+ if (!userDoc) {
1726
+ throw new UserNotFoundError();
1727
+ }
1728
+ const roles = await this.roleService.getUserRoles(userDoc._id);
1729
+ const tokenRoles = this.roleService.rolesToTokenRoles(roles);
1730
+ return RequestUserService.makeRequestUserDTO(userDoc, tokenRoles);
1731
+ },
1732
+ session,
1733
+ {
1734
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1735
+ },
1736
+ );
1737
+ }
1738
+
1739
+ /**
1740
+ * Updates multiple user settings at once
1741
+ * @param userId - The ID of the user
1742
+ * @param settings - Object containing settings to update
1743
+ * @param session - The session to use for the query
1744
+ * @returns The updated user
1745
+ */
1746
+ public async updateUserSettings(
1747
+ userId: string,
1748
+ settings: {
1749
+ email?: string;
1750
+ timezone?: string;
1751
+ siteLanguage?: string;
1752
+ currency?: string;
1753
+ darkMode?: boolean;
1754
+ directChallenge?: boolean;
1755
+ },
1756
+ session?: ClientSession,
1757
+ ): Promise<IRequestUserDTO> {
1758
+ const provider = getEnhancedNodeIdProvider<TID>();
1759
+ return await this.withTransaction<IRequestUserDTO>(
1760
+ async (sess: ClientSession | undefined) => {
1761
+ const UserModel = ModelRegistry.instance.getTypedModel<
1762
+ IUserDocument<TLanguage, TID>
1763
+ >(BaseModelName.User);
1764
+ const userDoc = await UserModel.findById(
1765
+ provider.idFromString(userId),
1766
+ ).session(sess ?? null);
1767
+ if (!userDoc) {
1768
+ throw new UserNotFoundError();
1769
+ }
1770
+
1771
+ // Check if email is changing and if it's already in use
1772
+ if (
1773
+ settings.email &&
1774
+ settings.email.toLowerCase() !== userDoc.email.toLowerCase()
1775
+ ) {
1776
+ const existingUser = await UserModel.findOne({
1777
+ email: settings.email.toLowerCase(),
1778
+ _id: { $ne: userDoc._id },
1779
+ }).session(sess ?? null);
1780
+ if (existingUser) {
1781
+ throw new EmailInUseError();
1782
+ }
1783
+ // Send verification email for new address
1784
+ userDoc.email = settings.email.toLowerCase();
1785
+ await this.createAndSendEmailTokenDirect(
1786
+ userDoc,
1787
+ EmailTokenType.AccountVerification,
1788
+ sess!,
1789
+ this.application.environment.debug,
1790
+ );
1791
+ }
1792
+
1793
+ // Update other settings
1794
+ if (settings.timezone !== undefined)
1795
+ userDoc.timezone = settings.timezone;
1796
+ if (settings.siteLanguage !== undefined)
1797
+ userDoc.siteLanguage = settings.siteLanguage as TLanguage;
1798
+ if (settings.darkMode !== undefined)
1799
+ userDoc.darkMode = settings.darkMode;
1800
+ if (settings.currency !== undefined)
1801
+ userDoc.currency = settings.currency;
1802
+ if (settings.directChallenge !== undefined)
1803
+ userDoc.directChallenge = settings.directChallenge;
1804
+
1805
+ await userDoc.save({ session: sess });
1806
+
1807
+ const roles = await this.roleService.getUserRoles(userDoc._id);
1808
+ const tokenRoles = this.roleService.rolesToTokenRoles(roles);
1809
+ return RequestUserService.makeRequestUserDTO(userDoc, tokenRoles);
1810
+ },
1811
+ session,
1812
+ {
1813
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1814
+ },
1815
+ );
1816
+ }
1817
+
1818
+ /**
1819
+ * Changes the user's password by re-wrapping their master key
1820
+ * @param userId - The ID of the user
1821
+ * @param currentPassword - The current password
1822
+ * @param newPassword - The new password
1823
+ * @param session - The session to use for the query
1824
+ * @returns void
1825
+ */
1826
+ public async changePassword(
1827
+ userId: string,
1828
+ currentPassword: string,
1829
+ newPassword: string,
1830
+ session?: ClientSession,
1831
+ ): Promise<void> {
1832
+ const provider = getEnhancedNodeIdProvider<TID>();
1833
+ return await this.withTransaction<void>(
1834
+ async (sess: ClientSession | undefined) => {
1835
+ const UserModel = ModelRegistry.instance.getTypedModel<
1836
+ IUserDocument<TLanguage, TID>
1837
+ >(BaseModelName.User);
1838
+ const userDoc = await UserModel.findById(
1839
+ provider.idFromString(userId),
1840
+ ).session(sess ?? null);
1841
+ if (!userDoc || !userDoc.passwordWrappedPrivateKey) {
1842
+ throw new UserNotFoundError();
1843
+ }
1844
+
1845
+ if (!this.application.constants.PasswordRegex.test(newPassword)) {
1846
+ throw new InvalidNewPasswordError();
1847
+ }
1848
+
1849
+ const currentPasswordSecure = new SecureString(currentPassword);
1850
+ const newPasswordSecure = new SecureString(newPassword);
1851
+
1852
+ try {
1853
+ // Unwrap existing private key and rewrap under new password
1854
+ const priv = this.keyWrappingService.unwrapSecret(
1855
+ userDoc.passwordWrappedPrivateKey,
1856
+ currentPasswordSecure,
1857
+ );
1858
+ try {
1859
+ const wrapped = this.keyWrappingService.wrapSecret(
1860
+ priv,
1861
+ newPasswordSecure,
1862
+ this.application.constants,
1863
+ );
1864
+ userDoc.passwordWrappedPrivateKey = wrapped;
1865
+ await userDoc.save({ session: sess });
1866
+ } finally {
1867
+ priv.dispose();
1868
+ }
1869
+ } catch (error: unknown) {
1870
+ // Re-throw original error so controller can map it properly
1871
+ // Re-throw original error so controller can map it properly
1872
+ throw error as Error;
1873
+ } finally {
1874
+ currentPasswordSecure.dispose();
1875
+ newPasswordSecure.dispose();
1876
+ }
1877
+ },
1878
+ session,
1879
+ {
1880
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1881
+ },
1882
+ );
1883
+ }
1884
+
1885
+ /**
1886
+ * Retrieve an email token by its token string and type
1887
+ * @param token - The token string
1888
+ * @param type - The type of the email token
1889
+ * @param session - The session to use for the query
1890
+ * @returns The email token document or null if not found
1891
+ */
1892
+ public async findEmailToken(
1893
+ token: string,
1894
+ type?: EmailTokenType,
1895
+ session?: ClientSession,
1896
+ ): Promise<IEmailTokenDocument | null> {
1897
+ const EmailTokenModel =
1898
+ ModelRegistry.instance.getTypedModel<IEmailTokenDocument>(
1899
+ BaseModelName.EmailToken,
1900
+ );
1901
+ return await EmailTokenModel.findOne({
1902
+ token: token.toLowerCase().trim(),
1903
+ ...(type ? { type } : {}),
1904
+ expiresAt: { $gt: new Date() },
1905
+ }).session(session ?? null);
1906
+ }
1907
+
1908
+ /**
1909
+ * Verify email token is valid
1910
+ * @param token - The email token
1911
+ * @param session - The session to use for the query
1912
+ * @returns void
1913
+ */
1914
+ public async verifyEmailToken(
1915
+ token: string,
1916
+ type: EmailTokenType,
1917
+ session?: ClientSession,
1918
+ ): Promise<void> {
1919
+ return await this.withTransaction<void>(
1920
+ async (sess: ClientSession | undefined) => {
1921
+ // Find and validate the token
1922
+ const emailToken = await this.findEmailToken(token, type, sess);
1923
+
1924
+ if (!emailToken) {
1925
+ throw new EmailTokenUsedOrInvalidError();
1926
+ }
1927
+ },
1928
+ session,
1929
+ {
1930
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
1931
+ },
1932
+ );
1933
+ }
1934
+
1935
+ /**
1936
+ * Reset password using email token
1937
+ * @param token - The email token
1938
+ * @param newPassword - The new password
1939
+ * @param session - The session to use for the query
1940
+ * @returns void
1941
+ */
1942
+ public async resetPasswordWithToken(
1943
+ token: string,
1944
+ newPassword: string,
1945
+ credential?: string, // either mnemonic or current password; required
1946
+ session?: ClientSession,
1947
+ ): Promise<void> {
1948
+ if (!this.application.constants.PasswordRegex.test(newPassword)) {
1949
+ throw new InvalidNewPasswordError();
1950
+ }
1951
+ if (!credential) {
1952
+ throw new EmailTokenUsedOrInvalidError();
1953
+ }
1954
+
1955
+ return await this.withTransaction<void>(
1956
+ async (sess: ClientSession | undefined) => {
1957
+ const EmailTokenModel =
1958
+ ModelRegistry.instance.getTypedModel<IEmailTokenDocument>(
1959
+ BaseModelName.EmailToken,
1960
+ );
1961
+ const UserModel = ModelRegistry.instance.getTypedModel<
1962
+ IUserDocument<TLanguage, TID>
1963
+ >(BaseModelName.User);
1964
+
1965
+ // Find and validate the token
1966
+ const emailToken = await this.findEmailToken(
1967
+ token,
1968
+ EmailTokenType.PasswordReset,
1969
+ sess,
1970
+ );
1971
+
1972
+ if (!emailToken) {
1973
+ throw new EmailTokenUsedOrInvalidError();
1974
+ }
1975
+
1976
+ // Find the user
1977
+ const userDoc = await UserModel.findById(emailToken.userId).session(
1978
+ sess ?? null,
1979
+ );
1980
+ if (!userDoc) {
1981
+ throw new UserNotFoundError();
1982
+ }
1983
+ // Update password-wrapped secrets based on credential type (Option B)
1984
+ const newPasswordSecure = new SecureString(newPassword);
1985
+ try {
1986
+ if (this.application.constants.MnemonicRegex.test(credential)) {
1987
+ // Credential is mnemonic: verify it belongs to this user via public key
1988
+ const providedMnemonic = new SecureString(credential);
1989
+ try {
1990
+ const { wallet } =
1991
+ this.eciesService.walletAndSeedFromMnemonic(providedMnemonic);
1992
+ const privateKey = wallet.getPrivateKey();
1993
+ // Get compressed public key (already includes prefix)
1994
+ const pub = this.eciesService.getPublicKey(
1995
+ Buffer.from(privateKey),
1996
+ );
1997
+ if (pub.toString('hex') !== userDoc.publicKey) {
1998
+ throw new InvalidCredentialsError();
1999
+ }
2000
+
2001
+ // Wrap private key with new password
2002
+ const priv = new SecureBuffer(privateKey);
2003
+ try {
2004
+ const wrappedPriv = this.keyWrappingService.wrapSecret(
2005
+ priv,
2006
+ newPasswordSecure,
2007
+ this.application.constants,
2008
+ );
2009
+ userDoc.passwordWrappedPrivateKey = wrappedPriv;
2010
+ await userDoc.save({ session: sess });
2011
+ } finally {
2012
+ priv.dispose();
2013
+ }
2014
+ } finally {
2015
+ providedMnemonic.dispose();
2016
+ }
2017
+ } else {
2018
+ // Credential is current password: unwrap existing master key
2019
+ if (!userDoc.passwordWrappedPrivateKey) {
2020
+ throw new InvalidCredentialsError();
2021
+ }
2022
+ const privateKeyBuf =
2023
+ await this.keyWrappingService.unwrapSecretAsync(
2024
+ userDoc.passwordWrappedPrivateKey!,
2025
+ credential,
2026
+ this.application.constants,
2027
+ );
2028
+ try {
2029
+ // Re-wrap the existing private key under the new password
2030
+ const wrappedPriv = this.keyWrappingService.wrapSecret(
2031
+ privateKeyBuf,
2032
+ newPasswordSecure,
2033
+ this.application.constants,
2034
+ );
2035
+ userDoc.passwordWrappedPrivateKey = wrappedPriv;
2036
+ await userDoc.save({ session: sess });
2037
+ } finally {
2038
+ privateKeyBuf.dispose();
2039
+ }
2040
+ }
2041
+
2042
+ // Delete the used token
2043
+ await EmailTokenModel.deleteOne({ _id: emailToken._id }).session(
2044
+ sess ?? null,
2045
+ );
2046
+
2047
+ // Dispose temporary master key
2048
+ } finally {
2049
+ newPasswordSecure.dispose();
2050
+ }
2051
+ },
2052
+ session,
2053
+ {
2054
+ timeoutMs: this.application.environment.mongo.transactionTimeout * 5,
2055
+ },
2056
+ );
2057
+ }
2058
+
2059
+ /**
2060
+ * Generate a login challenge for the client to sign
2061
+ * @returns The login challenge in hex
2062
+ */
2063
+ public generateDirectLoginChallenge(): string {
2064
+ const adminMember = SystemUserService.getSystemUser<TID>(
2065
+ this.application.environment,
2066
+ this.application.constants,
2067
+ );
2068
+ const time = Buffer.alloc(8);
2069
+ time.writeBigUInt64BE(BigInt(new Date().getTime()));
2070
+ const nonce = randomBytes(32);
2071
+ const signature = adminMember.sign(Buffer.concat([time, nonce]));
2072
+ return Buffer.concat([time, nonce, signature]).toString('hex');
2073
+ }
2074
+
2075
+ /**
2076
+ * Verifies a direct login challenge response
2077
+ * @param serverSignedRequest The login challenge response in hex
2078
+ * @param session The mongoose session, if provided
2079
+ * @returns A promise with the user document and user member object
2080
+ */
2081
+ public async verifyDirectLoginChallenge(
2082
+ serverSignedRequest: string,
2083
+ signature: string,
2084
+ username?: string,
2085
+ email?: string,
2086
+ session?: ClientSession,
2087
+ ): Promise<{
2088
+ userDoc: IUserDocument<TLanguage, TID>;
2089
+ userMember: BackendMember<TID>;
2090
+ }> {
2091
+ return await this.withTransaction<{
2092
+ userDoc: IUserDocument<TLanguage, TID>;
2093
+ userMember: BackendMember<TID>;
2094
+ }>(
2095
+ async (sess: ClientSession | undefined) => {
2096
+ // serverSignedRequest is:
2097
+ // time (8) +
2098
+ // nonce (32) +
2099
+ // server signature (64) +
2100
+ // signature (64)
2101
+ if (
2102
+ serverSignedRequest.length <
2103
+ (8 + 32 + this.application.constants.ECIES.SIGNATURE_SIZE) * 2
2104
+ ) {
2105
+ throw new InvalidChallengeResponseError();
2106
+ }
2107
+ // get signed request into a buffer
2108
+ const requestBuffer = Buffer.from(serverSignedRequest, 'hex');
2109
+ // start tracking offset
2110
+ let offset = 0;
2111
+ // get the time
2112
+ const time = requestBuffer.subarray(offset, 8);
2113
+ offset += 8;
2114
+ // get the nonce
2115
+ const nonce = requestBuffer.subarray(offset, 40);
2116
+ offset += 32;
2117
+ // get the server signature
2118
+ const serverSignature = requestBuffer.subarray(
2119
+ offset,
2120
+ this.application.constants.ECIES.SIGNATURE_SIZE + 40,
2121
+ );
2122
+ offset += this.application.constants.ECIES.SIGNATURE_SIZE;
2123
+ const signedDataLength = offset;
2124
+ if (offset !== requestBuffer.length) {
2125
+ throw new InvalidChallengeResponseError();
2126
+ }
2127
+ // validate time is within acceptable range
2128
+ const timeMs = time.readBigUInt64BE();
2129
+ if (
2130
+ new Date().getTime() - Number(timeMs) >
2131
+ this.application.constants.LoginChallengeExpiration
2132
+ ) {
2133
+ throw new LoginChallengeExpiredError();
2134
+ }
2135
+ // validate the server's signature on the time + nonce portion
2136
+ const adminMember = SystemUserService.getSystemUser<TID>(
2137
+ this.application.environment,
2138
+ this.application.constants,
2139
+ );
2140
+ if (
2141
+ !adminMember.verify(
2142
+ serverSignature as SignatureBuffer,
2143
+ Buffer.concat([time, nonce]),
2144
+ )
2145
+ ) {
2146
+ throw new InvalidChallengeResponseError();
2147
+ }
2148
+ // locate the user by email or username
2149
+ const userDoc = await this.findUser(email, username, sess);
2150
+ if (!userDoc) {
2151
+ throw new InvalidChallengeResponseError();
2152
+ }
2153
+ // get the user's member object
2154
+ const user = await this.makeUserFromUserDoc(
2155
+ userDoc,
2156
+ undefined,
2157
+ undefined,
2158
+ undefined,
2159
+ undefined,
2160
+ sess,
2161
+ );
2162
+ // get the signed portion of the response
2163
+ const signedData = requestBuffer.subarray(0, signedDataLength);
2164
+ // verify the user's signature on the signed portion
2165
+ if (
2166
+ !user.verify(
2167
+ Buffer.from(signature, 'hex') as SignatureBuffer,
2168
+ signedData,
2169
+ )
2170
+ ) {
2171
+ throw new InvalidChallengeResponseError();
2172
+ }
2173
+
2174
+ if (userDoc.directChallenge !== true) {
2175
+ throw new DirectChallengeNotEnabledError();
2176
+ }
2177
+
2178
+ // if the user is valid, try to use the token (prevents replay attacks)
2179
+ await DirectLoginTokenService.useToken<TID>(
2180
+ this.application,
2181
+ userDoc._id,
2182
+ nonce.toString('hex'),
2183
+ );
2184
+
2185
+ // if successful, update lastLogin
2186
+ await this.updateLastLogin(userDoc._id);
2187
+
2188
+ // return the user document and member object
2189
+ return { userDoc, userMember: user };
2190
+ },
2191
+ session,
2192
+ { timeoutMs: this.application.environment.mongo.transactionTimeout },
2193
+ );
2194
+ }
2195
+
2196
+ /**
2197
+ * Request a login link via email
2198
+ * @param email Email address
2199
+ * @param username Username
2200
+ * @param session Existing session, if any
2201
+ * @returns void
2202
+ */
2203
+ public async requestEmailLogin(
2204
+ email?: string,
2205
+ username?: string,
2206
+ session?: ClientSession,
2207
+ ): Promise<void> {
2208
+ return this.withTransaction<void>(
2209
+ async (sess: ClientSession | undefined) => {
2210
+ const userDoc = await this.findUser(email, username, sess);
2211
+ if (!userDoc) {
2212
+ return;
2213
+ }
2214
+ await this.createAndSendEmailToken(
2215
+ userDoc,
2216
+ EmailTokenType.LoginRequest,
2217
+ sess,
2218
+ this.application.environment.debug,
2219
+ );
2220
+ },
2221
+ session,
2222
+ {
2223
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
2224
+ },
2225
+ );
2226
+ }
2227
+
2228
+ /**
2229
+ * Validate an email login token challenge
2230
+ * @param token The token to challenge
2231
+ * @param signature The signature of the token by the user's private key
2232
+ * @param session The session to use for the query
2233
+ * @returns The user document if the challenge is valid
2234
+ */
2235
+ public async validateEmailLoginTokenChallenge(
2236
+ token: string,
2237
+ signature: string,
2238
+ session?: ClientSession,
2239
+ ): Promise<IUserDocument<TLanguage, TID>> {
2240
+ return this.withTransaction<IUserDocument<TLanguage, TID>>(
2241
+ async (sess: ClientSession | undefined) => {
2242
+ const emailToken = await this.findEmailToken(
2243
+ token,
2244
+ EmailTokenType.LoginRequest,
2245
+ sess,
2246
+ );
2247
+ if (!emailToken) {
2248
+ throw new EmailTokenUsedOrInvalidError();
2249
+ }
2250
+ const userDoc = await this.findUser(emailToken.email, undefined, sess);
2251
+ if (!userDoc) {
2252
+ throw new UserNotFoundError();
2253
+ }
2254
+ const user = await this.makeUserFromUserDoc(
2255
+ userDoc,
2256
+ undefined,
2257
+ undefined,
2258
+ undefined,
2259
+ undefined,
2260
+ sess,
2261
+ );
2262
+ const result = user.verify(
2263
+ Buffer.from(signature, 'hex') as SignatureBuffer,
2264
+ Buffer.from(token, 'hex'),
2265
+ );
2266
+ if (!result) {
2267
+ throw new InvalidChallengeResponseError();
2268
+ }
2269
+ await emailToken.deleteOne({ session: sess ?? null });
2270
+ await this.updateLastLogin(userDoc._id);
2271
+ return userDoc;
2272
+ },
2273
+ session,
2274
+ {
2275
+ timeoutMs: this.application.environment.mongo.transactionTimeout,
2276
+ },
2277
+ );
2278
+ }
2279
+
2280
+ /**
2281
+ * Updates the user's last login time atomically
2282
+ * @param userId - The ID of the user
2283
+ * @returns void
2284
+ */
2285
+ public async updateLastLogin(userId: TID): Promise<void> {
2286
+ const UserModel = ModelRegistry.instance.get('User')?.model;
2287
+ try {
2288
+ // Check if the database connection is still open
2289
+ const connection = this.application.db.connection;
2290
+ if (connection.readyState !== 1) {
2291
+ // Connection is not open (0 = disconnected, 1 = connected, 2 = connecting, 3 = disconnecting)
2292
+ return; // Silently return if connection is not available
2293
+ }
2294
+
2295
+ // Use atomic update to avoid conflicts and ensure we only update lastLogin
2296
+ // Use a separate session to avoid interfering with any ongoing transactions
2297
+ await UserModel.updateOne(
2298
+ { _id: userId },
2299
+ {
2300
+ $set: { lastLogin: new Date() },
2301
+ $setOnInsert: {}, // Prevent any unintended document creation
2302
+ },
2303
+ {
2304
+ upsert: false, // Never create a new document
2305
+ runValidators: false, // Skip validation for performance since we're only updating lastLogin
2306
+ // Don't use any session to avoid transaction conflicts
2307
+ },
2308
+ );
2309
+ } catch (error) {
2310
+ // Check if the error is due to client being closed
2311
+ if (
2312
+ error instanceof Error &&
2313
+ (error.message.includes('client was closed') ||
2314
+ error.message.includes('MongoClientClosedError') ||
2315
+ error.name === 'MongoClientClosedError')
2316
+ ) {
2317
+ // This is expected during shutdown, don't log it as an error
2318
+ return;
2319
+ }
2320
+
2321
+ // If this fails, it's not critical for login functionality. Ignore and move on.
2322
+ }
2323
+ }
2324
+ }