diff --git a/server/auth/actions.ts b/server/auth/actions.ts index 094437f43..01748b6b9 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -131,7 +131,11 @@ export enum ActionsEnum { viewLogs = "viewLogs", exportLogs = "exportLogs", listApprovals = "listApprovals", - updateApprovals = "updateApprovals" + updateApprovals = "updateApprovals", + listResourcePolicies = "listResourcePolicies", + createResourcePolicies = "createResourcePolicies", + updateResourcePolicies = "updateResourcePolicies", + deleteResourcePolicies = "deleteResourcePolicies", } export async function checkUserActionPermission( diff --git a/server/private/routers/external.ts b/server/private/routers/external.ts index dae10a954..31724e8cd 100644 --- a/server/private/routers/external.ts +++ b/server/private/routers/external.ts @@ -25,6 +25,7 @@ import * as logs from "#private/routers/auditLogs"; import * as misc from "#private/routers/misc"; import * as reKey from "#private/routers/re-key"; import * as approval from "#private/routers/approvals"; +import * as resource from "#private/routers/resource"; import { verifyOrgAccess, @@ -340,6 +341,18 @@ authenticated.get( approval.countApprovals ); +authenticated.get( + "/org/:orgId/resource-policies", + verifyValidLicense, + // verifyValidSubscription(tierMatrix.loginPageDomain), // todo: use the correct subscription ? + verifyOrgAccess, + verifyLimits, + verifyUserHasAction(ActionsEnum.listResourcePolicies), + logActionAudit(ActionsEnum.listResourcePolicies), + resource.listResourcePolicies +); + + authenticated.put( "/org/:orgId/approvals/:approvalId", verifyValidLicense, diff --git a/server/private/routers/resource/index.ts b/server/private/routers/resource/index.ts index f82b55524..4bae8e982 100644 --- a/server/private/routers/resource/index.ts +++ b/server/private/routers/resource/index.ts @@ -12,3 +12,4 @@ */ export * from "./getMaintenanceInfo"; +export * from "./listResourcePolicies"; diff --git a/server/private/routers/resource/listResourcePolicies.ts b/server/private/routers/resource/listResourcePolicies.ts index 57ab0d4af..cc280f235 100644 --- a/server/private/routers/resource/listResourcePolicies.ts +++ b/server/private/routers/resource/listResourcePolicies.ts @@ -32,7 +32,7 @@ import { import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; -import { sql, eq, or, inArray, and, count } from "drizzle-orm"; +import { sql, eq, or, inArray, and, count, ilike, asc } from "drizzle-orm"; import logger from "@server/logger"; import { fromZodError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; @@ -97,9 +97,9 @@ function queryResourcePoliciesBase() { } // TODO: replaced with `PaginatedResponse` when paginated table PR is merged -export type ListResourcesResponse = { +export type ListResourcePoliciesResponse = { policies: Awaited>; - total: number; pageSize: number; page: number; + pagination: { total: number; pageSize: number; page: number; }; }; registry.registerPath({ @@ -166,6 +166,82 @@ export async function listResourcePolicies( ); } + let accessibleResourcePolicies: Array<{ resourcePolicyId: number; }>; + if (req.user) { + accessibleResourcePolicies = await db + .select({ + resourcePolicyId: sql`COALESCE(${userResources.resourcePolicyId}, ${roleResources.resourcePolicyId})` + }) + .from(userResources) + .fullJoin( + roleResources, + eq(userResources.resourcePolicyId, roleResources.resourcePolicyId) + ) + .where( + or( + eq(userResources.userId, req.user!.userId), + eq(roleResources.roleId, req.userOrgRoleId!) + ) + ); + } else { + accessibleResourcePolicies = await db + .select({ + resourcePolicyId: resourcePolicies.resourcePolicyId + }) + .from(resourcePolicies) + .where(eq(resourcePolicies.orgId, orgId)); + } + + const accessibleResourceIds = accessibleResourcePolicies.map( + (resource) => resource.resourcePolicyId + ); + + const conditions = [ + and( + inArray(resourcePolicies.resourcePolicyId, accessibleResourceIds), + eq(resourcePolicies.orgId, orgId) + ) + ]; + + if (query) { + conditions.push( + or( + ilike(resourcePolicies.name, "%" + query + "%"), + ilike(resourcePolicies.niceId, "%" + query + "%"), + ) + ); + } + + const baseQuery = queryResourcePoliciesBase() + .where(and(...conditions)); + + // we need to add `as` so that drizzle filters the result as a subquery + const countQuery = db.$count(baseQuery.as("filtered_policies")); + + const [rows, totalCount] = await Promise.all([ + baseQuery + .limit(pageSize) + .offset(pageSize * (page - 1)) + .orderBy(asc(resourcePolicies.resourcePolicyId)), + countQuery + ]); + + return response(res, { + data: { + policies: rows, + pagination: { + total: totalCount, + pageSize, + page + } + }, + success: true, + error: false, + message: "Resources retrieved successfully", + status: HttpCode.OK + }); + + } catch (error) { logger.error(error); return next(