diff --git a/libs/common/src/auth/auth.module.ts b/libs/common/src/auth/auth.module.ts index 236fea3..c74aa81 100644 --- a/libs/common/src/auth/auth.module.ts +++ b/libs/common/src/auth/auth.module.ts @@ -7,22 +7,22 @@ import { JwtStrategy } from './strategies/jwt.strategy'; import { UserSessionRepository } from '../modules/session/repositories/session.repository'; import { AuthService } from './services/auth.service'; import { UserRepository } from '../modules/user/repositories'; +import { RefreshTokenStrategy } from './strategies/refresh-token.strategy'; @Module({ imports: [ ConfigModule.forRoot(), PassportModule, - JwtModule.registerAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: async (configService: ConfigService) => ({ - secret: configService.get('JWT_SECRET'), - signOptions: { expiresIn: configService.get('JWT_EXPIRE_TIME') }, - }), - }), + JwtModule.register({}), HelperModule, ], - providers: [JwtStrategy, UserSessionRepository, AuthService, UserRepository], + providers: [ + JwtStrategy, + RefreshTokenStrategy, + UserSessionRepository, + AuthService, + UserRepository, + ], exports: [AuthService], }) export class AuthModule {} diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index d305d94..5e57550 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -1,9 +1,11 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import * as argon2 from 'argon2'; import { HelperHashService } from '../../helper/services'; import { UserRepository } from '../../../../common/src/modules/user/repositories'; import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository'; import { UserSessionEntity } from '../../../../common/src/modules/session/entities'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class AuthService { @@ -12,6 +14,7 @@ export class AuthService { private readonly userRepository: UserRepository, private readonly sessionRepository: UserSessionRepository, private readonly helperHashService: HelperHashService, + private readonly configService: ConfigService, ) {} async validateUser(email: string, pass: string): Promise { @@ -40,6 +43,24 @@ export class AuthService { return await this.sessionRepository.save(data); } + async getTokens(payload) { + const [accessToken, refreshToken] = await Promise.all([ + this.jwtService.signAsync(payload, { + secret: this.configService.get('JWT_SECRET'), + expiresIn: '24h', + }), + this.jwtService.signAsync(payload, { + secret: this.configService.get('JWT_SECRET'), + expiresIn: '7d', + }), + ]); + + return { + accessToken, + refreshToken, + }; + } + async login(user: any) { const payload = { email: user.email, @@ -48,8 +69,22 @@ export class AuthService { type: user.type, sessionId: user.sessionId, }; - return { - access_token: this.jwtService.sign(payload), - }; + const tokens = await this.getTokens(payload); + await this.updateRefreshToken(user.uuid, tokens.refreshToken); + return tokens; + } + + async updateRefreshToken(userId: string, refreshToken: string) { + const hashedRefreshToken = await this.hashData(refreshToken); + await this.userRepository.update( + { uuid: userId }, + { + refreshToken: hashedRefreshToken, + }, + ); + } + + hashData(data: string) { + return argon2.hash(data); } } diff --git a/libs/common/src/auth/strategies/jwt.strategy.ts b/libs/common/src/auth/strategies/jwt.strategy.ts index ac43543..67faecf 100644 --- a/libs/common/src/auth/strategies/jwt.strategy.ts +++ b/libs/common/src/auth/strategies/jwt.strategy.ts @@ -6,7 +6,7 @@ import { UserSessionRepository } from '../../../src/modules/session/repositories import { AuthInterface } from '../interfaces/auth.interface'; @Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { +export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { constructor( private readonly sessionRepository: UserSessionRepository, private readonly configService: ConfigService, diff --git a/libs/common/src/auth/strategies/refresh-token.strategy.ts b/libs/common/src/auth/strategies/refresh-token.strategy.ts new file mode 100644 index 0000000..9b21010 --- /dev/null +++ b/libs/common/src/auth/strategies/refresh-token.strategy.ts @@ -0,0 +1,42 @@ +import { ConfigService } from '@nestjs/config'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import { PassportStrategy } from '@nestjs/passport'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { UserSessionRepository } from '../../../src/modules/session/repositories/session.repository'; +import { AuthInterface } from '../interfaces/auth.interface'; + +@Injectable() +export class RefreshTokenStrategy extends PassportStrategy( + Strategy, + 'jwt-refresh', +) { + constructor( + private readonly sessionRepository: UserSessionRepository, + private readonly configService: ConfigService, + ) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET'), + }); + } + + async validate(payload: AuthInterface) { + const validateUser = await this.sessionRepository.findOne({ + where: { + uuid: payload.sessionId, + isLoggedOut: false, + }, + }); + if (validateUser) { + return { + email: payload.email, + userId: payload.id, + uuid: payload.uuid, + sessionId: payload.sessionId, + }; + } else { + throw new BadRequestException('Unauthorized'); + } + } +} diff --git a/libs/common/src/constants/permission-type.enum.ts b/libs/common/src/constants/permission-type.enum.ts new file mode 100644 index 0000000..67fea31 --- /dev/null +++ b/libs/common/src/constants/permission-type.enum.ts @@ -0,0 +1,4 @@ +export enum PermissionType { + READ = 'READ', + CONTROLLABLE = 'CONTROLLABLE', +} diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 543f522..06cff73 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -7,6 +7,8 @@ import { UserSessionEntity } from '../modules/session/entities/session.entity'; import { UserOtpEntity } from '../modules/user-otp/entities'; import { HomeEntity } from '../modules/home/entities'; import { ProductEntity } from '../modules/product/entities'; +import { DeviceEntity, DeviceUserPermissionEntity } from '../modules/device/entities'; +import { PermissionTypeEntity } from '../modules/permission/entities'; import { SpaceEntity } from '../modules/space/entities'; import { SpaceTypeEntity } from '../modules/space-type/entities'; import { UserSpaceEntity } from '../modules/user-space/entities'; @@ -31,6 +33,9 @@ import { GroupEntity } from '../modules/group/entities'; UserOtpEntity, HomeEntity, ProductEntity, + DeviceUserPermissionEntity, + DeviceEntity, + PermissionTypeEntity, SpaceEntity, SpaceTypeEntity, UserSpaceEntity, diff --git a/libs/common/src/guards/jwt-refresh.auth.guard.ts b/libs/common/src/guards/jwt-refresh.auth.guard.ts new file mode 100644 index 0000000..62c23d8 --- /dev/null +++ b/libs/common/src/guards/jwt-refresh.auth.guard.ts @@ -0,0 +1,11 @@ +import { UnauthorizedException } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +export class RefreshTokenGuard extends AuthGuard('jwt-refresh') { + handleRequest(err, user) { + if (err || !user) { + throw err || new UnauthorizedException(); + } + return user; + } +} diff --git a/libs/common/src/modules/device/device.repository.module.ts b/libs/common/src/modules/device/device.repository.module.ts new file mode 100644 index 0000000..b3d35d7 --- /dev/null +++ b/libs/common/src/modules/device/device.repository.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { DeviceEntity, DeviceUserPermissionEntity } from './entities'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [ + TypeOrmModule.forFeature([DeviceEntity, DeviceUserPermissionEntity]), + ], +}) +export class DeviceRepositoryModule {} diff --git a/libs/common/src/modules/device/dtos/device-user-type.dto.ts b/libs/common/src/modules/device/dtos/device-user-type.dto.ts new file mode 100644 index 0000000..0571356 --- /dev/null +++ b/libs/common/src/modules/device/dtos/device-user-type.dto.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class DeviceUserTypeDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; + + @IsString() + @IsNotEmpty() + public permissionTypeUuid: string; +} diff --git a/libs/common/src/modules/device/dtos/device.dto.ts b/libs/common/src/modules/device/dtos/device.dto.ts new file mode 100644 index 0000000..fbc07c3 --- /dev/null +++ b/libs/common/src/modules/device/dtos/device.dto.ts @@ -0,0 +1,20 @@ +import { IsNotEmpty, IsString } from "class-validator"; + +export class DeviceDto{ + + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + spaceUuid:string; + + @IsString() + @IsNotEmpty() + deviceTuyaUuid:string; + + @IsString() + @IsNotEmpty() + productUuid:string; +} \ No newline at end of file diff --git a/libs/common/src/modules/device/dtos/index.ts b/libs/common/src/modules/device/dtos/index.ts new file mode 100644 index 0000000..9d647c8 --- /dev/null +++ b/libs/common/src/modules/device/dtos/index.ts @@ -0,0 +1,2 @@ +export * from './device.dto'; +export * from './device-user-type.dto'; diff --git a/libs/common/src/modules/device/entities/device-user-type.entity.ts b/libs/common/src/modules/device/entities/device-user-type.entity.ts new file mode 100644 index 0000000..904b18a --- /dev/null +++ b/libs/common/src/modules/device/entities/device-user-type.entity.ts @@ -0,0 +1,42 @@ +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceUserTypeDto } from '../dtos/device-user-type.dto'; +import { DeviceEntity } from './device.entity'; +import { PermissionTypeEntity } from '../../permission/entities'; + +@Entity({ name: 'device-user-permission' }) +export class DeviceUserPermissionEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public userUuid: string; + + @Column({ + nullable: false, + }) + deviceUuid: string; + + @Column({ + nullable: false, + }) + public permissionTypeUuid: string; + + @ManyToOne(() => DeviceEntity, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'device_uuid', referencedColumnName: 'uuid' }) + device: DeviceEntity; + + @ManyToOne(() => PermissionTypeEntity, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'permission_type_uuid', referencedColumnName: 'uuid' }) + type: PermissionTypeEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts new file mode 100644 index 0000000..d90d2d1 --- /dev/null +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -0,0 +1,44 @@ +import { Column, Entity, OneToMany } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceDto } from '../dtos/device.dto'; +import { DeviceUserPermissionEntity } from './device-user-type.entity'; + +@Entity({ name: 'device' }) +export class DeviceEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public spaceUuid: string; + + @Column({ + nullable: false, + }) + deviceTuyaUuid: string; + + @Column({ + nullable: false, + }) + public productUuid: string; + + @Column({ + nullable: true, + default: true, + }) + isActive: true; + + @OneToMany( + () => DeviceUserPermissionEntity, + (permission) => permission.device, + { + nullable: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }, + ) + permission: DeviceUserPermissionEntity[]; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/device/entities/index.ts b/libs/common/src/modules/device/entities/index.ts new file mode 100644 index 0000000..d6cf7b2 --- /dev/null +++ b/libs/common/src/modules/device/entities/index.ts @@ -0,0 +1,2 @@ +export * from './device.entity'; +export * from './device-user-type.entity'; diff --git a/libs/common/src/modules/device/index.ts b/libs/common/src/modules/device/index.ts new file mode 100644 index 0000000..0529a42 --- /dev/null +++ b/libs/common/src/modules/device/index.ts @@ -0,0 +1 @@ +export * from './device.repository.module'; diff --git a/libs/common/src/modules/device/repositories/device-user-type.repository.ts b/libs/common/src/modules/device/repositories/device-user-type.repository.ts new file mode 100644 index 0000000..e3d2176 --- /dev/null +++ b/libs/common/src/modules/device/repositories/device-user-type.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { DeviceUserPermissionEntity } from '../entities'; + +@Injectable() +export class DeviceUserTypeRepository extends Repository { + constructor(private dataSource: DataSource) { + super(DeviceUserPermissionEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/device/repositories/device.repository.ts b/libs/common/src/modules/device/repositories/device.repository.ts new file mode 100644 index 0000000..267c06c --- /dev/null +++ b/libs/common/src/modules/device/repositories/device.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { DeviceEntity } from '../entities'; + +@Injectable() +export class DeviceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(DeviceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/device/repositories/index.ts b/libs/common/src/modules/device/repositories/index.ts new file mode 100644 index 0000000..f91e07f --- /dev/null +++ b/libs/common/src/modules/device/repositories/index.ts @@ -0,0 +1,2 @@ +export * from './device.repository'; +export * from './device-user-type.repository'; diff --git a/libs/common/src/modules/permission/dtos/index.ts b/libs/common/src/modules/permission/dtos/index.ts new file mode 100644 index 0000000..cd80da6 --- /dev/null +++ b/libs/common/src/modules/permission/dtos/index.ts @@ -0,0 +1 @@ +export * from './permission.dto' \ No newline at end of file diff --git a/libs/common/src/modules/permission/dtos/permission.dto.ts b/libs/common/src/modules/permission/dtos/permission.dto.ts new file mode 100644 index 0000000..86912ee --- /dev/null +++ b/libs/common/src/modules/permission/dtos/permission.dto.ts @@ -0,0 +1,11 @@ +import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; + +export class PermissionTypeDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsEnum(PermissionType) + public type: PermissionType; +} diff --git a/libs/common/src/modules/permission/entities/index.ts b/libs/common/src/modules/permission/entities/index.ts new file mode 100644 index 0000000..2d9cedb --- /dev/null +++ b/libs/common/src/modules/permission/entities/index.ts @@ -0,0 +1 @@ +export * from './permission.entity' \ No newline at end of file diff --git a/libs/common/src/modules/permission/entities/permission.entity.ts b/libs/common/src/modules/permission/entities/permission.entity.ts new file mode 100644 index 0000000..d4d17de --- /dev/null +++ b/libs/common/src/modules/permission/entities/permission.entity.ts @@ -0,0 +1,18 @@ +import { Column, Entity } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { PermissionTypeDto } from '../dtos/permission.dto'; + +@Entity({ name: 'permission-type' }) +export class PermissionTypeEntity extends AbstractEntity { + @Column({ + nullable: false, + enum: Object.values(PermissionType), + }) + type: string; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/permission/permission.repository.module.ts b/libs/common/src/modules/permission/permission.repository.module.ts new file mode 100644 index 0000000..6819e9b --- /dev/null +++ b/libs/common/src/modules/permission/permission.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PermissionTypeEntity } from './entities/permission.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([PermissionTypeEntity])], +}) +export class PermissionTypeRepositoryModule {} diff --git a/libs/common/src/modules/permission/repositories/index.ts b/libs/common/src/modules/permission/repositories/index.ts new file mode 100644 index 0000000..cdd0f3a --- /dev/null +++ b/libs/common/src/modules/permission/repositories/index.ts @@ -0,0 +1 @@ +export * from './permission.repository' \ No newline at end of file diff --git a/libs/common/src/modules/permission/repositories/permission.repository.ts b/libs/common/src/modules/permission/repositories/permission.repository.ts new file mode 100644 index 0000000..c9a3f94 --- /dev/null +++ b/libs/common/src/modules/permission/repositories/permission.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { PermissionTypeEntity } from '../entities/permission.entity'; + +@Injectable() +export class PermissionTypeRepository extends Repository { + constructor(private dataSource: DataSource) { + super(PermissionTypeEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 3212662..0981bcc 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -31,12 +31,23 @@ export class UserEntity extends AbstractEntity { }) public lastName: string; + @Column({ + nullable: true, + }) + public refreshToken: string; + @Column({ nullable: true, default: false, }) public isUserVerified: boolean; + @Column({ + nullable: false, + default: true, + }) + public isActive: boolean; + @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user) userSpaces: UserSpaceEntity[]; diff --git a/package-lock.json b/package-lock.json index a41b90f..f333ed5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", "@tuya/tuya-connector-nodejs": "^2.1.2", + "argon2": "^0.40.1", "axios": "^1.6.7", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", @@ -2111,6 +2112,14 @@ "npm": ">=5.0.0" } }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3032,6 +3041,20 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "devOptional": true }, + "node_modules/argon2": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz", + "integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==", + "hasInstallScript": true, + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" + }, + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -7168,6 +7191,14 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-addon-api": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", + "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "engines": { + "node": "^16 || ^18 || >= 20" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", @@ -7196,6 +7227,16 @@ } } }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index 759b7e8..a8502be 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@nestjs/swagger": "^7.3.0", "@nestjs/typeorm": "^10.0.2", "@tuya/tuya-connector-nodejs": "^2.1.2", + "argon2": "^0.40.1", "axios": "^1.6.7", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", diff --git a/src/app.module.ts b/src/app.module.ts index 7050ec6..d6daa3d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { HomeModule } from './home/home.module'; import { RoomModule } from './room/room.module'; 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 { BuildingModule } from './building/building.module'; import { FloorModule } from './floor/floor.module'; @@ -28,6 +29,7 @@ import { UnitModule } from './unit/unit.module'; RoomModule, GroupModule, DeviceModule, + UserDevicePermissionModule ], controllers: [AuthenticationController], }) diff --git a/src/auth/controllers/user-auth.controller.ts b/src/auth/controllers/user-auth.controller.ts index c434ef8..f2a4b6f 100644 --- a/src/auth/controllers/user-auth.controller.ts +++ b/src/auth/controllers/user-auth.controller.ts @@ -2,9 +2,11 @@ import { Body, Controller, Delete, + Get, HttpStatus, Param, Post, + Req, UseGuards, } from '@nestjs/common'; import { UserAuthService } from '../services/user-auth.service'; @@ -14,6 +16,8 @@ import { ResponseMessage } from '../../../libs/common/src/response/response.deco import { UserLoginDto } from '../dtos/user-login.dto'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; +import { Request } from 'express'; +import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard'; @Controller({ version: '1', @@ -93,4 +97,33 @@ export class UserAuthController { message: 'Password changed successfully', }; } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('user/list') + async userList(@Req() req) { + const userList = await this.userAuthService.userList(); + return { + statusCode: HttpStatus.OK, + data: userList, + message: 'User List Fetched Successfully', + }; + } + + @ApiBearerAuth() + @UseGuards(RefreshTokenGuard) + @Get('refresh-token') + async refreshToken(@Req() req) { + const refreshToken = await this.userAuthService.refreshToken( + req.user.uuid, + req.headers.authorization, + req.user.type, + req.user.sessionId, + ); + return { + statusCode: HttpStatus.OK, + data: refreshToken, + message: 'Refresh Token added Successfully', + }; + } } diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index 6c1e172..7b278b2 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -1,6 +1,7 @@ import { UserRepository } from '../../../libs/common/src/modules/user/repositories'; import { BadRequestException, + ForbiddenException, Injectable, UnauthorizedException, } from '@nestjs/common'; @@ -14,7 +15,7 @@ import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; import { EmailService } from '../../../libs/common/src/util/email.service'; import { OtpType } from '../../../libs/common/src/constants/otp-type.enum'; import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity'; -import { ILoginResponse } from '../constants/login.response.constant'; +import * as argon2 from 'argon2'; @Injectable() export class UserAuthService { @@ -64,7 +65,7 @@ export class UserAuthService { ); } - async userLogin(data: UserLoginDto): Promise { + async userLogin(data: UserLoginDto) { const user = await this.authService.validateUser(data.email, data.password); if (!user) { throw new UnauthorizedException('Invalid login credentials.'); @@ -86,7 +87,7 @@ export class UserAuthService { return await this.authService.login({ email: user.email, - userId: user.id, + userId: user.uuid, uuid: user.uuid, sessionId: session[1].uuid, }); @@ -97,7 +98,7 @@ export class UserAuthService { if (!user) { throw new BadRequestException('User does not found'); } - return await this.userRepository.delete({ uuid }); + return await this.userRepository.update({ uuid }, { isActive: false }); } async findOneById(id: string): Promise { @@ -148,4 +149,41 @@ export class UserAuthService { return true; } + + async userList(): Promise { + return await this.userRepository.find({ + where: { isActive: true }, + select: { + firstName: true, + lastName: true, + email: true, + isActive: true, + }, + }); + } + + async refreshToken( + userId: string, + refreshToken: string, + type: string, + sessionId: string, + ) { + const user = await this.userRepository.findOne({ where: { uuid: userId } }); + if (!user || !user.refreshToken) + throw new ForbiddenException('Access Denied'); + const refreshTokenMatches = await argon2.verify( + user.refreshToken, + refreshToken, + ); + if (!refreshTokenMatches) throw new ForbiddenException('Access Denied'); + const tokens = await this.authService.getTokens({ + email: user.email, + userId: user.uuid, + uuid: user.uuid, + type, + sessionId, + }); + await this.authService.updateRefreshToken(user.uuid, tokens.refreshToken); + return tokens; + } } diff --git a/src/config/index.ts b/src/config/index.ts index 0dfc023..d7d0014 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,4 +1,4 @@ import AuthConfig from './auth.config'; import AppConfig from './app.config'; - -export default [AuthConfig, AppConfig]; +import JwtConfig from './jwt.config'; +export default [AuthConfig, AppConfig, JwtConfig]; diff --git a/src/user-device-permission/controllers/index.ts b/src/user-device-permission/controllers/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/user-device-permission/controllers/user-device-permission.controller.ts b/src/user-device-permission/controllers/user-device-permission.controller.ts new file mode 100644 index 0000000..bfd98cb --- /dev/null +++ b/src/user-device-permission/controllers/user-device-permission.controller.ts @@ -0,0 +1,68 @@ +import { + Body, + Controller, + HttpStatus, + Param, + Post, + Put, + UseGuards, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { UserDevicePermissionService } from '../services/user-device-permission.service'; +import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; + +@ApiTags('Device Permission Module') +@Controller({ + version: '1', + path: 'device-permission', +}) +export class UserDevicePermissionController { + constructor( + private readonly userDevicePermissionService: UserDevicePermissionService, + ) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('add') + async addDevicePermission( + @Body() userDevicePermissionDto: UserDevicePermissionAddDto, + ) { + try { + const addDetails = + await this.userDevicePermissionService.addUserPermission( + userDevicePermissionDto, + ); + return { + statusCode: HttpStatus.CREATED, + message: 'User Permission for Devices Added Successfully', + data: addDetails, + }; + } catch (err) { + throw new Error(err); + } + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('edit/:userId') + async editDevicePermission( + @Param('userId') userId: string, + @Body() userDevicePermissionEditDto: UserDevicePermissionEditDto, + ) { + try { + await this.userDevicePermissionService.editUserPermission( + userId, + userDevicePermissionEditDto, + ); + return { + statusCode: HttpStatus.OK, + message: 'User Permission for Devices Updated Successfully', + data: {}, + }; + } catch (err) { + throw new Error(err); + } + } +} diff --git a/src/user-device-permission/dtos/index.ts b/src/user-device-permission/dtos/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/user-device-permission/dtos/user-device-permission.add.dto.ts b/src/user-device-permission/dtos/user-device-permission.add.dto.ts new file mode 100644 index 0000000..c7459df --- /dev/null +++ b/src/user-device-permission/dtos/user-device-permission.add.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UserDevicePermissionAddDto { + @ApiProperty({ + description: 'user id', + required: true, + }) + @IsString() + @IsNotEmpty() + userId: string; + + @ApiProperty({ + description: 'permission type id', + required: true, + }) + @IsString() + @IsNotEmpty() + permissionTypeId: string; + + @ApiProperty({ + description: 'device id', + required: true, + }) + @IsString() + @IsNotEmpty() + deviceId: string; +} diff --git a/src/user-device-permission/dtos/user-device-permission.edit.dto.ts b/src/user-device-permission/dtos/user-device-permission.edit.dto.ts new file mode 100644 index 0000000..7cc4fce --- /dev/null +++ b/src/user-device-permission/dtos/user-device-permission.edit.dto.ts @@ -0,0 +1,7 @@ +import { OmitType } from '@nestjs/swagger'; +import { UserDevicePermissionAddDto } from './user-device-permission.add.dto'; + +export class UserDevicePermissionEditDto extends OmitType( + UserDevicePermissionAddDto, + ['userId'], +) {} diff --git a/src/user-device-permission/services/index.ts b/src/user-device-permission/services/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/user-device-permission/services/user-device-permission.service.ts b/src/user-device-permission/services/user-device-permission.service.ts new file mode 100644 index 0000000..7dbb971 --- /dev/null +++ b/src/user-device-permission/services/user-device-permission.service.ts @@ -0,0 +1,32 @@ +import { DeviceUserTypeRepository } from '@app/common/modules/device/repositories'; +import { Injectable } from '@nestjs/common'; +import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.dto'; +import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; + +@Injectable() +export class UserDevicePermissionService { + constructor( + private readonly deviceUserTypeRepository: DeviceUserTypeRepository, + ) {} + + async addUserPermission(userDevicePermissionDto: UserDevicePermissionAddDto) { + return await this.deviceUserTypeRepository.save({ + userUuid: userDevicePermissionDto.userId, + deviceUuid: userDevicePermissionDto.deviceId, + permissionTypeUuid: userDevicePermissionDto.permissionTypeId, + }); + } + + async editUserPermission( + userId: string, + userDevicePermissionEditDto: UserDevicePermissionEditDto, + ) { + return await this.deviceUserTypeRepository.update( + { userUuid: userId }, + { + deviceUuid: userDevicePermissionEditDto.deviceId, + permissionTypeUuid: userDevicePermissionEditDto.permissionTypeId, + }, + ); + } +} diff --git a/src/user-device-permission/user-device-permission.module.ts b/src/user-device-permission/user-device-permission.module.ts new file mode 100644 index 0000000..edd9991 --- /dev/null +++ b/src/user-device-permission/user-device-permission.module.ts @@ -0,0 +1,21 @@ +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { + DeviceRepository, + DeviceUserTypeRepository, +} from '@app/common/modules/device/repositories'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { UserDevicePermissionService } from './services/user-device-permission.service'; +import { UserDevicePermissionController } from './controllers/user-device-permission.controller'; + +@Module({ + imports: [ConfigModule, DeviceRepositoryModule], + controllers: [UserDevicePermissionController], + providers: [ + DeviceUserTypeRepository, + DeviceRepository, + UserDevicePermissionService, + ], + exports: [UserDevicePermissionService], +}) +export class UserDevicePermissionModule {}