ERP-node/docs/카테고리_멀티테넌시_버그_분석.md

262 lines
7.3 KiB
Markdown
Raw Permalink Normal View History

2025-11-06 17:01:13 +09:00
# 카테고리 시스템 멀티테넌시 버그 분석
> **작성일**: 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<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:**
```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` 수정 진행