mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-16 22:14:29 +00:00
♻️ create table for resource policies associations with users
This commit is contained in:
@@ -1251,7 +1251,8 @@
|
|||||||
"sidebarResources": "Resources",
|
"sidebarResources": "Resources",
|
||||||
"sidebarProxyResources": "Public",
|
"sidebarProxyResources": "Public",
|
||||||
"sidebarClientResources": "Private",
|
"sidebarClientResources": "Private",
|
||||||
"sidebarResourcePolicies": "Policies",
|
"sidebarPolicies": "Policies",
|
||||||
|
"sidebarResourcePolicies": "Resources",
|
||||||
"sidebarAccessControl": "Access Control",
|
"sidebarAccessControl": "Access Control",
|
||||||
"sidebarLogsAndAnalytics": "Logs & Analytics",
|
"sidebarLogsAndAnalytics": "Logs & Analytics",
|
||||||
"sidebarUsers": "Users",
|
"sidebarUsers": "Users",
|
||||||
|
|||||||
@@ -94,8 +94,10 @@ export const sites = pgTable("sites", {
|
|||||||
|
|
||||||
export const resources = pgTable("resources", {
|
export const resources = pgTable("resources", {
|
||||||
resourceId: serial("resourceId").primaryKey(),
|
resourceId: serial("resourceId").primaryKey(),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId").references(
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
() => resourcePolicies.resourcePolicyId,
|
||||||
|
{ onDelete: "cascade" }
|
||||||
|
),
|
||||||
resourceGuid: varchar("resourceGuid", { length: 36 })
|
resourceGuid: varchar("resourceGuid", { length: 36 })
|
||||||
.unique()
|
.unique()
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -420,10 +422,7 @@ export const roleResources = pgTable("roleResources", {
|
|||||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||||
resourceId: integer("resourceId")
|
resourceId: integer("resourceId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" })
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userResources = pgTable("userResources", {
|
export const userResources = pgTable("userResources", {
|
||||||
@@ -432,10 +431,29 @@ export const userResources = pgTable("userResources", {
|
|||||||
.references(() => users.userId, { onDelete: "cascade" }),
|
.references(() => users.userId, { onDelete: "cascade" }),
|
||||||
resourceId: integer("resourceId")
|
resourceId: integer("resourceId")
|
||||||
.notNull()
|
.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")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.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", {
|
export const userInvites = pgTable("userInvites", {
|
||||||
@@ -460,7 +478,9 @@ export const resourcePincode = pgTable("resourcePincode", {
|
|||||||
digitLength: integer("digitLength").notNull(),
|
digitLength: integer("digitLength").notNull(),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
.references(() => resourcePolicies.resourcePolicyId, {
|
||||||
|
onDelete: "cascade"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resourcePassword = pgTable("resourcePassword", {
|
export const resourcePassword = pgTable("resourcePassword", {
|
||||||
@@ -471,7 +491,9 @@ export const resourcePassword = pgTable("resourcePassword", {
|
|||||||
passwordHash: varchar("passwordHash").notNull(),
|
passwordHash: varchar("passwordHash").notNull(),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
.references(() => resourcePolicies.resourcePolicyId, {
|
||||||
|
onDelete: "cascade"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resourceHeaderAuth = pgTable("resourceHeaderAuth", {
|
export const resourceHeaderAuth = pgTable("resourceHeaderAuth", {
|
||||||
@@ -482,7 +504,9 @@ export const resourceHeaderAuth = pgTable("resourceHeaderAuth", {
|
|||||||
headerAuthHash: varchar("headerAuthHash").notNull(),
|
headerAuthHash: varchar("headerAuthHash").notNull(),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
.references(() => resourcePolicies.resourcePolicyId, {
|
||||||
|
onDelete: "cascade"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resourceHeaderAuthExtendedCompatibility = pgTable(
|
export const resourceHeaderAuthExtendedCompatibility = pgTable(
|
||||||
@@ -496,7 +520,9 @@ export const resourceHeaderAuthExtendedCompatibility = pgTable(
|
|||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
.references(() => resourcePolicies.resourcePolicyId, {
|
||||||
|
onDelete: "cascade"
|
||||||
|
}),
|
||||||
extendedCompatibilityIsActivated: boolean(
|
extendedCompatibilityIsActivated: boolean(
|
||||||
"extendedCompatibilityIsActivated"
|
"extendedCompatibilityIsActivated"
|
||||||
)
|
)
|
||||||
@@ -571,7 +597,9 @@ export const resourceWhitelist = pgTable("resourceWhitelist", {
|
|||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
.references(() => resourcePolicies.resourcePolicyId, {
|
||||||
|
onDelete: "cascade"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export const resourceOtp = pgTable("resourceOtp", {
|
export const resourceOtp = pgTable("resourceOtp", {
|
||||||
@@ -581,7 +609,9 @@ export const resourceOtp = pgTable("resourceOtp", {
|
|||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
.references(() => resourcePolicies.resourcePolicyId, {
|
||||||
|
onDelete: "cascade"
|
||||||
|
}),
|
||||||
email: varchar("email").notNull(),
|
email: varchar("email").notNull(),
|
||||||
otpHash: varchar("otpHash").notNull(),
|
otpHash: varchar("otpHash").notNull(),
|
||||||
expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
|
expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
|
||||||
@@ -599,7 +629,9 @@ export const resourceRules = pgTable("resourceRules", {
|
|||||||
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
.references(() => resources.resourceId, { onDelete: "cascade" }),
|
||||||
resourcePolicyId: integer("resourcePolicyId")
|
resourcePolicyId: integer("resourcePolicyId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => resourcePolicies.resourcePolicyId, { onDelete: "cascade" }),
|
.references(() => resourcePolicies.resourcePolicyId, {
|
||||||
|
onDelete: "cascade"
|
||||||
|
}),
|
||||||
enabled: boolean("enabled").notNull().default(true),
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
priority: integer("priority").notNull(),
|
priority: integer("priority").notNull(),
|
||||||
action: varchar("action").notNull(), // ACCEPT, DROP, PASS
|
action: varchar("action").notNull(), // ACCEPT, DROP, PASS
|
||||||
@@ -607,21 +639,40 @@ export const resourceRules = pgTable("resourceRules", {
|
|||||||
value: varchar("value").notNull()
|
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", {
|
export const resourcePolicies = pgTable("resourcePolicies", {
|
||||||
resourcePolicyId: serial('resourcePolicyId').primaryKey(),
|
resourcePolicyId: serial("resourcePolicyId").primaryKey(),
|
||||||
sso: boolean("sso").notNull().default(true),
|
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, {
|
idpId: integer("idpId").references(() => idp.idpId, {
|
||||||
onDelete: "set null"
|
onDelete: "set null"
|
||||||
}),
|
}),
|
||||||
niceId: text("niceId").notNull(),
|
niceId: text("niceId").notNull(),
|
||||||
isDefault: boolean("isDefault").notNull().default(true),
|
|
||||||
name: varchar("name").notNull(),
|
name: varchar("name").notNull(),
|
||||||
orgId: varchar("orgId")
|
orgId: varchar("orgId")
|
||||||
.references(() => orgs.orgId, {
|
.references(() => orgs.orgId, {
|
||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
})
|
})
|
||||||
.notNull(),
|
.notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const supporterKey = pgTable("supporterKey", {
|
export const supporterKey = pgTable("supporterKey", {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* This file is not licensed under the AGPLv3.
|
* 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 response from "@server/lib/response";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
@@ -51,8 +51,7 @@ function queryResourcePoliciesBase() {
|
|||||||
resourcePolicyId: resourcePolicies.resourcePolicyId,
|
resourcePolicyId: resourcePolicies.resourcePolicyId,
|
||||||
name: resourcePolicies.name,
|
name: resourcePolicies.name,
|
||||||
niceId: resourcePolicies.niceId,
|
niceId: resourcePolicies.niceId,
|
||||||
orgId: resourcePolicies.orgId,
|
orgId: resourcePolicies.orgId
|
||||||
isDefault: resourcePolicies.isDefault
|
|
||||||
})
|
})
|
||||||
.from(resourcePolicies);
|
.from(resourcePolicies);
|
||||||
}
|
}
|
||||||
@@ -124,20 +123,20 @@ export async function listResourcePolicies(
|
|||||||
if (req.user) {
|
if (req.user) {
|
||||||
accessibleResourcePolicies = await db
|
accessibleResourcePolicies = await db
|
||||||
.select({
|
.select({
|
||||||
resourcePolicyId: sql<number>`COALESCE(${userResources.resourcePolicyId}, ${roleResources.resourcePolicyId})`
|
resourcePolicyId: sql<number>`COALESCE(${userPolicies.resourcePolicyId}, ${rolePolicies.resourcePolicyId})`
|
||||||
})
|
})
|
||||||
.from(userResources)
|
.from(userPolicies)
|
||||||
.fullJoin(
|
.fullJoin(
|
||||||
roleResources,
|
rolePolicies,
|
||||||
eq(
|
eq(
|
||||||
userResources.resourcePolicyId,
|
userPolicies.resourcePolicyId,
|
||||||
roleResources.resourcePolicyId
|
rolePolicies.resourcePolicyId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
or(
|
or(
|
||||||
eq(userResources.userId, req.user!.userId),
|
eq(userPolicies.userId, req.user!.userId),
|
||||||
eq(roleResources.roleId, req.userOrgRoleId!)
|
eq(rolePolicies.roleId, req.userOrgRoleId!)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -159,7 +158,8 @@ export async function listResourcePolicies(
|
|||||||
resourcePolicies.resourcePolicyId,
|
resourcePolicies.resourcePolicyId,
|
||||||
accessibleResourceIds
|
accessibleResourceIds
|
||||||
),
|
),
|
||||||
eq(resourcePolicies.orgId, orgId)
|
eq(resourcePolicies.orgId, orgId),
|
||||||
|
eq(resourcePolicies.scope, "global")
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,6 @@ export type GetMaintenanceInfoResponse = {
|
|||||||
|
|
||||||
export type ListResourcePoliciesResponse = PaginatedResponse<{
|
export type ListResourcePoliciesResponse = PaginatedResponse<{
|
||||||
policies: Array<
|
policies: Array<
|
||||||
Pick<
|
Pick<ResourcePolicy, "resourcePolicyId" | "niceId" | "name" | "orgId">
|
||||||
ResourcePolicy,
|
|
||||||
"resourcePolicyId" | "niceId" | "name" | "orgId" | "isDefault"
|
|
||||||
>
|
|
||||||
>;
|
>;
|
||||||
}>;
|
}>;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
CreditCard,
|
CreditCard,
|
||||||
Fingerprint,
|
Fingerprint,
|
||||||
Globe,
|
Globe,
|
||||||
|
GlobeIcon,
|
||||||
GlobeLock,
|
GlobeLock,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
Laptop,
|
Laptop,
|
||||||
@@ -63,18 +64,7 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
title: "sidebarClientResources",
|
title: "sidebarClientResources",
|
||||||
href: "/{orgId}/settings/resources/client",
|
href: "/{orgId}/settings/resources/client",
|
||||||
icon: <GlobeLock className="size-4 flex-none" />
|
icon: <GlobeLock className="size-4 flex-none" />
|
||||||
},
|
}
|
||||||
...(build !== "oss"
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
title: "sidebarResourcePolicies",
|
|
||||||
href: "/{orgId}/settings/resources/policies",
|
|
||||||
icon: (
|
|
||||||
<ShieldIcon className="size-4 flex-none" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -133,6 +123,24 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
href: "/{orgId}/settings/access/roles",
|
href: "/{orgId}/settings/access/roles",
|
||||||
icon: <Users className="size-4 flex-none" />
|
icon: <Users className="size-4 flex-none" />
|
||||||
},
|
},
|
||||||
|
...(build !== "oss"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
title: "sidebarPolicies",
|
||||||
|
|
||||||
|
icon: <ShieldIcon className="size-4 flex-none" />,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "sidebarResourcePolicies",
|
||||||
|
href: "/{orgId}/settings/policies/resources",
|
||||||
|
icon: (
|
||||||
|
<GlobeIcon className="size-4 flex-none" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
// PaidFeaturesAlert
|
// PaidFeaturesAlert
|
||||||
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
||||||
build === "saas" ||
|
build === "saas" ||
|
||||||
|
|||||||
@@ -72,24 +72,7 @@ export function ResourcePoliciesTable({
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
friendlyName: t("name"),
|
friendlyName: t("name"),
|
||||||
header: () => <span className="p-3">{t("name")}</span>,
|
header: () => <span className="p-3">{t("name")}</span>,
|
||||||
cell({ row }) {
|
cell: ({ row }) => <span>{row.original.name}</span>
|
||||||
const r = row.original;
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span>{r.name}</span>
|
|
||||||
{r.isDefault && (
|
|
||||||
<>
|
|
||||||
<Badge
|
|
||||||
variant="outlinePrimary"
|
|
||||||
className="flex items-center gap-1"
|
|
||||||
>
|
|
||||||
{t("resourcePoliciesDefaultBadgeText")}
|
|
||||||
</Badge>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "niceId",
|
id: "niceId",
|
||||||
@@ -183,7 +166,7 @@ export function ResourcePoliciesTable({
|
|||||||
onAdd={() =>
|
onAdd={() =>
|
||||||
startNavigation(() =>
|
startNavigation(() =>
|
||||||
router.push(
|
router.push(
|
||||||
`/${orgId}/settings/resources/policies/create`
|
`/${orgId}/settings/policies/resources/create`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user