import { Inject , Injectable , ForbiddenException , LoggerService , BadRequestException } from "@nestjs/common" ; import * as _ from "lodash" ; import * as jwt from "jsonwebtoken" ; import { Op } from "sequelize" ; import { RoleName , DbDiToken , Role } from "../../db" ; import { AuthDiToken } from "../auth.di" ; import { LoggerDiToken } from "../../logger" ; import { PasswordService } from "./password.service" ; import { CurrentUserService } from "./current-user.service" ; import { UserUpdatePasswordDto } from "../../user/dto" ; import { ConfigDiToken , Config } from "../../config" ; import { EmailNotificationService } from "./../../notification/services/email-notification.service" ; import { UserRegistrationDto , RoleDto } from "../dto" ; import { UserDiToken , UserService , UserDto } from "../../user" ; import { EmailPayload , NotificationDiToken } from "../../notification" ; import { ValidatorService , NotEmptyValidator , ValidationError } from "../../shared/validator" ; import { getFullNameSafe , ContactDto , ContactService , ContactDiToken } from "../../contact" ; import { UserLoginDto , JwtUserData , ResendInviteDto , ResetPasswordDto } from "../auth.interfaces" ; @ Injectable ( ) export class AuthService { public static SECRET : string ; public constructor ( @ Inject ( ConfigDiToken . CONFIG ) private readonly config , @ Inject ( UserDiToken . USER_SERVICE ) private readonly userService , @ Inject ( DbDiToken . ROLE_REPOSITORY ) private readonly rolePerository , @ Inject ( LoggerDiToken . LOGGER ) private readonly logger , @ Inject ( AuthDiToken . PASSWORD_SERVICE ) private readonly passwordService , @ Inject ( ContactDiToken . CONTACT_SERVICE ) private readonly contactService , @ Inject ( NotificationDiToken . EMAIL_NOTIFICATION_SERVICE ) private readonly emailNotificationService , @ Inject ( AuthDiToken . CURRENT_USER_SERVICE ) private readonly currentUserService ) { AuthService . SECRET = this . config . auth . tokenSecret ; if ( ! AuthService . SECRET ) { throw new Error ( `No token secret specified in the configuration` ) ; } } public async getRoles ( ) { return _ . map ( await this . rolePerository . findAll ( { where : { name : { [ Op . ne ] : RoleName . SUPER_ADMIN } } } ) , role => role . toJSON ( ) ) ; } public async updatePassword ( id , data ) { const errors = ValidatorService . compose ( [ new NotEmptyValidator ( "New Pasword" , "newPassword" ) , new NotEmptyValidator ( "Old Pasword" , "newPassword" ) , ] , [ ] , { failOnError : false } ) ( data ) as ValidationError [ ] ; if ( ! _ . isEmpty ( errors ) ) { throw new ForbiddenException ( errors . join ( "\n" ) ) ; } const user = this . currentUserService . get ( ) ; if ( ! user || user . id !== id ) { throw new ForbiddenException ( ) ; } const userDto = await this . userService . getById ( id , true ) ; await this . passwordService . verify ( data . oldPassword , userDto . passwordHash ) ; const newPasswordHash = await this . passwordService . hash ( data . newPassword ) ; await this . userService . updatePasswordhash ( { id , passwordHash : newPasswordHash } ) ; } public async verifyResetPassword ( token ) { let payload ; try { payload = jwt . verify ( token , this . config . auth . resetPasswordTokenSecret ) as any ; } catch ( err ) { throw new ForbiddenException ( ) ; } const existingUser = await this . userService . getById ( payload . userId ) ; const userPassword = await this . passwordService . generate ( ) ; const passwordHash = await this . passwordService . hash ( userPassword ) ; const userToUpdate = _ . assign ( { } , existingUser , { passwordHash } ) ; await this . userService . updatePasswordhash ( userToUpdate as any ) ; const emailPayload = { to : existingUser . contact . email , subject : `Application Platform Password Reset` , text : `
                Your password has been reset.
                Your new password is:${ userPassword }
            ` , } ; return this . emailNotificationService . notify ( emailPayload ) ; } public async sendResetPasswordEmail ( data ) { const user = await this . getUserByEmailOrUsername ( data ) ; const resetPasswordToken = this . createResetPasswordToken ( user ) ; const emailPayload = { to : user . contact . email , subject : `Application Platform Password Reset` , text : `
                To confirm password recovery, please, follow link:
                ${ this . config . app . url }?token=${ resetPasswordToken }
            ` , } ; return this . emailNotificationService . notify ( emailPayload ) ; } public async register ( user ) { const passwordHash = await this . passwordService . hash ( user . password ) ; const contact = await this . contactService . create ( user . contact ) ; const userToCreate = _ . assign ( { } , _ . pick ( user , [ "username" , "roleId" ] ) , { isActive : false , passwordHash , contactId : contact . id } ) ; const createdUser = _ . assign ( { } , await this . userService . create ( userToCreate as any ) , { contact } ) ; await this . sendInviteEmail ( createdUser ) ; return _ . omit ( createdUser , "passwordHash" ) as UserDto ; } public async login ( userInfo ) { let user ; try { user = await this . userService . getByUsername ( userInfo . username ) ; } catch ( err ) { this . logger . error ( err ) ; throw new ForbiddenException ( ) ; } await this . passwordService . verify ( userInfo . password , user . passwordHash ) ; const token = await this . createAuthToken ( user , userInfo . rememberMe ) ; this . userService . updateLoginDate ( user . id ) ; return { token } ; } public async verifyInvite ( token ) { if ( ! token ) { throw new ForbiddenException ( ) ; } let payload ; try { payload = jwt . verify ( token , this . config . auth . inviteTokenSecret ) as any ; } catch ( err ) { throw new ForbiddenException ( ) ; } await this . userService . activate ( payload . userId ) ; } public async resendInviteEmail ( data ) { const user = await this . getUserByEmailOrUsername ( data ) ; if ( user . isActive ) { throw new ForbiddenException ( `User is already verified` ) ; } await this . sendInviteEmail ( user ) ; } private async sendInviteEmail ( user ) { const token = this . createInviteToken ( user ) ; const payload = { to : user . contact . email , subject : `Registration confirmation` , text : `
                Your account has been successfully created! To confirm registration, please, follow link:
                ${ this . config . app . url }?token=${ token }
            ` , } ; await this . emailNotificationService . notify ( payload ) ; } private createAuthToken ( user , rememberMe ? ) { const tokenExpiration = rememberMe ? this . config . auth . longTokenExpiration : this . config . auth . tokenExpiration ; const userFullName = getFullNameSafe ( user . contact . firstName , user . contact . lastName ) ; const payload = { id : user . id , name : userFullName , email : user . contact . email , role : { id : user . role . id , name : user . role . name } , } ; return this . generateToken ( payload , { expiresIn : tokenExpiration , secret : this . config . auth . tokenSecret } ) ; } private createInviteToken ( user ) { const inviteTokenPayload = { userId : user . id } ; const { inviteTokenExpiration , inviteTokenSecret } = this . config . auth ; return this . generateToken ( inviteTokenPayload , { expiresIn : inviteTokenExpiration , secret : inviteTokenSecret } ) ; } private createResetPasswordToken ( user ) { const inviteTokenPayload = { userId : user . id } ; const { resetPasswordTokenExpiration , resetPasswordTokenSecret } = this . config . auth ; return this . generateToken ( inviteTokenPayload , { expiresIn : resetPasswordTokenExpiration , secret : resetPasswordTokenSecret } ) ; } private generateToken ( payload , opts ) { return jwt . sign ( payload , opts . secret , { expiresIn : opts . expiresIn } ) ; } private async getUserByEmailOrUsername ( data ) { if ( _ . isEmpty ( data ) ) { throw new BadRequestException ( `username of email must be provided` ) ; } let user ; try { user = data . username ? await this . userService . getByUsername ( data . username ) : await this . userService . getByEmail ( data . email ) ; } catch ( err ) { this . logger . error ( err ) ; throw new ForbiddenException ( ) ; } return user ; } }