mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat(server): remove phone number from authentication process
This commit is contained in:
@@ -1,26 +1,23 @@
|
|||||||
import { Request, Response, Router } from 'express';
|
import { Request, Response, Router } from 'express';
|
||||||
import { check, ValidationChain } from 'express-validator';
|
import { check, ValidationChain } from 'express-validator';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import countries from 'country-codes-list';
|
|
||||||
import parsePhoneNumber from 'libphonenumber-js';
|
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import AuthenticationService from '@/services/Authentication';
|
|
||||||
import { ILoginDTO, ISystemUser, IRegisterDTO } from '@/interfaces';
|
import { ILoginDTO, ISystemUser, IRegisterDTO } from '@/interfaces';
|
||||||
import { ServiceError, ServiceErrors } from '@/exceptions';
|
import { ServiceError, ServiceErrors } from '@/exceptions';
|
||||||
import { DATATYPES_LENGTH } from '@/data/DataTypes';
|
import { DATATYPES_LENGTH } from '@/data/DataTypes';
|
||||||
import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware';
|
import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware';
|
||||||
import config from '@/config';
|
import AuthenticationApplication from '@/services/Authentication/AuthApplication';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class AuthenticationController extends BaseController {
|
export default class AuthenticationController extends BaseController {
|
||||||
@Inject()
|
@Inject()
|
||||||
authService: AuthenticationService;
|
private authApplication: AuthenticationApplication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor method.
|
* Constructor method.
|
||||||
*/
|
*/
|
||||||
router() {
|
public router() {
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -56,9 +53,10 @@ export default class AuthenticationController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login schema.
|
* Login validation schema.
|
||||||
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get loginSchema(): ValidationChain[] {
|
private get loginSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('crediential').exists().isEmail(),
|
check('crediential').exists().isEmail(),
|
||||||
check('password').exists().isLength({ min: 5 }),
|
check('password').exists().isLength({ min: 5 }),
|
||||||
@@ -66,9 +64,10 @@ export default class AuthenticationController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register schema.
|
* Register validation schema.
|
||||||
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get registerSchema(): ValidationChain[] {
|
private get registerSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('first_name')
|
check('first_name')
|
||||||
.exists()
|
.exists()
|
||||||
@@ -89,71 +88,20 @@ export default class AuthenticationController extends BaseController {
|
|||||||
.trim()
|
.trim()
|
||||||
.escape()
|
.escape()
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
check('phone_number')
|
|
||||||
.exists()
|
|
||||||
.isString()
|
|
||||||
.trim()
|
|
||||||
.escape()
|
|
||||||
.custom(this.phoneNumberValidator)
|
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
|
||||||
check('password')
|
check('password')
|
||||||
.exists()
|
.exists()
|
||||||
.isString()
|
.isString()
|
||||||
.trim()
|
.trim()
|
||||||
.escape()
|
.escape()
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
||||||
check('country')
|
|
||||||
.exists()
|
|
||||||
.isString()
|
|
||||||
.trim()
|
|
||||||
.escape()
|
|
||||||
.custom(this.countryValidator)
|
|
||||||
.isLength({ max: DATATYPES_LENGTH.STRING }),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Country validator.
|
|
||||||
*/
|
|
||||||
countryValidator(value, { req }) {
|
|
||||||
const {
|
|
||||||
countries: { whitelist, blacklist },
|
|
||||||
} = config.registration;
|
|
||||||
const foundCountry = countries.findOne('countryCode', value);
|
|
||||||
|
|
||||||
if (!foundCountry) {
|
|
||||||
throw new Error('The country code is invalid.');
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
// Focus with me! In case whitelist is not empty and the given coutry is not
|
|
||||||
// in whitelist throw the error.
|
|
||||||
//
|
|
||||||
// Or in case the blacklist is not empty and the given country exists
|
|
||||||
// in the blacklist throw the goddamn error.
|
|
||||||
(whitelist.length > 0 && whitelist.indexOf(value) === -1) ||
|
|
||||||
(blacklist.length > 0 && blacklist.indexOf(value) !== -1)
|
|
||||||
) {
|
|
||||||
throw new Error('The country code is not supported yet.');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Phone number validator.
|
|
||||||
*/
|
|
||||||
phoneNumberValidator(value, { req }) {
|
|
||||||
const phoneNumber = parsePhoneNumber(value, req.body.country);
|
|
||||||
|
|
||||||
if (!phoneNumber || !phoneNumber.isValid()) {
|
|
||||||
throw new Error('Phone number is invalid with the given country code.');
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset password schema.
|
* Reset password schema.
|
||||||
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get resetPasswordSchema(): ValidationChain[] {
|
private get resetPasswordSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('password')
|
check('password')
|
||||||
.exists()
|
.exists()
|
||||||
@@ -170,8 +118,9 @@ export default class AuthenticationController extends BaseController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Send reset password validation schema.
|
* Send reset password validation schema.
|
||||||
|
* @returns {ValidationChain[]}
|
||||||
*/
|
*/
|
||||||
get sendResetPasswordSchema(): ValidationChain[] {
|
private get sendResetPasswordSchema(): ValidationChain[] {
|
||||||
return [check('email').exists().isEmail().trim().escape()];
|
return [check('email').exists().isEmail().trim().escape()];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,11 +129,11 @@ export default class AuthenticationController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async login(req: Request, res: Response, next: Function): Response {
|
private async login(req: Request, res: Response, next: Function): Response {
|
||||||
const userDTO: ILoginDTO = this.matchedBodyData(req);
|
const userDTO: ILoginDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { token, user, tenant } = await this.authService.signIn(
|
const { token, user, tenant } = await this.authApplication.signIn(
|
||||||
userDTO.crediential,
|
userDTO.crediential,
|
||||||
userDTO.password
|
userDTO.password
|
||||||
);
|
);
|
||||||
@@ -199,14 +148,13 @@ export default class AuthenticationController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async register(req: Request, res: Response, next: Function) {
|
private async register(req: Request, res: Response, next: Function) {
|
||||||
const registerDTO: IRegisterDTO = this.matchedBodyData(req);
|
const registerDTO: IRegisterDTO = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registeredUser: ISystemUser = await this.authService.register(
|
const registeredUser: ISystemUser = await this.authApplication.signUp(
|
||||||
registerDTO
|
registerDTO
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
code: 'REGISTER.SUCCESS',
|
code: 'REGISTER.SUCCESS',
|
||||||
@@ -222,11 +170,11 @@ export default class AuthenticationController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async sendResetPassword(req: Request, res: Response, next: Function) {
|
private async sendResetPassword(req: Request, res: Response, next: Function) {
|
||||||
const { email } = this.matchedBodyData(req);
|
const { email } = this.matchedBodyData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.authService.sendResetPassword(email);
|
await this.authApplication.sendResetPassword(email);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
code: 'SEND_RESET_PASSWORD_SUCCESS',
|
code: 'SEND_RESET_PASSWORD_SUCCESS',
|
||||||
@@ -244,12 +192,12 @@ export default class AuthenticationController extends BaseController {
|
|||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
*/
|
*/
|
||||||
async resetPassword(req: Request, res: Response, next: Function) {
|
private async resetPassword(req: Request, res: Response, next: Function) {
|
||||||
const { token } = req.params;
|
const { token } = req.params;
|
||||||
const { password } = req.body;
|
const { password } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.authService.resetPassword(token, password);
|
await this.authApplication.resetPassword(token, password);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
type: 'RESET_PASSWORD_SUCCESS',
|
type: 'RESET_PASSWORD_SUCCESS',
|
||||||
@@ -263,7 +211,7 @@ export default class AuthenticationController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* Handles the service errors.
|
* Handles the service errors.
|
||||||
*/
|
*/
|
||||||
handlerErrors(error, req: Request, res: Response, next: Function) {
|
private handlerErrors(error, req: Request, res: Response, next: Function) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
if (
|
if (
|
||||||
['INVALID_DETAILS', 'invalid_password'].indexOf(error.errorType) !== -1
|
['INVALID_DETAILS', 'invalid_password'].indexOf(error.errorType) !== -1
|
||||||
|
|||||||
@@ -1,29 +1,77 @@
|
|||||||
import { ISystemUser } from './User';
|
import { ISystemUser } from './User';
|
||||||
import { ITenant } from './Tenancy';
|
import { ITenant } from './Tenancy';
|
||||||
|
import { SystemUser } from '@/system/models';
|
||||||
|
|
||||||
export interface IRegisterDTO {
|
export interface IRegisterDTO {
|
||||||
firstName: string,
|
firstName: string;
|
||||||
lastName: string,
|
lastName: string;
|
||||||
email: string,
|
email: string;
|
||||||
password: string,
|
password: string;
|
||||||
organizationName: string,
|
organizationName: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface ILoginDTO {
|
export interface ILoginDTO {
|
||||||
crediential: string,
|
crediential: string;
|
||||||
password: string,
|
password: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IPasswordReset {
|
export interface IPasswordReset {
|
||||||
id: number,
|
id: number;
|
||||||
email: string,
|
email: string;
|
||||||
token: string,
|
token: string;
|
||||||
createdAt: Date,
|
createdAt: Date;
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface IAuthenticationService {
|
export interface IAuthenticationService {
|
||||||
signIn(emailOrPhone: string, password: string): Promise<{ user: ISystemUser, token: string, tenant: ITenant }>;
|
signIn(
|
||||||
|
email: string,
|
||||||
|
password: string
|
||||||
|
): Promise<{ user: ISystemUser; token: string; tenant: ITenant }>;
|
||||||
register(registerDTO: IRegisterDTO): Promise<ISystemUser>;
|
register(registerDTO: IRegisterDTO): Promise<ISystemUser>;
|
||||||
sendResetPassword(email: string): Promise<IPasswordReset>;
|
sendResetPassword(email: string): Promise<IPasswordReset>;
|
||||||
resetPassword(token: string, password: string): Promise<void>;
|
resetPassword(token: string, password: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthSigningInEventPayload {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
user: ISystemUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthSignedInEventPayload {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
user: ISystemUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthSigningUpEventPayload {
|
||||||
|
signupDTO: IRegisterDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthSignedUpEventPayload {
|
||||||
|
signupDTO: IRegisterDTO;
|
||||||
|
tenant: ITenant;
|
||||||
|
user: ISystemUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthSignInPOJO {
|
||||||
|
user: ISystemUser;
|
||||||
|
token: string;
|
||||||
|
tenant: ITenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAuthResetedPasswordEventPayload {
|
||||||
|
user: SystemUser;
|
||||||
|
token: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface IAuthSendingResetPassword {
|
||||||
|
user: ISystemUser,
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
export interface IAuthSendedResetPassword {
|
||||||
|
user: ISystemUser,
|
||||||
|
token: string;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Container, Inject } from 'typedi';
|
import { Container, Inject } from 'typedi';
|
||||||
import AuthenticationService from '@/services/Authentication';
|
import AuthenticationService from '@/services/Authentication/AuthApplication';
|
||||||
|
|
||||||
export default class WelcomeEmailJob {
|
export default class WelcomeEmailJob {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Container, Inject } from 'typedi';
|
import { Container, Inject } from 'typedi';
|
||||||
import AuthenticationService from '@/services/Authentication';
|
import AuthenticationService from '@/services/Authentication/AuthApplication';
|
||||||
|
|
||||||
export default class WelcomeSMSJob {
|
export default class WelcomeSMSJob {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import AuthenticationService from '@/services/Authentication';
|
import AuthenticationService from '@/services/Authentication/AuthApplication';
|
||||||
|
|
||||||
export default class WelcomeEmailJob {
|
export default class WelcomeEmailJob {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { Service, Inject, Container } from 'typedi';
|
||||||
|
import { IRegisterDTO, ISystemUser, IPasswordReset } from '@/interfaces';
|
||||||
|
import { AuthSigninService } from './AuthSignin';
|
||||||
|
import { AuthSignupService } from './AuthSignup';
|
||||||
|
import { AuthSendResetPassword } from './AuthSendResetPassword';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class AuthenticationApplication {
|
||||||
|
@Inject()
|
||||||
|
private authSigninService: AuthSigninService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private authSignupService: AuthSignupService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private authResetPasswordService: AuthSendResetPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signin and generates JWT token.
|
||||||
|
* @throws {ServiceError}
|
||||||
|
* @param {string} email - Email address.
|
||||||
|
* @param {string} password - Password.
|
||||||
|
* @return {Promise<{user: IUser, token: string}>}
|
||||||
|
*/
|
||||||
|
public async signIn(email: string, password: string) {
|
||||||
|
return this.authSigninService.signIn(email, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signup a new user.
|
||||||
|
* @param {IRegisterDTO} signupDTO
|
||||||
|
* @returns {Promise<ISystemUser>}
|
||||||
|
*/
|
||||||
|
public async signUp(signupDTO: IRegisterDTO): Promise<ISystemUser> {
|
||||||
|
return this.authSignupService.signUp(signupDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and retrieve password reset token for the given user email.
|
||||||
|
* @param {string} email
|
||||||
|
* @return {<Promise<IPasswordReset>}
|
||||||
|
*/
|
||||||
|
public async sendResetPassword(email: string): Promise<IPasswordReset> {
|
||||||
|
return this.authResetPasswordService.sendResetPassword(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets a user password from given token.
|
||||||
|
* @param {string} token - Password reset token.
|
||||||
|
* @param {string} password - New Password.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
public async resetPassword(token: string, password: string): Promise<void> {
|
||||||
|
return this.authResetPasswordService.resetPassword(token, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import uniqid from 'uniqid';
|
||||||
|
import moment from 'moment';
|
||||||
|
import config from '@/config';
|
||||||
|
import {
|
||||||
|
IAuthResetedPasswordEventPayload,
|
||||||
|
IAuthSendedResetPassword,
|
||||||
|
IAuthSendingResetPassword,
|
||||||
|
IPasswordReset,
|
||||||
|
ISystemUser,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { PasswordReset } from '@/system/models';
|
||||||
|
import { ERRORS } from './_constants';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { hashPassword } from '@/utils';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class AuthSendResetPassword {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject('repositories')
|
||||||
|
private sysRepositories: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates and retrieve password reset token for the given user email.
|
||||||
|
* @param {string} email
|
||||||
|
* @return {<Promise<IPasswordReset>}
|
||||||
|
*/
|
||||||
|
public async sendResetPassword(email: string): Promise<PasswordReset> {
|
||||||
|
const user = await this.validateEmailExistance(email);
|
||||||
|
|
||||||
|
const token: string = uniqid();
|
||||||
|
|
||||||
|
// Triggers sending reset password event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.sendingResetPassword, {
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
} as IAuthSendingResetPassword);
|
||||||
|
|
||||||
|
// Delete all stored tokens of reset password that associate to the give email.
|
||||||
|
this.deletePasswordResetToken(email);
|
||||||
|
|
||||||
|
// Creates a new password reset row with unique token.
|
||||||
|
const passwordReset = await PasswordReset.query().insert({ email, token });
|
||||||
|
|
||||||
|
// Triggers sent reset password event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.sendResetPassword, {
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
} as IAuthSendedResetPassword);
|
||||||
|
|
||||||
|
return passwordReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets a user password from given token.
|
||||||
|
* @param {string} token - Password reset token.
|
||||||
|
* @param {string} password - New Password.
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
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) {
|
||||||
|
throw new ServiceError(ERRORS.TOKEN_INVALID);
|
||||||
|
}
|
||||||
|
// Different between tokne creation datetime and current time.
|
||||||
|
if (
|
||||||
|
moment().diff(tokenModel.createdAt, 'seconds') >
|
||||||
|
config.resetPasswordSeconds
|
||||||
|
) {
|
||||||
|
// Deletes the expired token by expired token email.
|
||||||
|
await this.deletePasswordResetToken(tokenModel.email);
|
||||||
|
throw new ServiceError(ERRORS.TOKEN_EXPIRED);
|
||||||
|
}
|
||||||
|
const user = await systemUserRepository.findOneByEmail(tokenModel.email);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new ServiceError(ERRORS.USER_NOT_FOUND);
|
||||||
|
}
|
||||||
|
const hashedPassword = await hashPassword(password);
|
||||||
|
|
||||||
|
await systemUserRepository.update(
|
||||||
|
{ password: hashedPassword },
|
||||||
|
{ id: user.id }
|
||||||
|
);
|
||||||
|
// Deletes the used token.
|
||||||
|
await this.deletePasswordResetToken(tokenModel.email);
|
||||||
|
|
||||||
|
// Triggers `onResetPassword` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.resetPassword, {
|
||||||
|
user,
|
||||||
|
token,
|
||||||
|
password,
|
||||||
|
} as IAuthResetedPasswordEventPayload);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the password reset token by the given email.
|
||||||
|
* @param {string} email
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
private async deletePasswordResetToken(email: string) {
|
||||||
|
return PasswordReset.query().where('email', email).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given email existance on the storage.
|
||||||
|
* @throws {ServiceError}
|
||||||
|
* @param {string} email - email address.
|
||||||
|
*/
|
||||||
|
private async validateEmailExistance(email: string): Promise<ISystemUser> {
|
||||||
|
const { systemUserRepository } = this.sysRepositories;
|
||||||
|
const userByEmail = await systemUserRepository.findOneByEmail(email);
|
||||||
|
|
||||||
|
if (!userByEmail) {
|
||||||
|
throw new ServiceError(ERRORS.EMAIL_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return userByEmail;
|
||||||
|
}
|
||||||
|
}
|
||||||
101
packages/server/src/services/Authentication/AuthSignin.ts
Normal file
101
packages/server/src/services/Authentication/AuthSignin.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import { Container, Inject } from 'typedi';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { Tenant } from '@/system/models';
|
||||||
|
import {
|
||||||
|
IAuthSigningInEventPayload,
|
||||||
|
IAuthSignInPOJO,
|
||||||
|
ISystemUser,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { generateToken } from './_utils';
|
||||||
|
import { ERRORS } from './_constants';
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
export class AuthSigninService {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject('repositories')
|
||||||
|
private sysRepositories: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the given email and password.
|
||||||
|
* @param {ISystemUser} user
|
||||||
|
* @param {string} email
|
||||||
|
* @param {string} password
|
||||||
|
*/
|
||||||
|
public async validateSignIn(
|
||||||
|
user: ISystemUser,
|
||||||
|
email: string,
|
||||||
|
password: string
|
||||||
|
) {
|
||||||
|
const loginThrottler = Container.get('rateLimiter.login');
|
||||||
|
|
||||||
|
// Validate if the user is not exist.
|
||||||
|
if (!user) {
|
||||||
|
await loginThrottler.hit(email);
|
||||||
|
throw new ServiceError(ERRORS.INVALID_DETAILS);
|
||||||
|
}
|
||||||
|
// Validate if the given user's password is wrong.
|
||||||
|
if (!user.verifyPassword(password)) {
|
||||||
|
await loginThrottler.hit(email);
|
||||||
|
throw new ServiceError(ERRORS.INVALID_DETAILS);
|
||||||
|
}
|
||||||
|
// Validate if the given user is inactive.
|
||||||
|
if (!user.active) {
|
||||||
|
throw new ServiceError(ERRORS.USER_INACTIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signin and generates JWT token.
|
||||||
|
* @throws {ServiceError}
|
||||||
|
* @param {string} email - Email address.
|
||||||
|
* @param {string} password - Password.
|
||||||
|
* @return {Promise<{user: IUser, token: string}>}
|
||||||
|
*/
|
||||||
|
public async signIn(
|
||||||
|
email: string,
|
||||||
|
password: string
|
||||||
|
): Promise<IAuthSignInPOJO> {
|
||||||
|
const { systemUserRepository } = this.sysRepositories;
|
||||||
|
|
||||||
|
// Finds the user of the given email address.
|
||||||
|
const user = await systemUserRepository.findOneByEmail(email);
|
||||||
|
|
||||||
|
// Validate the given email and password.
|
||||||
|
await this.validateSignIn(user, email, password);
|
||||||
|
|
||||||
|
// Triggers on signing-in event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.logining, {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
user,
|
||||||
|
} as IAuthSigningInEventPayload);
|
||||||
|
|
||||||
|
const token = generateToken(user);
|
||||||
|
|
||||||
|
// Update the last login at of the user.
|
||||||
|
await systemUserRepository.patchLastLoginAt(user.id);
|
||||||
|
|
||||||
|
// Triggers `onLogin` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.login, {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
const tenant = await Tenant.query()
|
||||||
|
.findById(user.tenantId)
|
||||||
|
.withGraphFetched('metadata');
|
||||||
|
|
||||||
|
// Keep the user object immutable.
|
||||||
|
const outputUser = cloneDeep(user);
|
||||||
|
|
||||||
|
// Remove password property from user object.
|
||||||
|
Reflect.deleteProperty(outputUser, 'password');
|
||||||
|
|
||||||
|
return { user: outputUser, token, tenant };
|
||||||
|
}
|
||||||
|
}
|
||||||
77
packages/server/src/services/Authentication/AuthSignup.ts
Normal file
77
packages/server/src/services/Authentication/AuthSignup.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { omit } from 'lodash';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import {
|
||||||
|
IAuthSignedUpEventPayload,
|
||||||
|
IAuthSigningUpEventPayload,
|
||||||
|
IRegisterDTO,
|
||||||
|
ISystemUser,
|
||||||
|
} from '@/interfaces';
|
||||||
|
import { ERRORS } from './_constants';
|
||||||
|
import { Inject } from 'typedi';
|
||||||
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import TenantsManagerService from '../Tenancy/TenantsManager';
|
||||||
|
import events from '@/subscribers/events';
|
||||||
|
import { hashPassword } from '@/utils';
|
||||||
|
|
||||||
|
export class AuthSignupService {
|
||||||
|
@Inject()
|
||||||
|
private eventPublisher: EventPublisher;
|
||||||
|
|
||||||
|
@Inject('repositories')
|
||||||
|
private sysRepositories: any;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenantsManager: TenantsManagerService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new tenant with user from user input.
|
||||||
|
* @throws {ServiceErrors}
|
||||||
|
* @param {IRegisterDTO} signupDTO
|
||||||
|
* @returns {Promise<ISystemUser>}
|
||||||
|
*/
|
||||||
|
public async signUp(signupDTO: IRegisterDTO): Promise<ISystemUser> {
|
||||||
|
const { systemUserRepository } = this.sysRepositories;
|
||||||
|
|
||||||
|
// Validates the given email uniqiness.
|
||||||
|
await this.validateEmailUniqiness(signupDTO.email);
|
||||||
|
|
||||||
|
const hashedPassword = await hashPassword(signupDTO.password);
|
||||||
|
|
||||||
|
// Triggers signin up event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.registering, {
|
||||||
|
signupDTO,
|
||||||
|
} as IAuthSigningUpEventPayload);
|
||||||
|
|
||||||
|
const tenant = await this.tenantsManager.createTenant();
|
||||||
|
const registeredUser = await systemUserRepository.create({
|
||||||
|
...omit(signupDTO, 'country'),
|
||||||
|
active: true,
|
||||||
|
password: hashedPassword,
|
||||||
|
tenantId: tenant.id,
|
||||||
|
inviteAcceptedAt: moment().format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
// Triggers signed up event.
|
||||||
|
await this.eventPublisher.emitAsync(events.auth.register, {
|
||||||
|
signupDTO,
|
||||||
|
tenant,
|
||||||
|
user: registeredUser,
|
||||||
|
} as IAuthSignedUpEventPayload);
|
||||||
|
|
||||||
|
return registeredUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates email uniqiness on the storage.
|
||||||
|
* @throws {ServiceErrors}
|
||||||
|
* @param {string} email - Email address
|
||||||
|
*/
|
||||||
|
private async validateEmailUniqiness(email: string) {
|
||||||
|
const { systemUserRepository } = this.sysRepositories;
|
||||||
|
const isEmailExists = await systemUserRepository.findOneByEmail(email);
|
||||||
|
|
||||||
|
if (isEmailExists) {
|
||||||
|
throw new ServiceError(ERRORS.EMAIL_EXISTS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
packages/server/src/services/Authentication/_constants.ts
Normal file
10
packages/server/src/services/Authentication/_constants.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export 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',
|
||||||
|
};
|
||||||
22
packages/server/src/services/Authentication/_utils.ts
Normal file
22
packages/server/src/services/Authentication/_utils.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import JWT from 'jsonwebtoken';
|
||||||
|
import { ISystemUser } from '@/interfaces';
|
||||||
|
import config from '@/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates JWT token for the given user.
|
||||||
|
* @param {ISystemUser} user
|
||||||
|
* @return {string} token
|
||||||
|
*/
|
||||||
|
export const generateToken = (user: ISystemUser): string => {
|
||||||
|
const today = new Date();
|
||||||
|
const exp = new Date(today);
|
||||||
|
exp.setDate(today.getDate() + 60);
|
||||||
|
|
||||||
|
return JWT.sign(
|
||||||
|
{
|
||||||
|
id: user.id, // We are gonna use this in the middleware 'isAuth'
|
||||||
|
exp: exp.getTime() / 1000,
|
||||||
|
},
|
||||||
|
config.jwtSecret
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
import { Service, Inject, Container } from 'typedi';
|
|
||||||
import JWT from 'jsonwebtoken';
|
|
||||||
import uniqid from 'uniqid';
|
|
||||||
import { omit, cloneDeep } from 'lodash';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { PasswordReset, Tenant } from '@/system/models';
|
|
||||||
import {
|
|
||||||
IRegisterDTO,
|
|
||||||
ITenant,
|
|
||||||
ISystemUser,
|
|
||||||
IPasswordReset,
|
|
||||||
IAuthenticationService,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import { hashPassword } from 'utils';
|
|
||||||
import { ServiceError, ServiceErrors } from '@/exceptions';
|
|
||||||
import config from '@/config';
|
|
||||||
import events from '@/subscribers/events';
|
|
||||||
import AuthenticationMailMessages from '@/services/Authentication/AuthenticationMailMessages';
|
|
||||||
import TenantsManager from '@/services/Tenancy/TenantsManager';
|
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
|
||||||
|
|
||||||
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')
|
|
||||||
logger: any;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
eventPublisher: EventPublisher;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
mailMessages: AuthenticationMailMessages;
|
|
||||||
|
|
||||||
@Inject('repositories')
|
|
||||||
sysRepositories: any;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
tenantsManager: TenantsManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signin and generates JWT token.
|
|
||||||
* @throws {ServiceError}
|
|
||||||
* @param {string} emailOrPhone - Email or phone number.
|
|
||||||
* @param {string} password - Password.
|
|
||||||
* @return {Promise<{user: IUser, token: string}>}
|
|
||||||
*/
|
|
||||||
public async signIn(
|
|
||||||
emailOrPhone: string,
|
|
||||||
password: string
|
|
||||||
): Promise<{
|
|
||||||
user: ISystemUser;
|
|
||||||
token: string;
|
|
||||||
tenant: ITenant;
|
|
||||||
}> {
|
|
||||||
this.logger.info('[login] Someone trying to login.', {
|
|
||||||
emailOrPhone,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
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(ERRORS.INVALID_DETAILS);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info('[login] check password validation.', {
|
|
||||||
emailOrPhone,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
if (!user.verifyPassword(password)) {
|
|
||||||
// Hits the loging throttler to the given crediential.
|
|
||||||
await loginThrottler.hit(emailOrPhone);
|
|
||||||
|
|
||||||
throw new ServiceError(ERRORS.INVALID_DETAILS);
|
|
||||||
}
|
|
||||||
if (!user.active) {
|
|
||||||
this.logger.info('[login] user inactive.', { userId: user.id });
|
|
||||||
throw new ServiceError(ERRORS.USER_INACTIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info('[login] generating JWT token.', { userId: user.id });
|
|
||||||
const token = this.generateToken(user);
|
|
||||||
|
|
||||||
this.logger.info('[login] updating user last login at.', {
|
|
||||||
userId: user.id,
|
|
||||||
});
|
|
||||||
await systemUserRepository.patchLastLoginAt(user.id);
|
|
||||||
|
|
||||||
this.logger.info('[login] Logging success.', { user, token });
|
|
||||||
|
|
||||||
// Triggers `onLogin` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.auth.login, {
|
|
||||||
emailOrPhone,
|
|
||||||
password,
|
|
||||||
user,
|
|
||||||
});
|
|
||||||
const tenant = await Tenant.query().findById(user.tenantId).withGraphFetched('metadata');
|
|
||||||
|
|
||||||
// Keep the user object immutable.
|
|
||||||
const outputUser = cloneDeep(user);
|
|
||||||
|
|
||||||
// Remove password property from user object.
|
|
||||||
Reflect.deleteProperty(outputUser, 'password');
|
|
||||||
|
|
||||||
return { user: outputUser, token, tenant };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates email and phone number uniqiness on the storage.
|
|
||||||
* @throws {ServiceErrors}
|
|
||||||
* @param {IRegisterDTO} registerDTO - Register data object.
|
|
||||||
*/
|
|
||||||
private async validateEmailAndPhoneUniqiness(registerDTO: IRegisterDTO) {
|
|
||||||
const { systemUserRepository } = this.sysRepositories;
|
|
||||||
|
|
||||||
const isEmailExists = await systemUserRepository.findOneByEmail(
|
|
||||||
registerDTO.email
|
|
||||||
);
|
|
||||||
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(ERRORS.PHONE_NUMBER_EXISTS));
|
|
||||||
}
|
|
||||||
if (isEmailExists) {
|
|
||||||
this.logger.info('[register] email exists on the storage.');
|
|
||||||
errorReasons.push(new ServiceError(ERRORS.EMAIL_EXISTS));
|
|
||||||
}
|
|
||||||
if (errorReasons.length > 0) {
|
|
||||||
throw new ServiceErrors(errorReasons);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a new tenant with user from user input.
|
|
||||||
* @throws {ServiceErrors}
|
|
||||||
* @param {IUserDTO} user
|
|
||||||
*/
|
|
||||||
public async register(registerDTO: IRegisterDTO): Promise<ISystemUser> {
|
|
||||||
this.logger.info('[register] Someone trying to register.');
|
|
||||||
await this.validateEmailAndPhoneUniqiness(registerDTO);
|
|
||||||
|
|
||||||
this.logger.info('[register] Creating a new tenant organization.');
|
|
||||||
const tenant = await this.newTenantOrganization();
|
|
||||||
|
|
||||||
this.logger.info('[register] Trying hashing the password.');
|
|
||||||
const hashedPassword = await hashPassword(registerDTO.password);
|
|
||||||
|
|
||||||
const { systemUserRepository } = this.sysRepositories;
|
|
||||||
const registeredUser = await systemUserRepository.create({
|
|
||||||
...omit(registerDTO, 'country'),
|
|
||||||
active: true,
|
|
||||||
password: hashedPassword,
|
|
||||||
tenantId: tenant.id,
|
|
||||||
inviteAcceptedAt: moment().format('YYYY-MM-DD'),
|
|
||||||
});
|
|
||||||
// Triggers `onRegister` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.auth.register, {
|
|
||||||
registerDTO,
|
|
||||||
tenant,
|
|
||||||
user: registeredUser,
|
|
||||||
});
|
|
||||||
return registeredUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates and insert new tenant organization id.
|
|
||||||
* @async
|
|
||||||
* @return {Promise<ITenant>}
|
|
||||||
*/
|
|
||||||
private async newTenantOrganization(): Promise<ITenant> {
|
|
||||||
return this.tenantsManager.createTenant();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate the given email existance on the storage.
|
|
||||||
* @throws {ServiceError}
|
|
||||||
* @param {string} email - email address.
|
|
||||||
*/
|
|
||||||
private async validateEmailExistance(email: string): Promise<ISystemUser> {
|
|
||||||
const { systemUserRepository } = this.sysRepositories;
|
|
||||||
const userByEmail = await systemUserRepository.findOneByEmail(email);
|
|
||||||
|
|
||||||
if (!userByEmail) {
|
|
||||||
this.logger.info('[send_reset_password] The given email not found.');
|
|
||||||
throw new ServiceError(ERRORS.EMAIL_NOT_FOUND);
|
|
||||||
}
|
|
||||||
return userByEmail;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates and retrieve password reset token for the given user email.
|
|
||||||
* @param {string} email
|
|
||||||
* @return {<Promise<IPasswordReset>}
|
|
||||||
*/
|
|
||||||
public async sendResetPassword(email: string): Promise<IPasswordReset> {
|
|
||||||
this.logger.info('[send_reset_password] Trying to send reset password.');
|
|
||||||
const user = await this.validateEmailExistance(email);
|
|
||||||
|
|
||||||
// Delete all stored tokens of reset password that associate to the give email.
|
|
||||||
this.logger.info(
|
|
||||||
'[send_reset_password] trying to delete all tokens by email.'
|
|
||||||
);
|
|
||||||
this.deletePasswordResetToken(email);
|
|
||||||
|
|
||||||
const token: string = uniqid();
|
|
||||||
|
|
||||||
this.logger.info('[send_reset_password] insert the generated token.');
|
|
||||||
const passwordReset = await PasswordReset.query().insert({ email, token });
|
|
||||||
|
|
||||||
// Triggers `onSendResetPassword` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.auth.sendResetPassword, {
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
});
|
|
||||||
return passwordReset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets a user password from given token.
|
|
||||||
* @param {string} token - Password reset token.
|
|
||||||
* @param {string} password - New Password.
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
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(ERRORS.TOKEN_INVALID);
|
|
||||||
}
|
|
||||||
// Different between tokne creation datetime and current time.
|
|
||||||
if (
|
|
||||||
moment().diff(tokenModel.createdAt, 'seconds') >
|
|
||||||
config.resetPasswordSeconds
|
|
||||||
) {
|
|
||||||
this.logger.info('[reset_password] token expired.');
|
|
||||||
|
|
||||||
// Deletes the expired token by expired token email.
|
|
||||||
await this.deletePasswordResetToken(tokenModel.email);
|
|
||||||
throw new ServiceError(ERRORS.TOKEN_EXPIRED);
|
|
||||||
}
|
|
||||||
const user = await systemUserRepository.findOneByEmail(tokenModel.email);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new ServiceError(ERRORS.USER_NOT_FOUND);
|
|
||||||
}
|
|
||||||
const hashedPassword = await hashPassword(password);
|
|
||||||
|
|
||||||
this.logger.info('[reset_password] saving a new hashed password.');
|
|
||||||
await systemUserRepository.update(
|
|
||||||
{ password: hashedPassword },
|
|
||||||
{ id: user.id }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Deletes the used token.
|
|
||||||
await this.deletePasswordResetToken(tokenModel.email);
|
|
||||||
|
|
||||||
// Triggers `onResetPassword` event.
|
|
||||||
await this.eventPublisher.emitAsync(events.auth.resetPassword, {
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
password,
|
|
||||||
});
|
|
||||||
this.logger.info('[reset_password] reset password success.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the password reset token by the given email.
|
|
||||||
* @param {string} email
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
private async deletePasswordResetToken(email: string) {
|
|
||||||
this.logger.info('[reset_password] trying to delete all tokens by email.');
|
|
||||||
return PasswordReset.query().where('email', email).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates JWT token for the given user.
|
|
||||||
* @param {ISystemUser} user
|
|
||||||
* @return {string} token
|
|
||||||
*/
|
|
||||||
generateToken(user: ISystemUser): string {
|
|
||||||
const today = new Date();
|
|
||||||
const exp = new Date(today);
|
|
||||||
exp.setDate(today.getDate() + 60);
|
|
||||||
|
|
||||||
this.logger.silly(`Sign JWT for userId: ${user.id}`);
|
|
||||||
return JWT.sign(
|
|
||||||
{
|
|
||||||
id: user.id, // We are gonna use this in the middleware 'isAuth'
|
|
||||||
exp: exp.getTime() / 1000,
|
|
||||||
},
|
|
||||||
config.jwtSecret
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,11 @@ export default {
|
|||||||
*/
|
*/
|
||||||
auth: {
|
auth: {
|
||||||
login: 'onLogin',
|
login: 'onLogin',
|
||||||
|
logining: 'onLogining',
|
||||||
register: 'onRegister',
|
register: 'onRegister',
|
||||||
|
registering: 'onAuthRegistering',
|
||||||
sendResetPassword: 'onSendResetPassword',
|
sendResetPassword: 'onSendResetPassword',
|
||||||
|
sendingResetPassword: 'onSendingResetPassword',
|
||||||
resetPassword: 'onResetPassword',
|
resetPassword: 'onResetPassword',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.table('users', (table) => {
|
||||||
|
table.dropColumn('phone_number');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.table('users', (table) => {});
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user