Repository navigation
Expand file tree
/
Copy pathadmin-role.controller.ts
More file actions
129 lines (123 loc) · 4.18 KB
/
Copy pathadmin-role.controller.ts
File metadata and controls
129 lines (123 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import {
Controller,
Get,
Patch,
Delete,
Param,
Body,
Request,
UseGuards,
} from "@nestjs/common";
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
} from "@nestjs/swagger";
import { UserService } from "./user.service";
import { AssignRoleDto } from "./dto/assign-role.dto";
import {
Role,
ROLE_HIERARCHY,
getRolePermissions,
getUiCapabilities,
} from "src/common/guard/roles.enum";
import { Roles } from "src/common/guard/roles.decorator";
import { RolesGuard } from "src/common/guard/roles.guard";
import { JwtAuthGuard } from "src/core/auth/guards/jwt-auth.guard";
import { AdminTwoFactorGuard } from "src/core/auth/guards/admin-two-factor.guard";
/**
* AdminRoleController — admin-only role management.
*
* Every route requires an authenticated admin whose session is 2FA-verified:
* - {@link JwtAuthGuard} populates `request.user` from the bearer token.
* - {@link RolesGuard} enforces `@Roles(Role.ADMIN)`.
* - {@link AdminTwoFactorGuard} enforces mandatory 2FA for admins.
*
* Role assignment is delegated to {@link UserService.assignRole}, which
* validates the target user exists and rejects conflicting role pairs.
*/
@ApiTags("Admin — Role Management")
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RolesGuard, AdminTwoFactorGuard)
@Roles(Role.ADMIN)
@Controller("admin")
export class AdminRoleController {
constructor(private readonly userService: UserService) {}
@Get("roles")
@ApiOperation({
summary: "List assignable roles",
description: "Returns the canonical set of roles that can be assigned.",
})
@ApiResponse({ status: 200, description: "List of assignable roles" })
listRoles(): { roles: Role[] } {
return { roles: Object.values(Role) };
}
@Get("roles/matrix")
@ApiOperation({
summary: "Get roles and permissions matrix",
description:
"Returns the capabilities, permissions, and hierarchy for every canonical role.",
})
@ApiResponse({ status: 200, description: "Role matrix and capabilities" })
getRoleMatrix() {
return {
hierarchy: ROLE_HIERARCHY,
roles: Object.values(Role).map((role) => ({
role,
permissions: getRolePermissions(role),
capabilities: getUiCapabilities(role),
})),
};
}
@Get("users/:id/role")
@ApiOperation({ summary: "Get a user's current role" })
@ApiResponse({ status: 200, description: "The user's current role" })
@ApiResponse({ status: 404, description: "User not found" })
async getUserRole(
@Param("id") id: string,
): Promise<{ id: string; role: Role }> {
const user = await this.userService.findOneOrFail(id);
return { id: user.id, role: user.role };
}
@Patch("users/:id/role")
@ApiOperation({
summary: "Assign a role to a user",
description:
"Assigns the given canonical role. Rejects conflicting role pairs " +
"(e.g. GOVERNANCE_OPERATOR + KYC_OPERATOR) with 400.",
})
@ApiResponse({ status: 200, description: "Role assigned" })
@ApiResponse({ status: 400, description: "Invalid or conflicting role" })
@ApiResponse({ status: 404, description: "User not found" })
async assignRole(
@Param("id") id: string,
@Body() dto: AssignRoleDto,
@Request() request?: any,
): Promise<{ id: string; role: Role }> {
const user = await this.userService.assignRole(id, dto.role, {
actorId: request?.user?.sub ?? request?.user?.id ?? id,
actorRole: request?.user?.role ?? Role.ADMIN,
reason: dto.reason,
});
return { id: user.id, role: user.role };
}
@Delete("users/:id/role")
@ApiOperation({
summary: "Reset a user's role",
description: "Resets the user to the least-privileged USER role.",
})
@ApiResponse({ status: 200, description: "Role reset to USER" })
@ApiResponse({ status: 404, description: "User not found" })
async resetRole(
@Param("id") id: string,
@Request() request?: any,
): Promise<{ id: string; role: Role }> {
const user = await this.userService.assignRole(id, Role.USER, {
actorId: request?.user?.sub ?? request?.user?.id ?? id,
actorRole: request?.user?.role ?? Role.ADMIN,
reason: "Administrator reset the user to the least-privileged role",
});
return { id: user.id, role: user.role };
}
}