mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 15:50:32 +00:00
factoring: authentication errors.
This commit is contained in:
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user