diff options
Diffstat (limited to 'lib/permissions/service.ts')
| -rw-r--r-- | lib/permissions/service.ts | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/lib/permissions/service.ts b/lib/permissions/service.ts new file mode 100644 index 00000000..3ef1ff04 --- /dev/null +++ b/lib/permissions/service.ts @@ -0,0 +1,434 @@ +// lib/permission/servicee.ts + +"use server"; + +import db from "@/db/db"; +import { eq, and, inArray, or, ilike } from "drizzle-orm"; +import { + permissions, + rolePermissions, + userPermissions, + permissionAuditLogs, + userRoles, + menuAssignments, + menuRequiredPermissions, + users, + vendors, + roles, +} from "@/db/schema"; +import { getServerSession } from "next-auth/next" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" + +// 역할에 권한 할당 +export async function assignPermissionsToRole( + roleId: number, + permissionIds: 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("권한 관리 권한이 없습니다."); + } + + await db.transaction(async (tx) => { + // 기존 권한 삭제 + await tx.delete(rolePermissions) + .where(eq(rolePermissions.roleId, roleId)); + + // 새 권한 추가 + if (permissionIds.length > 0) { + await tx.insert(rolePermissions).values( + permissionIds.map(permissionId => ({ + roleId, + permissionId, + grantedBy: currentUserId, + })) + ); + + // 감사 로그 + await tx.insert(permissionAuditLogs).values( + permissionIds.map(permissionId => ({ + targetType: "role", + targetId: roleId, + permissionId, + action: "grant", + performedBy: currentUserId, + reason: "역할 권한 일괄 업데이트", + })) + ); + } + }); + + return { success: true }; +} + + +// 역할의 권한 목록 조회 +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, + }) + .from(rolePermissions) + .where(eq(rolePermissions.roleId, roleId)); + + return { + permissions: allPermissions, + assignedPermissionIds: rolePerms.map(rp => rp.permissionId), + }; +} + +// 권한 체크 함수 +export async function checkUserPermission( + userId: number, + permissionKey: string +): Promise<boolean> { + // 역할 기반 권한 + const roleBasedPerms = await db + .selectDistinct({ permissionKey: permissions.permissionKey }) + .from(userRoles) + .innerJoin(rolePermissions, eq(rolePermissions.roleId, userRoles.roleId)) + .innerJoin(permissions, eq(permissions.id, rolePermissions.permissionId)) + .where( + and( + eq(userRoles.userId, userId), + eq(permissions.permissionKey, permissionKey), + eq(permissions.isActive, true), + eq(rolePermissions.isActive, true) + ) + ); + + if (roleBasedPerms.length > 0) return true; + + // 사용자 직접 권한 + const directPerms = await db + .selectDistinct({ permissionKey: permissions.permissionKey }) + .from(userPermissions) + .innerJoin(permissions, eq(permissions.id, userPermissions.permissionId)) + .where( + and( + eq(userPermissions.userId, userId), + eq(permissions.permissionKey, permissionKey), + eq(permissions.isActive, true), + eq(userPermissions.isActive, true), + eq(userPermissions.isGrant, true) // 부여된 권한만 + ) + ); + + return directPerms.length > 0; +} + +// 메뉴 접근 권한 체크 +export async function checkMenuAccess( + userId: number, + menuPath: string +): Promise<boolean> { + // 메뉴 담당자인 경우 자동 허용 + const isManager = await db + .selectDistinct({ id: menuAssignments.id }) + .from(menuAssignments) + .where( + and( + eq(menuAssignments.menuPath, menuPath), + or( + eq(menuAssignments.manager1Id, userId), + eq(menuAssignments.manager2Id, userId) + ) + ) + ); + + if (isManager.length > 0) return true; + + // 메뉴 필수 권한 체크 + const requiredPerms = await db + .select({ permissionKey: permissions.permissionKey }) + .from(menuRequiredPermissions) + .innerJoin(permissions, eq(permissions.id, menuRequiredPermissions.permissionId)) + .where( + and( + eq(menuRequiredPermissions.menuPath, menuPath), + eq(menuRequiredPermissions.isRequired, true) + ) + ); + + if (requiredPerms.length === 0) return true; // 필수 권한이 없으면 모두 접근 가능 + + // 사용자가 필수 권한을 모두 가지고 있는지 확인 + for (const perm of requiredPerms) { + if (!await checkUserPermission(userId, perm.permissionKey)) { + return false; + } + } + + return true; +} + + +export async function searchUsers(query: string) { + const usersData = await db + .select({ + id: users.id, + name: users.name, + email: users.email, + imageUrl: users.imageUrl, + domain: users.domain, + companyName: vendors.vendorName, + }) + .from(users) + .leftJoin(vendors, eq(vendors.id, users.companyId)) + .where( + or( + ilike(users.name, `%${query}%`), + ilike(users.email, `%${query}%`) + ) + ) + .limit(20); + + // 각 사용자의 역할 조회 + const usersWithRoles = await Promise.all( + usersData.map(async (user) => { + const userRolesData = await db + .select({ + id: roles.id, + name: roles.name, + }) + .from(userRoles) + .innerJoin(roles, eq(roles.id, userRoles.roleId)) + .where(eq(userRoles.userId, user.id)); + + return { + ...user, + roles: userRolesData, + }; + }) + ); + + return usersWithRoles; +} + +export async function getUserPermissionDetails(userId: number) { + // 역할 기반 권한 + const rolePermissionsData = await db + .select({ + id: permissions.id, + permissionKey: permissions.permissionKey, + name: permissions.name, + description: permissions.description, + permissionType: permissions.permissionType, + resource: permissions.resource, + action: permissions.action, + scope: permissions.scope, + menuPath: permissions.menuPath, + roleName: roles.name, + }) + .from(userRoles) + .innerJoin(roles, eq(roles.id, userRoles.roleId)) + .innerJoin(rolePermissions, eq(rolePermissions.roleId, roles.id)) + .innerJoin(permissions, eq(permissions.id, rolePermissions.permissionId)) + .where(eq(userRoles.userId, userId)); + + // 직접 부여된 권한 + const directPermissions = await db + .select({ + id: permissions.id, + permissionKey: permissions.permissionKey, + name: permissions.name, + description: permissions.description, + permissionType: permissions.permissionType, + resource: permissions.resource, + action: permissions.action, + scope: permissions.scope, + menuPath: permissions.menuPath, + isGrant: userPermissions.isGrant, + grantedBy: users.name, + grantedAt: userPermissions.grantedAt, + expiresAt: userPermissions.expiresAt, + reason: userPermissions.reason, + }) + .from(userPermissions) + .innerJoin(permissions, eq(permissions.id, userPermissions.permissionId)) + .leftJoin(users, eq(users.id, userPermissions.grantedBy)) + .where(eq(userPermissions.userId, userId)); + + // 모든 권한 목록 + const allPermissions = await db.select().from(permissions); + + return { + permissions: [ + ...rolePermissionsData.map(p => ({ ...p, source: "role" as const })), + ...directPermissions.map(p => ({ ...p, source: "direct" as const })), + ], + availablePermissions: allPermissions, + }; +} + +export async function grantPermissionToUser(params: { + userId: number; + permissionIds: number[]; + isGrant: boolean; + reason?: string; + expiresAt?: Date; +}) { + const session = await getServerSession(authOptions) + if (!session?.user?.id) { + throw new Error("인증이 필요합니다.") + } + const currentUserId = Number(session.user.id) + + await db.transaction(async (tx) => { + for (const permissionId of params.permissionIds) { + await tx.insert(userPermissions).values({ + userId: params.userId, + permissionId, + isGrant: params.isGrant, + grantedBy: Number(session.user.id), + reason: params.reason, + expiresAt: params.expiresAt, + }).onConflictDoUpdate({ + target: [userPermissions.userId, userPermissions.permissionId], + set: { + isGrant: params.isGrant, + grantedBy: Number(session.user.id), + grantedAt: new Date(), + reason: params.reason, + expiresAt: params.expiresAt, + isActive: true, + } + }); + + // 감사 로그 + await tx.insert(permissionAuditLogs).values({ + targetType: "user", + targetId: params.userId, + permissionId, + action: params.isGrant ? "grant" : "restrict", + performedBy: currentUserId, + reason: params.reason, + }); + } + }); +} + +export async function revokePermissionFromUser(userId: number, permissionId: number) { + const session = await getServerSession(authOptions) + if (!session?.user?.id) { + throw new Error("인증이 필요합니다.") + } + + await db.transaction(async (tx) => { + await tx.update(userPermissions) + .set({ isActive: false }) + .where( + and( + eq(userPermissions.userId, userId), + eq(userPermissions.permissionId, permissionId) + ) + ); + + // 감사 로그 + await tx.insert(permissionAuditLogs).values({ + targetType: "user", + targetId: userId, + permissionId, + action: "revoke", + performedBy: Number(session.user.id), + }); + }); +} + + +export async function getMenuPermissions(domain: string = "all") { + const menus = await db + .select({ + menuPath: menuAssignments.menuPath, + menuTitle: menuAssignments.menuTitle, + menuDescription: menuAssignments.menuDescription, + sectionTitle: menuAssignments.sectionTitle, + menuGroup: menuAssignments.menuGroup, + domain: menuAssignments.domain, + isActive: menuAssignments.isActive, + manager1Id: menuAssignments.manager1Id, + manager2Id: menuAssignments.manager2Id, + }) + .from(menuAssignments) + .where(domain === "all" ? undefined : eq(menuAssignments.domain, domain)); + + // 각 메뉴의 권한과 담당자 정보 조회 + const menusWithDetails = await Promise.all( + menus.map(async (menu) => { + // 필수 권한 조회 + const requiredPerms = await db + .select({ + id: permissions.id, + permissionKey: permissions.permissionKey, + name: permissions.name, + description: permissions.description, + isRequired: menuRequiredPermissions.isRequired, + }) + .from(menuRequiredPermissions) + .innerJoin(permissions, eq(permissions.id, menuRequiredPermissions.permissionId)) + .where(eq(menuRequiredPermissions.menuPath, menu.menuPath)); + + // 담당자 정보 조회 + const [manager1, manager2] = await Promise.all([ + menu.manager1Id ? db.select({ + id: users.id, + name: users.name, + email: users.email, + imageUrl: users.imageUrl, + }).from(users).where(eq(users.id, menu.manager1Id)).then(r => r[0]) : null, + menu.manager2Id ? db.select({ + id: users.id, + name: users.name, + email: users.email, + imageUrl: users.imageUrl, + }).from(users).where(eq(users.id, menu.manager2Id)).then(r => r[0]) : null, + ]); + + return { + ...menu, + requiredPermissions: requiredPerms, + manager1, + manager2, + }; + }) + ); + + // 사용 가능한 모든 권한 목록 + const availablePermissions = await db.select().from(permissions); + + return { + menus: menusWithDetails, + availablePermissions, + }; +} + +export async function updateMenuPermissions( + menuPath: string, + permissions: Array<{ id: number; isRequired: boolean }> +) { + await db.transaction(async (tx) => { + // 기존 권한 삭제 + await tx.delete(menuRequiredPermissions) + .where(eq(menuRequiredPermissions.menuPath, menuPath)); + + // 새 권한 추가 + if (permissions.length > 0) { + await tx.insert(menuRequiredPermissions).values( + permissions.map(p => ({ + menuPath, + permissionId: p.id, + isRequired: p.isRequired, + })) + ); + } + }); +}
\ No newline at end of file |
