From 335411de4c682e889dd6d3ee07a843ed2252a026 Mon Sep 17 00:00:00 2001 From: Fred KISSIE Date: Tue, 24 Feb 2026 03:05:51 +0100 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20create=20table=20for=20res?= =?UTF-8?q?ource=20policies=20associations=20with=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- messages/en-US.json | 3 +- server/db/pg/schema/schema.ts | 89 +++++++++++++++---- .../routers/resource/listResourcePolicies.ts | 22 ++--- server/routers/resource/types.ts | 5 +- .../resources}/create/page.tsx | 0 .../policies => policies/resources}/page.tsx | 0 src/app/navigation.tsx | 32 ++++--- src/components/ResourcePoliciesTable.tsx | 21 +---- 8 files changed, 106 insertions(+), 66 deletions(-) rename src/app/[orgId]/settings/(private)/{resources/policies => policies/resources}/create/page.tsx (100%) rename src/app/[orgId]/settings/(private)/{resources/policies => policies/resources}/page.tsx (100%) diff --git a/messages/en-US.json b/messages/en-US.json index 8b46ee128..3f0c1982c 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1251,7 +1251,8 @@ "sidebarResources": "Resources", "sidebarProxyResources": "Public", "sidebarClientResources": "Private", - "sidebarResourcePolicies": "Policies", + "sidebarPolicies": "Policies", + "sidebarResourcePolicies": "Resources", "sidebarAccessControl": "Access Control", "sidebarLogsAndAnalytics": "Logs & Analytics", "sidebarUsers": "Users", diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index ed8d87df4..edc7702e6 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -94,8 +94,10 @@ export const sites = pgTable("sites", { export const resources = pgTable("resources", { resourceId: serial("resourceId").primaryKey(), - resourcePolicyId: integer("resourcePolicyId") - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + resourcePolicyId: integer("resourcePolicyId").references( + () => resourcePolicies.resourcePolicyId, + { onDelete: "cascade" } + ), resourceGuid: varchar("resourceGuid", { length: 36 }) .unique() .notNull() @@ -420,10 +422,7 @@ export const roleResources = pgTable("roleResources", { .references(() => roles.roleId, { onDelete: "cascade" }), resourceId: integer("resourceId") .notNull() - .references(() => resources.resourceId, { onDelete: "cascade" }), - resourcePolicyId: integer("resourcePolicyId") - .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resources.resourceId, { onDelete: "cascade" }) }); export const userResources = pgTable("userResources", { @@ -432,10 +431,29 @@ export const userResources = pgTable("userResources", { .references(() => users.userId, { onDelete: "cascade" }), resourceId: integer("resourceId") .notNull() - .references(() => resources.resourceId, { onDelete: "cascade" }), + .references(() => resources.resourceId, { onDelete: "cascade" }) +}); + +export const rolePolicies = pgTable("rolePolicies", { + roleId: integer("roleId") + .notNull() + .references(() => roles.roleId, { onDelete: "cascade" }), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }) +}); + +export const userPolicies = pgTable("userPolicies", { + userId: varchar("userId") + .notNull() + .references(() => users.userId, { onDelete: "cascade" }), + resourcePolicyId: integer("resourcePolicyId") + .notNull() + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }) }); export const userInvites = pgTable("userInvites", { @@ -460,7 +478,9 @@ export const resourcePincode = pgTable("resourcePincode", { digitLength: integer("digitLength").notNull(), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }) }); export const resourcePassword = pgTable("resourcePassword", { @@ -471,7 +491,9 @@ export const resourcePassword = pgTable("resourcePassword", { passwordHash: varchar("passwordHash").notNull(), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }) }); export const resourceHeaderAuth = pgTable("resourceHeaderAuth", { @@ -482,7 +504,9 @@ export const resourceHeaderAuth = pgTable("resourceHeaderAuth", { headerAuthHash: varchar("headerAuthHash").notNull(), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }) }); export const resourceHeaderAuthExtendedCompatibility = pgTable( @@ -496,7 +520,9 @@ export const resourceHeaderAuthExtendedCompatibility = pgTable( .references(() => resources.resourceId, { onDelete: "cascade" }), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }), extendedCompatibilityIsActivated: boolean( "extendedCompatibilityIsActivated" ) @@ -571,7 +597,9 @@ export const resourceWhitelist = pgTable("resourceWhitelist", { .references(() => resources.resourceId, { onDelete: "cascade" }), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }) }); export const resourceOtp = pgTable("resourceOtp", { @@ -581,7 +609,9 @@ export const resourceOtp = pgTable("resourceOtp", { .references(() => resources.resourceId, { onDelete: "cascade" }), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }), email: varchar("email").notNull(), otpHash: varchar("otpHash").notNull(), expiresAt: bigint("expiresAt", { mode: "number" }).notNull() @@ -599,7 +629,9 @@ export const resourceRules = pgTable("resourceRules", { .references(() => resources.resourceId, { onDelete: "cascade" }), resourcePolicyId: integer("resourcePolicyId") .notNull() - .references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }), + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }), enabled: boolean("enabled").notNull().default(true), priority: integer("priority").notNull(), action: varchar("action").notNull(), // ACCEPT, DROP, PASS @@ -607,21 +639,40 @@ export const resourceRules = pgTable("resourceRules", { value: varchar("value").notNull() }); +export const policyRules = pgTable("policyRules", { + ruleId: serial("ruleId").primaryKey(), + resourcePolicyId: integer("resourcePolicyId") + .notNull() + .references(() => resourcePolicies.resourcePolicyId, { + onDelete: "cascade" + }), + enabled: boolean("enabled").notNull().default(true), + priority: integer("priority").notNull(), + action: varchar("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(), + match: varchar("match").$type<"CIDR" | "PATH" | "IP">().notNull(), + value: varchar("value").notNull() +}); + export const resourcePolicies = pgTable("resourcePolicies", { - resourcePolicyId: serial('resourcePolicyId').primaryKey(), + resourcePolicyId: serial("resourcePolicyId").primaryKey(), sso: boolean("sso").notNull().default(true), - emailWhitelistEnabled: boolean("emailWhitelistEnabled").notNull().default(false), + scope: varchar("scope") + .$type<"global" | "resource">() + .notNull() + .default("global"), + emailWhitelistEnabled: boolean("emailWhitelistEnabled") + .notNull() + .default(false), idpId: integer("idpId").references(() => idp.idpId, { onDelete: "set null" }), niceId: text("niceId").notNull(), - isDefault: boolean("isDefault").notNull().default(true), name: varchar("name").notNull(), orgId: varchar("orgId") .references(() => orgs.orgId, { onDelete: "cascade" }) - .notNull(), + .notNull() }); export const supporterKey = pgTable("supporterKey", { diff --git a/server/private/routers/resource/listResourcePolicies.ts b/server/private/routers/resource/listResourcePolicies.ts index 0f2089bbc..14b74729e 100644 --- a/server/private/routers/resource/listResourcePolicies.ts +++ b/server/private/routers/resource/listResourcePolicies.ts @@ -11,7 +11,7 @@ * This file is not licensed under the AGPLv3. */ -import { db, resourcePolicies, roleResources, userResources } from "@server/db"; +import { db, resourcePolicies, rolePolicies, userPolicies } from "@server/db"; import response from "@server/lib/response"; import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; @@ -51,8 +51,7 @@ function queryResourcePoliciesBase() { resourcePolicyId: resourcePolicies.resourcePolicyId, name: resourcePolicies.name, niceId: resourcePolicies.niceId, - orgId: resourcePolicies.orgId, - isDefault: resourcePolicies.isDefault + orgId: resourcePolicies.orgId }) .from(resourcePolicies); } @@ -124,20 +123,20 @@ export async function listResourcePolicies( if (req.user) { accessibleResourcePolicies = await db .select({ - resourcePolicyId: sql`COALESCE(${userResources.resourcePolicyId}, ${roleResources.resourcePolicyId})` + resourcePolicyId: sql`COALESCE(${userPolicies.resourcePolicyId}, ${rolePolicies.resourcePolicyId})` }) - .from(userResources) + .from(userPolicies) .fullJoin( - roleResources, + rolePolicies, eq( - userResources.resourcePolicyId, - roleResources.resourcePolicyId + userPolicies.resourcePolicyId, + rolePolicies.resourcePolicyId ) ) .where( or( - eq(userResources.userId, req.user!.userId), - eq(roleResources.roleId, req.userOrgRoleId!) + eq(userPolicies.userId, req.user!.userId), + eq(rolePolicies.roleId, req.userOrgRoleId!) ) ); } else { @@ -159,7 +158,8 @@ export async function listResourcePolicies( resourcePolicies.resourcePolicyId, accessibleResourceIds ), - eq(resourcePolicies.orgId, orgId) + eq(resourcePolicies.orgId, orgId), + eq(resourcePolicies.scope, "global") ) ]; diff --git a/server/routers/resource/types.ts b/server/routers/resource/types.ts index 6e0ea3d50..223154a01 100644 --- a/server/routers/resource/types.ts +++ b/server/routers/resource/types.ts @@ -14,9 +14,6 @@ export type GetMaintenanceInfoResponse = { export type ListResourcePoliciesResponse = PaginatedResponse<{ policies: Array< - Pick< - ResourcePolicy, - "resourcePolicyId" | "niceId" | "name" | "orgId" | "isDefault" - > + Pick >; }>; diff --git a/src/app/[orgId]/settings/(private)/resources/policies/create/page.tsx b/src/app/[orgId]/settings/(private)/policies/resources/create/page.tsx similarity index 100% rename from src/app/[orgId]/settings/(private)/resources/policies/create/page.tsx rename to src/app/[orgId]/settings/(private)/policies/resources/create/page.tsx diff --git a/src/app/[orgId]/settings/(private)/resources/policies/page.tsx b/src/app/[orgId]/settings/(private)/policies/resources/page.tsx similarity index 100% rename from src/app/[orgId]/settings/(private)/resources/policies/page.tsx rename to src/app/[orgId]/settings/(private)/policies/resources/page.tsx diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 85d7d010a..bf68837f5 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -7,6 +7,7 @@ import { CreditCard, Fingerprint, Globe, + GlobeIcon, GlobeLock, KeyRound, Laptop, @@ -63,18 +64,7 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ title: "sidebarClientResources", href: "/{orgId}/settings/resources/client", icon: - }, - ...(build !== "oss" - ? [ - { - title: "sidebarResourcePolicies", - href: "/{orgId}/settings/resources/policies", - icon: ( - - ) - } - ] - : []) + } ] }, { @@ -133,6 +123,24 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [ href: "/{orgId}/settings/access/roles", icon: }, + ...(build !== "oss" + ? [ + { + title: "sidebarPolicies", + + icon: , + items: [ + { + title: "sidebarResourcePolicies", + href: "/{orgId}/settings/policies/resources", + icon: ( + + ) + } + ] + } + ] + : []), // PaidFeaturesAlert ...((build === "oss" && !env?.flags.disableEnterpriseFeatures) || build === "saas" || diff --git a/src/components/ResourcePoliciesTable.tsx b/src/components/ResourcePoliciesTable.tsx index 1473f72d4..01f2a8cdc 100644 --- a/src/components/ResourcePoliciesTable.tsx +++ b/src/components/ResourcePoliciesTable.tsx @@ -72,24 +72,7 @@ export function ResourcePoliciesTable({ enableHiding: false, friendlyName: t("name"), header: () => {t("name")}, - cell({ row }) { - const r = row.original; - return ( -
- {r.name} - {r.isDefault && ( - <> - - {t("resourcePoliciesDefaultBadgeText")} - - - )} -
- ); - } + cell: ({ row }) => {row.original.name} }, { id: "niceId", @@ -183,7 +166,7 @@ export function ResourcePoliciesTable({ onAdd={() => startNavigation(() => router.push( - `/${orgId}/settings/resources/policies/create` + `/${orgId}/settings/policies/resources/create` ) ) }