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

> **작성일**: 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` 수정 진행

