admin perms decorator
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-24 17:16:54 +03:30
parent 857d10bf44
commit b793dad234
6 changed files with 83 additions and 79 deletions
@@ -0,0 +1,18 @@
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
import type { Request } from 'express';
/**
* Decorator to extract userId from the authenticated request.
* Must be used after AdminAuthGuard or AuthGuard that sets request.userId.
*
* @example
* @Get('/profile')
* @UseGuards(AdminAuthGuard)
* getProfile(@UserId() userId: string) {
* return this.userService.findById(userId);
* }
*/
export const AdminPerms = createParamDecorator((data: unknown, ctx: ExecutionContext): string[] => {
const request = ctx.switchToHttp().getRequest<Request & { permissions?: string[] }>();
return request.permissions || [];
});
@@ -9,6 +9,7 @@ import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { Admin } from '../entities/admin.entity';
import { PermissionEnum } from 'src/common/enums/permission.enum';
import { Permissions } from 'src/common/decorators/permissions.decorator';
import { AdminPerms } from 'src/common/decorators/admin-perms.decorator';
@Controller()
@ApiTags('admin')
@@ -20,7 +21,7 @@ export class AdminController {
@Post('admin/admin')
@Permissions(PermissionEnum.MANAGE_ADMINS)
@ApiOperation({ summary: 'Create a new admin' })
async create(@Body() dto: CreateAdminDto,) {
async create(@Body() dto: CreateAdminDto) {
const admin = await this.adminService.create(dto);
return admin;
}
@@ -40,21 +41,21 @@ export class AdminController {
@Get('admin/admins/me')
@ApiOperation({ summary: 'Get current authenticated admin profile' })
async getMe(@AdminId() adminId: string,) {
const admin = await this.adminService.findById(adminId,);
async getMe(@AdminId() adminId: string) {
const admin = await this.adminService.findById(adminId);
return admin;
}
@Patch('admin/admins/me')
@ApiOperation({ summary: 'Update current authenticated admin profile' })
updateMe(@AdminId() adminId: string, @Body() dto: UpdateAdminMeDto,): Promise<Admin> {
updateMe(@AdminId() adminId: string, @Body() dto: UpdateAdminMeDto): Promise<Admin> {
return this.adminService.updateMe(adminId, dto);
}
@Get('admin/menu/counts')
@ApiOperation({ summary: 'Get metrics for admin panel' })
getDashboardCounts(@AdminId() adminId: string) {
return this.adminService.getDashboardCounts(adminId);
getDashboardCounts(@AdminId() adminId: string, @AdminPerms() permissions: string[]) {
return this.adminService.getDashboardCounts(adminId, permissions);
}
@Get('admin/home/stats')
@@ -71,26 +72,24 @@ export class AdminController {
return this.adminService.getWeeklyOrders();
}
@Patch('admin/admins/:adminId')
@Permissions(PermissionEnum.MANAGE_ADMINS)
@ApiOperation({ summary: 'Update an admin' })
update(@Param('adminId') adminId: string, @Body() dto: UpdateAdminDto,): Promise<Admin> {
update(@Param('adminId') adminId: string, @Body() dto: UpdateAdminDto): Promise<Admin> {
return this.adminService.update(adminId, dto);
}
@Get('admin/admins/:adminId')
@Permissions(PermissionEnum.MANAGE_ADMINS)
@ApiOperation({ summary: 'Get an admin by ID' })
getById(@Param('adminId') adminId: string,): Promise<Admin | null> {
return this.adminService.findById(adminId,);
getById(@Param('adminId') adminId: string): Promise<Admin | null> {
return this.adminService.findById(adminId);
}
@Delete('admin/admins/:adminId')
@Permissions(PermissionEnum.MANAGE_ADMINS)
@ApiOperation({ summary: 'Delete an admin by ID' })
deleteById(@Param('adminId') adminId: string,): Promise<void> {
return this.adminService.softDelete(adminId,);
deleteById(@Param('adminId') adminId: string): Promise<void> {
return this.adminService.softDelete(adminId);
}
}
+3 -22
View File
@@ -148,29 +148,10 @@ export class AdminService {
}
return admins
}
async ownedPermissions(adminId: string, permissionNames: PermissionEnum[]): Promise<PermissionEnum[]> {
if (permissionNames.length === 0) return []
const admin = await this.adminRepository.findOne(
{ id: adminId },
{ populate: ['role', 'role.permissions'] },
)
if (!admin?.role?.permissions) return []
const adminPermissionNames = new Set(
admin.role.permissions.getItems().map((p) => p.name),
)
return permissionNames.filter((name) => adminPermissionNames.has(name))
}
async getDashboardCounts(adminId: string) {
const permissions = await this.ownedPermissions(adminId, [
PermissionEnum.VIEW_ORDERS,
PermissionEnum.VIEW_ASSIGNED_ORDERS,
]);
async getDashboardCounts(adminId: string, permissions: string[]) {
const canViewAnyOrder = permissions.includes(PermissionEnum.VIEW_ORDERS);
const canViewAssignedOrder = permissions.includes(PermissionEnum.VIEW_ASSIGNED_ORDERS);
+13 -14
View File
@@ -17,6 +17,7 @@ import { AuthService } from '../services/auth.service';
export interface AdminAuthRequest extends Request {
adminId: string;
permissions: string[];
}
@Injectable()
@@ -61,26 +62,24 @@ export class AdminAuthGuard implements CanActivate {
request['adminId'] = payload.adminId;
const adminPermissions = await this.authService.getAdminPermissionsFromCache(payload.adminId);
request['permissions'] = adminPermissions;
// check if the user has the required permissions
const requiredPermissions =
this.reflector.getAllAndOverride<string[]>(PERMISSIONS_KEY, [context.getHandler(), context.getClass()]) ?? [];
if (!requiredPermissions || requiredPermissions.length === 0) {
return true;
}
if (requiredPermissions.length > 0) {
const hasPermission = requiredPermissions.every(p => adminPermissions.includes(p));
const adminPermissions = await this.authService.getAdminPermissionsFromCache(payload.adminId)
const hasPermission = requiredPermissions.every(p => adminPermissions.includes(p));
if (!hasPermission) {
this.logger.warn('Insufficient permissions', {
adminId: payload.adminId,
required: requiredPermissions,
has: hasPermission,
});
throw new ForbiddenException('You are not authorized to access this resource');
if (!hasPermission) {
this.logger.warn('Insufficient permissions', {
adminId: payload.adminId,
required: requiredPermissions,
has: hasPermission,
});
throw new ForbiddenException('You are not authorized to access this resource');
}
}
return true;
@@ -7,6 +7,7 @@ import { FindOrdersDto } from '../dto/find-orders.dto';
import { CreateOrderAsAdminDto } from '../dto/create-order.dto';
import { UpdateOrderAsAdminDto } from '../dto/update-order.dto';
import { AdminId } from 'src/common/decorators/admin-id.decorator';
import { AdminPerms } from 'src/common/decorators/admin-perms.decorator';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
import { Permissions } from 'src/common/decorators/permissions.decorator';
import { PermissionEnum } from 'src/common/enums/permission.enum';
@@ -39,7 +40,7 @@ export class OrderController {
@Get('public/orders/stats')
@UseGuards(AuthGuard)
@ApiOperation({ summary: 'Get User Orders stats' })
getStats( @UserId() userId: string) {
getStats(@UserId() userId: string) {
return this.orderService.getStats(userId);
}
/*========================== Admin Routes =====================*/
@@ -55,17 +56,24 @@ export class OrderController {
// Permission handled inside service
@Get('admin/orders')
@UseGuards(AdminAuthGuard)
// @Permissions(PermissionEnum.VIEW_ORDERS)
@ApiOperation({ summary: 'Get all orders with pagination and filters' })
findAllAsAdmin(@Query() dto: FindOrdersDto, @AdminId() adminId: string) {
return this.orderService.findOrdersAsAdmin(adminId, dto);
findAllAsAdmin(
@Query() dto: FindOrdersDto,
@AdminId() adminId: string,
@AdminPerms() permissions: string[],
) {
return this.orderService.findOrdersAsAdmin(adminId, permissions, dto);
}
@Get('admin/orders/tab-counts')
@UseGuards(AdminAuthGuard)
@ApiOperation({ summary: 'Get order tab counts for admin order list' })
getAdminOrderTabCounts(@Query() dto: FindOrdersDto, @AdminId() adminId: string) {
return this.orderService.getAdminOrderTabCounts(adminId, dto);
getAdminOrderTabCounts(
@Query() dto: FindOrdersDto,
@AdminId() adminId: string,
@AdminPerms() permissions: string[],
) {
return this.orderService.getAdminOrderTabCounts(adminId, permissions, dto);
}
@Get('admin/orders/:id')
@@ -106,8 +114,13 @@ export class OrderController {
@Post('admin/orders/:orderId/status')
@UseGuards(AdminAuthGuard)
@ApiOperation({ summary: 'Assign Order Designer ' })
updateStatus(@Param('orderId') orderId: string,@AdminId()adminId:string, @Body() dto: UpdateStatusDto) {
return this.orderService.updateStatus(orderId,adminId, dto);
updateStatus(
@Param('orderId') orderId: string,
@AdminId() adminId: string,
@AdminPerms() permissions: string[],
@Body() dto: UpdateStatusDto,
) {
return this.orderService.updateStatus(orderId, adminId, permissions, dto);
}
+16 -22
View File
@@ -158,10 +158,11 @@ export class OrderService {
})
}
async findOrdersAsAdmin(adminId: string, dto: FindOrdersDto): Promise<PaginatedResult<Order>> {
const permissions = await this.adminService.ownedPermissions(adminId,
[PermissionEnum.VIEW_ORDERS, PermissionEnum.VIEW_ASSIGNED_ORDERS])
async findOrdersAsAdmin(
adminId: string,
permissions: string[],
dto: FindOrdersDto,
): Promise<PaginatedResult<Order>> {
if (permissions.includes(PermissionEnum.VIEW_ORDERS)) {
return this.orderRepository.findAllPaginated(dto)
@@ -179,12 +180,11 @@ export class OrderService {
}
}
async getAdminOrderTabCounts(adminId: string, dto: FindOrdersDto): Promise<AdminOrderTabCounts> {
const permissions = await this.adminService.ownedPermissions(adminId, [
PermissionEnum.VIEW_ORDERS,
PermissionEnum.VIEW_ASSIGNED_ORDERS,
]);
async getAdminOrderTabCounts(
adminId: string,
permissions: string[],
dto: FindOrdersDto,
): Promise<AdminOrderTabCounts> {
const canViewAnyOrder = permissions.includes(PermissionEnum.VIEW_ORDERS);
const canViewAssignedOrder = permissions.includes(PermissionEnum.VIEW_ASSIGNED_ORDERS);
@@ -300,18 +300,12 @@ export class OrderService {
async updateStatus(orderId: string, adminId: string, dto: UpdateStatusDto): Promise<Order> {
const statusPermissions = [
PermissionEnum.CHANGE_ORDER_STATUS_FROM_ANY_TO_ANY,
PermissionEnum.CHANGE_ORDER_STATUS_TO_CANCELLED,
PermissionEnum.CHANGE_ORDER_STATUS_IN_PROGRESS,
PermissionEnum.CHANGE_ORDER_STATUS_FROM_IN_PROGRESS_TO_FINISHED,
PermissionEnum.CHANGE_ORDER_STATUS_FROM_FINISHED_TO_INVOICED,
];
const permissions = await this.adminService.ownedPermissions(adminId,
[PermissionEnum.VIEW_ORDERS, PermissionEnum.VIEW_ASSIGNED_ORDERS, ...statusPermissions]);
async updateStatus(
orderId: string,
adminId: string,
permissions: string[],
dto: UpdateStatusDto,
): Promise<Order> {
const canViewAnyOrder = permissions.includes(PermissionEnum.VIEW_ORDERS);
const canViewAssignedOrder = permissions.includes(PermissionEnum.VIEW_ASSIGNED_ORDERS);