fix: invite user to the system.

fix: soft delete system user.
This commit is contained in:
a.bouhuolia
2021-01-19 13:18:48 +02:00
parent ef53b25c18
commit 59f44d9ef6
17 changed files with 320 additions and 136 deletions

View File

@@ -23,7 +23,6 @@ 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',
@@ -32,7 +31,7 @@ const ERRORS = {
USER_NOT_FOUND: 'USER_NOT_FOUND',
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
EMAIL_EXISTS: 'EMAIL_EXISTS'
EMAIL_EXISTS: 'EMAIL_EXISTS',
};
@Service()
export default class AuthenticationService implements IAuthenticationService {
@@ -136,6 +135,7 @@ export default class AuthenticationService implements IAuthenticationService {
*/
private async validateEmailAndPhoneUniqiness(registerDTO: IRegisterDTO) {
const { systemUserRepository } = this.sysRepositories;
const isEmailExists = await systemUserRepository.findOneByEmail(
registerDTO.email
);
@@ -279,7 +279,10 @@ export default class AuthenticationService implements IAuthenticationService {
const hashedPassword = await hashPassword(password);
this.logger.info('[reset_password] saving a new hashed password.');
await systemUserRepository.update({ password: hashedPassword }, { id: user.id });
await systemUserRepository.update(
{ password: hashedPassword },
{ id: user.id }
);
// Deletes the used token.
await this.deletePasswordResetToken(tokenModel.email);

View File

@@ -12,13 +12,14 @@ import { hashPassword } from 'utils';
import TenancyService from 'services/Tenancy/TenancyService';
import InviteUsersMailMessages from 'services/InviteUsers/InviteUsersMailMessages';
import events from 'subscribers/events';
import { ISystemUser, IInviteUserInput } from 'interfaces';
import { ISystemUser, IInviteUserInput, IUserInvite } from 'interfaces';
import TenantsManagerService from 'services/Tenancy/TenantsManager';
const ERRORS = {
EMAIL_ALREADY_INVITED: 'EMAIL_ALREADY_INVITED',
INVITE_TOKEN_INVALID: 'INVITE_TOKEN_INVALID',
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS'
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
EMAIL_EXISTS: 'EMAIL_EXISTS'
};
@Service()
export default class InviteUserService {
@@ -66,12 +67,16 @@ export default class InviteUserService {
const user = await systemUserRepository.findOneByEmail(inviteToken.email);
// Sets the invited user details after invite accepting.
const updateUserOper = systemUserRepository.update({
...inviteUserInput,
active: 1,
inviteAcceptedAt: moment().format('YYYY-MM-DD'),
password: hashedPassword,
}, { id: user.id });
const systemUserOper = systemUserRepository.create(
{
...inviteUserInput,
email: inviteToken.email,
tenantId: inviteToken.tenantId,
active: 1,
inviteAcceptedAt: moment().format('YYYY-MM-DD'),
password: hashedPassword,
},
);
this.logger.info('[accept_invite] trying to delete the given token.');
const deleteInviteTokenOper = Invite.query()
@@ -79,14 +84,14 @@ export default class InviteUserService {
.delete();
// Await all async operations.
const [updatedUser] = await Promise.all([
updateUserOper,
const [systemUser] = await Promise.all([
systemUserOper,
deleteInviteTokenOper,
]);
// Triggers `onUserAcceptInvite` event.
this.eventDispatcher.dispatch(events.inviteUser.acceptInvite, {
inviteToken,
user: updatedUser,
user: systemUser,
});
}
@@ -96,21 +101,21 @@ export default class InviteUserService {
* @param {string} email -
* @param {IUser} authorizedUser -
*
* @return {Promise<IInvite>}
* @return {Promise<IUserInvite>}
*/
public async sendInvite(
tenantId: number,
email: string,
authorizedUser: ISystemUser
): Promise<{
invite: IInvite,
user: ISystemUser
invite: IUserInvite;
}> {
const { systemUserRepository } = this.sysRepositories;
// Throw error in case user email exists.
await this.throwErrorIfUserEmailExists(email);
// Throws service error in case the user already invited.
await this.throwErrorIfUserInvited(email);
this.logger.info('[send_invite] trying to store invite token.');
const invite = await Invite.query().insert({
email,
@@ -121,16 +126,13 @@ export default class InviteUserService {
this.logger.info(
'[send_invite] trying to store user with email and tenant.'
);
const user = await systemUserRepository.create({
email,
tenant_id: authorizedUser.tenantId,
active: 1,
});
// Triggers `onUserSendInvite` event.
this.eventDispatcher.dispatch(events.inviteUser.sendInvite, {
invite, authorizedUser, tenantId
invite,
authorizedUser,
tenantId,
});
return { invite, user };
return { invite };
}
/**
@@ -140,7 +142,7 @@ export default class InviteUserService {
*/
public async checkInvite(
token: string
): Promise<{ inviteToken: string; orgName: object }> {
): Promise<{ inviteToken: IUserInvite; orgName: object }> {
const inviteToken = await this.getInviteOrThrowError(token);
// Find the tenant that associated to the given token.
@@ -170,14 +172,27 @@ export default class InviteUserService {
*/
private async throwErrorIfUserEmailExists(
email: string
): Promise<ISystemUser> {
): Promise<void> {
const { systemUserRepository } = this.sysRepositories;
const foundUser = await systemUserRepository.findOneByEmail(email);
if (foundUser) {
throw new ServiceError(ERRORS.EMAIL_EXISTS);
}
}
/**
* Throws service error if the user already invited.
* @param {string} email -
*/
private async throwErrorIfUserInvited(
email: string,
): Promise<void> {
const inviteToken = await Invite.query().findOne('email', email);
if (inviteToken) {
throw new ServiceError(ERRORS.EMAIL_ALREADY_INVITED);
}
return foundUser;
}
/**
@@ -186,7 +201,7 @@ export default class InviteUserService {
* @throws {ServiceError}
* @returns {Invite}
*/
private async getInviteOrThrowError(token: string) {
private async getInviteOrThrowError(token: string): Promise<IUserInvite> {
const inviteToken = await Invite.query().findOne('token', token);
if (!inviteToken) {

View File

@@ -1,9 +1,17 @@
import { Inject, Service } from 'typedi';
import TenancyService from 'services/Tenancy/TenancyService';
import { SystemUser } from 'system/models';
import { Inject, Service } from 'typedi';
import { ServiceError, ServiceErrors } from 'exceptions';
import { ISystemUser, ISystemUserDTO } from 'interfaces';
import systemRepositories from 'loaders/systemRepositories';
const ERRORS = {
CANNOT_DELETE_LAST_USER: 'CANNOT_DELETE_LAST_USER',
USER_ALREADY_ACTIVE: 'USER_ALREADY_ACTIVE',
USER_ALREADY_INACTIVE: 'USER_ALREADY_INACTIVE',
EMAIL_ALREADY_EXISTS: 'EMAIL_ALREADY_EXISTS',
PHONE_NUMBER_ALREADY_EXIST: 'PHONE_NUMBER_ALREADY_EXIST',
USER_NOT_FOUND: 'USER_NOT_FOUND',
USER_SAME_THE_AUTHORIZED_USER: 'USER_SAME_THE_AUTHORIZED_USER',
};
@Service()
export default class UsersService {
@@ -23,7 +31,7 @@ export default class UsersService {
* @param {IUserDTO} userDTO
* @return {Promise<ISystemUser>}
*/
async editUser(
public async editUser(
tenantId: number,
userId: number,
userDTO: ISystemUserDTO
@@ -36,49 +44,24 @@ export default class UsersService {
});
const userByPhoneNumber = await systemUserRepository.findOne({
phoneNumber: userDTO.phoneNumber,
id: userId
id: userId,
});
const serviceErrors: ServiceError[] = [];
if (userByEmail) {
serviceErrors.push(new ServiceError('email_already_exists'));
serviceErrors.push(new ServiceError(ERRORS.EMAIL_ALREADY_EXISTS));
}
if (userByPhoneNumber) {
serviceErrors.push(new ServiceError('phone_number_already_exist'));
serviceErrors.push(new ServiceError(ERRORS.PHONE_NUMBER_ALREADY_EXIST));
}
if (serviceErrors.length > 0) {
throw new ServiceErrors(serviceErrors);
}
const updateSystemUser = await systemUserRepository
.update({ ...userDTO, }, { id: userId });
return updateSystemUser;
}
/**
* Validate user existance throw error in case user was not found.,
* @param {number} tenantId -
* @param {number} userId -
* @returns {ISystemUser}
*/
async getUserOrThrowError(
tenantId: number,
userId: number
): Promise<ISystemUser> {
const { systemUserRepository } = this.repositories;
const user = await systemUserRepository.findOneByIdAndTenant(
userId,
tenantId
const updateSystemUser = await systemUserRepository.update(
{ ...userDTO },
{ id: userId }
);
if (!user) {
this.logger.info('[users] the given user not found.', {
tenantId,
userId,
});
throw new ServiceError('user_not_found');
}
return user;
return updateSystemUser;
}
/**
@@ -86,14 +69,20 @@ export default class UsersService {
* @param {number} tenantId
* @param {number} userId
*/
async deleteUser(tenantId: number, userId: number): Promise<void> {
public async deleteUser(tenantId: number, userId: number): Promise<void> {
const { systemUserRepository } = this.repositories;
await this.getUserOrThrowError(tenantId, userId);
// Retrieve user details or throw not found service error.
const oldUser = await this.getUserOrThrowError(tenantId, userId);
this.logger.info('[users] trying to delete the given user.', {
tenantId,
userId,
});
// Validate the delete user should not be the last user.
await this.validateNotLastUserDelete(tenantId);
// Delete user from the storage.
await systemUserRepository.deleteById(userId);
this.logger.info('[users] the given user deleted successfully.', {
@@ -104,18 +93,24 @@ export default class UsersService {
/**
* Activate the given user id.
* @param {number} tenantId
* @param {number} userId
* @param {number} tenantId - Tenant id.
* @param {number} userId - User id.
* @return {Promise<void>}
*/
async activateUser(
public async activateUser(
tenantId: number,
userId: number,
authorizedUser: ISystemUser
): Promise<void> {
this.throwErrorIfUserIdSameAuthorizedUser(userId, authorizedUser);
const { systemUserRepository } = this.repositories;
// Throw service error if the given user is equals the authorized user.
this.throwErrorIfUserSameAuthorizedUser(userId, authorizedUser);
// Retrieve the user or throw not found service error.
const user = await this.getUserOrThrowError(tenantId, userId);
// Throw serivce error if the user is already activated.
this.throwErrorIfUserActive(user);
await systemUserRepository.activateUser(userId);
@@ -127,15 +122,20 @@ export default class UsersService {
* @param {number} userId
* @return {Promise<void>}
*/
async inactivateUser(
public async inactivateUser(
tenantId: number,
userId: number,
authorizedUser: ISystemUser
): Promise<void> {
this.throwErrorIfUserIdSameAuthorizedUser(userId, authorizedUser);
const { systemUserRepository } = this.repositories;
// Throw service error if the given user is equals the authorized user.
this.throwErrorIfUserSameAuthorizedUser(userId, authorizedUser);
// Retrieve the user or throw not found service error.
const user = await this.getUserOrThrowError(tenantId, userId);
// Throw serivce error if the user is already inactivated.
this.throwErrorIfUserInactive(user);
await systemUserRepository.inactivateById(userId);
@@ -146,10 +146,10 @@ export default class UsersService {
* @param {number} tenantId
* @param {object} filter
*/
async getList(tenantId: number) {
const users = await SystemUser.query()
.whereNotDeleted()
.where('tenant_id', tenantId);
public async getList(tenantId: number) {
const { systemUserRepository } = this.repositories;
const users = await systemUserRepository.find({ tenantId });
return users;
}
@@ -159,18 +159,58 @@ export default class UsersService {
* @param {number} tenantId - Tenant id.
* @param {number} userId - User id.
*/
async getUser(tenantId: number, userId: number) {
public async getUser(tenantId: number, userId: number) {
return this.getUserOrThrowError(tenantId, userId);
}
/**
* Validate user existance throw error in case user was not found.,
* @param {number} tenantId -
* @param {number} userId -
* @returns {ISystemUser}
*/
async getUserOrThrowError(
tenantId: number,
userId: number
): Promise<ISystemUser> {
const { systemUserRepository } = this.repositories;
const user = await systemUserRepository.findOneByIdAndTenant(
userId,
tenantId
);
if (!user) {
this.logger.info('[users] the given user not found.', {
tenantId,
userId,
});
throw new ServiceError(ERRORS.USER_NOT_FOUND);
}
return user;
}
/**
* Validate the delete user should not be the last user.
* @param {number} tenantId
*/
private async validateNotLastUserDelete(tenantId: number) {
const { systemUserRepository } = this.repositories;
const usersFound = await systemUserRepository.find({ tenantId });
if (usersFound.length === 1) {
throw new ServiceError(ERRORS.CANNOT_DELETE_LAST_USER);
}
}
/**
* Throws service error in case the user was already active.
* @param {ISystemUser} user
* @throws {ServiceError}
*/
throwErrorIfUserActive(user: ISystemUser) {
private throwErrorIfUserActive(user: ISystemUser) {
if (user.active) {
throw new ServiceError('user_already_active');
throw new ServiceError(ERRORS.USER_ALREADY_ACTIVE);
}
}
@@ -179,9 +219,9 @@ export default class UsersService {
* @param {ISystemUser} user
* @throws {ServiceError}
*/
throwErrorIfUserInactive(user: ISystemUser) {
private throwErrorIfUserInactive(user: ISystemUser) {
if (!user.active) {
throw new ServiceError('user_already_inactive');
throw new ServiceError(ERRORS.USER_ALREADY_INACTIVE);
}
}
@@ -190,12 +230,12 @@ export default class UsersService {
* @param {number} userId
* @param {ISystemUser} authorizedUser
*/
throwErrorIfUserIdSameAuthorizedUser(
private throwErrorIfUserSameAuthorizedUser(
userId: number,
authorizedUser: ISystemUser
) {
if (userId === authorizedUser.id) {
throw new ServiceError('user_same_the_authorized_user');
throw new ServiceError(ERRORS.USER_SAME_THE_AUTHORIZED_USER);
}
}
}