feat: Implement company code validation in flow management

- Enhanced the FlowController to include user company code validation for flow definitions, ensuring that users can only access and modify flows belonging to their company.
- Updated the FlowDefinitionService to accept company code as a parameter for create, update, and delete operations, enforcing ownership checks.
- Introduced sanitization methods in FlowConditionParser to prevent SQL injection for column and table names.
- Modified the FlowDataMoveService to validate table names and column names during data movement operations, enhancing security.
- Updated the frontend components to support batch data movement with proper validation and error handling.
This commit is contained in:
kjs
2026-03-03 10:38:38 +09:00
parent e2d88f01e3
commit fd5c61b12a
9 changed files with 207 additions and 56 deletions

View File

@@ -144,8 +144,9 @@ export class FlowController {
try {
const { id } = req.params;
const flowId = parseInt(id);
const userCompanyCode = (req as any).user?.companyCode;
const definition = await this.flowDefinitionService.findById(flowId);
const definition = await this.flowDefinitionService.findById(flowId, userCompanyCode);
if (!definition) {
res.status(404).json({
success: false,
@@ -182,12 +183,13 @@ export class FlowController {
const { id } = req.params;
const flowId = parseInt(id);
const { name, description, isActive } = req.body;
const userCompanyCode = (req as any).user?.companyCode;
const flowDef = await this.flowDefinitionService.update(flowId, {
name,
description,
isActive,
});
}, userCompanyCode);
if (!flowDef) {
res.status(404).json({
@@ -217,8 +219,9 @@ export class FlowController {
try {
const { id } = req.params;
const flowId = parseInt(id);
const userCompanyCode = (req as any).user?.companyCode;
const success = await this.flowDefinitionService.delete(flowId);
const success = await this.flowDefinitionService.delete(flowId, userCompanyCode);
if (!success) {
res.status(404).json({
@@ -275,6 +278,7 @@ export class FlowController {
try {
const { flowId } = req.params;
const flowDefinitionId = parseInt(flowId);
const userCompanyCode = (req as any).user?.companyCode;
const {
stepName,
stepOrder,
@@ -293,6 +297,16 @@ export class FlowController {
return;
}
// 플로우 소유권 검증
const flowDef = await this.flowDefinitionService.findById(flowDefinitionId, userCompanyCode);
if (!flowDef) {
res.status(404).json({
success: false,
message: "Flow definition not found or access denied",
});
return;
}
const step = await this.flowStepService.create({
flowDefinitionId,
stepName,
@@ -324,6 +338,7 @@ export class FlowController {
try {
const { stepId } = req.params;
const id = parseInt(stepId);
const userCompanyCode = (req as any).user?.companyCode;
const {
stepName,
stepOrder,
@@ -342,6 +357,19 @@ export class FlowController {
displayConfig,
} = req.body;
// 스텝 소유권 검증: 스텝이 속한 플로우가 사용자 회사 소유인지 확인
const existingStep = await this.flowStepService.findById(id);
if (existingStep) {
const flowDef = await this.flowDefinitionService.findById(existingStep.flowDefinitionId, userCompanyCode);
if (!flowDef) {
res.status(403).json({
success: false,
message: "Access denied: flow does not belong to your company",
});
return;
}
}
const step = await this.flowStepService.update(id, {
stepName,
stepOrder,
@@ -388,6 +416,20 @@ export class FlowController {
try {
const { stepId } = req.params;
const id = parseInt(stepId);
const userCompanyCode = (req as any).user?.companyCode;
// 스텝 소유권 검증
const existingStep = await this.flowStepService.findById(id);
if (existingStep) {
const flowDef = await this.flowDefinitionService.findById(existingStep.flowDefinitionId, userCompanyCode);
if (!flowDef) {
res.status(403).json({
success: false,
message: "Access denied: flow does not belong to your company",
});
return;
}
}
const success = await this.flowStepService.delete(id);
@@ -446,6 +488,7 @@ export class FlowController {
createConnection = async (req: Request, res: Response): Promise<void> => {
try {
const { flowDefinitionId, fromStepId, toStepId, label } = req.body;
const userCompanyCode = (req as any).user?.companyCode;
if (!flowDefinitionId || !fromStepId || !toStepId) {
res.status(400).json({
@@ -455,6 +498,28 @@ export class FlowController {
return;
}
// 플로우 소유권 검증
const flowDef = await this.flowDefinitionService.findById(flowDefinitionId, userCompanyCode);
if (!flowDef) {
res.status(404).json({
success: false,
message: "Flow definition not found or access denied",
});
return;
}
// fromStepId, toStepId가 해당 flow에 속하는지 검증
const fromStep = await this.flowStepService.findById(fromStepId);
const toStep = await this.flowStepService.findById(toStepId);
if (!fromStep || fromStep.flowDefinitionId !== flowDefinitionId ||
!toStep || toStep.flowDefinitionId !== flowDefinitionId) {
res.status(400).json({
success: false,
message: "fromStepId and toStepId must belong to the specified flow",
});
return;
}
const connection = await this.flowConnectionService.create({
flowDefinitionId,
fromStepId,
@@ -482,6 +547,20 @@ export class FlowController {
try {
const { connectionId } = req.params;
const id = parseInt(connectionId);
const userCompanyCode = (req as any).user?.companyCode;
// 연결 소유권 검증
const existingConn = await this.flowConnectionService.findById(id);
if (existingConn) {
const flowDef = await this.flowDefinitionService.findById(existingConn.flowDefinitionId, userCompanyCode);
if (!flowDef) {
res.status(403).json({
success: false,
message: "Access denied: flow does not belong to your company",
});
return;
}
}
const success = await this.flowConnectionService.delete(id);
@@ -670,23 +749,24 @@ export class FlowController {
*/
moveData = async (req: Request, res: Response): Promise<void> => {
try {
const { flowId, recordId, toStepId, note } = req.body;
const { flowId, fromStepId, recordId, toStepId, note } = req.body;
const userId = (req as any).user?.userId || "system";
if (!flowId || !recordId || !toStepId) {
if (!flowId || !fromStepId || !recordId || !toStepId) {
res.status(400).json({
success: false,
message: "flowId, recordId, and toStepId are required",
message: "flowId, fromStepId, recordId, and toStepId are required",
});
return;
}
await this.flowDataMoveService.moveDataToStep(
flowId,
recordId,
fromStepId,
toStepId,
recordId,
userId,
note
note ? { note } : undefined
);
res.json({