factoring: authentication errors.

This commit is contained in:
a.bouhuolia
2020-12-17 18:52:23 +02:00
parent cf9d2bcc60
commit 56a4a5638f
2 changed files with 82 additions and 59 deletions

View File

@@ -28,25 +28,29 @@ export default class AuthenticationController extends BaseController{
this.loginSchema, this.loginSchema,
this.validationResult, this.validationResult,
LoginThrottlerMiddleware, LoginThrottlerMiddleware,
asyncMiddleware(this.login.bind(this)) asyncMiddleware(this.login.bind(this)),
this.handlerErrors
); );
router.post( router.post(
'/register', '/register',
this.registerSchema, this.registerSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.register.bind(this)) asyncMiddleware(this.register.bind(this)),
this.handlerErrors
); );
router.post( router.post(
'/send_reset_password', '/send_reset_password',
this.sendResetPasswordSchema, this.sendResetPasswordSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.sendResetPassword.bind(this)) asyncMiddleware(this.sendResetPassword.bind(this)),
this.handlerErrors
); );
router.post( router.post(
'/reset/:token', '/reset/:token',
this.resetPasswordSchema, this.resetPasswordSchema,
this.validationResult, this.validationResult,
asyncMiddleware(this.resetPassword.bind(this)) asyncMiddleware(this.resetPassword.bind(this)),
this.handlerErrors
); );
return router; return router;
} }
@@ -184,18 +188,6 @@ export default class AuthenticationController extends BaseController{
); );
return res.status(200).send({ token, user, tenant }); return res.status(200).send({ token, user, tenant });
} catch (error) { } 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); next(error);
} }
} }
@@ -217,19 +209,6 @@ export default class AuthenticationController extends BaseController{
message: 'Register organization has been success.', message: 'Register organization has been success.',
}); });
} catch (error) { } 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); next(error);
} }
} }
@@ -250,11 +229,7 @@ export default class AuthenticationController extends BaseController{
}); });
} catch(error) { } catch(error) {
if (error instanceof ServiceError) { if (error instanceof ServiceError) {
if (error.errorType === 'email_not_found') {
return res.status(400).send({
errors: [{ type: 'EMAIL.NOT.REGISTERED', code: 200 }],
});
}
} }
next(error); next(error);
} }
@@ -276,19 +251,53 @@ export default class AuthenticationController extends BaseController{
type: 'RESET_PASSWORD_SUCCESS', type: 'RESET_PASSWORD_SUCCESS',
}) })
} catch(error) { } 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); 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 });
}
}
}
}; };

View File

@@ -23,6 +23,17 @@ import AuthenticationMailMessages from 'services/Authentication/AuthenticationMa
import AuthenticationSMSMessages from 'services/Authentication/AuthenticationSMSMessages'; import AuthenticationSMSMessages from 'services/Authentication/AuthenticationSMSMessages';
import TenantsManager from 'services/Tenancy/TenantsManager'; 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() @Service()
export default class AuthenticationService implements IAuthenticationService { export default class AuthenticationService implements IAuthenticationService {
@Inject('logger') @Inject('logger')
@@ -65,13 +76,15 @@ export default class AuthenticationService implements IAuthenticationService {
const { systemUserRepository } = this.sysRepositories; const { systemUserRepository } = this.sysRepositories;
const loginThrottler = Container.get('rateLimiter.login'); const loginThrottler = Container.get('rateLimiter.login');
// Finds the user of the given email or phone number.
const user = await systemUserRepository.findByCrediential(emailOrPhone); const user = await systemUserRepository.findByCrediential(emailOrPhone);
if (!user) { if (!user) {
// Hits the loging throttler to the given crediential.
await loginThrottler.hit(emailOrPhone); await loginThrottler.hit(emailOrPhone);
this.logger.info('[login] invalid data'); this.logger.info('[login] invalid data');
throw new ServiceError('invalid_details'); throw new ServiceError(ERRORS.INVALID_DETAILS);
} }
this.logger.info('[login] check password validation.', { this.logger.info('[login] check password validation.', {
@@ -79,14 +92,14 @@ export default class AuthenticationService implements IAuthenticationService {
password, password,
}); });
if (!user.verifyPassword(password)) { if (!user.verifyPassword(password)) {
// Hits the loging throttler to the given crediential.
await loginThrottler.hit(emailOrPhone); await loginThrottler.hit(emailOrPhone);
throw new ServiceError('invalid_password'); throw new ServiceError(ERRORS.INVALID_DETAILS);
} }
if (!user.active) { if (!user.active) {
this.logger.info('[login] user inactive.', { userId: user.id }); 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 }); 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'); const tenant = await user.$relatedQuery('tenant');
// Keep the user object immutable // Keep the user object immutable.
const outputUser = cloneDeep(user); const outputUser = cloneDeep(user);
// Remove password property from user object. // Remove password property from user object.
@@ -129,16 +142,15 @@ export default class AuthenticationService implements IAuthenticationService {
const isPhoneExists = await systemUserRepository.findOneByPhoneNumber( const isPhoneExists = await systemUserRepository.findOneByPhoneNumber(
registerDTO.phoneNumber registerDTO.phoneNumber
); );
const errorReasons: ServiceError[] = []; const errorReasons: ServiceError[] = [];
if (isPhoneExists) { if (isPhoneExists) {
this.logger.info('[register] phone number exists on the storage.'); 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) { if (isEmailExists) {
this.logger.info('[register] email exists on the storage.'); 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) { if (errorReasons.length > 0) {
throw new ServiceErrors(errorReasons); throw new ServiceErrors(errorReasons);
@@ -196,7 +208,7 @@ export default class AuthenticationService implements IAuthenticationService {
if (!userByEmail) { if (!userByEmail) {
this.logger.info('[send_reset_password] The given email not found.'); 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; return userByEmail;
} }
@@ -237,14 +249,16 @@ export default class AuthenticationService implements IAuthenticationService {
*/ */
public async resetPassword(token: string, password: string): Promise<void> { public async resetPassword(token: string, password: string): Promise<void> {
const { systemUserRepository } = this.sysRepositories; const { systemUserRepository } = this.sysRepositories;
// Finds the password reset token.
const tokenModel: IPasswordReset = await PasswordReset.query().findOne( const tokenModel: IPasswordReset = await PasswordReset.query().findOne(
'token', 'token',
token token
); );
// In case the password reset token not found throw token invalid error..
if (!tokenModel) { if (!tokenModel) {
this.logger.info('[reset_password] token invalid.'); 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. // Different between tokne creation datetime and current time.
if ( if (
@@ -255,12 +269,12 @@ export default class AuthenticationService implements IAuthenticationService {
// Deletes the expired token by expired token email. // Deletes the expired token by expired token email.
await this.deletePasswordResetToken(tokenModel.email); await this.deletePasswordResetToken(tokenModel.email);
throw new ServiceError('token_expired'); throw new ServiceError(ERRORS.TOKEN_EXPIRED);
} }
const user = await systemUserRepository.findOneByEmail(tokenModel.email); const user = await systemUserRepository.findOneByEmail(tokenModel.email);
if (!user) { if (!user) {
throw new ServiceError('user_not_found'); throw new ServiceError(ERRORS.USER_NOT_FOUND);
} }
const hashedPassword = await hashPassword(password); const hashedPassword = await hashPassword(password);