Files
vexplor/docs/카테고리_멀티테넌시_버그_분석.md
2025-11-06 17:01:13 +09:00

7.3 KiB

카테고리 시스템 멀티테넌시 버그 분석

작성일: 2025-11-06
상태: 🔴 버그 발견, 수정 대기


🐛 발견된 버그

영향 받는 서비스

  1. CommonCodeService (commonCodeService.ts) - 정상 (이미 올바르게 구현됨)
  2. 🔴 TableCategoryValueService (tableCategoryValueService.ts) - 버그 존재 (7곳)

📊 현재 상태 확인

데이터베이스 현황

SELECT value_id, table_name, column_name, value_label, company_code
FROM table_column_category_values
ORDER BY created_at DESC
LIMIT 10;

결과: 모든 카테고리 값이 company_code = "*" (최고 관리자 전용)

value_id table_name column_name value_label company_code
16 item_info material 원자재 *
15 item_info material 153 *
1-8 projects project_type/status ... *

문제: 일반 회사 사용자도 이 데이터들을 볼 수 있음!


🔍 버그 상세 분석

1. tableCategoryValueService.ts

버그 위치 (7곳)

메서드 라인 버그 패턴 심각도
getCategoryColumns() 31 AND (cv.company_code = $2 OR cv.company_code = '*') 🔴 높음 (READ)
getCategoryValues() 93 AND (company_code = $3 OR company_code = '*') 🔴 높음 (READ)
addCategoryValue() 139 AND (company_code = $4 OR company_code = '*') 🟡 중간 (중복 체크)
updateCategoryValue() 269 AND (company_code = $${paramIndex++} OR company_code = '*') 🟢 낮음 (UPDATE)
deleteCategoryValue() - 하위 체크 317 AND (company_code = $2 OR company_code = '*') 🟡 중간 (READ)
deleteCategoryValue() - 삭제 332 AND (company_code = $2 OR company_code = '*') 🟢 낮음 (UPDATE)
bulkDeleteCategoryValues() 362 AND (company_code = $2 OR company_code = '*') 🟢 낮음 (UPDATE)
reorderCategoryValues() 395 AND (company_code = $3 OR company_code = '*') 🟢 낮음 (UPDATE)

버그 코드 예시

잘못된 코드 (93번 라인)

async getCategoryValues(
  tableName: string,
  columnName: string,
  companyCode: string,
  includeInactive: boolean = false
): Promise<TableCategoryValue[]> {
  const query = `
    SELECT * 
    FROM table_column_category_values
    WHERE table_name = $1
      AND column_name = $2
      AND (company_code = $3 OR company_code = '*')  -- 🔴 버그!
  `;
  
  const result = await pool.query(query, [tableName, columnName, companyCode]);
  return result.rows;
}

문제점:

  • 일반 회사 (예: COMPANY_A)로 로그인해도 company_code = "*" 데이터가 조회됨
  • 멀티테넌시 원칙 위반

수정 방안

패턴 1: Read 작업 (getCategoryColumns, getCategoryValues)

Before:

AND (company_code = $3 OR company_code = '*')

After:

let query: string;
let params: any[];

if (companyCode === "*") {
  // 최고 관리자: 모든 데이터 조회
  query = `
    SELECT * FROM table_column_category_values
    WHERE table_name = $1 AND column_name = $2
  `;
  params = [tableName, columnName];
} else {
  // 일반 회사: 자신의 데이터만 조회
  query = `
    SELECT * FROM table_column_category_values
    WHERE table_name = $1 
      AND column_name = $2 
      AND company_code = $3
  `;
  params = [tableName, columnName, companyCode];
}

패턴 2: Update/Delete 작업

UPDATE/DELETE 작업은 이미 회사 코드가 매칭되는 경우에만 작동하므로, 보안상 큰 문제는 없지만 일관성을 위해 수정:

Before:

WHERE value_id = $1 AND (company_code = $2 OR company_code = '*')

After:

WHERE value_id = $1 AND company_code = $2

