Merge branch 'feat/device_permissions' into SP-171-be-create-groups-to-control-devices-per-group

This commit is contained in:
faris Aljohari
2024-04-18 10:05:51 +03:00
40 changed files with 616 additions and 19 deletions
+9 -9
View File
@@ -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 {}
+38 -3
View File
@@ -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<any> {
@@ -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<string>('JWT_SECRET'),
expiresIn: '24h',
}),
this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('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);
}
}
@@ -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,
@@ -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');
}
}
}
@@ -0,0 +1,4 @@
export enum PermissionType {
READ = 'READ',
CONTROLLABLE = 'CONTROLLABLE',
}
@@ -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,
@@ -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;
}
}
@@ -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 {}
@@ -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;
}
@@ -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;
}
@@ -0,0 +1,2 @@
export * from './device.dto';
export * from './device-user-type.dto';
@@ -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<DeviceUserTypeDto> {
@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<DeviceUserPermissionEntity>) {
super();
Object.assign(this, partial);
}
}
@@ -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<DeviceDto> {
@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<DeviceEntity>) {
super();
Object.assign(this, partial);
}
}
@@ -0,0 +1,2 @@
export * from './device.entity';
export * from './device-user-type.entity';
+1
View File
@@ -0,0 +1 @@
export * from './device.repository.module';
@@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { DeviceUserPermissionEntity } from '../entities';
@Injectable()
export class DeviceUserTypeRepository extends Repository<DeviceUserPermissionEntity> {
constructor(private dataSource: DataSource) {
super(DeviceUserPermissionEntity, dataSource.createEntityManager());
}
}
@@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { DeviceEntity } from '../entities';
@Injectable()
export class DeviceRepository extends Repository<DeviceEntity> {
constructor(private dataSource: DataSource) {
super(DeviceEntity, dataSource.createEntityManager());
}
}
@@ -0,0 +1,2 @@
export * from './device.repository';
export * from './device-user-type.repository';
@@ -0,0 +1 @@
export * from './permission.dto'
@@ -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;
}
@@ -0,0 +1 @@
export * from './permission.entity'
@@ -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<PermissionTypeDto> {
@Column({
nullable: false,
enum: Object.values(PermissionType),
})
type: string;
constructor(partial: Partial<PermissionTypeEntity>) {
super();
Object.assign(this, partial);
}
}
@@ -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 {}
@@ -0,0 +1 @@
export * from './permission.repository'
@@ -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<PermissionTypeEntity> {
constructor(private dataSource: DataSource) {
super(PermissionTypeEntity, dataSource.createEntityManager());
}
}
@@ -31,12 +31,23 @@ export class UserEntity extends AbstractEntity<UserDto> {
})
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[];