diff --git a/server/src/services/organization_users.service.ts b/server/src/services/organization_users.service.ts index 50528ee55c..1310c782b1 100644 --- a/server/src/services/organization_users.service.ts +++ b/server/src/services/organization_users.service.ts @@ -10,69 +10,111 @@ import { EmailService } from './email.service'; @Injectable() export class OrganizationUsersService { - constructor( @InjectRepository(OrganizationUser) private organizationUsersRepository: Repository, private usersService: UsersService, private emailService: EmailService, - ) { } + ) {} async findOne(id: string): Promise { return await this.organizationUsersRepository.findOne({ id: id }); } - async inviteNewUser(currentUser: User, params: any): Promise { - - const userParams = { + async inviteNewUser( + currentUser: User, + params: any, + ): Promise { + const userParams = { firstName: params['first_name'], lastName: params['last_name'], - email: params['email'] - } + email: params['email'], + }; - const user = await this.usersService.create(userParams, currentUser.organization); - const organizationUser = await this.create(user, currentUser.organization, params.role); + const user = await this.usersService.create( + userParams, + currentUser.organization, + ); + const organizationUser = await this.create( + user, + currentUser.organization, + params.role, + ); - this.emailService.sendOrganizationUserWelcomeEmail(user.email, user.firstName, currentUser.firstName, user.invitationToken); + this.emailService.sendOrganizationUserWelcomeEmail( + user.email, + user.firstName, + currentUser.firstName, + user.invitationToken, + ); return organizationUser; } - async create(user: User, organization: Organization, role: string): Promise { - return await this.organizationUsersRepository.save(this.organizationUsersRepository.create({ - user, - organization, - role, - createdAt: new Date(), - updatedAt: new Date(), - })); + async create( + user: User, + organization: Organization, + role: string, + ): Promise { + return await this.organizationUsersRepository.save( + this.organizationUsersRepository.create({ + user, + organization, + role, + createdAt: new Date(), + updatedAt: new Date(), + }), + ); } async changeRole(user: User, id: string, role: string) { const organizationUser = await this.organizationUsersRepository.findOne(id); + if (organizationUser.role == 'admin') { + const lastActiveAdmin = await this.lastActiveAdmin( + organizationUser.organizationId, + ); + + if (lastActiveAdmin) { + throw new BadRequestException( + 'Atleast one active admin is required.', + ); + } + } return await this.organizationUsersRepository.update(id, { role }); } async archive(id: string) { - const organizationUser = await this.organizationUsersRepository.findOne(id); - if(organizationUser.role === 'admin') { - // Check if this is the last admin of the org - const adminsCount = await this.organizationUsersRepository.count({ - where: { - organizationId: organizationUser.organizationId, - role: 'admin', - status: 'active' - } - }); - - if(adminsCount === 1) { - throw new BadRequestException('You cannot archive this user as there are no other active admin users.'); + if (organizationUser.role === 'admin') { + const lastActiveAdmin = await this.lastActiveAdmin( + organizationUser.organizationId, + ); + + if (lastActiveAdmin) { + throw new BadRequestException( + 'You cannot archive this user as there are no other active admin users.', + ); } } await this.organizationUsersRepository.update(id, { status: 'archived' }); return true; } + + async lastActiveAdmin(organizationId: string): Promise { + const adminsCount = await this.activeAdminCount(organizationId); + + return adminsCount <= 1; + } + + async activeAdminCount(organizationId: string) { + return await this.organizationUsersRepository.count({ + where: { + organizationId: organizationId, + role: 'admin', + status: 'active', + }, + }); + } } diff --git a/server/test/controllers/organization_users.e2e-spec.ts b/server/test/controllers/organization_users.e2e-spec.ts index 56347d4f50..15f8cb651c 100644 --- a/server/test/controllers/organization_users.e2e-spec.ts +++ b/server/test/controllers/organization_users.e2e-spec.ts @@ -174,6 +174,23 @@ describe('organization users controller', () => { expect(developerUserData.orgUser.role).toBe('developer'); }); + it('should not allow to change role from admin when no other admins are present', async () => { + const adminUserData = await createUser(app, { + email: 'admin@tooljet.io', + role: 'admin', + status: 'active', + }); + + const response = await request(app.getHttpServer()) + .post(`/organization_users/${adminUserData.orgUser.id}/change_role`) + .set('Authorization', authHeaderForUser(adminUserData.user)) + .send({ role: 'viewer' }) + .expect(400); + + await adminUserData.orgUser.reload(); + expect(adminUserData.orgUser.role).toBe('admin'); + }); + it('should allow only admin users to archive org users', async () => { const adminUserData = await createUser(app, { email: 'admin@tooljet.io', diff --git a/server/test/test.helper.ts b/server/test/test.helper.ts index cfbe035014..5931c52927 100644 --- a/server/test/test.helper.ts +++ b/server/test/test.helper.ts @@ -94,9 +94,10 @@ export async function createApplicationVersion(app, application) { ); } + export async function createUser( app, - { firstName, lastName, email, role, organization }: any, + { firstName, lastName, email, role, organization, status }: any, ) { let userRepository: Repository; let organizationRepository: Repository; @@ -133,6 +134,7 @@ export async function createUser( user: user, organization, role: role || 'admin', + status: status || 'invited', createdAt: new Date(), updatedAt: new Date(), }),