단, 최고 관리자는 모든 데이터 수정 가능해야 하므로:

if (companyCode === "*") {
  query = `UPDATE ... WHERE value_id = $1`;
} else {
  query = `UPDATE ... WHERE value_id = $1 AND company_code = $2`;
}

📋 수정 체크리스트

tableCategoryValueService.ts

  • getCategoryColumns() (31번 라인)

    • JOIN 조건에서 OR company_code = '*' 제거
    • 최고 관리자/일반 회사 분기 처리
  • getCategoryValues() (93번 라인)

    • WHERE 조건에서 OR company_code = '*' 제거
    • 최고 관리자/일반 회사 분기 처리
  • addCategoryValue() (139번 라인)

    • 중복 체크 시 OR company_code = '*' 제거
    • 최고 관리자/일반 회사 분기 처리
  • updateCategoryValue() (269번 라인)

    • UPDATE 조건에서 OR company_code = '*' 제거
    • 최고 관리자는 company_code 조건 제거
  • deleteCategoryValue() (317, 332번 라인)

    • 하위 체크 및 삭제 조건 수정
    • 최고 관리자/일반 회사 분기 처리
  • bulkDeleteCategoryValues() (362번 라인)

    • 일괄 삭제 조건 수정
  • reorderCategoryValues() (395번 라인)

    • 순서 변경 조건 수정

🧪 테스트 시나리오

시나리오 1: 최고 관리자로 카테고리 값 조회

# 로그인
POST /api/auth/login
{ "userId": "admin", "companyCode": "*" }

# 카테고리 값 조회
GET /api/table-category-values/projects/project_type

# 예상 결과: 모든 카테고리 값 조회 가능
[
  { "valueId": 1, "valueLabel": "개발", "companyCode": "*" },
  { "valueId": 2, "valueLabel": "유지보수", "companyCode": "*" },
  { "valueId": 100, "valueLabel": "COMPANY_A 전용", "companyCode": "COMPANY_A" }
]

시나리오 2: 일반 회사로 카테고리 값 조회

# 로그인
POST /api/auth/login
{ "userId": "user_a", "companyCode": "COMPANY_A" }

# 카테고리 값 조회
GET /api/table-category-values/projects/project_type

# 수정 전 (버그): company_code="*" 포함
[
  { "valueId": 1, "valueLabel": "개발", "companyCode": "*" },  ← 보면 안 됨!
  { "valueId": 100, "valueLabel": "COMPANY_A 전용", "companyCode": "COMPANY_A" }
]

# 수정 후 (정상): 자신의 데이터만
[
  { "valueId": 100, "valueLabel": "COMPANY_A 전용", "companyCode": "COMPANY_A" }
]

🔗 관련 파일

  • 버그 존재: backend-node/src/services/tableCategoryValueService.ts
  • 정상 참고: backend-node/src/services/commonCodeService.ts (78-86번 라인)
  • 정상 참고: backend-node/src/services/numberingRuleService.ts (수정 완료)

📝 수정 우선순위

  1. 🔴 높음 (즉시 수정 필요):

    • getCategoryColumns() (31번)
    • getCategoryValues() (93번) → 일반 회사가 최고 관리자 데이터를 볼 수 있음
  2. 🟡 중간 (가능한 빨리):

    • addCategoryValue() (139번) - 중복 체크
    • deleteCategoryValue() (317번) - 하위 체크
  3. 🟢 낮음 (일관성 유지):

    • updateCategoryValue() (269번)
    • deleteCategoryValue() (332번)
    • bulkDeleteCategoryValues() (362번)
    • reorderCategoryValues() (395번)

🚨 다른 서비스 확인 필요

다음 서비스들도 같은 패턴의 버그가 있을 가능성:

cd backend-node/src/services
grep -n "OR company_code = '\*'" *.ts

검색 결과: tableCategoryValueService.ts 에만 존재


다음 단계: 사용자 승인 후 tableCategoryValueService.ts 수정 진행