Files
Edu/services/iam/src/iam/rbac.controller.ts

256 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Req,
} from "@nestjs/common";
import { IamService } from "./iam.service.js";
import {
Permissions,
RequirePermission,
} from "../middleware/permission.guard.js";
import {
type AuthenticatedRequest,
extractAuditContext,
} from "../middleware/auth.middleware.js";
import {
createRoleSchema,
updateRoleSchema,
updateRolePermissionsSchema,
createPermissionSchema,
updatePermissionSchema,
createViewportSchema,
updateViewportSchema,
verifyTotpSchema,
} from "./iam.dto.js";
/**
* RBAC 管理端点:角色/权限/视口 CRUD + TOTP 2FAadmin-portal 使用)。
*/
@Controller("v1/iam")
export class RbacController {
constructor(private readonly service: IamService) {}
private maybeContext(req: AuthenticatedRequest) {
return extractAuditContext(req);
}
// ============ 角色 ============
@Get("roles")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async roles(): Promise<{ success: true; data: unknown[] }> {
const data = await this.service.getAllRoles();
return { success: true as const, data };
}
@Post("roles")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async createRole(
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: unknown }> {
const dto = createRoleSchema.parse(body);
const data = await this.service.createRole(dto, this.maybeContext(req));
return { success: true as const, data };
}
@Patch("roles/:id")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async updateRole(
@Param("id") id: string,
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: unknown }> {
const dto = updateRoleSchema.parse(body);
const data = await this.service.updateRole(id, dto, this.maybeContext(req));
return { success: true as const, data };
}
@Patch("roles/:id/permissions")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async updateRolePermissions(
@Param("id") id: string,
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: { success: boolean } }> {
const dto = updateRolePermissionsSchema.parse(body);
await this.service.updateRolePermissions(
id,
dto.permissionIds,
this.maybeContext(req),
);
return { success: true as const, data: { success: true } };
}
@Post("roles/:roleId/permissions/:permissionId")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async grantPermission(
@Param("roleId") roleId: string,
@Param("permissionId") permissionId: string,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: { success: boolean } }> {
await this.service.grantPermissionToRole(
roleId,
permissionId,
this.maybeContext(req),
);
return { success: true as const, data: { success: true } };
}
@Delete("roles/:roleId/permissions/:permissionId")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async revokePermission(
@Param("roleId") roleId: string,
@Param("permissionId") permissionId: string,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: { success: boolean } }> {
await this.service.revokePermissionFromRole(
roleId,
permissionId,
this.maybeContext(req),
);
return { success: true as const, data: { success: true } };
}
// ============ 权限 ============
@Get("permissions")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async permissions(): Promise<{ success: true; data: unknown[] }> {
const data = await this.service.getAllPermissions();
return { success: true as const, data };
}
@Post("permissions")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async createPermission(
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: unknown }> {
const dto = createPermissionSchema.parse(body);
const data = await this.service.createPermission(
dto,
this.maybeContext(req),
);
return { success: true as const, data };
}
@Patch("permissions/:id")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async updatePermission(
@Param("id") id: string,
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: unknown }> {
const dto = updatePermissionSchema.parse(body);
const data = await this.service.updatePermission(
id,
dto,
this.maybeContext(req),
);
return { success: true as const, data };
}
@Delete("permissions/:id")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async deletePermission(
@Param("id") id: string,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: { success: boolean } }> {
await this.service.deletePermission(id, this.maybeContext(req));
return { success: true as const, data: { success: true } };
}
// ============ 视口 ============
@Post("viewports")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async createViewport(
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: unknown }> {
const dto = createViewportSchema.parse(body);
const data = await this.service.createViewport(dto, this.maybeContext(req));
return { success: true as const, data };
}
@Patch("viewports/:id")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async updateViewport(
@Param("id") id: string,
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: unknown }> {
const dto = updateViewportSchema.parse(body);
const data = await this.service.updateViewport(
id,
dto,
this.maybeContext(req),
);
return { success: true as const, data };
}
@Delete("viewports/:id")
@RequirePermission(Permissions.IAM_ROLE_MANAGE)
async deleteViewport(
@Param("id") id: string,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: { success: boolean } }> {
await this.service.deleteViewport(id, this.maybeContext(req));
return { success: true as const, data: { success: true } };
}
// ============ TOTP 2FA ============
@Post("totp/enable")
@RequirePermission(Permissions.IAM_USER_READ)
async enableTotp(@Req() req: AuthenticatedRequest): Promise<{
success: true;
data: { secret: string; qrUrl: string; backupCodes: string[] };
}> {
const userId = req.userId;
if (!userId) {
throw new Error("Missing user identity");
}
const data = await this.service.enableTotp(userId);
return { success: true as const, data };
}
@Post("totp/verify")
@RequirePermission(Permissions.IAM_USER_READ)
async verifyTotp(
@Body() body: unknown,
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: { verified: boolean } }> {
const userId = req.userId;
if (!userId) {
throw new Error("Missing user identity");
}
const dto = verifyTotpSchema.parse(body);
const data = await this.service.verifyTotp(
userId,
dto.code,
this.maybeContext(req),
);
return { success: true as const, data };
}
@Post("totp/disable")
@RequirePermission(Permissions.IAM_USER_READ)
async disableTotp(
@Req() req: AuthenticatedRequest,
): Promise<{ success: true; data: { success: boolean } }> {
const userId = req.userId;
if (!userId) {
throw new Error("Missing user identity");
}
await this.service.disableTotp(userId, this.maybeContext(req));
return { success: true as const, data: { success: true } };
}
}