refactor: remove role property from User model and update related logic for role handling
This commit is contained in:
parent
e3365cdff7
commit
7aab149bb2
@ -10,7 +10,6 @@ datasource db {
|
|||||||
model User {
|
model User {
|
||||||
id String @id
|
id String @id
|
||||||
username String
|
username String
|
||||||
role Role @default(STUDENT)
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
Class Class[]
|
Class Class[]
|
||||||
@ -105,8 +104,3 @@ model RoomSurveyAnswerUser {
|
|||||||
Answer RoomSurveyAnswer @relation(fields: [answerId], references: [id])
|
Answer RoomSurveyAnswer @relation(fields: [answerId], references: [id])
|
||||||
answerId Int
|
answerId Int
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Role {
|
|
||||||
STUDENT
|
|
||||||
ADMIN
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { SetMetadata } from "@nestjs/common";
|
import { SetMetadata } from "@nestjs/common";
|
||||||
import { $Enums } from "@prisma/client";
|
|
||||||
|
|
||||||
export const Roles = (roles: $Enums.Role[]) => SetMetadata("roles", roles);
|
export const Roles = (roles: string[]) => SetMetadata("roles", roles);
|
||||||
|
@ -7,12 +7,13 @@ import {
|
|||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { Reflector } from "@nestjs/core";
|
import { Reflector } from "@nestjs/core";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
|
import { decode, JwtPayload, UserJwtPayload } from "jsonwebtoken";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RolesGuard implements CanActivate {
|
export class RolesGuard implements CanActivate {
|
||||||
constructor(private readonly reflector: Reflector) {}
|
constructor(private readonly reflector: Reflector) {}
|
||||||
|
|
||||||
canActivate(context: ExecutionContext): boolean {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const RolesHandler = this.reflector.get<string[]>(
|
const RolesHandler = this.reflector.get<string[]>(
|
||||||
"roles",
|
"roles",
|
||||||
context.getHandler(),
|
context.getHandler(),
|
||||||
@ -29,11 +30,21 @@ export class RolesGuard implements CanActivate {
|
|||||||
|
|
||||||
if (!user) throw new ForbiddenException("User not authenticated");
|
if (!user) throw new ForbiddenException("User not authenticated");
|
||||||
|
|
||||||
|
const decodedToken = await this.decodeToken(
|
||||||
|
this.extractTokenFromHeader(request.headers.authorization),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if the user has the right role
|
||||||
|
// On the handler level
|
||||||
const hasRoleHandler =
|
const hasRoleHandler =
|
||||||
RolesHandler?.some((role) => user.role?.includes(role)) ??
|
RolesHandler?.some((role) =>
|
||||||
false,
|
decodedToken.realm_access.roles?.includes(role),
|
||||||
|
) ?? false,
|
||||||
|
// On the class level
|
||||||
hasRoleClass =
|
hasRoleClass =
|
||||||
RolesClass?.some((role) => user.role?.includes(role)) ?? false;
|
RolesClass?.some((role) =>
|
||||||
|
decodedToken.realm_access.roles?.includes(role),
|
||||||
|
) ?? false;
|
||||||
|
|
||||||
if (hasRoleHandler) return true;
|
if (hasRoleHandler) return true;
|
||||||
else if (hasRoleClass) return true;
|
else if (hasRoleClass) return true;
|
||||||
@ -42,4 +53,20 @@ export class RolesGuard implements CanActivate {
|
|||||||
`User doesn't have the right role, expected: ${RolesHandler ?? RolesClass}`,
|
`User doesn't have the right role, expected: ${RolesHandler ?? RolesClass}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async decodeToken(token: string) {
|
||||||
|
try {
|
||||||
|
return decode(token) as UserJwtPayload;
|
||||||
|
} catch (error) {
|
||||||
|
throw new UnauthorizedException("Invalid token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractTokenFromHeader(header: string) {
|
||||||
|
const token = header.split(" ")[1];
|
||||||
|
|
||||||
|
if (!token) throw new UnauthorizedException("Token not found");
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ import { ClassRoomEntity } from "./entities/room.entity";
|
|||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Roles(["ADMIN"])
|
@Roles(["admin"])
|
||||||
@ApiUnauthorizedResponse(UnauthorizedResponse)
|
@ApiUnauthorizedResponse(UnauthorizedResponse)
|
||||||
export class ClassController {
|
export class ClassController {
|
||||||
constructor(private readonly classService: ClassService) { }
|
constructor(private readonly classService: ClassService) { }
|
||||||
|
@ -23,8 +23,8 @@ export const UsersResponse = {
|
|||||||
example: {
|
example: {
|
||||||
summary: "A list of users",
|
summary: "A list of users",
|
||||||
value: [
|
value: [
|
||||||
{ id: "1", role: "ADMIN", username: "admin" },
|
{ id: "1", username: "admin" },
|
||||||
{ id: "2", role: "STUDENT", username: "student" },
|
{ id: "2", username: "student" },
|
||||||
] as UserEntity[],
|
] as UserEntity[],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
import { Role } from "@prisma/client";
|
|
||||||
import { IsString } from "class-validator";
|
import { IsString } from "class-validator";
|
||||||
|
|
||||||
export class CreateUserDTO {
|
export class CreateUserDTO {
|
||||||
@ -10,8 +9,4 @@ export class CreateUserDTO {
|
|||||||
@IsString()
|
@IsString()
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@ApiProperty()
|
|
||||||
role: Role;
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ApiProperty, ApiSchema } from "@nestjs/swagger";
|
import { ApiProperty, ApiSchema } from "@nestjs/swagger";
|
||||||
import { $Enums } from "@prisma/client";
|
|
||||||
import { Expose } from "class-transformer";
|
import { Expose } from "class-transformer";
|
||||||
|
|
||||||
@ApiSchema({ name: "User" })
|
@ApiSchema({ name: "User" })
|
||||||
@ -12,10 +11,6 @@ export class UserEntity {
|
|||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
@Expose()
|
|
||||||
@ApiProperty()
|
|
||||||
role: $Enums.Role;
|
|
||||||
|
|
||||||
constructor(partial: Partial<UserEntity>) {
|
constructor(partial: Partial<UserEntity>) {
|
||||||
Object.assign(this, partial);
|
Object.assign(this, partial);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ import { Request } from "express";
|
|||||||
@UseGuards(RolesGuard)
|
@UseGuards(RolesGuard)
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@Roles(["ADMIN"])
|
@Roles(["admin"])
|
||||||
@ApiUnauthorizedResponse(UnauthorizedResponse)
|
@ApiUnauthorizedResponse(UnauthorizedResponse)
|
||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly userService: UserService) {}
|
||||||
|
@ -45,13 +45,10 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
const isFirstUser = (await this.prisma.user.count()) === 0;
|
|
||||||
|
|
||||||
user = await this.prisma.user.create({
|
user = await this.prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
id,
|
id,
|
||||||
username,
|
username
|
||||||
role: isFirstUser ? "ADMIN" : "STUDENT",
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (user.username !== username) {
|
} else if (user.username !== username) {
|
||||||
@ -80,7 +77,6 @@ export class UserService {
|
|||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
username: updateUserInput.username,
|
username: updateUserInput.username,
|
||||||
role: updateUserInput.role,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -107,7 +103,6 @@ export class UserService {
|
|||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
role: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
14
src/types/jwt.d.ts
vendored
Normal file
14
src/types/jwt.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import * as jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
declare module "jsonwebtoken" {
|
||||||
|
export interface UserJwtPayload extends jwt.JwtPayload {
|
||||||
|
name: string;
|
||||||
|
preferred_username: string;
|
||||||
|
email: string;
|
||||||
|
given_name: string;
|
||||||
|
family_name: string;
|
||||||
|
realm_access: {
|
||||||
|
roles: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user