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:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user