Files
vexplor/docs/카테고리_멀티테넌시_버그_수정_완료.md
2025-11-06 17:01:13 +09:00

9.9 KiB

카테고리 멀티테넌시 버그 수정 완료

작성일: 2025-11-06
상태: 완료


🐛 문제 발견

증상

  • 다른 회사 계정으로 로그인했는데 company_code = "*" (최고 관리자 전용) 카테고리 값이 보임
  • 채번 규칙과 동일한 멀티테넌시 버그

원인

backend-node/src/services/tableCategoryValueService.ts7개 메서드에서 잘못된 WHERE 조건 사용:

// ❌ 잘못된 쿼리 (버그)
AND (company_code = $3 OR company_code = '*')

수정 내용

수정된 메서드 (7개)

메서드 라인 작업 유형 수정 내용
getCategoryColumns() 12-77 READ (JOIN) 멀티테넌시 분기 추가
getCategoryValues() 82-183 READ 멀티테넌시 분기 추가
addCategoryValue() 188-269 CREATE (중복 체크) 멀티테넌시 분기 추가
updateCategoryValue() 274-403 UPDATE 멀티테넌시 분기 추가
deleteCategoryValue() 409-485 DELETE 멀티테넌시 분기 추가
bulkDeleteCategoryValues() 490-531 DELETE (일괄) 멀티테넌시 분기 추가
reorderCategoryValues() 536-586 UPDATE (순서) 멀티테넌시 분기 추가

📊 수정 전후 비교

1. getCategoryValues() - 카테고리 값 목록 조회

Before:

const query = `
  SELECT * FROM table_column_category_values
  WHERE table_name = $1
    AND column_name = $2
    AND (company_code = $3 OR company_code = '*')  -- 🔴 버그!
`;
const params = [tableName, columnName, companyCode];

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. getCategoryColumns() - 카테고리 컬럼 목록 조회 (JOIN)

Before:

const query = `
  SELECT ...
  FROM table_type_columns tc
  LEFT JOIN table_column_category_values cv 
    ON tc.table_name = cv.table_name 
    AND tc.column_name = cv.column_name
    AND cv.is_active = true
    AND (cv.company_code = $2 OR cv.company_code = '*')  -- 🔴 버그!
  WHERE tc.table_name = $1
`;

After:

if (companyCode === "*") {
  // 최고 관리자: JOIN 조건에서 company_code 제외
  query = `
    SELECT ...
    FROM table_type_columns tc
    LEFT JOIN table_column_category_values cv 
      ON tc.table_name = cv.table_name 
      AND tc.column_name = cv.column_name
      AND cv.is_active = true
    WHERE tc.table_name = $1
  `;
} else {
  // 일반 회사: JOIN 조건에 company_code 추가
  query = `
    SELECT ...
    FROM table_type_columns tc
    LEFT JOIN table_column_category_values cv 
      ON tc.table_name = cv.table_name 
      AND tc.column_name = cv.column_name
      AND cv.is_active = true
      AND cv.company_code = $2
    WHERE tc.table_name = $1
  `;
}

3. updateCategoryValue() - 카테고리 값 수정

Before:

const updateQuery = `
  UPDATE table_column_category_values
  SET ...
  WHERE value_id = $${paramIndex++}
    AND (company_code = $${paramIndex++} OR company_code = '*')  -- 🔴 버그!
`;

After:

if (companyCode === "*") {
  // 최고 관리자: company_code 조건 제외
  updateQuery = `
    UPDATE table_column_category_values
    SET ...
    WHERE value_id = $${paramIndex++}
  `;
} else {
  // 일반 회사: company_code 조건 포함
  updateQuery = `
    UPDATE table_column_category_values
    SET ...
    WHERE value_id = $${paramIndex++}
      AND company_code = $${paramIndex++}
  `;
}

🔍 데이터베이스 현황

현재 카테고리 값 (수정 전)

SELECT value_id, table_name, column_name, value_label, company_code
FROM table_column_category_values
ORDER BY created_at DESC
LIMIT 10;
value_id table_name column_name value_label company_code
1-8 projects project_type/status 개발/유지보수/... *
15-16 item_info material 원자재/153 *

문제: 일반 회사 사용자도 이 데이터를 볼 수 있음!

