diff options
Diffstat (limited to 'lib/permissions')
| -rw-r--r-- | lib/permissions/permission-group-actions.ts | 9 | ||||
| -rw-r--r-- | lib/permissions/permission-group-assignment-actions.ts | 496 | ||||
| -rw-r--r-- | lib/permissions/permission-settings-actions.ts | 54 | ||||
| -rw-r--r-- | lib/permissions/service.ts | 88 |
4 files changed, 625 insertions, 22 deletions
diff --git a/lib/permissions/permission-group-actions.ts b/lib/permissions/permission-group-actions.ts index 51e3c2c0..474dc21b 100644 --- a/lib/permissions/permission-group-actions.ts +++ b/lib/permissions/permission-group-actions.ts @@ -117,10 +117,13 @@ export async function updatePermissionGroup(id: number, data: any) { // 권한 그룹 삭제 export async function deletePermissionGroup(id: number) { - const currentUser = await getCurrentUser(); - if (!currentUser) throw new Error("Unauthorized"); + const session = await getServerSession(authOptions) + if (!session?.user?.id) { + throw new Error("인증이 필요합니다.") + } + const currentUserId = Number(session.user.id) - if (!await checkUserPermission(currentUser.id, "admin.permissions.manage")) { + if (!await checkUserPermission(currentUserId, "admin.permissions.manage")) { throw new Error("권한 관리 권한이 없습니다."); } diff --git a/lib/permissions/permission-group-assignment-actions.ts b/lib/permissions/permission-group-assignment-actions.ts new file mode 100644 index 00000000..d1311559 --- /dev/null +++ b/lib/permissions/permission-group-assignment-actions.ts @@ -0,0 +1,496 @@ +// app/actions/permission-group-assignment-actions.ts + +"use server"; + +import db from "@/db/db"; +import { eq, and, inArray, sql, ne, notInArray, or } from "drizzle-orm"; +import { + permissionGroups, + permissionGroupMembers, + permissions, + rolePermissions, + userPermissions, + roles, + users, + userRoles, + permissionAuditLogs +} from "@/db/schema"; +import { checkUserPermission } from "./service"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; + +// 권한 그룹 할당 정보 조회 +export async function getPermissionGroupAssignments(groupId?: number) { + try { + if (!groupId) { + // 모든 그룹 목록 반환 + const groups = await db + .select({ + id: permissionGroups.id, + groupKey: permissionGroups.groupKey, + name: permissionGroups.name, + description: permissionGroups.description, + domain: permissionGroups.domain, + isActive: permissionGroups.isActive, + permissionCount: sql<number>`count(distinct ${permissionGroupMembers.permissionId})`.mapWith(Number), + }) + .from(permissionGroups) + .leftJoin(permissionGroupMembers, eq(permissionGroupMembers.groupId, permissionGroups.id)) + .where(eq(permissionGroups.isActive, true)) + .groupBy(permissionGroups.id) + .orderBy(permissionGroups.name); + + return { groups }; + } + + // 특정 그룹의 할당 정보 조회 + // 그룹에 속한 모든 권한 ID 조회 + const groupPermissionIds = await db + .select({ permissionId: permissionGroupMembers.permissionId }) + .from(permissionGroupMembers) + .where(eq(permissionGroupMembers.groupId, groupId)); + + const permissionIds = groupPermissionIds.map(p => p.permissionId); + + if (permissionIds.length === 0) { + return { roles: [], users: [] }; + } + + // 해당 그룹의 권한들이 할당된 역할 조회 + const assignedRoles = await db + .selectDistinct({ + id: roles.id, + name: roles.name, + domain: roles.domain, + grantedAt: rolePermissions.grantedAt, + grantedBy: rolePermissions.grantedBy, + }) + .from(rolePermissions) + .innerJoin(roles, eq(roles.id, rolePermissions.roleId)) + .where( + and( + eq(rolePermissions.permissionGroupId, groupId), + eq(rolePermissions.isActive, true) + ) + ); + + // 역할별 사용자 수 조회 + const rolesWithUserCount = await Promise.all( + assignedRoles.map(async (role) => { + const userCount = await db + .select({ count: sql<number>`count(*)`.mapWith(Number) }) + .from(userRoles) + .where(eq(userRoles.roleId, role.id)); + + // grantedBy가 userId인 경우 사용자 정보 조회 + let assignedBy = 'system'; + if (role.grantedBy) { + const [user] = await db + .select({ name: users.name }) + .from(users) + .where(eq(users.id, role.grantedBy)); + if (user) assignedBy = user.name; + } + + return { + ...role, + userCount: userCount[0]?.count || 0, + assignedAt: role.grantedAt, + assignedBy + }; + }) + ); + + // 해당 그룹의 권한들이 직접 할당된 사용자 조회 + const assignedUsers = await db + .selectDistinct({ + id: users.id, + name: users.name, + email: users.email, + imageUrl: users.imageUrl, + domain: users.domain, + grantedAt: userPermissions.grantedAt, + grantedBy: userPermissions.grantedBy, + }) + .from(userPermissions) + .innerJoin(users, eq(users.id, userPermissions.userId)) + .where( + and( + eq(userPermissions.permissionGroupId, groupId), + eq(userPermissions.isActive, true), + eq(userPermissions.isGrant, true) + ) + ); + + // 사용자별 회사 정보 및 할당자 정보 추가 + const usersWithDetails = await Promise.all( + assignedUsers.map(async (user) => { + // 회사 정보는 companyId로 조회 (vendors 테이블 필요) + let assignedBy = 'system'; + if (user.grantedBy) { + const [grantUser] = await db + .select({ name: users.name }) + .from(users) + .where(eq(users.id, user.grantedBy)); + if (grantUser) assignedBy = grantUser.name; + } + + return { + ...user, + companyName: null, // 필요시 vendors 테이블 조인 + assignedAt: user.grantedAt, + assignedBy + }; + }) + ); + + return { + roles: rolesWithUserCount, + users: usersWithDetails + }; + } catch (error) { + console.error('Failed to get permission group assignments:', error); + throw new Error('권한 그룹 할당 정보 조회에 실패했습니다.'); + } +} + +// 역할 검색 (그룹에 아직 할당되지 않은) +export async function searchRoles(groupId: number) { + try { + // 이미 해당 그룹이 할당된 역할 ID 조회 + const assignedRoleIds = await db + .selectDistinct({ roleId: rolePermissions.roleId }) + .from(rolePermissions) + .where( + and( + eq(rolePermissions.permissionGroupId, groupId), + eq(rolePermissions.isActive, true) + ) + ); + + const assignedIds = assignedRoleIds.map(r => r.roleId); + + // 할당되지 않은 역할 조회 + const availableRoles = await db + .select({ + id: roles.id, + name: roles.name, + domain: roles.domain, + }) + .from(roles) + .where( + assignedIds.length > 0 + ? notInArray(roles.id, assignedIds) + : undefined + ); + + // 역할별 사용자 수 추가 + const rolesWithUserCount = await Promise.all( + availableRoles.map(async (role) => { + const userCount = await db + .select({ count: sql<number>`count(*)`.mapWith(Number) }) + .from(userRoles) + .where(eq(userRoles.roleId, role.id)); + + return { + ...role, + userCount: userCount[0]?.count || 0 + }; + }) + ); + + return rolesWithUserCount; + } catch (error) { + console.error('Failed to search roles:', error); + throw new Error('역할 검색에 실패했습니다.'); + } +} + +// 사용자 검색 (그룹에 아직 할당되지 않은) +export async function searchUsers(query: string, groupId: number) { + try { + // 이미 해당 그룹이 할당된 사용자 ID 조회 + const assignedUserIds = await db + .selectDistinct({ userId: userPermissions.userId }) + .from(userPermissions) + .where( + and( + eq(userPermissions.permissionGroupId, groupId), + eq(userPermissions.isActive, true), + eq(userPermissions.isGrant, true) + ) + ); + + const assignedIds = assignedUserIds.map(u => u.userId); + + // 할당되지 않은 사용자 검색 + const availableUsers = await db + .select({ + id: users.id, + name: users.name, + email: users.email, + imageUrl: users.imageUrl, + domain: users.domain, + }) + .from(users) + .where( + and( + or( + sql`${users.name} ILIKE ${`%${query}%`}`, + sql`${users.email} ILIKE ${`%${query}%`}` + ), + eq(users.isActive, true), + assignedIds.length > 0 + ? notInArray(users.id, assignedIds) + : undefined + ) + ) + .limit(20); + + return availableUsers.map(user => ({ + ...user, + companyName: null // 필요시 vendors 테이블 조인 + })); + } catch (error) { + console.error('Failed to search users:', error); + throw new Error('사용자 검색에 실패했습니다.'); + } +} + +// 그룹을 역할에 할당 +export async function assignGroupToRoles(groupId: number, roleIds: number[]) { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + throw new Error("인증이 필요합니다."); + } + const currentUserId = Number(session.user.id); + + if (!await checkUserPermission(currentUserId, "admin.permissions.manage")) { + throw new Error("권한 관리 권한이 없습니다."); + } + + try { + // 그룹에 속한 모든 권한 ID 조회 + const groupPermissions = await db + .select({ permissionId: permissionGroupMembers.permissionId }) + .from(permissionGroupMembers) + .where(eq(permissionGroupMembers.groupId, groupId)); + + if (groupPermissions.length === 0) { + throw new Error("그룹에 권한이 없습니다."); + } + + await db.transaction(async (tx) => { + for (const roleId of roleIds) { + // 각 역할에 대해 그룹의 모든 권한 할당 + for (const { permissionId } of groupPermissions) { + // 기존 할당 확인 (중복 방지) + const existing = await tx + .select() + .from(rolePermissions) + .where( + and( + eq(rolePermissions.roleId, roleId), + eq(rolePermissions.permissionId, permissionId), + eq(rolePermissions.permissionGroupId, groupId) + ) + ) + .limit(1); + + if (existing.length === 0) { + await tx.insert(rolePermissions).values({ + roleId, + permissionId, + permissionGroupId: groupId, + grantedBy: currentUserId, + grantedAt: new Date(), + isActive: true + }); + } + } + + // 감사 로그 추가 + await tx.insert(permissionAuditLogs).values({ + targetType: 'role', + targetId: roleId, + permissionGroupId: groupId, + action: 'grant', + performedBy: currentUserId, + reason: `권한 그룹 ${groupId} 할당` + }); + } + }); + + return { success: true, count: roleIds.length }; + } catch (error) { + console.error('Failed to assign group to roles:', error); + throw new Error('역할에 권한 그룹 할당에 실패했습니다.'); + } +} + +// 그룹을 사용자에 할당 +export async function assignGroupToUsers(groupId: number, userIds: number[]) { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + throw new Error("인증이 필요합니다."); + } + const currentUserId = Number(session.user.id); + + if (!await checkUserPermission(currentUserId, "admin.permissions.manage")) { + throw new Error("권한 관리 권한이 없습니다."); + } + + try { + // 그룹에 속한 모든 권한 ID 조회 + const groupPermissions = await db + .select({ permissionId: permissionGroupMembers.permissionId }) + .from(permissionGroupMembers) + .where(eq(permissionGroupMembers.groupId, groupId)); + + if (groupPermissions.length === 0) { + throw new Error("그룹에 권한이 없습니다."); + } + + await db.transaction(async (tx) => { + for (const userId of userIds) { + // 각 사용자에 대해 그룹의 모든 권한 할당 + for (const { permissionId } of groupPermissions) { + // 기존 할당 확인 (중복 방지) + const existing = await tx + .select() + .from(userPermissions) + .where( + and( + eq(userPermissions.userId, userId), + eq(userPermissions.permissionId, permissionId) + ) + ) + .limit(1); + + if (existing.length === 0) { + await tx.insert(userPermissions).values({ + userId, + permissionId, + permissionGroupId: groupId, + isGrant: true, + grantedBy: currentUserId, + grantedAt: new Date(), + isActive: true + }); + } else if (existing[0].permissionGroupId !== groupId) { + // 다른 그룹으로 할당되어 있다면 업데이트 + await tx.update(userPermissions) + .set({ + permissionGroupId: groupId, + grantedBy: currentUserId, + grantedAt: new Date() + }) + .where( + and( + eq(userPermissions.userId, userId), + eq(userPermissions.permissionId, permissionId) + ) + ); + } + } + + // 감사 로그 추가 + await tx.insert(permissionAuditLogs).values({ + targetType: 'user', + targetId: userId, + permissionGroupId: groupId, + action: 'grant', + performedBy: currentUserId, + reason: `권한 그룹 ${groupId} 할당` + }); + } + }); + + return { success: true, count: userIds.length }; + } catch (error) { + console.error('Failed to assign group to users:', error); + throw new Error('사용자에게 권한 그룹 할당에 실패했습니다.'); + } +} + +// 역할에서 그룹 제거 +export async function removeGroupFromRole(groupId: number, roleId: number) { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + throw new Error("인증이 필요합니다."); + } + const currentUserId = Number(session.user.id); + + if (!await checkUserPermission(currentUserId, "admin.permissions.manage")) { + throw new Error("권한 관리 권한이 없습니다."); + } + + try { + await db.transaction(async (tx) => { + // 해당 그룹으로 할당된 모든 권한 제거 + await tx.delete(rolePermissions) + .where( + and( + eq(rolePermissions.roleId, roleId), + eq(rolePermissions.permissionGroupId, groupId) + ) + ); + + // 감사 로그 추가 + await tx.insert(permissionAuditLogs).values({ + targetType: 'role', + targetId: roleId, + permissionGroupId: groupId, + action: 'revoke', + performedBy: currentUserId, + reason: `권한 그룹 ${groupId} 제거` + }); + }); + + return { success: true }; + } catch (error) { + console.error('Failed to remove group from role:', error); + throw new Error('역할에서 권한 그룹 제거에 실패했습니다.'); + } +} + +// 사용자에서 그룹 제거 +export async function removeGroupFromUser(groupId: number, userId: number) { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + throw new Error("인증이 필요합니다."); + } + const currentUserId = Number(session.user.id); + + if (!await checkUserPermission(currentUserId, "admin.permissions.manage")) { + throw new Error("권한 관리 권한이 없습니다."); + } + + try { + await db.transaction(async (tx) => { + // 해당 그룹으로 할당된 모든 권한 제거 + await tx.delete(userPermissions) + .where( + and( + eq(userPermissions.userId, userId), + eq(userPermissions.permissionGroupId, groupId) + ) + ); + + // 감사 로그 추가 + await tx.insert(permissionAuditLogs).values({ + targetType: 'user', + targetId: userId, + permissionGroupId: groupId, + action: 'revoke', + performedBy: currentUserId, + reason: `권한 그룹 ${groupId} 제거` + }); + }); + + return { success: true }; + } catch (error) { + console.error('Failed to remove group from user:', error); + throw new Error('사용자에서 권한 그룹 제거에 실패했습니다.'); + } +}
\ No newline at end of file diff --git a/lib/permissions/permission-settings-actions.ts b/lib/permissions/permission-settings-actions.ts index 5d04a1d3..bb82b456 100644 --- a/lib/permissions/permission-settings-actions.ts +++ b/lib/permissions/permission-settings-actions.ts @@ -12,6 +12,37 @@ import { import { getServerSession } from "next-auth/next" import { authOptions } from "@/app/api/auth/[...nextauth]/route" import { checkUserPermission } from "./service"; +import fs from 'fs/promises'; +import path from 'path'; + +// i18n 번역 파일을 읽어오는 헬퍼 함수 +async function getTranslations(locale: string = 'ko') { + try { + const filePath = path.join(process.cwd(), 'i18n', 'locales', locale, 'menu.json'); + const fileContent = await fs.readFile(filePath, 'utf-8'); + return JSON.parse(fileContent); + } catch (error) { + console.error(`Failed to load translations for ${locale}:`, error); + return {}; + } +} + +// 중첩된 객체에서 키로 값을 가져오는 헬퍼 함수 +function getNestedValue(obj: any, key: string): string { + const keys = key.split('.'); + let value = obj; + + for (const k of keys) { + if (value && typeof value === 'object' && k in value) { + value = value[k]; + } else { + return key; // 키를 찾지 못하면 원본 키를 반환 + } + } + + return typeof value === 'string' ? value : key; +} + // 모든 권한 조회 export async function getAllPermissions() { @@ -110,7 +141,11 @@ export async function deletePermission(id: number) { } // 메뉴 권한 분석 + export async function analyzeMenuPermissions() { + // 한국어 번역 파일 로드 + const translations = await getTranslations('ko'); + const menus = await db.select().from(menuAssignments); const analysis = await Promise.all( @@ -126,20 +161,26 @@ export async function analyzeMenuPermissions() { .innerJoin(permissions, eq(permissions.id, menuRequiredPermissions.permissionId)) .where(eq(menuRequiredPermissions.menuPath, menu.menuPath)); + // i18n 키를 실제 텍스트로 변환 + const menuTitleTranslated = getNestedValue(translations, menu.menuTitle); + const menuDescriptionTranslated = menu.menuDescription + ? getNestedValue(translations, menu.menuDescription) + : ''; + // 제안할 권한 생성 const suggestedPermissions = []; const resourceName = menu.menuPath.split('/').pop() || 'unknown'; - // 기본 메뉴 접근 권한 + // 기본 메뉴 접근 권한 (번역된 제목 사용) suggestedPermissions.push({ permissionKey: `${resourceName}.menu_access`, - name: `${menu.menuTitle} 접근`, + name: `${menuTitleTranslated} 접근`, permissionType: "menu_access", action: "access", scope: "assigned", }); - // CRUD 권한 제안 + // CRUD 권한 제안 (번역된 제목 사용) const actions = [ { action: "view", name: "조회", type: "data_read" }, { action: "create", name: "생성", type: "data_write" }, @@ -150,7 +191,7 @@ export async function analyzeMenuPermissions() { actions.forEach(({ action, name, type }) => { suggestedPermissions.push({ permissionKey: `${resourceName}.${action}`, - name: `${menu.menuTitle} ${name}`, + name: `${menuTitleTranslated} ${name}`, permissionType: type, action, scope: "assigned", @@ -159,7 +200,9 @@ export async function analyzeMenuPermissions() { return { menuPath: menu.menuPath, - menuTitle: menu.menuTitle, + menuTitle: menuTitleTranslated, // 번역된 제목 + menuTitleKey: menu.menuTitle, // 원본 i18n 키 (필요한 경우) + menuDescription: menuDescriptionTranslated, // 번역된 설명 domain: menu.domain, existingPermissions: existing, suggestedPermissions: suggestedPermissions.filter( @@ -172,6 +215,7 @@ export async function analyzeMenuPermissions() { return analysis; } + // 메뉴 기반 권한 생성 export async function generateMenuPermissions( permissionsToCreate: Array<{ diff --git a/lib/permissions/service.ts b/lib/permissions/service.ts index 3ef1ff04..b3e6b4bc 100644 --- a/lib/permissions/service.ts +++ b/lib/permissions/service.ts @@ -3,7 +3,7 @@ "use server"; import db from "@/db/db"; -import { eq, and, inArray, or, ilike } from "drizzle-orm"; +import { eq, and, inArray, or, ilike, sql } from "drizzle-orm"; import { permissions, rolePermissions, @@ -70,21 +70,58 @@ export async function assignPermissionsToRole( // 역할의 권한 목록 조회 +// 역할 권한 조회 (기존 함수) export async function getRolePermissions(roleId: number) { - const allPermissions = await db.select().from(permissions) - .where(eq(permissions.isActive, true)); - - const rolePerms = await db.select({ - permissionId: rolePermissions.permissionId, - }) + try { + // 역할에 할당된 권한 조회 + const assignedPermissions = await db + .select({ + id: permissions.id, + permissionKey: permissions.permissionKey, + name: permissions.name, + description: permissions.description, + resource: permissions.resource, + action: permissions.action, + permissionType: permissions.permissionType, + scope: permissions.scope, + menuPath: permissions.menuPath, + }) .from(rolePermissions) - .where(eq(rolePermissions.roleId, roleId)); - - return { + .innerJoin(permissions, eq(permissions.id, rolePermissions.permissionId)) + .where( + and( + eq(rolePermissions.roleId, roleId), + eq(rolePermissions.isActive, true) + ) + ); + + // 모든 활성 권한 조회 + const allPermissions = await db + .select({ + id: permissions.id, + permissionKey: permissions.permissionKey, + name: permissions.name, + description: permissions.description, + resource: permissions.resource, + action: permissions.action, + permissionType: permissions.permissionType, + scope: permissions.scope, + menuPath: permissions.menuPath, + }) + .from(permissions) + .where(eq(permissions.isActive, true)) + .orderBy(permissions.resource, permissions.name); + + return { permissions: allPermissions, - assignedPermissionIds: rolePerms.map(rp => rp.permissionId), - }; -} + assignedPermissionIds: assignedPermissions.map(p => p.id) + }; + } catch (error) { + console.error('Failed to get role permissions:', error); + throw new Error('역할 권한 조회에 실패했습니다.'); + } + } + // 권한 체크 함수 export async function checkUserPermission( @@ -431,4 +468,27 @@ export async function updateMenuPermissions( ); } }); -}
\ No newline at end of file +} + +// 역할 목록 조회 +export async function getRoles() { + try { + const rolesData = await db + .select({ + id: roles.id, + name: roles.name, + domain: roles.domain, + description: roles.description, + userCount: sql<number>`count(distinct ${userRoles.userId})`.mapWith(Number), + }) + .from(roles) + .leftJoin(userRoles, eq(userRoles.roleId, roles.id)) + .groupBy(roles.id) + .orderBy(roles.domain, roles.name); + + return rolesData; + } catch (error) { + console.log('Failed to get roles:', error); + throw new Error('역할 목록 조회에 실패했습니다.'); + } + }
\ No newline at end of file |
