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.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 });
}
}
}
};

View File

@@ -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<void> {
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);