262 lines
7.3 KiB
Markdown
262 lines
7.3 KiB
Markdown
# 카테고리 시스템 멀티테넌시 버그 분석
|
|
|
|
> **작성일**: 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` 수정 진행
|
|
|