diff options
Diffstat (limited to 'lib/menu-v2/permission-service.ts')
| -rw-r--r-- | lib/menu-v2/permission-service.ts | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/lib/menu-v2/permission-service.ts b/lib/menu-v2/permission-service.ts new file mode 100644 index 00000000..e495ba23 --- /dev/null +++ b/lib/menu-v2/permission-service.ts @@ -0,0 +1,186 @@ +'use server'; + +import { getServerSession } from "next-auth"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { getAllScreens, getAuthsByScreenId, getUserRoles, type ScreenEvcp, type RoleRelEvcp } from "@/lib/nonsap/db"; +import { getActiveMenuTree } from "./service"; +import type { MenuDomain, MenuTreeNode, MenuTreeActiveResult } from "./types"; +import db from "@/db/db"; +import { users } from "@/db/schema/users"; +import { eq } from "drizzle-orm"; + +/** + * Oracle 권한 체크 스킵 여부 확인 + * SKIP_ORACLE_PERMISSION_CHECK=true인 경우 Oracle DB 권한 체크를 건너뜀 + */ +function shouldSkipOraclePermissionCheck(): boolean { + return process.env.SKIP_ORACLE_PERMISSION_CHECK === 'true'; +} + +/** + * 사용자 ID로 employeeNumber 조회 + */ +async function getEmployeeNumberByUserId(userId: number): Promise<string | null> { + const [user] = await db.select({ employeeNumber: users.employeeNumber }) + .from(users) + .where(eq(users.id, userId)) + .limit(1); + + return user?.employeeNumber || null; +} + +/** + * Get menu tree filtered by user permissions + * + * @param domain - Domain (evcp | partners) + * @param userId - Optional user ID. If not provided, gets from session. + * + * Environment variable SKIP_ORACLE_PERMISSION_CHECK=true skips Oracle permission check + */ +export async function getVisibleMenuTree( + domain: MenuDomain, + userId?: number +): Promise<MenuTreeActiveResult> { + const { tree: menuTree } = await getActiveMenuTree(domain); + + // Partners domain uses its own permission system (not implemented) + if (domain === 'partners') { + return { tree: menuTree }; + } + + // Skip Oracle permission check in development + if (shouldSkipOraclePermissionCheck()) { + return { tree: menuTree }; + } + + // Get userId from session if not provided + let effectiveUserId = userId; + if (!effectiveUserId) { + const session = await getServerSession(authOptions); + effectiveUserId = session?.user?.id ? parseInt(session.user.id, 10) : undefined; + } + + if (!effectiveUserId) { + return { tree: menuTree }; + } + + // Get employeeNumber from userId + const empNo = await getEmployeeNumberByUserId(effectiveUserId); + if (!empNo) { + return { tree: menuTree }; + } + + let screens: ScreenEvcp[]; + let userRoles: RoleRelEvcp[]; + + try { + [screens, userRoles] = await Promise.all([ + getAllScreens(), + getUserRoles(empNo) + ]); + } catch (error) { + // Oracle DB 연결 실패 시 전체 메뉴 반환 (에러로 인한 접근 차단 방지) + console.error('[menu-v2] Oracle permission check failed, returning all menus:', error); + return { tree: menuTree }; + } + + const userRoleIds = new Set(userRoles.map(r => r.ROLE_ID)); + const screenMap = new Map<string, ScreenEvcp>(screens.map(s => [s.SCR_URL, s])); + + // 메뉴 필터링 (최상위 menu, menu_group, group 모두 처리) + async function filterByPermission(nodes: MenuTreeNode[]): Promise<MenuTreeNode[]> { + const result: MenuTreeNode[] = []; + + for (const node of nodes) { + // 메뉴 노드 (최상위 단일 링크 또는 하위 메뉴) + if (node.nodeType === 'menu' && node.menuPath) { + const screen = screenMap.get(node.menuPath); + + // 화면 정보가 없거나 SCRT_CHK_YN === 'N' 이면 표시 + if (!screen || screen.SCRT_CHK_YN === 'N') { + result.push(node); + continue; + } + + // SCRT_CHK_YN === 'Y' 이면 권한 체크 + if (screen.SCRT_CHK_YN === 'Y') { + const scrIdToCheck = node.scrId || screen.SCR_ID; + const auths = await getAuthsByScreenId(scrIdToCheck); + + const hasAccess = auths.some(auth => { + if (auth.ACSR_GB_CD === 'U' && auth.ACSR_ID === empNo) return true; + if (auth.ACSR_GB_CD === 'R' && userRoleIds.has(auth.ACSR_ID)) return true; + return false; + }); + + if (hasAccess) result.push(node); + } + } + // 메뉴그룹 또는 그룹 (자식 필터링 후 자식이 있으면 포함) + else if (node.nodeType === 'menu_group' || node.nodeType === 'group') { + const filteredChildren = await filterByPermission(node.children || []); + if (filteredChildren.length > 0) { + result.push({ ...node, children: filteredChildren }); + } + } + } + + return result; + } + + const filteredTree = await filterByPermission(menuTree); + + return { tree: filteredTree }; +} + +/** + * 특정 메뉴 경로에 대한 접근 권한 확인 + * + * 환경변수 SKIP_ORACLE_PERMISSION_CHECK=true인 경우 항상 true 반환 + */ +export async function checkMenuAccess( + menuPath: string, + userId: number +): Promise<boolean> { + // Oracle 권한 체크 스킵 설정된 경우 + if (shouldSkipOraclePermissionCheck()) { + return true; + } + + const empNo = await getEmployeeNumberByUserId(userId); + if (!empNo) return false; + + try { + const screens = await getAllScreens(); + const screen = screens.find(s => s.SCR_URL === menuPath); + + // 등록되지 않은 화면 또는 권한 체크가 필요 없는 화면 + if (!screen || screen.SCRT_CHK_YN === 'N') { + return true; + } + + // 삭제된 화면 + if (screen.DEL_YN === 'Y') { + return false; + } + + // 권한 체크 + const [auths, userRoles] = await Promise.all([ + getAuthsByScreenId(screen.SCR_ID), + getUserRoles(empNo) + ]); + + const userRoleIds = new Set(userRoles.map(r => r.ROLE_ID)); + + return auths.some(auth => { + if (auth.ACSR_GB_CD === 'U' && auth.ACSR_ID === empNo) return true; + if (auth.ACSR_GB_CD === 'R' && userRoleIds.has(auth.ACSR_ID)) return true; + return false; + }); + } catch (error) { + // Oracle DB 연결 실패 시 접근 허용 (에러로 인한 차단 방지) + console.error('[menu-v2] Oracle permission check failed for path:', menuPath, error); + return true; + } +} + |
