UNPKG

lbx-jwt

Version:

Provides JWT authentication for loopback applications. Includes storing roles inside tokens and handling refreshing. Built-in reuse detection.

147 lines 7.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseUserService = void 0; const tslib_1 = require("tslib"); const crypto_1 = require("crypto"); const core_1 = require("@loopback/core"); const repository_1 = require("@loopback/repository"); const rest_1 = require("@loopback/rest"); const security_1 = require("@loopback/security"); const base_biometric_credentials_service_1 = require("./base-biometric-credentials.service"); const base_mail_service_1 = require("./mail/base-mail.service"); const bcrypt_utilities_1 = require("../encapsulation/bcrypt.utilities"); const keys_1 = require("../keys"); const repositories_1 = require("../repositories"); const password_reset_token_repository_1 = require("../repositories/password-reset-token.repository"); /** * The base user service used for authentication and authorization. */ // eslint-disable-next-line stylistic/max-len let BaseUserService = class BaseUserService { constructor(userRepository, passwordResetTokenRepository, passwordResetTokenExpiresInMs, dataSource, mailService, biometricCredentialsService, biometricCredentialsRepository) { this.userRepository = userRepository; this.passwordResetTokenRepository = passwordResetTokenRepository; this.passwordResetTokenExpiresInMs = passwordResetTokenExpiresInMs; this.dataSource = dataSource; this.mailService = mailService; this.biometricCredentialsService = biometricCredentialsService; this.biometricCredentialsRepository = biometricCredentialsRepository; this.INVALID_CREDENTIALS_ERROR_MESSAGE = 'Invalid email or password.'; } // eslint-disable-next-line jsdoc/require-jsdoc async verifyCredentials(credentials) { if (this.isEmailPasswordCredentials(credentials)) { return this.verifyEmailPasswordCredentials(credentials); } return this.verifyBiometricCredentials(credentials); } isEmailPasswordCredentials(value) { return !!value.email; } /** * Verify the identity of a user with email and password. * @param credentials - Email and password. * @returns The identified user. */ async verifyEmailPasswordCredentials(credentials) { const foundUser = await this.userRepository.findOne({ where: { email: credentials.email } }); if (!foundUser) { throw new rest_1.HttpErrors.Unauthorized(this.INVALID_CREDENTIALS_ERROR_MESSAGE); } const credentialsFound = await this.userRepository.credentials(foundUser.id).get(); const passwordMatched = await bcrypt_utilities_1.BcryptUtilities.compare(credentials.password, credentialsFound.password); if (!passwordMatched) { throw new rest_1.HttpErrors.Unauthorized(this.INVALID_CREDENTIALS_ERROR_MESSAGE); } return foundUser; } /** * Verify the identity of a user with a biometric credential. * @param credentials - The biometric credentials. * @returns The identified user. */ async verifyBiometricCredentials(credentials) { const biometricCredentials = await this.userRepository.biometricCredentials(credentials.userId).find(); const biometricCredential = biometricCredentials.find(bc => bc.credentialId === credentials.id); if (!biometricCredential) { throw new rest_1.HttpErrors.NotFound('Could not find the biometric credential'); } const oldCounter = biometricCredential.counter; try { const verification = await this.biometricCredentialsService.verifyAuthenticationResponse(credentials, biometricCredential); if (!verification.verified) { throw new Error('Could not verify the biometric credential'); } await this.biometricCredentialsRepository.updateById(biometricCredential.id, { counter: verification.authenticationInfo.newCounter }); return this.userRepository.findById(credentials.userId); } catch (error) { await this.biometricCredentialsRepository.updateById(biometricCredential.id, { counter: oldCounter }); throw new rest_1.HttpErrors.Unauthorized('Could not verify the biometric credential'); } } // eslint-disable-next-line jsdoc/require-jsdoc convertToUserProfile(user) { return { [security_1.securityId]: user.id, id: user.id, email: user.email, roles: user.roles }; } /** * Requests the reset of the password. * @param requestResetPassword - Contains the email of the user which password should be reset. */ async requestResetPassword(requestResetPassword) { const user = await this.userRepository.findOne({ where: { email: requestResetPassword.email } }); if (!user) { throw new rest_1.HttpErrors.NotFound(`No User with email ${requestResetPassword.email} found.`); } if (await this.activeResetLinkAlreadyExists(user)) { throw new rest_1.HttpErrors.TooManyRequests('A reset link has already been requested for this account.'); } const transaction = await this.dataSource.beginTransaction(repository_1.IsolationLevel.READ_COMMITTED); try { const resetTokenData = { expirationDate: new Date(Date.now() + this.passwordResetTokenExpiresInMs), baseUserId: user.id, value: (0, crypto_1.randomBytes)(16).toString('hex') }; const resetToken = await this.passwordResetTokenRepository.create(resetTokenData, { transaction: transaction }); await this.mailService.sendResetPasswordMail(user, resetToken); await transaction.commit(); } catch (error) { await transaction.rollback(); throw error; } } async activeResetLinkAlreadyExists(user) { const existingToken = await this.passwordResetTokenRepository.findOne({ where: { baseUserId: user.id } }); if (existingToken) { if (new Date(existingToken.expirationDate).getTime() > Date.now()) { return true; } await this.passwordResetTokenRepository.deleteById(existingToken.id); } return false; } }; exports.BaseUserService = BaseUserService; exports.BaseUserService = BaseUserService = tslib_1.__decorate([ tslib_1.__param(0, (0, core_1.inject)(keys_1.LbxJwtBindings.BASE_USER_REPOSITORY)), tslib_1.__param(1, (0, core_1.inject)(keys_1.LbxJwtBindings.PASSWORD_RESET_TOKEN_REPOSITORY)), tslib_1.__param(2, (0, core_1.inject)(keys_1.LbxJwtBindings.PASSWORD_RESET_TOKEN_EXPIRES_IN_MS)), tslib_1.__param(3, (0, core_1.inject)(keys_1.LbxJwtBindings.DATASOURCE_KEY)), tslib_1.__param(4, (0, core_1.inject)(keys_1.LbxJwtBindings.MAIL_SERVICE)), tslib_1.__param(5, (0, core_1.inject)(keys_1.LbxJwtBindings.BIOMETRIC_CREDENTIALS_SERVICE)), tslib_1.__param(6, (0, core_1.inject)(keys_1.LbxJwtBindings.BIOMETRIC_CREDENTIALS_REPOSITORY)), tslib_1.__metadata("design:paramtypes", [repositories_1.BaseUserRepository, password_reset_token_repository_1.PasswordResetTokenRepository, Number, repository_1.juggler.DataSource, base_mail_service_1.BaseMailService, base_biometric_credentials_service_1.BaseBiometricCredentialsService, repositories_1.BiometricCredentialsRepository]) ], BaseUserService); //# sourceMappingURL=base-user.service.js.map