690414:1113 Update README.md /.agents/skills, /.windsurf/workflows

This commit is contained in:
2026-04-14 11:13:42 +07:00
parent 02400fd88c
commit 6d45bdaeb5
194 changed files with 12708 additions and 8762 deletions
@@ -0,0 +1,86 @@
// File: src/modules/workflow-engine/guards/workflow-transition.guard.ts
// Guard ตรวจสอบสิทธิ์ 4-Level RBAC สำหรับ Workflow Transition ตาม ADR-021 §6
import {
CanActivate,
ExecutionContext,
ForbiddenException,
Injectable,
Logger,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { WorkflowInstance } from '../entities/workflow-instance.entity';
import { UserService } from '../../../modules/user/user.service';
import type { RequestWithUser } from '../../../common/interfaces/request-with-user.interface';
/**
* WorkflowTransitionGuard — ตรวจสอบสิทธิ์ 4 ระดับก่อนอนุญาตให้เปลี่ยนสถานะ Workflow
*
* Level 1: system.manage_all (Superadmin) → ผ่านทันที
* Level 2: organization.manage_users + สังกัดองค์กรเดียวกับเอกสาร → ผ่าน
* Level 3: Assigned Handler (context.assignedUserId === req.user.user_id) → ผ่าน
* Level 4: ผู้ใช้ทั่วไป → ForbiddenException
*/
@Injectable()
export class WorkflowTransitionGuard implements CanActivate {
private readonly logger = new Logger(WorkflowTransitionGuard.name);
constructor(
@InjectRepository(WorkflowInstance)
private readonly instanceRepo: Repository<WorkflowInstance>,
private readonly userService: UserService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<RequestWithUser>();
const instanceId = request.params['id'];
const user = request.user;
// ดึงสิทธิ์ทั้งหมดของ User จาก DB (ตาม pattern เดียวกับ RbacGuard)
const userPermissions = await this.userService.getUserPermissions(
user.user_id
);
// Level 1: Superadmin — ผ่านทุกการตรวจสอบ
if (userPermissions.includes('system.manage_all')) {
return true;
}
// ดึง Instance เพื่อตรวจสอบ Context
const instance = await this.instanceRepo.findOne({
where: { id: instanceId },
});
if (!instance) {
throw new NotFoundException('Workflow Instance', instanceId);
}
// Level 2: Org Admin — organization.manage_users + สังกัดองค์กรเดียวกับเอกสาร
const docOrgId = instance.context?.organizationId as number | undefined;
if (
userPermissions.includes('organization.manage_users') &&
docOrgId !== undefined &&
user.primaryOrganizationId === docOrgId
) {
return true;
}
// Level 3: Assigned Handler — User นี้ถูก Assign มาให้ทำ Step นี้โดยตรง
const assignedUserId = instance.context?.assignedUserId as
| number
| undefined;
if (assignedUserId !== undefined && user.user_id === assignedUserId) {
return true;
}
this.logger.warn(
`Unauthorized transition attempt: User ${user.user_id} on Instance ${instanceId}`
);
throw new ForbiddenException({
userMessage: 'คุณไม่มีสิทธิ์ดำเนินการในขั้นตอนนี้',
recoveryAction: 'ติดต่อผู้รับผิดชอบหรือ Admin หากคิดว่านี่เป็นข้อผิดพลาด',
});
}
}