From 852299a273c1a486424e06d948a999a413ca3e7b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:16:24 -0600 Subject: [PATCH 1/9] Refactor user role handling to use single role object --- .../src/auth/interfaces/auth.interface.ts | 2 +- libs/common/src/auth/services/auth.service.ts | 5 +- .../src/auth/strategies/jwt.strategy.ts | 2 +- .../auth/strategies/refresh-token.strategy.ts | 2 +- libs/common/src/constants/role.type.enum.ts | 2 + .../src/seed/services/role.type.seeder.ts | 7 ++- .../src/seed/services/supper.admin.seeder.ts | 25 +++++---- src/guards/admin.role.guard.ts | 11 ++-- src/guards/community.permission.guard.ts | 8 +-- src/guards/super.admin.role.guard.ts | 5 +- src/role/controllers/role.controller.ts | 15 ------ src/role/role.module.ts | 2 - src/role/services/role.service.ts | 53 +++---------------- 13 files changed, 43 insertions(+), 96 deletions(-) diff --git a/libs/common/src/auth/interfaces/auth.interface.ts b/libs/common/src/auth/interfaces/auth.interface.ts index 9b73050..ba674ee 100644 --- a/libs/common/src/auth/interfaces/auth.interface.ts +++ b/libs/common/src/auth/interfaces/auth.interface.ts @@ -4,5 +4,5 @@ export class AuthInterface { uuid: string; sessionId: string; id: number; - roles?: string[]; + role?: object; } diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 5792eb0..7f4f03a 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -39,7 +39,7 @@ export class AuthService { } : undefined, }, - relations: ['roles.roleType'], + relations: ['roleType'], }); if (!user.isUserVerified) { @@ -85,9 +85,8 @@ export class AuthService { email: user.email, userId: user.userId, uuid: user.uuid, - type: user.type, sessionId: user.sessionId, - roles: user?.roles, + role: user?.role, googleCode: user.googleCode, }; if (payload.googleCode) { diff --git a/libs/common/src/auth/strategies/jwt.strategy.ts b/libs/common/src/auth/strategies/jwt.strategy.ts index d548dd8..88ecb99 100644 --- a/libs/common/src/auth/strategies/jwt.strategy.ts +++ b/libs/common/src/auth/strategies/jwt.strategy.ts @@ -31,7 +31,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { userUuid: payload.uuid, uuid: payload.uuid, sessionId: payload.sessionId, - roles: payload?.roles, + role: payload?.role, }; } else { throw new BadRequestException('Unauthorized'); diff --git a/libs/common/src/auth/strategies/refresh-token.strategy.ts b/libs/common/src/auth/strategies/refresh-token.strategy.ts index b6d6c2a..6061bd2 100644 --- a/libs/common/src/auth/strategies/refresh-token.strategy.ts +++ b/libs/common/src/auth/strategies/refresh-token.strategy.ts @@ -34,7 +34,7 @@ export class RefreshTokenStrategy extends PassportStrategy( userUuid: payload.uuid, uuid: payload.uuid, sessionId: payload.sessionId, - roles: payload?.roles, + role: payload?.role, }; } else { throw new BadRequestException('Unauthorized'); diff --git a/libs/common/src/constants/role.type.enum.ts b/libs/common/src/constants/role.type.enum.ts index 3051b04..edaf2bf 100644 --- a/libs/common/src/constants/role.type.enum.ts +++ b/libs/common/src/constants/role.type.enum.ts @@ -1,4 +1,6 @@ export enum RoleType { SUPER_ADMIN = 'SUPER_ADMIN', ADMIN = 'ADMIN', + SPACE_OWNER = 'SPACE_OWNER', + SPACE_MEMBER = 'SPACE_MEMBER', } diff --git a/libs/common/src/seed/services/role.type.seeder.ts b/libs/common/src/seed/services/role.type.seeder.ts index 5f7a4b2..1294666 100644 --- a/libs/common/src/seed/services/role.type.seeder.ts +++ b/libs/common/src/seed/services/role.type.seeder.ts @@ -19,7 +19,12 @@ export class RoleTypeSeeder { if (!roleTypeNames.includes(RoleType.ADMIN)) { missingRoleTypes.push(RoleType.ADMIN); } - + if (!roleTypeNames.includes(RoleType.SPACE_OWNER)) { + missingRoleTypes.push(RoleType.SPACE_OWNER); + } + if (!roleTypeNames.includes(RoleType.SPACE_MEMBER)) { + missingRoleTypes.push(RoleType.SPACE_MEMBER); + } if (missingRoleTypes.length > 0) { await this.addRoleTypeData(missingRoleTypes); } diff --git a/libs/common/src/seed/services/supper.admin.seeder.ts b/libs/common/src/seed/services/supper.admin.seeder.ts index 9cfdfee..5f7ef21 100644 --- a/libs/common/src/seed/services/supper.admin.seeder.ts +++ b/libs/common/src/seed/services/supper.admin.seeder.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { UserRoleRepository } from '@app/common/modules/user/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; import { ConfigService } from '@nestjs/config'; import { HelperHashService } from '../../helper/services'; @@ -11,19 +10,23 @@ export class SuperAdminSeeder { constructor( private readonly configService: ConfigService, private readonly userRepository: UserRepository, - private readonly userRoleRepository: UserRoleRepository, private readonly roleTypeRepository: RoleTypeRepository, private readonly helperHashService: HelperHashService, ) {} async createSuperAdminIfNotFound(): Promise { try { - const superAdminData = await this.userRoleRepository.find({ - where: { roleType: { type: RoleType.SUPER_ADMIN } }, + const superAdmin = await this.userRepository.findOne({ + where: { + roleType: { type: RoleType.SUPER_ADMIN }, + email: this.configService.get( + 'super-admin.SUPER_ADMIN_EMAIL', + ), + }, relations: ['roleType'], }); - if (superAdminData.length <= 0) { + if (!superAdmin) { // Create the super admin user if not found console.log('Creating super admin user...'); @@ -48,20 +51,16 @@ export class SuperAdminSeeder { salt, ); try { - const user = await this.userRepository.save({ + const defaultUserRoleUuid = await this.getRoleUuidByRoleType( + RoleType.SUPER_ADMIN, + ); + await this.userRepository.save({ email: this.configService.get('super-admin.SUPER_ADMIN_EMAIL'), password: hashedPassword, firstName: 'Super', lastName: 'Admin', isUserVerified: true, isActive: true, - }); - const defaultUserRoleUuid = await this.getRoleUuidByRoleType( - RoleType.SUPER_ADMIN, - ); - - await this.userRoleRepository.save({ - user: { uuid: user.uuid }, roleType: { uuid: defaultUserRoleUuid }, }); } catch (err) { diff --git a/src/guards/admin.role.guard.ts b/src/guards/admin.role.guard.ts index f7d64a0..db41d20 100644 --- a/src/guards/admin.role.guard.ts +++ b/src/guards/admin.role.guard.ts @@ -7,11 +7,12 @@ export class AdminRoleGuard extends AuthGuard('jwt') { if (err || !user) { throw err || new UnauthorizedException(); } else { - const isAdmin = user.roles.some( - (role) => - role.type === RoleType.SUPER_ADMIN || role.type === RoleType.ADMIN, - ); - if (!isAdmin) { + if ( + !( + user.role.type === RoleType.ADMIN || + user.role.type === RoleType.SUPER_ADMIN + ) + ) { throw new BadRequestException('Only admin role can access this route'); } } diff --git a/src/guards/community.permission.guard.ts b/src/guards/community.permission.guard.ts index 10d9c5e..12d87a8 100644 --- a/src/guards/community.permission.guard.ts +++ b/src/guards/community.permission.guard.ts @@ -20,10 +20,10 @@ export class CommunityPermissionGuard implements CanActivate { if ( user && - user.roles && - user.roles.some( - (role) => - role.type === RoleType.ADMIN || role.type === RoleType.SUPER_ADMIN, + user.role && + !( + user.role.type === RoleType.ADMIN || + user.role.type === RoleType.SUPER_ADMIN ) ) { return true; diff --git a/src/guards/super.admin.role.guard.ts b/src/guards/super.admin.role.guard.ts index ef93a75..b183d78 100644 --- a/src/guards/super.admin.role.guard.ts +++ b/src/guards/super.admin.role.guard.ts @@ -7,10 +7,7 @@ export class SuperAdminRoleGuard extends AuthGuard('jwt') { if (err || !user) { throw err || new UnauthorizedException(); } else { - const isSuperAdmin = user.roles.some( - (role) => role.type === RoleType.SUPER_ADMIN, - ); - if (!isSuperAdmin) { + if (!(user.role.type === RoleType.SUPER_ADMIN)) { throw new BadRequestException( 'Only super admin role can access this route', ); diff --git a/src/role/controllers/role.controller.ts b/src/role/controllers/role.controller.ts index a53cd1c..4317170 100644 --- a/src/role/controllers/role.controller.ts +++ b/src/role/controllers/role.controller.ts @@ -36,19 +36,4 @@ export class RoleController { data: roleTypes, }; } - - @ApiBearerAuth() - @UseGuards(SuperAdminRoleGuard) - @Post() - @ApiOperation({ - summary: ControllerRoute.ROLE.ACTIONS.ADD_USER_ROLE_SUMMARY, - description: ControllerRoute.ROLE.ACTIONS.ADD_USER_ROLE_DESCRIPTION, - }) - async addUserRoleType(@Body() addUserRoleDto: AddUserRoleDto) { - await this.roleService.addUserRoleType(addUserRoleDto); - return { - statusCode: HttpStatus.OK, - message: 'User Role Added Successfully', - }; - } } diff --git a/src/role/role.module.ts b/src/role/role.module.ts index 4e51725..fca78e0 100644 --- a/src/role/role.module.ts +++ b/src/role/role.module.ts @@ -7,7 +7,6 @@ import { RoleController } from './controllers/role.controller'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; -import { UserRoleRepository } from '@app/common/modules/user/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], @@ -18,7 +17,6 @@ import { UserRoleRepository } from '@app/common/modules/user/repositories'; DeviceRepository, RoleService, RoleTypeRepository, - UserRoleRepository, ], exports: [RoleService], }) diff --git a/src/role/services/role.service.ts b/src/role/services/role.service.ts index ba10f44..5f75571 100644 --- a/src/role/services/role.service.ts +++ b/src/role/services/role.service.ts @@ -1,55 +1,16 @@ +import { Injectable } from '@nestjs/common'; import { RoleTypeRepository } from './../../../libs/common/src/modules/role-type/repositories/role.type.repository'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { AddUserRoleDto } from '../dtos/role.add.dto'; -import { UserRoleRepository } from '@app/common/modules/user/repositories'; -import { QueryFailedError } from 'typeorm'; -import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class RoleService { - constructor( - private readonly roleTypeRepository: RoleTypeRepository, - private readonly userRoleRepository: UserRoleRepository, - ) {} - - async addUserRoleType(addUserRoleDto: AddUserRoleDto) { - try { - const roleType = await this.fetchRoleByType(addUserRoleDto.roleType); - - if (roleType.uuid) { - return await this.userRoleRepository.save({ - user: { uuid: addUserRoleDto.userUuid }, - roleType: { uuid: roleType.uuid }, - }); - } - } catch (error) { - if ( - error instanceof QueryFailedError && - error.driverError.code === CommonErrorCodes.DUPLICATE_ENTITY - ) { - // Postgres unique constraint violation error code - throw new HttpException( - 'This role already exists for this user', - HttpStatus.CONFLICT, - ); - } - throw new HttpException( - error.message || 'Internal Server Error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } + constructor(private readonly roleTypeRepository: RoleTypeRepository) {} async fetchRoleTypes() { const roleTypes = await this.roleTypeRepository.find(); - - return roleTypes; - } - private async fetchRoleByType(roleType: string) { - return await this.roleTypeRepository.findOne({ - where: { - type: roleType, - }, - }); + const roles = roleTypes.filter( + (roleType) => roleType.type !== RoleType.SUPER_ADMIN, + ); + return roles; } } From 57397e653abcd953c1641d74999724207a4234c5 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:17:50 -0600 Subject: [PATCH 2/9] Add Invite User Module and Update User and Space Entities This commit introduces a new module for handling user invitations, including DTOs, entities, and repositories. It also updates the User and Space entities to include relationships with the new Invite User entities. --- libs/common/src/constants/controller-route.ts | 20 ++++ libs/common/src/constants/user-status.enum.ts | 5 + .../Invite-user.repository.module.ts | 13 ++ .../Invite-user/dtos/Invite-user.dto.ts | 42 +++++++ .../src/modules/Invite-user/dtos/index.ts | 1 + .../entities/Invite-user.entity.ts | 112 ++++++++++++++++++ .../src/modules/Invite-user/entities/index.ts | 1 + .../repositories/Invite-user.repository.ts | 16 +++ .../modules/Invite-user/repositories/index.ts | 1 + .../modules/space/entities/space.entity.ts | 7 +- libs/common/src/modules/user/dtos/user.dto.ts | 14 --- .../src/modules/user/entities/user.entity.ts | 46 ++++--- .../user/repositories/user.repository.ts | 8 -- libs/common/src/seed/seeder.module.ts | 2 - .../services/invite-user.service.ts | 103 ++++++++++++++++ 15 files changed, 341 insertions(+), 50 deletions(-) create mode 100644 libs/common/src/constants/user-status.enum.ts create mode 100644 libs/common/src/modules/Invite-user/Invite-user.repository.module.ts create mode 100644 libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts create mode 100644 libs/common/src/modules/Invite-user/dtos/index.ts create mode 100644 libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts create mode 100644 libs/common/src/modules/Invite-user/entities/index.ts create mode 100644 libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts create mode 100644 libs/common/src/modules/Invite-user/repositories/index.ts create mode 100644 src/invite-user/services/invite-user.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 10e20d9..c26deca 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -721,4 +721,24 @@ export class ControllerRoute { 'This endpoint deletes a user’s subscription for device messages.'; }; }; + static INVITE_USER = class { + public static readonly ROUTE = 'invite-user'; + static ACTIONS = class { + public static readonly CREATE_USER_INVITATION_SUMMARY = + 'Create user invitation'; + + public static readonly CREATE_USER_INVITATION_DESCRIPTION = + 'This endpoint creates an invitation for a user to assign to role and spaces.'; + }; + }; + static PERMISSION = class { + public static readonly ROUTE = 'permission'; + static ACTIONS = class { + public static readonly GET_PERMISSION_BY_ROLE_SUMMARY = + 'Get permissions by role'; + + public static readonly GET_PERMISSION_BY_ROLE_DESCRIPTION = + 'This endpoint retrieves the permissions associated with a specific role.'; + }; + }; } diff --git a/libs/common/src/constants/user-status.enum.ts b/libs/common/src/constants/user-status.enum.ts new file mode 100644 index 0000000..b0b9817 --- /dev/null +++ b/libs/common/src/constants/user-status.enum.ts @@ -0,0 +1,5 @@ +export enum UserStatusEnum { + ACTIVE = 'active', + INVITED = 'invited', + DISABLED = 'disabled', +} diff --git a/libs/common/src/modules/Invite-user/Invite-user.repository.module.ts b/libs/common/src/modules/Invite-user/Invite-user.repository.module.ts new file mode 100644 index 0000000..4cc8596 --- /dev/null +++ b/libs/common/src/modules/Invite-user/Invite-user.repository.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { InviteUserEntity, InviteUserSpaceEntity } from './entities'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [ + TypeOrmModule.forFeature([InviteUserEntity, InviteUserSpaceEntity]), + ], +}) +export class InviteUserRepositoryModule {} diff --git a/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts new file mode 100644 index 0000000..d7db8dc --- /dev/null +++ b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts @@ -0,0 +1,42 @@ +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; + +export class InviteUserDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public email: string; + @IsString() + @IsNotEmpty() + public jobTitle: string; + @IsEnum(UserStatusEnum) + @IsNotEmpty() + public status: UserStatusEnum; + @IsString() + @IsNotEmpty() + public firstName: string; + + @IsString() + @IsNotEmpty() + public lastName: string; +} +export class InviteUserSpaceDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public inviteUserUuid: string; + + @IsString() + @IsNotEmpty() + public spaceUuid: string; + + @IsString() + @IsNotEmpty() + public invitationCode: string; +} diff --git a/libs/common/src/modules/Invite-user/dtos/index.ts b/libs/common/src/modules/Invite-user/dtos/index.ts new file mode 100644 index 0000000..2385037 --- /dev/null +++ b/libs/common/src/modules/Invite-user/dtos/index.ts @@ -0,0 +1 @@ +export * from './Invite-user.dto'; diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts new file mode 100644 index 0000000..06e5105 --- /dev/null +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -0,0 +1,112 @@ +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + OneToOne, + Unique, +} from 'typeorm'; +import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { RoleTypeEntity } from '../../role-type/entities'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { UserEntity } from '../../user/entities'; +import { SpaceEntity } from '../../space/entities'; + +@Entity({ name: 'invite-user' }) +@Unique(['email', 'invitationCode']) +export class InviteUserEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + unique: true, + }) + email: string; + + @Column({ + nullable: false, + }) + jobTitle: string; + + @Column({ + nullable: false, + enum: Object.values(UserStatusEnum), + }) + status: string; + + @Column() + public firstName: string; + + @Column({ + nullable: false, + }) + public lastName: string; + @Column({ + nullable: false, + }) + public phoneNumber: string; + + @Column({ + nullable: false, + default: true, + }) + public isEnabled: boolean; + + @Column({ + nullable: false, + default: true, + }) + public isActive: boolean; + @Column({ + nullable: false, + unique: true, + }) + public invitationCode: string; + // Relation with RoleTypeEntity + @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.invitedUsers, { + nullable: false, + onDelete: 'CASCADE', + }) + public roleType: RoleTypeEntity; + @OneToOne(() => UserEntity, (user) => user.inviteUser, { nullable: true }) + @JoinColumn({ name: 'user_uuid' }) // Foreign key in InviteUserEntity + user: UserEntity; + @OneToMany( + () => InviteUserSpaceEntity, + (inviteUserSpace) => inviteUserSpace.inviteUser, + ) + spaces: InviteUserSpaceEntity[]; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} +@Entity({ name: 'invite-user-space' }) +@Unique(['inviteUser', 'space']) +export class InviteUserSpaceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @ManyToOne(() => InviteUserEntity, (inviteUser) => inviteUser.spaces) + @JoinColumn({ name: 'invite_user_uuid' }) + public inviteUser: InviteUserEntity; + + @ManyToOne(() => SpaceEntity, (space) => space.invitedUsers) + @JoinColumn({ name: 'space_uuid' }) + public space: SpaceEntity; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/Invite-user/entities/index.ts b/libs/common/src/modules/Invite-user/entities/index.ts new file mode 100644 index 0000000..3f0da22 --- /dev/null +++ b/libs/common/src/modules/Invite-user/entities/index.ts @@ -0,0 +1 @@ +export * from './Invite-user.entity'; diff --git a/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts new file mode 100644 index 0000000..3df7390 --- /dev/null +++ b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts @@ -0,0 +1,16 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { InviteUserEntity, InviteUserSpaceEntity } from '../entities/'; + +@Injectable() +export class InviteUserRepository extends Repository { + constructor(private dataSource: DataSource) { + super(InviteUserEntity, dataSource.createEntityManager()); + } +} +@Injectable() +export class InviteUserSpaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(InviteUserSpaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/Invite-user/repositories/index.ts b/libs/common/src/modules/Invite-user/repositories/index.ts new file mode 100644 index 0000000..a60f19a --- /dev/null +++ b/libs/common/src/modules/Invite-user/repositories/index.ts @@ -0,0 +1 @@ +export * from './Invite-user.repository'; diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 103f0a2..cb8367c 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -15,6 +15,7 @@ import { SubspaceEntity } from './subspace.entity'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; +import { InviteUserSpaceEntity } from '../../Invite-user/entities'; @Entity({ name: 'space' }) @Unique(['invitationCode']) @@ -97,7 +98,11 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => SceneEntity, (scene) => scene.space) scenes: SceneEntity[]; - + @OneToMany( + () => InviteUserSpaceEntity, + (inviteUserSpace) => inviteUserSpace.space, + ) + invitedUsers: InviteUserSpaceEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/user/dtos/user.dto.ts b/libs/common/src/modules/user/dtos/user.dto.ts index 0a4bda2..01c0c24 100644 --- a/libs/common/src/modules/user/dtos/user.dto.ts +++ b/libs/common/src/modules/user/dtos/user.dto.ts @@ -58,20 +58,6 @@ export class UserOtpDto { public expiryTime: string; } -export class UserRoleDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public userUuid: string; - - @IsString() - @IsNotEmpty() - public roleTypeUuid: string; -} - export class UserSpaceDto { @IsString() @IsNotEmpty() diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index c9a11e6..04697fa 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -2,15 +2,16 @@ import { Column, DeleteDateColumn, Entity, + JoinColumn, ManyToOne, OneToMany, + OneToOne, Unique, } from 'typeorm'; import { UserDto, UserNotificationDto, UserOtpDto, - UserRoleDto, UserSpaceDto, } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; @@ -26,6 +27,8 @@ import { OtpType } from '../../../../src/constants/otp-type.enum'; import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities'; import { VisitorPasswordEntity } from '../../visitor-password/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; +import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -100,10 +103,7 @@ export class UserEntity extends AbstractEntity { (deviceUserNotification) => deviceUserNotification.user, ) deviceUserNotification: DeviceNotificationEntity[]; - @OneToMany(() => UserRoleEntity, (role) => role.user, { - nullable: true, - }) - roles: UserRoleEntity[]; + @ManyToOne(() => RegionEntity, (region) => region.users, { nullable: true }) region: RegionEntity; @ManyToOne(() => TimeZoneEntity, (timezone) => timezone.users, { @@ -116,6 +116,21 @@ export class UserEntity extends AbstractEntity { ) public visitorPasswords: VisitorPasswordEntity[]; + @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.users, { + nullable: true, + }) + public roleType: RoleTypeEntity; + @OneToOne(() => InviteUserEntity, (inviteUser) => inviteUser.user, { + nullable: true, + }) + @JoinColumn({ name: 'invite_user_uuid' }) + inviteUser: InviteUserEntity; + + @ManyToOne(() => ProjectEntity, (project) => project.users, { + nullable: true, + }) + @JoinColumn({ name: 'project_uuid' }) + public project: ProjectEntity; constructor(partial: Partial) { super(); Object.assign(this, partial); @@ -125,7 +140,7 @@ export class UserEntity extends AbstractEntity { @Entity({ name: 'user-notification' }) @Unique(['user', 'subscriptionUuid']) export class UserNotificationEntity extends AbstractEntity { - @ManyToOne(() => UserEntity, (user) => user.roles, { + @ManyToOne(() => UserEntity, (user) => user.roleType, { nullable: false, }) user: UserEntity; @@ -178,25 +193,6 @@ export class UserOtpEntity extends AbstractEntity { } } -@Entity({ name: 'user-role' }) -@Unique(['user', 'roleType']) -export class UserRoleEntity extends AbstractEntity { - @ManyToOne(() => UserEntity, (user) => user.roles, { - nullable: false, - }) - user: UserEntity; - - @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.roles, { - nullable: false, - }) - roleType: RoleTypeEntity; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} - @Entity({ name: 'user-space' }) @Unique(['user', 'space']) export class UserSpaceEntity extends AbstractEntity { diff --git a/libs/common/src/modules/user/repositories/user.repository.ts b/libs/common/src/modules/user/repositories/user.repository.ts index ffc1aa1..d2303f9 100644 --- a/libs/common/src/modules/user/repositories/user.repository.ts +++ b/libs/common/src/modules/user/repositories/user.repository.ts @@ -4,7 +4,6 @@ import { UserEntity, UserNotificationEntity, UserOtpEntity, - UserRoleEntity, UserSpaceEntity, } from '../entities/'; @@ -29,13 +28,6 @@ export class UserOtpRepository extends Repository { } } -@Injectable() -export class UserRoleRepository extends Repository { - constructor(private dataSource: DataSource) { - super(UserRoleEntity, dataSource.createEntityManager()); - } -} - @Injectable() export class UserSpaceRepository extends Repository { constructor(private dataSource: DataSource) { diff --git a/libs/common/src/seed/seeder.module.ts b/libs/common/src/seed/seeder.module.ts index eeb245b..6de5585 100644 --- a/libs/common/src/seed/seeder.module.ts +++ b/libs/common/src/seed/seeder.module.ts @@ -10,7 +10,6 @@ import { RoleTypeSeeder } from './services/role.type.seeder'; import { SpaceRepositoryModule } from '../modules/space/space.repository.module'; import { SuperAdminSeeder } from './services/supper.admin.seeder'; import { UserRepository } from '../modules/user/repositories'; -import { UserRoleRepository } from '../modules/user/repositories'; import { UserRepositoryModule } from '../modules/user/user.repository.module'; import { RegionSeeder } from './services/regions.seeder'; import { RegionRepository } from '../modules/region/repositories'; @@ -28,7 +27,6 @@ import { SceneIconRepository } from '../modules/scene/repositories'; RoleTypeRepository, SuperAdminSeeder, UserRepository, - UserRoleRepository, RegionSeeder, RegionRepository, TimeZoneSeeder, diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts new file mode 100644 index 0000000..931c163 --- /dev/null +++ b/src/invite-user/services/invite-user.service.ts @@ -0,0 +1,103 @@ +import { InviteUserSpaceRepository } from './../../../libs/common/src/modules/Invite-user/repositories/Invite-user.repository'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { AddUserInvitationDto } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositories'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { generateRandomString } from '@app/common/helper/randomString'; +import { IsNull, Not } from 'typeorm'; +import { DataSource } from 'typeorm'; +import { UserEntity } from '@app/common/modules/user/entities'; + +@Injectable() +export class InviteUserService { + constructor( + private readonly inviteUserRepository: InviteUserRepository, + private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, + private readonly dataSource: DataSource, + ) {} + + async createUserInvitation( + dto: AddUserInvitationDto, + ): Promise { + const { + firstName, + lastName, + email, + jobTitle, + phoneNumber, + roleUuid, + spaceUuids, + } = dto; + + const invitationCode = generateRandomString(6); + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.startTransaction(); + + try { + const userRepo = queryRunner.manager.getRepository(UserEntity); + + const user = await userRepo.findOne({ + where: { + email, + project: Not(IsNull()), + }, + }); + + if (user) { + throw new HttpException( + 'User already has a project', + HttpStatus.BAD_REQUEST, + ); + } + + const inviteUser = this.inviteUserRepository.create({ + firstName, + lastName, + email, + jobTitle, + phoneNumber, + roleType: { uuid: roleUuid }, + status: UserStatusEnum.INVITED, + invitationCode, + }); + + const invitedUser = await queryRunner.manager.save(inviteUser); + + const spacePromises = spaceUuids.map(async (spaceUuid) => { + const inviteUserSpace = this.inviteUserSpaceRepository.create({ + inviteUser: { uuid: invitedUser.uuid }, + space: { uuid: spaceUuid }, + }); + return queryRunner.manager.save(inviteUserSpace); + }); + + await Promise.all(spacePromises); + + await queryRunner.commitTransaction(); + + return new SuccessResponseDto({ + statusCode: HttpStatus.CREATED, + success: true, + data: { + invitationCode: invitedUser.invitationCode, + }, + message: 'User invited successfully', + }); + } catch (error) { + await queryRunner.rollbackTransaction(); + if (error instanceof HttpException) { + throw error; + } + console.error('Error creating user invitation:', error); + throw new HttpException( + error.message || 'An unexpected error occurred while inviting the user', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + await queryRunner.release(); + } + } +} From 64027d3a16c0e978f0cd0278da4e55cd875a2f50 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:19:14 -0600 Subject: [PATCH 3/9] Add permission and role management features --- .../src/constants/permissions-mapping.ts | 43 ++++++ libs/common/src/constants/role-permissions.ts | 130 ++++++++++++++++++ libs/common/src/database/database.module.ts | 8 +- .../project/entities/project.entity.ts | 4 + .../role-type/entities/role.type.entity.ts | 11 +- .../modules/user/user.repository.module.ts | 2 - src/app.module.ts | 6 +- src/auth/auth.module.ts | 6 +- src/auth/services/user-auth.service.ts | 5 +- src/decorators/permissions.decorator.ts | 4 + src/guards/permissions.guard.ts | 44 ++++++ src/invite-user/controllers/index.ts | 1 + .../controllers/invite-user.controller.ts | 34 +++++ src/invite-user/dtos/add.invite-user.dto.ts | 75 ++++++++++ src/invite-user/dtos/index.ts | 1 + src/invite-user/invite-user.module.ts | 23 ++++ src/invite-user/services/index.ts | 1 + src/permission/controllers/index.ts | 1 + .../controllers/permission.controller.ts | 24 ++++ src/permission/permission.module.ts | 14 ++ src/permission/services/index.ts | 1 + src/permission/services/permission.service.ts | 52 +++++++ 22 files changed, 473 insertions(+), 17 deletions(-) create mode 100644 libs/common/src/constants/permissions-mapping.ts create mode 100644 libs/common/src/constants/role-permissions.ts create mode 100644 src/decorators/permissions.decorator.ts create mode 100644 src/guards/permissions.guard.ts create mode 100644 src/invite-user/controllers/index.ts create mode 100644 src/invite-user/controllers/invite-user.controller.ts create mode 100644 src/invite-user/dtos/add.invite-user.dto.ts create mode 100644 src/invite-user/dtos/index.ts create mode 100644 src/invite-user/invite-user.module.ts create mode 100644 src/invite-user/services/index.ts create mode 100644 src/permission/controllers/index.ts create mode 100644 src/permission/controllers/permission.controller.ts create mode 100644 src/permission/permission.module.ts create mode 100644 src/permission/services/index.ts create mode 100644 src/permission/services/permission.service.ts diff --git a/libs/common/src/constants/permissions-mapping.ts b/libs/common/src/constants/permissions-mapping.ts new file mode 100644 index 0000000..a9db542 --- /dev/null +++ b/libs/common/src/constants/permissions-mapping.ts @@ -0,0 +1,43 @@ +export const PermissionMapping = { + DEVICE_MANAGEMENT: { + DEVICE: ['SINGLE_CONTROL', 'VIEW', 'DELETE', 'UPDATE', 'BATCH_CONTROL'], + FIRMWARE: ['CONTROL', 'VIEW'], + }, + COMMUNITY_MANAGEMENT: { + COMMUNITY: ['VIEW', 'ADD', 'UPDATE', 'DELETE'], + }, + SPACE_MANAGEMENT: { + SPACE: [ + 'VIEW', + 'ADD', + 'UPDATE', + 'DELETE', + 'MODULE_ADD', + 'ASSIGN_USER_TO_SPACE', + 'DELETE_USER_FROM_SPACE', + ], + SUBSPACE: [ + 'VIEW', + 'ADD', + 'UPDATE', + 'DELETE', + 'ASSIGN_DEVICE_TO_SUBSPACE', + 'DELETE_DEVICE_FROM_SUBSPACE', + ], + }, + DEVICE_WIZARD: { + DEVICE_WIZARD: ['VIEW_DEVICE_WIZARD'], + SPACE_DEVICE: ['VIEW_DEVICE_IN_SPACE', 'ASSIGN_DEVICE_TO_SPACE'], + SUBSPACE_DEVICE: ['VIEW_DEVICE_IN_SUBSPACE', 'UPDATE_DEVICE_IN_SUBSPACE'], + }, + AUTOMATION_MANAGEMENT: { + AUTOMATION: ['VIEW', 'ADD', 'UPDATE', 'DELETE', 'CONTROL'], + SCENES: ['VIEW', 'ADD', 'UPDATE', 'DELETE', 'CONTROL'], + }, + VISITOR_PASSWORD_MANAGEMENT: { + VISITOR_PASSWORD: ['VIEW', 'ADD', 'UPDATE', 'DELETE'], + }, + USER_MANAGEMENT: { + USER: ['ADD'], + }, +}; diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts new file mode 100644 index 0000000..ed61e5d --- /dev/null +++ b/libs/common/src/constants/role-permissions.ts @@ -0,0 +1,130 @@ +import { RoleType } from './role.type.enum'; + +export const RolePermissions = { + [RoleType.SUPER_ADMIN]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'DEVICE_DELETE', + 'DEVICE_UPDATE', + 'DEVICE_BATCH_CONTROL', + 'COMMUNITY_VIEW', + 'COMMUNITY_ADD', + 'COMMUNITY_UPDATE', + 'COMMUNITY_DELETE', + 'FIRMWARE_CONTROL', + 'SPACE_VIEW', + 'SPACE_ADD', + 'SPACE_UPDATE', + 'SPACE_DELETE', + 'SPACE_MODULE_ADD', + 'ASSIGN_USER_TO_SPACE', + 'DELETE_USER_FROM_SPACE', + 'SUBSPACE_VIEW', + 'SUBSPACE_ADD', + 'SUBSPACE_UPDATE', + 'SUBSPACE_DELETE', + 'ASSIGN_DEVICE_TO_SUBSPACE', + 'DELETE_DEVICE_FROM_SUBSPACE', + 'VIEW_DEVICE_WIZARD', + 'VIEW_DEVICE_IN_SUBSPACE', + 'VIEW_DEVICE_IN_SPACE', + 'UPDATE_DEVICE_IN_SUBSPACE', + 'ASSIGN_DEVICE_TO_SPACE', + 'AUTOMATION_VIEW', + 'AUTOMATION_ADD', + 'AUTOMATION_UPDATE', + 'AUTOMATION_DELETE', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_ADD', + 'SCENES_UPDATE', + 'SCENES_DELETE', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + 'VISITOR_PASSWORD_ADD', + 'USER_ADD', + ], + [RoleType.ADMIN]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'DEVICE_DELETE', + 'DEVICE_UPDATE', + 'DEVICE_BATCH_CONTROL', + 'COMMUNITY_VIEW', + 'COMMUNITY_ADD', + 'COMMUNITY_UPDATE', + 'COMMUNITY_DELETE', + 'FIRMWARE_CONTROL', + 'SPACE_VIEW', + 'SPACE_ADD', + 'SPACE_UPDATE', + 'SPACE_DELETE', + 'SPACE_MODULE_ADD', + 'ASSIGN_USER_TO_SPACE', + 'DELETE_USER_FROM_SPACE', + 'SUBSPACE_VIEW', + 'SUBSPACE_ADD', + 'SUBSPACE_UPDATE', + 'SUBSPACE_DELETE', + 'ASSIGN_DEVICE_TO_SUBSPACE', + 'DELETE_DEVICE_FROM_SUBSPACE', + 'VIEW_DEVICE_WIZARD', + 'VIEW_DEVICE_IN_SUBSPACE', + 'VIEW_DEVICE_IN_SPACE', + 'UPDATE_DEVICE_IN_SUBSPACE', + 'ASSIGN_DEVICE_TO_SPACE', + 'AUTOMATION_VIEW', + 'AUTOMATION_ADD', + 'AUTOMATION_UPDATE', + 'AUTOMATION_DELETE', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_ADD', + 'SCENES_UPDATE', + 'SCENES_DELETE', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + 'VISITOR_PASSWORD_ADD', + 'USER_ADD', + ], + [RoleType.SPACE_MEMBER]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'SPACE_VIEW', + 'SUBSPACE_VIEW', + 'VIEW_DEVICE_WIZARD', + 'VIEW_DEVICE_IN_SUBSPACE', + 'VIEW_DEVICE_IN_SPACE', + 'AUTOMATION_VIEW', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + ], + [RoleType.SPACE_OWNER]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'FIRMWARE_CONTROL', + 'FIRMWARE_VIEW', + 'SPACE_VIEW', + 'SPACE_MEMBER_ADD', + 'SUBSPACE_VIEW', + 'SUBSPACE_ADD', + 'SUBSPACE_UPDATE', + 'SUBSPACE_DELETE', + 'AUTOMATION_VIEW', + 'AUTOMATION_ADD', + 'AUTOMATION_UPDATE', + 'AUTOMATION_DELETE', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_ADD', + 'SCENES_UPDATE', + 'SCENES_DELETE', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + 'VISITOR_PASSWORD_ADD', + 'VISITOR_PASSWORD_UPDATE', + 'VISITOR_PASSWORD_DELETE', + ], +}; diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 44fd854..eeabcd4 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -15,7 +15,6 @@ import { } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; -import { UserRoleEntity } from '../modules/user/entities'; import { RoleTypeEntity } from '../modules/role-type/entities'; import { UserNotificationEntity } from '../modules/user/entities'; import { DeviceNotificationEntity } from '../modules/device/entities'; @@ -34,6 +33,10 @@ import { SpaceProductModelEntity, SubspaceModelEntity, } from '../modules/space-model/entities'; +import { + InviteUserEntity, + InviteUserSpaceEntity, +} from '../modules/Invite-user/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -63,7 +66,6 @@ import { SpaceProductEntity, UserSpaceEntity, DeviceUserPermissionEntity, - UserRoleEntity, RoleTypeEntity, UserNotificationEntity, DeviceNotificationEntity, @@ -78,6 +80,8 @@ import { SpaceProductModelEntity, SpaceProductItemModelEntity, SubspaceModelEntity, + InviteUserEntity, + InviteUserSpaceEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index f7213a2..01fba17 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -3,6 +3,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProjectDto } from '../dtos'; import { CommunityEntity } from '../../community/entities'; import { SpaceModelEntity } from '../../space-model'; +import { UserEntity } from '../../user/entities'; @Entity({ name: 'project' }) @Unique(['name']) @@ -28,6 +29,9 @@ export class ProjectEntity extends AbstractEntity { @OneToMany(() => CommunityEntity, (community) => community.project) communities: CommunityEntity[]; + @OneToMany(() => UserEntity, (user) => user.project) + public users: UserEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/role-type/entities/role.type.entity.ts b/libs/common/src/modules/role-type/entities/role.type.entity.ts index 10f30bd..b7289a3 100644 --- a/libs/common/src/modules/role-type/entities/role.type.entity.ts +++ b/libs/common/src/modules/role-type/entities/role.type.entity.ts @@ -2,7 +2,8 @@ import { Column, Entity, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeDto } from '../dtos/role.type.dto'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { UserRoleEntity } from '../../user/entities'; +import { UserEntity } from '../../user/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; @Entity({ name: 'role-type' }) @Unique(['type']) @@ -12,10 +13,14 @@ export class RoleTypeEntity extends AbstractEntity { enum: Object.values(RoleType), }) type: string; - @OneToMany(() => UserRoleEntity, (role) => role.roleType, { + @OneToMany(() => UserEntity, (inviteUser) => inviteUser.roleType, { nullable: true, }) - roles: UserRoleEntity[]; + users: UserEntity[]; + @OneToMany(() => InviteUserEntity, (inviteUser) => inviteUser.roleType, { + nullable: true, + }) + invitedUsers: InviteUserEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/user/user.repository.module.ts b/libs/common/src/modules/user/user.repository.module.ts index 11cefe0..e7f7d3e 100644 --- a/libs/common/src/modules/user/user.repository.module.ts +++ b/libs/common/src/modules/user/user.repository.module.ts @@ -4,7 +4,6 @@ import { UserEntity, UserNotificationEntity, UserOtpEntity, - UserRoleEntity, UserSpaceEntity, } from './entities'; @@ -17,7 +16,6 @@ import { UserEntity, UserNotificationEntity, UserOtpEntity, - UserRoleEntity, UserSpaceEntity, ]), ], diff --git a/src/app.module.ts b/src/app.module.ts index ac06b84..3606c0a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,7 +7,6 @@ import { GroupModule } from './group/group.module'; import { DeviceModule } from './device/device.module'; import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module'; import { CommunityModule } from './community/community.module'; -import { RoleModule } from './role/role.module'; import { SeederModule } from '@app/common/seed/seeder.module'; import { UserNotificationModule } from './user-notification/user-notification.module'; import { DeviceMessagesSubscriptionModule } from './device-messages/device-messages.module'; @@ -24,6 +23,8 @@ import { SpaceModule } from './space/space.module'; import { ProductModule } from './product'; import { ProjectModule } from './project'; import { SpaceModelModule } from './space-model'; +import { InviteUserModule } from './invite-user/invite-user.module'; +import { PermissionModule } from './permission/permission.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -31,7 +32,7 @@ import { SpaceModelModule } from './space-model'; }), AuthenticationModule, UserModule, - RoleModule, + InviteUserModule, CommunityModule, SpaceModule, @@ -51,6 +52,7 @@ import { SpaceModelModule } from './space-model'; ScheduleModule, ProductModule, ProjectModule, + PermissionModule, ], providers: [ { diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 66d335d..4af4688 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -6,10 +6,7 @@ import { UserAuthController } from './controllers'; import { UserAuthService } from './services'; import { UserRepository } from '@app/common/modules/user/repositories'; import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository'; -import { - UserRoleRepository, - UserOtpRepository, -} from '@app/common/modules/user/repositories'; +import { UserOtpRepository } from '@app/common/modules/user/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Module({ @@ -20,7 +17,6 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; UserRepository, UserSessionRepository, UserOtpRepository, - UserRoleRepository, RoleTypeRepository, ], exports: [UserAuthService], diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index 80be3c2..b9b3ed3 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -134,13 +134,12 @@ export class UserAuthService { isLoggedOut: false, }), ]); + const res = await this.authService.login({ email: user.email, userId: user.uuid, uuid: user.uuid, - roles: user?.roles?.map((role) => { - return { uuid: role.uuid, type: role.roleType.type }; - }), + role: user.roleType, sessionId: session[1].uuid, }); return res; diff --git a/src/decorators/permissions.decorator.ts b/src/decorators/permissions.decorator.ts new file mode 100644 index 0000000..ad21bf4 --- /dev/null +++ b/src/decorators/permissions.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const Permissions = (...permissions: string[]) => + SetMetadata('permissions', permissions); diff --git a/src/guards/permissions.guard.ts b/src/guards/permissions.guard.ts new file mode 100644 index 0000000..4075943 --- /dev/null +++ b/src/guards/permissions.guard.ts @@ -0,0 +1,44 @@ +import { Injectable, ExecutionContext } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { Reflector } from '@nestjs/core'; +import { RolePermissions } from '@app/common/constants/role-permissions'; +import { RoleType } from '@app/common/constants/role.type.enum'; + +@Injectable() +export class PermissionsGuard extends AuthGuard('jwt') { + constructor(private reflector: Reflector) { + super(); + } + + async canActivate(context: ExecutionContext): Promise { + // First, run the AuthGuard logic to validate the JWT + const isAuthenticated = await super.canActivate(context); + if (!isAuthenticated) { + return false; + } + + // Authorization logic + const requiredPermissions = this.reflector.get( + 'permissions', + context.getHandler(), + ); + + if (!requiredPermissions) { + return true; // Allow if no permissions are specified + } + + const request = context.switchToHttp().getRequest(); + const user = request.user; // User is now available after AuthGuard + console.log('user', user); + + const userRole = user?.role.type as RoleType; + if (!userRole || !RolePermissions[userRole]) { + return false; // Deny if role or permissions are missing + } + + const userPermissions = RolePermissions[userRole]; + + // Check if the user has the required permissions + return requiredPermissions.every((perm) => userPermissions.includes(perm)); + } +} diff --git a/src/invite-user/controllers/index.ts b/src/invite-user/controllers/index.ts new file mode 100644 index 0000000..045b43b --- /dev/null +++ b/src/invite-user/controllers/index.ts @@ -0,0 +1 @@ +export * from './invite-user.controller'; diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts new file mode 100644 index 0000000..7c25450 --- /dev/null +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -0,0 +1,34 @@ +import { InviteUserService } from '../services/invite-user.service'; +import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { AddUserInvitationDto } from '../dtos/add.invite-user.dto'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; + +@ApiTags('Invite User Module') +@Controller({ + version: '1', + path: ControllerRoute.INVITE_USER.ROUTE, +}) +export class InviteUserController { + constructor(private readonly inviteUserService: InviteUserService) {} + + @ApiBearerAuth() + @UseGuards(PermissionsGuard) + @Permissions('USER_ADD') + @Post() + @ApiOperation({ + summary: ControllerRoute.INVITE_USER.ACTIONS.CREATE_USER_INVITATION_SUMMARY, + description: + ControllerRoute.INVITE_USER.ACTIONS.CREATE_USER_INVITATION_DESCRIPTION, + }) + async createUserInvitation( + @Body() addUserInvitationDto: AddUserInvitationDto, + ): Promise { + return await this.inviteUserService.createUserInvitation( + addUserInvitationDto, + ); + } +} diff --git a/src/invite-user/dtos/add.invite-user.dto.ts b/src/invite-user/dtos/add.invite-user.dto.ts new file mode 100644 index 0000000..e47758d --- /dev/null +++ b/src/invite-user/dtos/add.invite-user.dto.ts @@ -0,0 +1,75 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + ArrayMinSize, + IsArray, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; + +export class AddUserInvitationDto { + @ApiProperty({ + description: 'The first name of the user', + example: 'John', + required: true, + }) + @IsString() + @IsNotEmpty() + public firstName: string; + + @ApiProperty({ + description: 'The last name of the user', + example: 'Doe', + required: true, + }) + @IsString() + @IsNotEmpty() + public lastName: string; + + @ApiProperty({ + description: 'The email of the user', + example: 'OqM9A@example.com', + required: true, + }) + @IsString() + @IsNotEmpty() + public email: string; + + @ApiProperty({ + description: 'The job title of the user', + example: 'Software Engineer', + required: true, + }) + @IsString() + @IsNotEmpty() + public jobTitle: string; + + @ApiProperty({ + description: 'The phone number of the user', + example: '+1234567890', + required: true, + }) + @IsString() + @IsOptional() + public phoneNumber?: string; + + @ApiProperty({ + description: 'The role uuid of the user', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + required: true, + }) + @IsString() + @IsNotEmpty() + public roleUuid: string; + @ApiProperty({ + description: 'The array of space UUIDs (at least one required)', + example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'], + required: true, + }) + @IsArray() + @ArrayMinSize(1) + public spaceUuids: string[]; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/invite-user/dtos/index.ts b/src/invite-user/dtos/index.ts new file mode 100644 index 0000000..6cdec2a --- /dev/null +++ b/src/invite-user/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.invite-user.dto'; diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts new file mode 100644 index 0000000..d7a19b6 --- /dev/null +++ b/src/invite-user/invite-user.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { InviteUserService } from './services/invite-user.service'; +import { InviteUserController } from './controllers/invite-user.controller'; +import { ConfigModule } from '@nestjs/config'; +import { + InviteUserRepository, + InviteUserSpaceRepository, +} from '@app/common/modules/Invite-user/repositories'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module'; + +@Module({ + imports: [ConfigModule, InviteUserRepositoryModule], + controllers: [InviteUserController], + providers: [ + InviteUserService, + InviteUserRepository, + UserRepository, + InviteUserSpaceRepository, + ], + exports: [InviteUserService], +}) +export class InviteUserModule {} diff --git a/src/invite-user/services/index.ts b/src/invite-user/services/index.ts new file mode 100644 index 0000000..024e5a5 --- /dev/null +++ b/src/invite-user/services/index.ts @@ -0,0 +1 @@ +export * from './invite-user.service'; diff --git a/src/permission/controllers/index.ts b/src/permission/controllers/index.ts new file mode 100644 index 0000000..e78a8bd --- /dev/null +++ b/src/permission/controllers/index.ts @@ -0,0 +1 @@ +export * from './permission.controller'; diff --git a/src/permission/controllers/permission.controller.ts b/src/permission/controllers/permission.controller.ts new file mode 100644 index 0000000..9b07db2 --- /dev/null +++ b/src/permission/controllers/permission.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { PermissionService } from '../services'; + +@ApiTags('Permission Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.PERMISSION.ROUTE, +}) +export class PermissionController { + constructor(private readonly permissionService: PermissionService) {} + + @Get(':roleUuid') + @ApiOperation({ + summary: ControllerRoute.PERMISSION.ACTIONS.GET_PERMISSION_BY_ROLE_SUMMARY, + description: + ControllerRoute.PERMISSION.ACTIONS.GET_PERMISSION_BY_ROLE_DESCRIPTION, + }) + async getPermissionsByRole(@Param('roleUuid') roleUuid: string) { + return await this.permissionService.getPermissionsByRole(roleUuid); + } +} diff --git a/src/permission/permission.module.ts b/src/permission/permission.module.ts new file mode 100644 index 0000000..8c74871 --- /dev/null +++ b/src/permission/permission.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { CommonModule } from '@app/common'; +import { PermissionController } from './controllers'; +import { PermissionService } from './services'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; + +@Module({ + imports: [ConfigModule, CommonModule], + controllers: [PermissionController], + providers: [PermissionService, RoleTypeRepository], + exports: [PermissionService], +}) +export class PermissionModule {} diff --git a/src/permission/services/index.ts b/src/permission/services/index.ts new file mode 100644 index 0000000..89f3aec --- /dev/null +++ b/src/permission/services/index.ts @@ -0,0 +1 @@ +export * from './permission.service'; diff --git a/src/permission/services/permission.service.ts b/src/permission/services/permission.service.ts new file mode 100644 index 0000000..33c5f18 --- /dev/null +++ b/src/permission/services/permission.service.ts @@ -0,0 +1,52 @@ +import { PermissionMapping } from '@app/common/constants/permissions-mapping'; +import { RolePermissions } from '@app/common/constants/role-permissions'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class PermissionService { + constructor(private readonly roleTypeRepository: RoleTypeRepository) {} + + async getPermissionsByRole(roleUuid: string) { + try { + const role = await this.roleTypeRepository.findOne({ + where: { + uuid: roleUuid, + }, + }); + + if (!role) { + throw new HttpException('Role not found', HttpStatus.NOT_FOUND); + } + + const permissions = this.mapPermissions(role.type.toString() as RoleType); + return permissions; + } catch (err) { + throw new HttpException( + err.message || 'Internal Server Error', + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + mapPermissions(role: RoleType): any[] { + const rolePermissions = RolePermissions[role]; // Permissions for the role + + const mappedPermissions = Object.entries(PermissionMapping).map( + ([title, subOptions]) => ({ + title, + subOptions: Object.entries(subOptions).map( + ([subTitle, permissions]) => ({ + title: subTitle, + subOptions: permissions.map((permission) => ({ + title: permission, + isChecked: rolePermissions.includes(`${subTitle}_${permission}`), // Check if the role has the permission + })), + }), + ), + }), + ); + + return mappedPermissions; + } +} From d169999675227265a19ebf5202029d095a51bf95 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:19:34 -0600 Subject: [PATCH 4/9] Replace JwtAuthGuard with PermissionsGuard and add specific permissions --- .../controllers/automation.controller.ts | 21 ++++-- .../controllers/community.controller.ts | 18 ++++-- src/device/controllers/device.controller.ts | 64 ++++++++++++------- src/group/controllers/group.controller.ts | 9 ++- src/scene/controllers/scene.controller.ts | 24 ++++--- .../controllers/space-model.controller.ts | 6 +- .../controllers/space-device.controller.ts | 6 +- .../controllers/space-scene.controller.ts | 6 +- .../controllers/space-user.controller.ts | 9 ++- src/space/controllers/space.controller.ts | 24 ++++--- .../subspace/subspace-device.controller.ts | 12 ++-- .../subspace/subspace.controller.ts | 18 ++++-- .../visitor-password.controller.ts | 21 ++++-- 13 files changed, 158 insertions(+), 80 deletions(-) diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index 8d9d1ab..265638c 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -16,10 +16,11 @@ import { UpdateAutomationDto, UpdateAutomationStatusDto, } from '../dtos/automation.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { AutomationParamDto, SpaceParamDto } from '../dtos'; import { ControllerRoute } from '@app/common/constants/controller-route'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Automation Module') @Controller({ @@ -30,7 +31,8 @@ export class AutomationController { constructor(private readonly automationService: AutomationService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_ADD') @Post() @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.ADD_AUTOMATION_SUMMARY, @@ -48,7 +50,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_VIEW') @Get(':spaceUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_BY_SPACE_SUMMARY, @@ -63,7 +66,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_VIEW') @Get('details/:automationUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_DETAILS_SUMMARY, @@ -78,7 +82,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_DELETE') @Delete(':automationUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.DELETE_AUTOMATION_SUMMARY, @@ -94,7 +99,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_UPDATE') @Put(':automationUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_SUMMARY, @@ -118,7 +124,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_UPDATE') @Put('status/:automationUuid') @ApiOperation({ summary: diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 5548e84..e823989 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -15,11 +15,12 @@ import { AddCommunityDto } from '../dtos/add.community.dto'; import { GetCommunityParams } from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; // import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; import { ProjectParam } from '../dtos'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Community Module') @Controller({ @@ -30,7 +31,8 @@ export class CommunityController { constructor(private readonly communityService: CommunityService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_ADD') @Post() @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_SUMMARY, @@ -44,7 +46,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_VIEW') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_SUMMARY, description: @@ -58,7 +61,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_VIEW') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_DESCRIPTION, @@ -72,7 +76,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_UPDATE') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_DESCRIPTION, @@ -86,7 +91,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_DELETE') @Delete('/:communityUuid') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_SUMMARY, diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index a49b1f7..26b49ea 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -28,15 +28,15 @@ import { GetSceneFourSceneDeviceDto, } from '../dtos/control.device.dto'; import { CheckRoomGuard } from 'src/guards/room.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckDeviceGuard } from 'src/guards/device.guard'; -import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.type.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { DeviceSceneParamDto } from '../dtos/device.param.dto'; import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Device Module') @Controller({ @@ -46,7 +46,8 @@ import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; export class DeviceController { constructor(private readonly deviceService: DeviceService) {} @ApiBearerAuth() - @UseGuards(SuperAdminRoleGuard, CheckDeviceGuard) + @UseGuards(PermissionsGuard, CheckDeviceGuard) + @Permissions('ASSIGN_DEVICE_TO_SPACE') @Post() @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.ADD_DEVICE_TO_USER_SUMMARY, @@ -63,7 +64,8 @@ export class DeviceController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get('user/:userUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_USER_SUMMARY, @@ -74,7 +76,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_IN_SPACE') @Get('space/:spaceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_SPACE_UUID_SUMMARY, @@ -85,7 +88,8 @@ export class DeviceController { return await this.deviceService.getDevicesBySpaceUuid(spaceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckRoomGuard) + @UseGuards(PermissionsGuard, CheckRoomGuard) + @Permissions('UPDATE_DEVICE_IN_SUBSPACE') @Put('space') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_IN_ROOM_SUMMARY, @@ -108,7 +112,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_DETAILS_SUMMARY, @@ -125,7 +130,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_UPDATE') @Put(':deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_SUMMARY, @@ -149,7 +155,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid/functions') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_INSTRUCTION_SUMMARY, @@ -162,7 +169,8 @@ export class DeviceController { return await this.deviceService.getDeviceInstructionByDeviceId(deviceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid/functions/status') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_STATUS_SUMMARY, @@ -173,7 +181,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_SINGLE_CONTROL') @Post(':deviceUuid/control') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.CONTROL_DEVICE_SUMMARY, @@ -186,7 +195,8 @@ export class DeviceController { return await this.deviceService.controlDevice(controlDeviceDto, deviceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('FIRMWARE_CONTROL') @Post(':deviceUuid/firmware/:firmwareVersion') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_FIRMWARE_SUMMARY, @@ -203,7 +213,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get('gateway/:gatewayUuid/devices') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_IN_GATEWAY_SUMMARY, @@ -214,7 +225,8 @@ export class DeviceController { return await this.deviceService.getDevicesInGateway(gatewayUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get() @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_SUMMARY, @@ -225,7 +237,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get('report-logs/:deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_LOGS_SUMMARY, @@ -238,7 +251,8 @@ export class DeviceController { return await this.deviceService.getDeviceLogs(deviceUuid, query); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_BATCH_CONTROL') @Post('control/batch') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.BATCH_CONTROL_DEVICES_SUMMARY, @@ -251,7 +265,8 @@ export class DeviceController { return await this.deviceService.batchControlDevices(batchControlDevicesDto); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_BATCH_CONTROL') @Get('status/batch') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.BATCH_STATUS_DEVICES_SUMMARY, @@ -264,7 +279,8 @@ export class DeviceController { return await this.deviceService.batchStatusDevices(batchStatusDevicesDto); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_DELETE') @Post('factory/reset/:deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.BATCH_FACTORY_RESET_DEVICES_SUMMARY, @@ -279,7 +295,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':powerClampUuid/power-clamp/status') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_POWER_CLAMP_STATUS_SUMMARY, @@ -294,7 +311,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @UseGuards(PermissionsGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Permissions('DEVICE_SINGLE_CONTROL') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_SUMMARY, description: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_DESCRIPTION, @@ -317,7 +335,8 @@ export class DeviceController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @UseGuards(PermissionsGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid/scenes') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_SCENES_BY_DEVICE_SUMMARY, @@ -334,7 +353,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_DELETE') @Delete(':deviceUuid/scenes') @ApiOperation({ summary: diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index a5d7cd7..513bbeb 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -1,9 +1,10 @@ import { GroupService } from '../services/group.service'; import { Controller, Get, UseGuards, Param, Req } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Group Module') @Controller({ @@ -14,7 +15,8 @@ export class GroupController { constructor(private readonly groupService: GroupService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_WIZARD') @Get(':spaceUuid') @ApiOperation({ summary: ControllerRoute.GROUP.ACTIONS.GET_GROUPS_BY_SPACE_UUID_SUMMARY, @@ -26,7 +28,8 @@ export class GroupController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_WIZARD') @Get(':spaceUuid/devices/:groupName') @ApiOperation({ summary: diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index eaf67ec..655efe5 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -16,11 +16,12 @@ import { AddSceneTapToRunDto, UpdateSceneTapToRunDto, } from '../dtos/scene.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { SceneParamDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { ControllerRoute } from '@app/common/constants/controller-route'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Scene Module') @Controller({ @@ -31,7 +32,8 @@ export class SceneController { constructor(private readonly sceneService: SceneService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_ADD') @Post('tap-to-run') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_SUMMARY, @@ -45,7 +47,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_DELETE') @Delete('tap-to-run/:sceneUuid') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_SUMMARY, @@ -59,7 +62,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_CONTROL') @Post('tap-to-run/:sceneUuid/trigger') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_SUMMARY, @@ -71,7 +75,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_VIEW') @Get('tap-to-run/:sceneUuid') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_SUMMARY, @@ -84,7 +89,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_UPDATE') @Put('tap-to-run/:sceneUuid') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_SUMMARY, @@ -102,7 +108,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_ADD') @Post('icon') async addSceneIcon(@Body() addSceneIconDto: AddSceneIconDto) { const tapToRunScene = await this.sceneService.addSceneIcon(addSceneIconDto); @@ -114,7 +121,8 @@ export class SceneController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_VIEW') @Get('icon') async getAllIcons() { const icons = await this.sceneService.getAllIcons(); diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts index 8475b6c..bedb81e 100644 --- a/src/space-model/controllers/space-model.controller.ts +++ b/src/space-model/controllers/space-model.controller.ts @@ -4,8 +4,9 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceModelService } from '../services'; import { CreateSpaceModelDto } from '../dtos'; import { ProjectParam } from 'src/community/dtos'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Model Module') @Controller({ @@ -16,7 +17,8 @@ export class SpaceModelController { constructor(private readonly spaceModelService: SpaceModelService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MODULE_ADD') @ApiOperation({ summary: ControllerRoute.SPACE_MODEL.ACTIONS.CREATE_SPACE_MODEL_SUMMARY, description: diff --git a/src/space/controllers/space-device.controller.ts b/src/space/controllers/space-device.controller.ts index 160a130..dbb1585 100644 --- a/src/space/controllers/space-device.controller.ts +++ b/src/space/controllers/space-device.controller.ts @@ -1,10 +1,11 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SpaceDeviceService } from '../services'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -15,7 +16,8 @@ export class SpaceDeviceController { constructor(private readonly spaceDeviceService: SpaceDeviceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_IN_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_SUMMARY, description: diff --git a/src/space/controllers/space-scene.controller.ts b/src/space/controllers/space-scene.controller.ts index 5517362..7d6a13d 100644 --- a/src/space/controllers/space-scene.controller.ts +++ b/src/space/controllers/space-scene.controller.ts @@ -1,11 +1,12 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceSceneService } from '../services'; import { GetSceneDto } from '../../scene/dtos'; import { GetSpaceParam } from '../dtos'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -16,7 +17,8 @@ export class SpaceSceneController { constructor(private readonly sceneService: SpaceSceneService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE_SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY, diff --git a/src/space/controllers/space-user.controller.ts b/src/space/controllers/space-user.controller.ts index 9efdac8..faf887a 100644 --- a/src/space/controllers/space-user.controller.ts +++ b/src/space/controllers/space-user.controller.ts @@ -3,8 +3,9 @@ import { Controller, Delete, Param, Post, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceUserService } from '../services'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { UserSpaceParam } from '../dtos'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -16,7 +17,8 @@ export class SpaceUserController { @ApiBearerAuth() @Post('/:userUuid') - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('ASSIGN_USER_TO_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION, @@ -31,7 +33,8 @@ export class SpaceUserController { @ApiBearerAuth() @Delete('/:userUuid') - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DELETE_USER_FROM_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_SUMMARY, description: diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index d31bb23..18d556d 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -11,10 +11,11 @@ import { Put, UseGuards, } from '@nestjs/common'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { AddSpaceDto, CommunitySpaceParam, UpdateSpaceDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { GetSpaceParam } from '../dtos/get.space.param'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -25,7 +26,8 @@ export class SpaceController { constructor(private readonly spaceService: SpaceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_ADD') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_DESCRIPTION, @@ -42,7 +44,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY, @@ -57,7 +60,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_DELETE') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_DESCRIPTION, @@ -68,7 +72,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_UPDATE') @Put('/:spaceUuid') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.UPDATE_SPACE_SUMMARY, @@ -82,7 +87,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.GET_SPACE_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.GET_SPACE_DESCRIPTION, @@ -93,7 +99,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_DESCRIPTION, @@ -107,7 +114,8 @@ export class SpaceController { //should it be post? @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MEMBER_ADD') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_SUMMARY, description: diff --git a/src/space/controllers/subspace/subspace-device.controller.ts b/src/space/controllers/subspace/subspace-device.controller.ts index 189d490..1bb3db4 100644 --- a/src/space/controllers/subspace/subspace-device.controller.ts +++ b/src/space/controllers/subspace/subspace-device.controller.ts @@ -1,5 +1,4 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { Controller, Delete, @@ -12,6 +11,8 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { SubspaceDeviceService } from 'src/space/services'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -22,7 +23,8 @@ export class SubSpaceDeviceController { constructor(private readonly subspaceDeviceService: SubspaceDeviceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_IN_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS.LIST_SUBSPACE_DEVICE_SUMMARY, @@ -37,7 +39,8 @@ export class SubSpaceDeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('ASSIGN_DEVICE_TO_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS.ASSOCIATE_SUBSPACE_DEVICE_SUMMARY, @@ -53,7 +56,8 @@ export class SubSpaceDeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DELETE_DEVICE_FROM_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS diff --git a/src/space/controllers/subspace/subspace.controller.ts b/src/space/controllers/subspace/subspace.controller.ts index 9f766c4..37e264b 100644 --- a/src/space/controllers/subspace/subspace.controller.ts +++ b/src/space/controllers/subspace/subspace.controller.ts @@ -14,8 +14,9 @@ import { SubSpaceService } from '../../services'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -26,7 +27,8 @@ export class SubSpaceController { constructor(private readonly subSpaceService: SubSpaceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_ADD') @Post() @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_SUMMARY, @@ -40,7 +42,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_DESCRIPTION, @@ -54,7 +57,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_DESCRIPTION, @@ -65,7 +69,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_UPDATE') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_DESCRIPTION, @@ -79,7 +84,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_DELETE') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_DESCRIPTION, diff --git a/src/vistor-password/controllers/visitor-password.controller.ts b/src/vistor-password/controllers/visitor-password.controller.ts index 55276be..a298114 100644 --- a/src/vistor-password/controllers/visitor-password.controller.ts +++ b/src/vistor-password/controllers/visitor-password.controller.ts @@ -15,9 +15,10 @@ import { AddDoorLockOnlineMultipleDto, AddDoorLockOnlineOneTimeDto, } from '../dtos/temp-pass.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Visitor Password Module') @Controller({ @@ -30,7 +31,8 @@ export class VisitorPasswordController { ) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/online/multiple-time') @ApiOperation({ summary: @@ -58,7 +60,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/online/one-time') @ApiOperation({ summary: @@ -86,7 +89,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/offline/one-time') @ApiOperation({ summary: @@ -114,7 +118,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/offline/multiple-time') @ApiOperation({ summary: @@ -143,7 +148,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_VIEW') @Get() @ApiOperation({ summary: @@ -156,7 +162,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_VIEW') @Get('/devices') @ApiOperation({ summary: From c1af930dc9a3d28ba8458e72b8aa88cee9479c2e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:36:40 -0600 Subject: [PATCH 5/9] Fix inconsistent casing in import paths for Invite-user module --- libs/common/src/database/database.module.ts | 2 +- .../Invite-user/repositories/Invite-user.repository.ts | 2 +- .../common/src/modules/role-type/entities/role.type.entity.ts | 2 +- libs/common/src/modules/space/entities/space.entity.ts | 2 +- libs/common/src/modules/user/entities/user.entity.ts | 2 +- src/invite-user/invite-user.module.ts | 4 ++-- src/invite-user/services/invite-user.service.ts | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index eeabcd4..bccffc1 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -36,7 +36,7 @@ import { import { InviteUserEntity, InviteUserSpaceEntity, -} from '../modules/Invite-user/entities'; +} from '../modules/invite-user/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ diff --git a/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts index 3df7390..463059a 100644 --- a/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts +++ b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { InviteUserEntity, InviteUserSpaceEntity } from '../entities/'; +import { InviteUserEntity, InviteUserSpaceEntity } from '../entities'; @Injectable() export class InviteUserRepository extends Repository { diff --git a/libs/common/src/modules/role-type/entities/role.type.entity.ts b/libs/common/src/modules/role-type/entities/role.type.entity.ts index b7289a3..a67d164 100644 --- a/libs/common/src/modules/role-type/entities/role.type.entity.ts +++ b/libs/common/src/modules/role-type/entities/role.type.entity.ts @@ -3,7 +3,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeDto } from '../dtos/role.type.dto'; import { RoleType } from '@app/common/constants/role.type.enum'; import { UserEntity } from '../../user/entities'; -import { InviteUserEntity } from '../../Invite-user/entities'; +import { InviteUserEntity } from '../../invite-user/entities'; @Entity({ name: 'role-type' }) @Unique(['type']) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index cb8367c..f87ed4c 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -15,7 +15,7 @@ import { SubspaceEntity } from './subspace.entity'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; -import { InviteUserSpaceEntity } from '../../Invite-user/entities'; +import { InviteUserSpaceEntity } from '../../invite-user/entities'; @Entity({ name: 'space' }) @Unique(['invitationCode']) diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 04697fa..6c3f721 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -27,7 +27,7 @@ import { OtpType } from '../../../../src/constants/otp-type.enum'; import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities'; import { VisitorPasswordEntity } from '../../visitor-password/entities'; -import { InviteUserEntity } from '../../Invite-user/entities'; +import { InviteUserEntity } from '../../invite-user/entities'; import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'user' }) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index d7a19b6..4eebe73 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -5,9 +5,9 @@ import { ConfigModule } from '@nestjs/config'; import { InviteUserRepository, InviteUserSpaceRepository, -} from '@app/common/modules/Invite-user/repositories'; +} from '@app/common/modules/invite-user/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module'; +import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/Invite-user.repository.module'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 931c163..1cef392 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -1,8 +1,8 @@ -import { InviteUserSpaceRepository } from './../../../libs/common/src/modules/Invite-user/repositories/Invite-user.repository'; +import { InviteUserSpaceRepository } from '../../../libs/common/src/modules/invite-user/repositories/Invite-user.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { AddUserInvitationDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { InviteUserRepository } from '@app/common/modules/Invite-user/repositories'; +import { InviteUserRepository } from '@app/common/modules/invite-user/repositories'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { generateRandomString } from '@app/common/helper/randomString'; From 9de1d7134aa092ee9ad76f87156e3d6ca8a0caf4 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:41:51 -0600 Subject: [PATCH 6/9] Fix inconsistent casing in file imports --- libs/common/src/modules/Invite-user/dtos/index.ts | 2 +- libs/common/src/modules/Invite-user/entities/index.ts | 2 +- libs/common/src/modules/Invite-user/repositories/index.ts | 2 +- src/invite-user/invite-user.module.ts | 2 +- src/invite-user/services/invite-user.service.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/common/src/modules/Invite-user/dtos/index.ts b/libs/common/src/modules/Invite-user/dtos/index.ts index 2385037..343abb8 100644 --- a/libs/common/src/modules/Invite-user/dtos/index.ts +++ b/libs/common/src/modules/Invite-user/dtos/index.ts @@ -1 +1 @@ -export * from './Invite-user.dto'; +export * from './invite-user.dto'; diff --git a/libs/common/src/modules/Invite-user/entities/index.ts b/libs/common/src/modules/Invite-user/entities/index.ts index 3f0da22..490fbbe 100644 --- a/libs/common/src/modules/Invite-user/entities/index.ts +++ b/libs/common/src/modules/Invite-user/entities/index.ts @@ -1 +1 @@ -export * from './Invite-user.entity'; +export * from './invite-user.entity'; diff --git a/libs/common/src/modules/Invite-user/repositories/index.ts b/libs/common/src/modules/Invite-user/repositories/index.ts index a60f19a..6f86fa1 100644 --- a/libs/common/src/modules/Invite-user/repositories/index.ts +++ b/libs/common/src/modules/Invite-user/repositories/index.ts @@ -1 +1 @@ -export * from './Invite-user.repository'; +export * from './invite-user.repository'; diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 4eebe73..cfb69ee 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -7,7 +7,7 @@ import { InviteUserSpaceRepository, } from '@app/common/modules/invite-user/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/Invite-user.repository.module'; +import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/invite-user.repository.module'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 1cef392..701b0c9 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -1,4 +1,4 @@ -import { InviteUserSpaceRepository } from '../../../libs/common/src/modules/invite-user/repositories/Invite-user.repository'; +import { InviteUserSpaceRepository } from '../../../libs/common/src/modules/invite-user/repositories/invite-user.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { AddUserInvitationDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; From 6f3891a68e8fcc580c9785436da4f8699476932a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:56:37 -0600 Subject: [PATCH 7/9] Add `invitedBy` field to `InviteUserDto` and `InviteUserEntity` --- .../src/modules/Invite-user/dtos/Invite-user.dto.ts | 8 ++++++++ .../Invite-user/entities/Invite-user.entity.ts | 11 +++++++++-- src/invite-user/controllers/invite-user.controller.ts | 5 ++++- src/invite-user/services/invite-user.service.ts | 3 +++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts index d7db8dc..50e826c 100644 --- a/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts +++ b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts @@ -1,3 +1,4 @@ +import { RoleType } from '@app/common/constants/role.type.enum'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; @@ -9,12 +10,15 @@ export class InviteUserDto { @IsString() @IsNotEmpty() public email: string; + @IsString() @IsNotEmpty() public jobTitle: string; + @IsEnum(UserStatusEnum) @IsNotEmpty() public status: UserStatusEnum; + @IsString() @IsNotEmpty() public firstName: string; @@ -22,6 +26,10 @@ export class InviteUserDto { @IsString() @IsNotEmpty() public lastName: string; + + @IsEnum(RoleType) + @IsNotEmpty() + public invitedBy: RoleType; } export class InviteUserSpaceDto { @IsString() diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 06e5105..818b8ba 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -13,6 +13,7 @@ import { RoleTypeEntity } from '../../role-type/entities'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { UserEntity } from '../../user/entities'; import { SpaceEntity } from '../../space/entities'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Entity({ name: 'invite-user' }) @Unique(['email', 'invitationCode']) @@ -69,14 +70,20 @@ export class InviteUserEntity extends AbstractEntity { unique: true, }) public invitationCode: string; - // Relation with RoleTypeEntity + + @Column({ + nullable: false, + enum: Object.values(RoleType), + }) + public invitedBy: string; + @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.invitedUsers, { nullable: false, onDelete: 'CASCADE', }) public roleType: RoleTypeEntity; @OneToOne(() => UserEntity, (user) => user.inviteUser, { nullable: true }) - @JoinColumn({ name: 'user_uuid' }) // Foreign key in InviteUserEntity + @JoinColumn({ name: 'user_uuid' }) user: UserEntity; @OneToMany( () => InviteUserSpaceEntity, diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 7c25450..756ab68 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -1,5 +1,5 @@ import { InviteUserService } from '../services/invite-user.service'; -import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddUserInvitationDto } from '../dtos/add.invite-user.dto'; import { ControllerRoute } from '@app/common/constants/controller-route'; @@ -26,9 +26,12 @@ export class InviteUserController { }) async createUserInvitation( @Body() addUserInvitationDto: AddUserInvitationDto, + @Req() request: any, ): Promise { + const user = request.user; return await this.inviteUserService.createUserInvitation( addUserInvitationDto, + user.role.type, ); } } diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 701b0c9..9fe0db5 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -9,6 +9,7 @@ import { generateRandomString } from '@app/common/helper/randomString'; import { IsNull, Not } from 'typeorm'; import { DataSource } from 'typeorm'; import { UserEntity } from '@app/common/modules/user/entities'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class InviteUserService { @@ -20,6 +21,7 @@ export class InviteUserService { async createUserInvitation( dto: AddUserInvitationDto, + roleType: RoleType, ): Promise { const { firstName, @@ -62,6 +64,7 @@ export class InviteUserService { roleType: { uuid: roleUuid }, status: UserStatusEnum.INVITED, invitationCode, + invitedBy: roleType, }); const invitedUser = await queryRunner.manager.save(inviteUser); From 61dae6d3f5ed0c1bfcf1f6c4868129ef42f0a86d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:57:35 -0600 Subject: [PATCH 8/9] Remove debug console log from PermissionsGuard --- src/guards/permissions.guard.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/guards/permissions.guard.ts b/src/guards/permissions.guard.ts index 4075943..ac5881e 100644 --- a/src/guards/permissions.guard.ts +++ b/src/guards/permissions.guard.ts @@ -29,7 +29,6 @@ export class PermissionsGuard extends AuthGuard('jwt') { const request = context.switchToHttp().getRequest(); const user = request.user; // User is now available after AuthGuard - console.log('user', user); const userRole = user?.role.type as RoleType; if (!userRole || !RolePermissions[userRole]) { From 5e9a8f3f7895a2ab1350e4c1582fc1eeb0ae03bc Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:00:10 -0600 Subject: [PATCH 9/9] Make roleType field non-nullable in UserEntity --- libs/common/src/modules/user/entities/user.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 6c3f721..3246c41 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -117,7 +117,7 @@ export class UserEntity extends AbstractEntity { public visitorPasswords: VisitorPasswordEntity[]; @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.users, { - nullable: true, + nullable: false, }) public roleType: RoleTypeEntity; @OneToOne(() => InviteUserEntity, (inviteUser) => inviteUser.user, {