7.3 KiB
7.3 KiB
카테고리 시스템 멀티테넌시 버그 분석
작성일: 2025-11-06
상태: 🔴 버그 발견, 수정 대기
🐛 발견된 버그
영향 받는 서비스
- ✅ CommonCodeService (
commonCodeService.ts) - 정상 (이미 올바르게 구현됨) - 🔴 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 = '*'제거 - 최고 관리자/일반 회사 분기 처리
- JOIN 조건에서
-
getCategoryValues()(93번 라인)- WHERE 조건에서
OR company_code = '*'제거 - 최고 관리자/일반 회사 분기 처리
- WHERE 조건에서
-
addCategoryValue()(139번 라인)- 중복 체크 시
OR company_code = '*'제거 - 최고 관리자/일반 회사 분기 처리
- 중복 체크 시
-
updateCategoryValue()(269번 라인)- UPDATE 조건에서
OR company_code = '*'제거 - 최고 관리자는 company_code 조건 제거
- UPDATE 조건에서
-
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(수정 완료)
📝 수정 우선순위
-
🔴 높음 (즉시 수정 필요):
getCategoryColumns()(31번)getCategoryValues()(93번) → 일반 회사가 최고 관리자 데이터를 볼 수 있음
-
🟡 중간 (가능한 빨리):
addCategoryValue()(139번) - 중복 체크deleteCategoryValue()(317번) - 하위 체크
-
🟢 낮음 (일관성 유지):
updateCategoryValue()(269번)deleteCategoryValue()(332번)bulkDeleteCategoryValues()(362번)reorderCategoryValues()(395번)
🚨 다른 서비스 확인 필요
다음 서비스들도 같은 패턴의 버그가 있을 가능성:
cd backend-node/src/services
grep -n "OR company_code = '\*'" *.ts
검색 결과: tableCategoryValueService.ts 에만 존재
다음 단계: 사용자 승인 후 tableCategoryValueService.ts 수정 진행