수정 후 동작

사용자 수정 전 수정 후
최고 관리자 (*) 모든 데이터 조회 모든 데이터 조회
일반 회사 A A데이터 + * 데이터 A데이터만
일반 회사 B B데이터 + * 데이터 B데이터만

🧪 테스트 시나리오

시나리오 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" }
]

시나리오 3: 카테고리 값 수정 (권한 체크)

# 일반 회사 A로 로그인
# company_code="*" 데이터 수정 시도
PUT /api/table-category-values/1
{ "valueLabel": "해킹 시도" }

# 수정 전: 성공 (보안 취약)
# 수정 후: 실패 (권한 없음)
{ "success": false, "message": "카테고리 값을 찾을 수 없습니다" }

📝 수정 상세 내역

공통 패턴

모든 메서드에 다음 패턴 적용:

// 멀티테넌시: 최고 관리자만 company_code="*" 데이터를 볼 수 있음
let query: string;
let params: any[];

if (companyCode === "*") {
  // 최고 관리자: company_code 필터링 제외
  query = `SELECT * FROM table WHERE ...`;
  params = [...];
  logger.info("최고 관리자 카테고리 작업");
} else {
  // 일반 회사: company_code 필터링 포함
  query = `SELECT * FROM table WHERE ... AND company_code = $N`;
  params = [..., companyCode];
  logger.info("회사별 카테고리 작업", { companyCode });
}

로깅 추가

각 메서드에 멀티테넌시 로깅 추가:

// 최고 관리자
logger.info("최고 관리자 카테고리 컬럼 조회");
logger.info("최고 관리자 카테고리 값 조회");

// 일반 회사
logger.info("회사별 카테고리 컬럼 조회", { companyCode });
logger.info("회사별 카테고리 값 조회", { companyCode });

🎯 멀티테넌시 원칙 재확인

핵심 원칙

company_code = "*"는 최고 관리자 전용 데이터이며, 일반 회사는 절대 접근할 수 없습니다.

작업 최고 관리자 (*) 일반 회사 (COMPANY_A)
조회 모든 데이터 자신의 데이터만
생성 모든 회사에 자신의 회사에만
수정 모든 데이터 자신의 데이터만
삭제 모든 데이터 자신의 데이터만

SQL 패턴

-- ❌ 잘못된 패턴 (버그)
WHERE company_code = $1 OR company_code = '*'

-- ✅ 올바른 패턴 (최고 관리자)
WHERE 1=1  -- company_code 필터링 없음

-- ✅ 올바른 패턴 (일반 회사)
WHERE company_code = $1  -- company_code="*" 자동 제외

🔗 관련 파일

  • 수정 완료: backend-node/src/services/tableCategoryValueService.ts
  • 정상 참고: backend-node/src/services/commonCodeService.ts (이미 올바르게 구현됨)
  • 정상 참고: backend-node/src/services/numberingRuleService.ts (수정 완료)

🚀 배포 전 체크리스트

  • 코드 수정 완료 (7개 메서드)
  • 린트 에러 없음
  • 로깅 추가 (최고 관리자 vs 일반 회사 구분)
  • 단위 테스트 작성 (선택)
  • 통합 테스트 (필수)
    • 최고 관리자로 로그인하여 모든 카테고리 값 조회 확인
    • 일반 회사로 로그인하여 자신의 카테고리 값만 조회 확인
    • 다른 회사 카테고리 값 접근 불가능 확인
    • 카테고리 값 생성/수정/삭제 권한 확인
  • 프론트엔드에서 카테고리 값 목록 재확인
  • 백엔드 재실행 (코드 변경 사항 반영)

📚 관련 문서


🔍 다른 서비스 확인 결과

cd backend-node/src/services
grep -n "OR company_code = '\*'" *.ts

결과: tableCategoryValueService.ts에만 버그 존재 (수정 완료)

확인된 정상 서비스:

  • commonCodeService.ts - 이미 올바르게 구현됨
  • numberingRuleService.ts - 수정 완료
  • tableCategoryValueService.ts - 수정 완료

수정 완료일: 2025-11-06
수정자: AI Assistant
영향 범위: tableCategoryValueService.ts 전체 (7개 메서드)
린트 에러: 없음