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