import { UserService } from '@loopback/authentication' ; import { inject } from '@loopback/context' ; import { repository } from '@loopback/repository' ; import { HttpErrors } from '@loopback/rest' ; import { securityId , UserProfile } from '@loopback/security' ; import { PasswordHasherBindings } from '../keys' ; import { User , UserWithPassword } from '../models' ; import { Credentials , UserRepository } from '../repositories' ; import { PasswordHasher } from './hash.password.bcryptjs' ; import _ from 'lodash' ; import { EmailService } from './email.service' ; import { v4 as uuidv4 } from 'uuid' ; import { subtractDates } from '../utils' ; import { SentMessageInfo } from 'nodemailer' ; export class UserManagementService implements UserService < User , Credentials > { constructor ( @ repository ( UserRepository ) public userRepository , @ inject ( PasswordHasherBindings . PASSWORD_HASHER ) public passwordHasher , @ inject ( 'services.EmailService' ) public emailService ) { } async verifyCredentials ( credentials ) { const { email , password } = credentials ; const invalidCredentialsError = 'Invalid email or password.' ; if ( ! email ) { throw new HttpErrors . Unauthorized ( invalidCredentialsError ) ; } const foundUser = await this . userRepository . findOne ( { where : { email } , } ) ; if ( ! foundUser ) { throw new HttpErrors . Unauthorized ( invalidCredentialsError ) ; } const credentialsFound = await this . userRepository . findCredentials ( foundUser . id ) ; if ( ! credentialsFound ) { throw new HttpErrors . Unauthorized ( invalidCredentialsError ) ; } const passwordMatched = await this . passwordHasher . comparePassword ( password , credentialsFound . password ) ; if ( ! passwordMatched ) { throw new HttpErrors . Unauthorized ( invalidCredentialsError ) ; } return foundUser ; } convertToUserProfile ( user ) { let userName = '' ; if ( user . firstName ) userName = `${ user . firstName }` ; if ( user . lastName ) userName = user . firstName ? `${ userName } ${ user . lastName }` : `${ user . lastName }` ; return { [ securityId ] : user . id , name : userName , id : user . id , roles : user . roles , } ; } async createUser ( userWithPassword ) { const password = await this . passwordHasher . hashPassword ( userWithPassword . password ) ; userWithPassword . password = password ; const user = await this . userRepository . create ( _ . omit ( userWithPassword , 'password' ) ) ; user . id = user . id . toString ( ) ; await this . userRepository . userCredentials ( user . id ) . create ( { password } ) ; return user ; } async requestPasswordReset ( email ) { const noAccountFoundError = 'No account associated with the provided email address.' ; const foundUser = await this . userRepository . findOne ( { where : { email } , } ) ; if ( ! foundUser ) { throw new HttpErrors . NotFound ( noAccountFoundError ) ; } const user = await this . updateResetRequestLimit ( foundUser ) ; try { await this . userRepository . updateById ( user . id , user ) ; } catch ( e ) { return e ; } return this . emailService . sendResetPasswordMail ( user ) ; } param user async updateResetRequestLimit ( user ) { const resetTimestampDate = new Date ( user . resetTimestamp ) ; const difference = await subtractDates ( resetTimestampDate ) ; if ( difference === 0 ) { user . resetCount = user . resetCount + 1 ; if ( user . resetCount > + ( process . env . PASSWORD_RESET_EMAIL_LIMIT ?? 2 ) ) { throw new HttpErrors . TooManyRequests ( 'Account has reached daily limit for sending password-reset requests' ) ; } } else { user . resetTimestamp = new Date ( ) . toLocaleDateString ( ) ; user . resetCount = 1 ; } user . resetKey = uuidv4 ( ) ; user . resetKeyTimestamp = new Date ( ) . toLocaleDateString ( ) ; return user ; } param user async validateResetKeyLifeSpan ( user ) { const resetKeyLifeSpan = new Date ( user . resetKeyTimestamp ) ; const difference = await subtractDates ( resetKeyLifeSpan ) ; user . resetKey = '' ; user . resetKeyTimestamp = '' ; if ( difference !== 0 ) { throw new HttpErrors . BadRequest ( 'The provided reset key has expired.' ) ; } return user ; } }