import * as crypto from 'crypto' ; import * as path from 'path' ; import { BadRequestException , ConflictException , GoneException , HttpService , Injectable , InternalServerErrorException , NotFoundException , } from '@nestjs/common' ; import { CertificateUserDTO } from '../dto/certificate-user.dto' ; import { ChangePasswordService } from './change-password.service' ; import { MailerService } from '@nest-modules/mailer' ; import { RoleService } from '../../SecurityModule/service/role.service' ; import { Role } from '../../SecurityModule/entity/role.entity' ; import { Transactional } from 'typeorm-transactional-cls-hooked' ; import { AppConfigService as ConfigService } from '../../ConfigModule/service/app-config.service' ; import { ChangePassword } from '../entity/change-password.entity' ; import { AdminChangePasswordDTO } from '../dto/admin-change-password.dto' ; import { UserRepository } from '../repository/user.repository' ; import { User } from '../entity/user.entity' ; import { ForgotPasswordDTO } from '../dto/forgot-password' ; import { ChangePasswordForgotFlowDTO } from '../dto/change-password-forgot-flow.dto' ; import { UserNotFoundError } from '../../SecurityModule/exception/user-not-found.error' ; import { NewUserDTO } from '../dto/new-user.dto' ; import { UserUpdateDTO } from '../dto/user-update.dto' ; import { ChangePasswordDTO } from '../dto/change-password.dto' ; import { AchievementService } from '../../GameficationModule/service/achievement.service' ; import { BadgeWithQuantityDTO } from '../../GameficationModule/dto/badge-with-quantity.dto' ; import { PublisherService } from '../../GameficationModule/service/publisher.service' ; import { UploadService } from '../../UploadModule/service/upload.service' ; import { RoleEnum } from '../../SecurityModule/enum/role.enum' ; import { SemearService } from './semear.service' ; import SecurePassword = require ( 'secure-password' ) ; import { SecurityIntegration } from '../../SecurityModule/integration/security.integration' ; const securePassword = require ( 'secure-password' ) ; @ Injectable ( ) export class UserService { constructor ( private readonly repository , private readonly http , private readonly changePasswordService , private readonly mailerService , private readonly configService , private readonly roleService , private readonly achievementService , private readonly uploadService , private readonly semearService , private readonly publisherService , private readonly securityIntegration ) { } @ Transactional ( ) public async getAll ( ) { return this . repository . find ( ) ; } public async findById ( id ) { const response = await this . repository . find ( { where : { id } , relations : [ 'role' ] , } ) ; const user = response [ 0 ] ; if ( ! user ) { throw new NotFoundException ( 'User not found' ) ; } return user ; } @ Transactional ( ) public async getCertificateByUser ( userId ) { const certificates = await this . repository . getCertificateByUser ( userId ) ; return certificates . map < CertificateUserDTO > ( ( certificate ) => { const c = new CertificateUserDTO ( ) ; c . id = certificate . certificate_id ; c . title = certificate . certificate_title ; c . userName = certificate . user_name ; c . text = certificate . certificate_text ; c . certificateBackgroundName = certificate . certificate_certificateBackgroundName ; return c ; } ) ; } public async getUsersQuantity ( ) { return this . repository . getUsersQuantity ( ) ; } public async countUsersInvitedByUserId ( id ) { return this . repository . countUsersInvitedByUserId ( id ) ; } public async hashUserPassword ( user , password ) { const pwd = securePassword ( ) ; const hashBuffer = await pwd . hash ( Buffer . from ( password ) ) ; return this . repository . save ( { ... user , password : hashBuffer . toString ( ) , } ) ; } public async addStudent ( user , inviteKey ) { let invitedByUserId ; if ( inviteKey ) { const invitedByUser = await this . repository . findByInviteKey ( inviteKey ) ; invitedByUserId = invitedByUser ? invitedByUser . id : null ; } const newStudent = await this . add ( { ... user , invitedByUserId } ) ; await this . securityIntegration . addNewStudent ( { id : newStudent . id , username : newStudent . email , password : user . password , } ) ; if ( inviteKey ) { this . publisherService . emitInviteUserReward ( inviteKey ) ; } return newStudent ; } public async add ( user ) { const role = await this . roleService . findByRoleName ( user . role ) ; const salt = this . createSalt ( ) ; const hashPassword = this . createHashedPassword ( user . password , salt ) ; try { return await this . repository . save ( { ... user , salt , password : hashPassword , inviteKey : `${ this . generateInviteKey ( ) }${ this . generateInviteKey ( ) }` , role , } ) ; } catch ( e ) { if ( e . code === 'ER_DUP_ENTRY' ) { throw new ConflictException ( 'User with same email already exists' ) ; } throw new InternalServerErrorException ( e . message ) ; } } @ Transactional ( ) public async delete ( id ) { await this . findById ( id ) ; await this . repository . delete ( id ) ; } public async update ( id , userUpdatedInfo ) { const { role , ... restUser } = await this . findById ( id ) ; const updatedUser = await this . repository . save ( { ... restUser , ... userUpdatedInfo , id , } ) ; this . publisherService . emitupdateStudent ( id ) ; this . semearService . sendSemearNotification ( updatedUser ) ; return updatedUser ; } public async forgotPassword ( forgotPasswordDTO ) { const user = await this . findByEmail ( forgotPasswordDTO . email ) ; const changePassword = await this . changePasswordService . createChangePasswordRequest ( user ) ; await this . sendChangePasswordEmail ( user , changePassword . id ) ; return changePassword . id ; } @ Transactional ( ) public async findByEmail ( email ) { const user = await this . repository . findByEmail ( email ) ; if ( ! user ) { throw new UserNotFoundError ( ) ; } return user ; } @ Transactional ( ) public async findByEmailAndPassword ( email , password ) { const user = await this . findByEmail ( email ) ; if ( ! user . validPassword ( password ) ) { throw new UserNotFoundError ( ) ; } return user ; } @ Transactional ( ) public async findByEmailAndFacebookId ( email , facebookId ) { const user = await this . repository . findByEmailAndFacebookId ( email , facebookId ) ; if ( ! user ) { throw new UserNotFoundError ( ) ; } return user ; } @ Transactional ( ) public async validateChangePassword ( changePasswordRequestId ) { const changePassword = await this . changePasswordService . findById ( changePasswordRequestId ) ; if ( Date . now ( ) > new Date ( changePassword . createdAt ) . getTime ( ) + changePassword . expirationTime ) { throw new GoneException ( ) ; } } @ Transactional ( ) public async adminChangePassword ( id , changePasswordDTO ) { if ( changePasswordDTO . newPassword !== changePasswordDTO . confirmNewPassword ) throw new BadRequestException ( 'New passwords are not the same' ) ; const user = await this . findById ( id ) ; const oldPassword = this . createHashedPassword ( changePasswordDTO . oldPassword , user . salt ) ; if ( oldPassword !== user . password ) throw new BadRequestException ( 'Old password does not match with current password' ) ; user . salt = this . createSalt ( ) ; user . password = this . createHashedPassword ( changePasswordDTO . newPassword , user . salt ) ; return await this . repository . save ( user ) ; } @ Transactional ( ) public async changePassword ( id , changePasswordDTO ) { if ( changePasswordDTO . newPassword !== changePasswordDTO . confirmNewPassword ) { throw new BadRequestException ( 'New passwords does not match' ) ; } const user = await this . findById ( id ) ; if ( ! user . validPassword ( changePasswordDTO . password ) ) { throw new BadRequestException ( 'Old password does not match with given password' ) ; } user . salt = this . createSalt ( ) ; user . password = this . createHashedPassword ( changePasswordDTO . newPassword , user . salt ) ; return await this . repository . save ( user ) ; } @ Transactional ( ) public async changePasswordForgotPasswordFlow ( changePasswordRequestId , changePasswordDTO ) { if ( changePasswordDTO . newPassword !== changePasswordDTO . confirmNewPassword ) { throw new BadRequestException ( 'passwords does not match' ) ; } const { user } = await this . changePasswordService . findById ( changePasswordRequestId ) ; user . salt = this . createSalt ( ) ; user . password = this . createHashedPassword ( changePasswordDTO . newPassword , user . salt ) ; return await this . repository . save ( user ) ; } async uploadUserPhoto ( file , userId ) { const acceptedFileExtensions = [ '.png' , '.jpg' , '.jpeg' ] ; const fileExtension = path . extname ( file . originalname ) ; if ( ! acceptedFileExtensions . includes ( fileExtension ) ) { throw new BadRequestException ( `Accepted file types are ${ acceptedFileExtensions . join ( ',' ) }, you upload a ${ fileExtension } file` ) ; } const user = await this . findById ( userId ) ; const photoPath = `${ user . id }/avatar.jpg` ; await this . uploadService . uploadUserPhoto ( photoPath , file . buffer ) ; await this . repository . save ( { ... user , photoPath } ) ; } private createSalt ( ) { return crypto . randomBytes ( 16 ) . toString ( 'hex' ) ; } private createHashedPassword ( password , salt ) { return crypto . pbkdf2Sync ( password , salt , 1000 , 64 , `sha512` ) . toString ( `hex` ) ; } private async sendChangePasswordEmail ( user , changePasswordRequestId ) { try { await this . mailerService . sendMail ( { to : user . email , from : this . configService . smtpFrom , subject : 'Troca de senha' , template : 'change-password' , context : { name : user . name , urlTrocaSenha : this . configService . getChangePasswordFrontUrl ( changePasswordRequestId ) , } , } ) ; } catch ( e ) { throw new InternalServerErrorException ( e ) ; } } async findBadgesWithQuantityByUserId ( id ) { const user = await this . findById ( id ) ; return this . achievementService . findBadgesWithQuantityByUser ( user ) ; } async getUserPhoto ( id ) { const user = await this . findById ( id ) ; return this . uploadService . getUserPhoto ( user . photoPath ) ; } public async acceptSemear ( id ) { const filePath = `semear/${ id }.json` ; const user = await this . findById ( id ) ; const fileExists = await this . uploadService . fileExists ( filePath ) ; if ( fileExists ) return ; await this . uploadService . uploadDataToS3 ( filePath , user ) ; } public async userAcceptedSemear ( id ) { const filePath = `semear/${ id }.json` ; return await this . uploadService . fileExists ( filePath ) ; } private generateInviteKey ( ) { return Math . random ( ) . toString ( 36 ) . substr ( 2 , 20 ) ; } public async getStudentsAcceptedSemearTerms ( ) { const data = await this . uploadService . getFilesInsideFolder ( 'semear' ) ; let students = [ ] ; for ( const content of data . Contents ) { const filedata = await this . uploadService . getFile ( content . Key ) ; const json = JSON . parse ( filedata . Body . toString ( 'utf-8' ) ) ; students = [ ... students , json ] ; } return students ; } }