# 카테고리 시스템 멀티테넌시 버그 분석 > **작성일**: 2025-11-06 > **상태**: 🔴 버그 발견, 수정 대기 --- ## 🐛 발견된 버그 ### 영향 받는 서비스 1. ✅ **CommonCodeService** (`commonCodeService.ts`) - 정상 (이미 올바르게 구현됨) 2. 🔴 **TableCategoryValueService** (`tableCategoryValueService.ts`) - **버그 존재 (7곳)** --- ## 📊 현재 상태 확인 ### 데이터베이스 현황 ```sql 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번 라인)** ```typescript async getCategoryValues( tableName: string, columnName: string, companyCode: string, includeInactive: boolean = false ): Promise { 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:** ```typescript AND (company_code = $3 OR company_code = '*') ``` **After:** ```typescript 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:** ```typescript WHERE value_id = $1 AND (company_code = $2 OR company_code = '*') ``` **After:** ```typescript WHERE value_id = $1 AND company_code = $2 ``` **단, 최고 관리자는 모든 데이터 수정 가능해야 하므로:** ```typescript 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: 최고 관리자로 카테고리 값 조회 ```bash # 로그인 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: 일반 회사로 카테고리 값 조회 ```bash # 로그인 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번) --- ## 🚨 다른 서비스 확인 필요 다음 서비스들도 같은 패턴의 버그가 있을 가능성: ```bash cd backend-node/src/services grep -n "OR company_code = '\*'" *.ts ``` **검색 결과**: `tableCategoryValueService.ts` 에만 존재 --- **다음 단계**: 사용자 승인 후 `tableCategoryValueService.ts` 수정 진행