summaryrefslogtreecommitdiff
path: root/lib/menu-v2/permission-service.ts
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-12-04 21:05:28 +0900
committerjoonhoekim <26rote@gmail.com>2025-12-04 21:05:28 +0900
commite5b36fa6a1b12446883f51fc5e7cd56d8df8d8f5 (patch)
treec8f9fb50eb593dd5322d26d9276947c155997858 /lib/menu-v2/permission-service.ts
parent240f4f31b3b6ff6a46436978fb988588a1972721 (diff)
parent04ed774ff60a83c00711d4e8615cb4122954dba5 (diff)
Merge branch 'jh-auth-menu' into dujinkim
Diffstat (limited to 'lib/menu-v2/permission-service.ts')
-rw-r--r--lib/menu-v2/permission-service.ts186
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;
+ }
+}
+