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
JavaScript
;
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