From 56a4a5638f69df8b17198bd3a2b0c9a83b111077 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Thu, 17 Dec 2020 18:52:23 +0200 Subject: [PATCH] factoring: authentication errors. --- server/src/api/controllers/Authentication.ts | 101 ++++++++++--------- server/src/services/Authentication/index.ts | 40 +++++--- 2 files changed, 82 insertions(+), 59 deletions(-) diff --git a/server/src/api/controllers/Authentication.ts b/server/src/api/controllers/Authentication.ts index 8fb5cd530..4c4978375 100644 --- a/server/src/api/controllers/Authentication.ts +++ b/server/src/api/controllers/Authentication.ts @@ -28,25 +28,29 @@ export default class AuthenticationController extends BaseController{ this.loginSchema, this.validationResult, LoginThrottlerMiddleware, - asyncMiddleware(this.login.bind(this)) + asyncMiddleware(this.login.bind(this)), + this.handlerErrors ); router.post( '/register', this.registerSchema, this.validationResult, - asyncMiddleware(this.register.bind(this)) + asyncMiddleware(this.register.bind(this)), + this.handlerErrors ); router.post( '/send_reset_password', this.sendResetPasswordSchema, this.validationResult, - asyncMiddleware(this.sendResetPassword.bind(this)) + asyncMiddleware(this.sendResetPassword.bind(this)), + this.handlerErrors ); router.post( '/reset/:token', this.resetPasswordSchema, this.validationResult, - asyncMiddleware(this.resetPassword.bind(this)) + asyncMiddleware(this.resetPassword.bind(this)), + this.handlerErrors ); return router; } @@ -184,18 +188,6 @@ export default class AuthenticationController extends BaseController{ ); return res.status(200).send({ token, user, tenant }); } catch (error) { - if (error instanceof ServiceError) { - if (['invalid_details', 'invalid_password'].indexOf(error.errorType) !== -1) { - return res.boom.badRequest(null, { - errors: [{ type: 'INVALID_DETAILS', code: 100 }], - }); - } - if (error.errorType === 'user_inactive') { - return res.boom.badRequest(null, { - errors: [{ type: 'USER_INACTIVE', code: 200 }], - }); - } - } next(error); } } @@ -217,19 +209,6 @@ export default class AuthenticationController extends BaseController{ message: 'Register organization has been success.', }); } catch (error) { - if (error instanceof ServiceErrors) { - const errorReasons = []; - - if (error.hasType('phone_number_exists')) { - errorReasons.push({ type: 'PHONE_NUMBER_EXISTS', code: 100 }); - } - if (error.hasType('email_exists')) { - errorReasons.push({ type: 'EMAIL.EXISTS', code: 200 }); - } - if (errorReasons.length > 0) { - return res.boom.badRequest(null, { errors: errorReasons }); - } - } next(error); } } @@ -250,11 +229,7 @@ export default class AuthenticationController extends BaseController{ }); } catch(error) { if (error instanceof ServiceError) { - if (error.errorType === 'email_not_found') { - return res.status(400).send({ - errors: [{ type: 'EMAIL.NOT.REGISTERED', code: 200 }], - }); - } + } next(error); } @@ -276,19 +251,53 @@ export default class AuthenticationController extends BaseController{ type: 'RESET_PASSWORD_SUCCESS', }) } catch(error) { - if (error instanceof ServiceError) { - if (error.errorType === 'token_invalid' || error.errorType === 'token_expired') { - return res.boom.badRequest(null, { - errors: [{ type: 'TOKEN_INVALID', code: 100 }], - }); - } - if (error.errorType === 'user_not_found') { - return res.boom.badRequest(null, { - errors: [{ type: 'USER_NOT_FOUND', code: 120 }], - }); - } - } next(error); } } + + /** + * Handles the service errors. + */ + handlerErrors(error, req: Request, res: Response, next: Function) { + if (error instanceof ServiceError) { + if (['INVALID_DETAILS', 'invalid_password'].indexOf(error.errorType) !== -1) { + return res.boom.badRequest(null, { + errors: [{ type: 'INVALID_DETAILS', code: 100 }], + }); + } + if (error.errorType === 'USER_INACTIVE') { + return res.boom.badRequest(null, { + errors: [{ type: 'USER_INACTIVE', code: 200 }], + }); + } + if (error.errorType === 'TOKEN_INVALID' || error.errorType === 'TOKEN_EXPIRED') { + return res.boom.badRequest(null, { + errors: [{ type: 'TOKEN_INVALID', code: 300 }], + }); + } + if (error.errorType === 'USER_NOT_FOUND') { + return res.boom.badRequest(null, { + errors: [{ type: 'USER_NOT_FOUND', code: 400 }], + }); + } + if (error.errorType === 'EMAIL_NOT_FOUND') { + return res.status(400).send({ + errors: [{ type: 'EMAIL.NOT.REGISTERED', code: 500 }], + }); + } + } + if (error instanceof ServiceErrors) { + const errorReasons = []; + + if (error.hasType('PHONE_NUMBER_EXISTS')) { + errorReasons.push({ type: 'PHONE_NUMBER_EXISTS', code: 100 }); + } + if (error.hasType('EMAIL_EXISTS')) { + errorReasons.push({ type: 'EMAIL.EXISTS', code: 200 }); + } + if (errorReasons.length > 0) { + return res.boom.badRequest(null, { errors: errorReasons }); + } + } + } }; diff --git a/server/src/services/Authentication/index.ts b/server/src/services/Authentication/index.ts index bd875ed57..678fce08f 100644 --- a/server/src/services/Authentication/index.ts +++ b/server/src/services/Authentication/index.ts @@ -23,6 +23,17 @@ import AuthenticationMailMessages from 'services/Authentication/AuthenticationMa import AuthenticationSMSMessages from 'services/Authentication/AuthenticationSMSMessages'; import TenantsManager from 'services/Tenancy/TenantsManager'; + +const ERRORS = { + INVALID_DETAILS: 'INVALID_DETAILS', + USER_INACTIVE: 'USER_INACTIVE', + EMAIL_NOT_FOUND: 'EMAIL_NOT_FOUND', + TOKEN_INVALID: 'TOKEN_INVALID', + USER_NOT_FOUND: 'USER_NOT_FOUND', + TOKEN_EXPIRED: 'TOKEN_EXPIRED', + PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS', + EMAIL_EXISTS: 'EMAIL_EXISTS' +}; @Service() export default class AuthenticationService implements IAuthenticationService { @Inject('logger') @@ -65,13 +76,15 @@ export default class AuthenticationService implements IAuthenticationService { const { systemUserRepository } = this.sysRepositories; const loginThrottler = Container.get('rateLimiter.login'); + // Finds the user of the given email or phone number. const user = await systemUserRepository.findByCrediential(emailOrPhone); if (!user) { + // Hits the loging throttler to the given crediential. await loginThrottler.hit(emailOrPhone); this.logger.info('[login] invalid data'); - throw new ServiceError('invalid_details'); + throw new ServiceError(ERRORS.INVALID_DETAILS); } this.logger.info('[login] check password validation.', { @@ -79,14 +92,14 @@ export default class AuthenticationService implements IAuthenticationService { password, }); if (!user.verifyPassword(password)) { + // Hits the loging throttler to the given crediential. await loginThrottler.hit(emailOrPhone); - throw new ServiceError('invalid_password'); + throw new ServiceError(ERRORS.INVALID_DETAILS); } - if (!user.active) { this.logger.info('[login] user inactive.', { userId: user.id }); - throw new ServiceError('user_inactive'); + throw new ServiceError(ERRORS.USER_INACTIVE); } this.logger.info('[login] generating JWT token.', { userId: user.id }); @@ -107,7 +120,7 @@ export default class AuthenticationService implements IAuthenticationService { }); const tenant = await user.$relatedQuery('tenant'); - // Keep the user object immutable + // Keep the user object immutable. const outputUser = cloneDeep(user); // Remove password property from user object. @@ -129,16 +142,15 @@ export default class AuthenticationService implements IAuthenticationService { const isPhoneExists = await systemUserRepository.findOneByPhoneNumber( registerDTO.phoneNumber ); - const errorReasons: ServiceError[] = []; if (isPhoneExists) { this.logger.info('[register] phone number exists on the storage.'); - errorReasons.push(new ServiceError('phone_number_exists')); + errorReasons.push(new ServiceError(ERRORS.PHONE_NUMBER_EXISTS)); } if (isEmailExists) { this.logger.info('[register] email exists on the storage.'); - errorReasons.push(new ServiceError('email_exists')); + errorReasons.push(new ServiceError(ERRORS.EMAIL_EXISTS)); } if (errorReasons.length > 0) { throw new ServiceErrors(errorReasons); @@ -196,7 +208,7 @@ export default class AuthenticationService implements IAuthenticationService { if (!userByEmail) { this.logger.info('[send_reset_password] The given email not found.'); - throw new ServiceError('email_not_found'); + throw new ServiceError(ERRORS.EMAIL_NOT_FOUND); } return userByEmail; } @@ -237,14 +249,16 @@ export default class AuthenticationService implements IAuthenticationService { */ public async resetPassword(token: string, password: string): Promise { const { systemUserRepository } = this.sysRepositories; + + // Finds the password reset token. const tokenModel: IPasswordReset = await PasswordReset.query().findOne( 'token', token ); - + // In case the password reset token not found throw token invalid error.. if (!tokenModel) { this.logger.info('[reset_password] token invalid.'); - throw new ServiceError('token_invalid'); + throw new ServiceError(ERRORS.TOKEN_INVALID); } // Different between tokne creation datetime and current time. if ( @@ -255,12 +269,12 @@ export default class AuthenticationService implements IAuthenticationService { // Deletes the expired token by expired token email. await this.deletePasswordResetToken(tokenModel.email); - throw new ServiceError('token_expired'); + throw new ServiceError(ERRORS.TOKEN_EXPIRED); } const user = await systemUserRepository.findOneByEmail(tokenModel.email); if (!user) { - throw new ServiceError('user_not_found'); + throw new ServiceError(ERRORS.USER_NOT_FOUND); } const hashedPassword = await hashPassword(password);