From 8d682ed9ad78fdaa098839957db77f3b2c6ff9d3 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Fri, 13 Feb 2026 05:39:35 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20list=20policies=20endpoint=20+?= =?UTF-8?q?=20list=20policies=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routers/resource/listResourcePolicies.ts | 175 ++++++++++++++++++ .../settings/resources/policies/page.tsx | 14 ++ 2 files changed, 189 insertions(+) create mode 100644 server/private/routers/resource/listResourcePolicies.ts create mode 100644 src/app/[orgId]/settings/resources/policies/page.tsx diff --git a/server/private/routers/resource/listResourcePolicies.ts b/server/private/routers/resource/listResourcePolicies.ts new file mode 100644 index 000000000..57ab0d4af --- /dev/null +++ b/server/private/routers/resource/listResourcePolicies.ts @@ -0,0 +1,175 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + + +import { Request, Response, NextFunction } from "express"; +import { z } from "zod"; +import { + db, + resourceHeaderAuth, + resourceHeaderAuthExtendedCompatibility, + resourcePolicies +} from "@server/db"; +import { + resources, + userResources, + roleResources, + resourcePassword, + resourcePincode, + targets, + targetHealthCheck +} from "@server/db"; +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 logger from "@server/logger"; +import { fromZodError } from "zod-validation-error"; +import { OpenAPITags, registry } from "@server/openApi"; + +const listResourcePoliciesParamsSchema = z.strictObject({ + orgId: z.string() +}); + +const listResourcePoliciesSchema = z.object({ + pageSize: z.coerce + .number() // for prettier formatting + .int() + .positive() + .optional() + .catch(20) + .default(20), + page: z.coerce + .number() // for prettier formatting + .int() + .min(0) + .optional() + .catch(1) + .default(1), + query: z.string().optional(), +}); + +function queryResourcePoliciesBase() { + return db + .select({ + resourcePolicyId: resourcePolicies.resourcePolicyId, + name: resourcePolicies.name, + niceId: resourcePolicies.niceId, + passwordId: resourcePassword.passwordId, + sso: resourcePolicies.sso, + pincodeId: resourcePincode.pincodeId, + whitelist: resourcePolicies.emailWhitelistEnabled, + headerAuthId: resourceHeaderAuth.headerAuthId, + headerAuthExtendedCompatibilityId: + resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId + }) + .from(resourcePolicies) + .leftJoin( + resourcePassword, + eq(resourcePassword.resourcePolicyId, resourcePolicies.resourcePolicyId) + ) + .leftJoin( + resourcePincode, + eq(resourcePincode.resourcePolicyId, resourcePolicies.resourcePolicyId) + ) + .leftJoin( + resourceHeaderAuth, + eq(resourceHeaderAuth.resourcePolicyId, resourcePolicies.resourcePolicyId) + ) + .leftJoin( + resourceHeaderAuthExtendedCompatibility, + eq( + resourceHeaderAuthExtendedCompatibility.resourcePolicyId, + resourcePolicies.resourcePolicyId + ) + ); + +} + +// TODO: replaced with `PaginatedResponse` when paginated table PR is merged +export type ListResourcesResponse = { + policies: Awaited>; + total: number; pageSize: number; page: number; +}; + +registry.registerPath({ + method: "get", + path: "/org/{orgId}/resource-policies", + description: "List resource policies for an organization.", + tags: [OpenAPITags.Org, OpenAPITags.Resource], + request: { + params: z.object({ + orgId: z.string() + }), + query: listResourcePoliciesSchema + }, + responses: {} +}); + + + +export async function listResourcePolicies( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedQuery = listResourcePoliciesSchema.safeParse(req.query); + if (!parsedQuery.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromZodError(parsedQuery.error) + ) + ); + } + const { page, pageSize, query, } = + parsedQuery.data; + + const parsedParams = listResourcePoliciesParamsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromZodError(parsedParams.error) + ) + ); + } + + const orgId = + parsedParams.data.orgId || + req.userOrg?.orgId || + req.apiKeyOrg?.orgId; + + if (!orgId) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID") + ); + } + + if (req.user && orgId && orgId !== req.userOrgId) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "User does not have access to this organization" + ) + ); + } + + } catch (error) { + logger.error(error); + return next( + createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") + ); + } +} \ No newline at end of file diff --git a/src/app/[orgId]/settings/resources/policies/page.tsx b/src/app/[orgId]/settings/resources/policies/page.tsx new file mode 100644 index 000000000..59e7120f9 --- /dev/null +++ b/src/app/[orgId]/settings/resources/policies/page.tsx @@ -0,0 +1,14 @@ +import { getTranslations } from "next-intl/server"; + +export interface ResourcePoliciesPageProps { + params: Promise<{ orgId: string }>; + searchParams: Promise<{ view?: string }>; +} + +export default async function ResourcePoliciesPage( + props: ResourcePoliciesPageProps +) { + const params = await props.params; + const t = await getTranslations(); + return <>; +}