이번 PR 은 invyone 멀티테넌시 SaaS 의 "관리 plane vs 테넌트 plane" 격리를
4 영역(PR #A~D) 에서 강화하고, 별도로 진행 중이던 부서관리 후속 작업을 포함한다.
# 보안 (plane 격리)
PR #A — controller/CompanyManagementController 인증 누락 패치
/api/company-management/* 가 JWT/role/host 체크 없이 외부에서 누구나 회사 삭제
+ 디스크 통계 호출 가능했던 critical 누수 막음. SuperAdminGuard.enforce() 적용.
PR #C — cross-tenant 컨트롤러 호스트 격리 + 감사 로그
CrossTenantContext.requireManagementHost() 헬퍼 추가, 5 컨트롤러
(CrossTenantContext/Controller/UserController/RoleController/DeptController) 모두
테넌트 호스트에서 호출 시 403. CompanyAuditLogService 에 cross-tenant write 4종
(USER_CREATE/DELETE, PW_RESET, ROLE_UPDATE) audit action 추가.
SuperAdminGuard.isTenantHost 가시성 public static 으로 승격.
PR #B — 프론트 솔루션 전용 admin 페이지 가드
admin/* 페이지 전수 분류 결과 솔루션 전용 3건 식별:
subdomainList / companyList / audit-log. 각 페이지에 isManagementHost
useEffect 가드 + redirect 추가. 사이드바도 같이 숨김.
PR #D — MENU_INFO.IS_SOLUTION_ONLY 컬럼 + DB-driven 메뉴 필터
V023 마이그레이션으로 컬럼 추가 + 솔루션 메뉴 3개 마킹.
admin.xml selectUserMenuList 에 호스트 기반 필터 추가, AdminController.getUserMenus
가 Host 헤더로 is_management_host 결정. 프론트 MANAGEMENT_ONLY_MENU_URLS
하드코딩 set 폐기 (DB 가 대신함). 페이지 자체 가드는 defense in depth 로 유지.
StartupSchemaMigrator 에 V023 등록되어 모든 테넌트 DB 부팅 시 자동 적용.
# 부서관리 후속 (이전 PR #18/#19 follow-up)
DepartmentController/Service + frontend deptMngList/department.ts 의 추가 작업분.
이번 격리 작업과 무관하지만 같이 정리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Frontend: body 키를 snake_case (user_id/new_password) 로 변환
- Controller: new_password 도 추출해서 service 에 전달
- Service: 2-arg 오버로드 추가, newPassword 입력값 사용 (blank 일 때만 Welcome1! fallback), userId null/blank 시 IllegalArgumentException
증상: 사용자관리에서 비밀번호 초기화 modal 입력 → backend 가 user_id=null 로 SQL 실행 (0행) + newPassword 무시 후 항상 Welcome1! 로 덮어쓰기.