- masterDataStore: 테넌트별 캐시 격리 로직 강화 - AuthContext: 인증 컨텍스트 안정성 개선 - IntegratedDetailTemplate: 상세 템플릿 동작 수정 - VendorDetail: 거래처 상세 수정 - AttendanceManagement: 타입 정의 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.0 KiB
5.0 KiB
masterDataStore 캐시 테넌트 격리 수정
작성일: 2026-01-29
타입: 버그 수정 (캐시 격리 누락)
관련 문서: [REF-2025-11-19] multi-tenancy-implementation.md
배경
멀티테넌시 검토 결과, TenantAwareCache(mes-{tenantId}-{key})는 테넌트별로 캐시가 격리되어 있지만, masterDataStore의 sessionStorage 캐시는 테넌트 구분 없이 page_config_{pageType} 키를 사용하고 있었음.
추가로 setCurrentTenantId 액션이 인터페이스에만 선언되어 있고 구현도, 호출도 없는 dead code 상태였음.
문제
1. 캐시 키에 tenantId 미포함
TenantAwareCache: mes-282-itemMasters ← 테넌트 격리됨
masterDataStore: page_config_item-master ← 테넌트 격리 안됨
2. 발생 가능한 시나리오
1. 테넌트 282 사용자가 품목관리 접속
→ sessionStorage: page_config_item-master = {테넌트282 설정}
2. 세션 내 테넌트 500으로 전환 (로그아웃 없이)
→ clearTenantCache()는 mes-282-* 만 삭제
→ page_config_item-master 는 삭제되지 않음
3. 테넌트 500 사용자에게 테넌트 282의 페이지 설정이 노출
3. setCurrentTenantId 미구현
// 인터페이스에 선언만 있고 구현 없음
interface MasterDataStore {
currentTenantId: number | null; // ← initialState에도 누락
setCurrentTenantId: (tenantId: number | null) => void; // ← 구현 없음
}
수정 내역
masterDataStore.ts
| 영역 | Before | After |
|---|---|---|
| initialState | currentTenantId 누락 |
currentTenantId: null 추가 |
| 캐시 키 포맷 | page_config_{pageType} |
page_config_{tenantId}_{pageType} |
| setCurrentTenantId | 인터페이스만 선언 | 구현 추가 |
| fetchPageConfig | tenantId 미사용 | currentTenantId를 캐시 함수에 전달 |
| invalidateConfig | tenantId 미사용 | currentTenantId 기반 삭제 |
| invalidateAllConfigs | tenantId 미사용 | currentTenantId 기반 삭제 |
| reset() | pageType 목록 순회 삭제 | page_config_ 프리픽스 기반 전체 삭제 |
핵심 변경: 캐시 키 생성 함수 추가
function getStorageKey(tenantId: number | null, pageType: PageType): string {
return tenantId != null
? `${STORAGE_PREFIX}${tenantId}_${pageType}` // page_config_282_item-master
: `${STORAGE_PREFIX}${pageType}`; // page_config_item-master (하위 호환)
}
핵심 변경: reset()을 프리픽스 기반으로 변경
// Before: 고정된 pageType 목록으로 삭제 (tenantId 포함 키를 찾지 못함)
pageTypes.forEach((pt) => removeConfigFromSessionStorage(pt));
// After: page_config_ 프리픽스로 모든 테넌트 캐시 일괄 삭제
Object.keys(window.sessionStorage).forEach(key => {
if (key.startsWith(STORAGE_PREFIX)) {
window.sessionStorage.removeItem(key);
}
});
AuthContext.tsx
| 영역 | Before | After |
|---|---|---|
| import | - | useMasterDataStore 추가 |
| tenantId 동기화 | 없음 | currentUser.tenant.id 변경 시 setCurrentTenantId() 호출 |
| clearTenantCache | mes-{tenantId}-* 만 삭제 |
mes-{tenantId}-* + page_config_{tenantId}_* 삭제 |
핵심 변경: tenantId 동기화 useEffect
useEffect(() => {
const tenantId = currentUser?.tenant?.id ?? null;
useMasterDataStore.getState().setCurrentTenantId(tenantId);
}, [currentUser?.tenant?.id]);
핵심 변경: clearTenantCache 범위 확장
const tenantAwarePrefix = `mes-${tenantId}-`;
const pageConfigPrefix = `page_config_${tenantId}_`;
Object.keys(sessionStorage).forEach(key => {
if (key.startsWith(tenantAwarePrefix) || key.startsWith(pageConfigPrefix)) {
sessionStorage.removeItem(key);
}
});
하위 호환
| 항목 | 영향 |
|---|---|
| 기존 캐시 키 | page_config_item-master → 키 불일치로 miss → API 재요청 → 새 포맷으로 자동 전환 |
| logout.ts | page_config_ 프리픽스 매칭이 새 키 포맷(page_config_282_item-master)도 커버 |
| sessionStorage TTL | 10분 만료이므로 기존 키는 자연 소멸 |
| tenantId가 null인 경우 | 기존 포맷(page_config_{pageType}) 유지하여 동작 보장 |
효과
- 세션 내 테넌트 전환 시 캐시 누수 차단:
clearTenantCache가page_config_{tenantId}_*까지 삭제 - 캐시 패턴 일관성: TenantAwareCache(
mes-{tenantId}-)와 masterDataStore(page_config_{tenantId}_) 모두 테넌트 격리 - dead code 해소:
currentTenantId필드와setCurrentTenantId액션이 실제로 동작
관련 파일
src/stores/masterDataStore.ts- 캐시 키 변경, setCurrentTenantId 구현src/contexts/AuthContext.tsx- tenantId 동기화, clearTenantCache 범위 확장src/lib/auth/logout.ts- 기존page_config_프리픽스 매칭 (변경 없음, 호환 확인)src/lib/cache/TenantAwareCache.ts- 참고 (기존 정상 동작)