mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-19 23:35:26 +00:00
Add policies to blueprints
This commit is contained in:
@@ -16,7 +16,15 @@ import {
|
|||||||
Transaction,
|
Transaction,
|
||||||
userOrgs,
|
userOrgs,
|
||||||
userResources,
|
userResources,
|
||||||
users
|
users,
|
||||||
|
resourcePolicies,
|
||||||
|
resourcePolicyPassword,
|
||||||
|
resourcePolicyPincode,
|
||||||
|
resourcePolicyHeaderAuth,
|
||||||
|
resourcePolicyRules,
|
||||||
|
resourcePolicyWhiteList,
|
||||||
|
rolePolicies,
|
||||||
|
userPolicies
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { resources, targets, sites } from "@server/db";
|
import { resources, targets, sites } from "@server/db";
|
||||||
import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm";
|
import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm";
|
||||||
@@ -30,6 +38,7 @@ import logger from "@server/logger";
|
|||||||
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
||||||
import { pickPort } from "@server/routers/target/helpers";
|
import { pickPort } from "@server/routers/target/helpers";
|
||||||
import { resourcePassword } from "@server/db";
|
import { resourcePassword } from "@server/db";
|
||||||
|
import { getUniqueResourcePolicyName } from "@server/db/names";
|
||||||
import { hashPassword } from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
||||||
import { isValidRegionId } from "@server/db/regions";
|
import { isValidRegionId } from "@server/db/regions";
|
||||||
@@ -242,6 +251,39 @@ export async function updateProxyResources(
|
|||||||
resourceData.maintenance = undefined;
|
resourceData.maintenance = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look up the admin role (needed for inline policy creation)
|
||||||
|
const [adminRole] = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!adminRole) {
|
||||||
|
throw new Error(`Admin role not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resourceData.policy) {
|
||||||
|
// SHARED POLICY MODE: look up shared policy by niceId
|
||||||
|
const [sharedPolicy] = await trx
|
||||||
|
.select()
|
||||||
|
.from(resourcePolicies)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(
|
||||||
|
resourcePolicies.niceId,
|
||||||
|
resourceData.policy
|
||||||
|
),
|
||||||
|
eq(resourcePolicies.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!sharedPolicy) {
|
||||||
|
throw new Error(
|
||||||
|
`Shared policy not found: ${resourceData.policy} in org ${orgId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
[resource] = await trx
|
[resource] = await trx
|
||||||
.update(resources)
|
.update(resources)
|
||||||
.set({
|
.set({
|
||||||
@@ -249,7 +291,9 @@ export async function updateProxyResources(
|
|||||||
protocol: protocol || "tcp",
|
protocol: protocol || "tcp",
|
||||||
http: http,
|
http: http,
|
||||||
proxyPort: http ? null : resourceData["proxy-port"],
|
proxyPort: http ? null : resourceData["proxy-port"],
|
||||||
fullDomain: http ? resourceData["full-domain"] : null,
|
fullDomain: http
|
||||||
|
? resourceData["full-domain"]
|
||||||
|
: null,
|
||||||
subdomain: domain ? domain.subdomain : null,
|
subdomain: domain ? domain.subdomain : null,
|
||||||
domainId: domain ? domain.domainId : null,
|
domainId: domain ? domain.domainId : null,
|
||||||
wildcard: domain ? domain.wildcard : false,
|
wildcard: domain ? domain.wildcard : false,
|
||||||
@@ -259,28 +303,37 @@ export async function updateProxyResources(
|
|||||||
resourceData.auth?.["auto-login-idp"] || null,
|
resourceData.auth?.["auto-login-idp"] || null,
|
||||||
ssl: resourceSsl,
|
ssl: resourceSsl,
|
||||||
setHostHeader: resourceData["host-header"] || null,
|
setHostHeader: resourceData["host-header"] || null,
|
||||||
tlsServerName: resourceData["tls-server-name"] || null,
|
tlsServerName:
|
||||||
|
resourceData["tls-server-name"] || null,
|
||||||
emailWhitelistEnabled: resourceData.auth?.[
|
emailWhitelistEnabled: resourceData.auth?.[
|
||||||
"whitelist-users"
|
"whitelist-users"
|
||||||
]
|
]
|
||||||
? resourceData.auth["whitelist-users"].length > 0
|
? resourceData.auth["whitelist-users"].length >
|
||||||
|
0
|
||||||
: false,
|
: false,
|
||||||
headers: headers || null,
|
headers: headers || null,
|
||||||
applyRules:
|
applyRules:
|
||||||
resourceData.rules && resourceData.rules.length > 0,
|
resourceData.rules &&
|
||||||
|
resourceData.rules.length > 0,
|
||||||
maintenanceModeEnabled:
|
maintenanceModeEnabled:
|
||||||
resourceData.maintenance?.enabled,
|
resourceData.maintenance?.enabled,
|
||||||
maintenanceModeType: resourceData.maintenance?.type,
|
maintenanceModeType: resourceData.maintenance?.type,
|
||||||
maintenanceTitle: resourceData.maintenance?.title,
|
maintenanceTitle: resourceData.maintenance?.title,
|
||||||
maintenanceMessage: resourceData.maintenance?.message,
|
maintenanceMessage:
|
||||||
|
resourceData.maintenance?.message,
|
||||||
maintenanceEstimatedTime:
|
maintenanceEstimatedTime:
|
||||||
resourceData.maintenance?.["estimated-time"]
|
resourceData.maintenance?.["estimated-time"],
|
||||||
|
resourcePolicyId: sharedPolicy.resourcePolicyId
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(resources.resourceId, existingResource.resourceId)
|
eq(
|
||||||
|
resources.resourceId,
|
||||||
|
existingResource.resourceId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
// Update OLD resource-level auth tables
|
||||||
await trx
|
await trx
|
||||||
.delete(resourcePassword)
|
.delete(resourcePassword)
|
||||||
.where(
|
.where(
|
||||||
@@ -293,7 +346,6 @@ export async function updateProxyResources(
|
|||||||
const passwordHash = await hashPassword(
|
const passwordHash = await hashPassword(
|
||||||
resourceData.auth.password
|
resourceData.auth.password
|
||||||
);
|
);
|
||||||
|
|
||||||
await trx.insert(resourcePassword).values({
|
await trx.insert(resourcePassword).values({
|
||||||
resourceId: existingResource.resourceId,
|
resourceId: existingResource.resourceId,
|
||||||
passwordHash
|
passwordHash
|
||||||
@@ -312,7 +364,6 @@ export async function updateProxyResources(
|
|||||||
const pincodeHash = await hashPassword(
|
const pincodeHash = await hashPassword(
|
||||||
resourceData.auth.pincode.toString()
|
resourceData.auth.pincode.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
await trx.insert(resourcePincode).values({
|
await trx.insert(resourcePincode).values({
|
||||||
resourceId: existingResource.resourceId,
|
resourceId: existingResource.resourceId,
|
||||||
pincodeHash,
|
pincodeHash,
|
||||||
@@ -328,7 +379,6 @@ export async function updateProxyResources(
|
|||||||
existingResource.resourceId
|
existingResource.resourceId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await trx
|
await trx
|
||||||
.delete(resourceHeaderAuthExtendedCompatibility)
|
.delete(resourceHeaderAuthExtendedCompatibility)
|
||||||
.where(
|
.where(
|
||||||
@@ -337,14 +387,13 @@ export async function updateProxyResources(
|
|||||||
existingResource.resourceId
|
existingResource.resourceId
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resourceData.auth?.["basic-auth"]) {
|
if (resourceData.auth?.["basic-auth"]) {
|
||||||
const headerAuthUser =
|
const headerAuthUser =
|
||||||
resourceData.auth?.["basic-auth"]?.user;
|
resourceData.auth["basic-auth"]?.user;
|
||||||
const headerAuthPassword =
|
const headerAuthPassword =
|
||||||
resourceData.auth?.["basic-auth"]?.password;
|
resourceData.auth["basic-auth"]?.password;
|
||||||
const headerAuthExtendedCompatibility =
|
const headerAuthExtendedCompatibility =
|
||||||
resourceData.auth?.["basic-auth"]
|
resourceData.auth["basic-auth"]
|
||||||
?.extendedCompatibility;
|
?.extendedCompatibility;
|
||||||
if (
|
if (
|
||||||
headerAuthUser &&
|
headerAuthUser &&
|
||||||
@@ -362,7 +411,9 @@ export async function updateProxyResources(
|
|||||||
headerAuthHash
|
headerAuthHash
|
||||||
}),
|
}),
|
||||||
trx
|
trx
|
||||||
.insert(resourceHeaderAuthExtendedCompatibility)
|
.insert(
|
||||||
|
resourceHeaderAuthExtendedCompatibility
|
||||||
|
)
|
||||||
.values({
|
.values({
|
||||||
resourceId: existingResource.resourceId,
|
resourceId: existingResource.resourceId,
|
||||||
extendedCompatibilityIsActivated:
|
extendedCompatibilityIsActivated:
|
||||||
@@ -373,35 +424,87 @@ export async function updateProxyResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.auth?.["sso-roles"]) {
|
if (resourceData.auth?.["sso-roles"]) {
|
||||||
const ssoRoles = resourceData.auth?.["sso-roles"];
|
|
||||||
await syncRoleResources(
|
await syncRoleResources(
|
||||||
existingResource.resourceId,
|
existingResource.resourceId,
|
||||||
ssoRoles,
|
resourceData.auth["sso-roles"],
|
||||||
orgId,
|
orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.auth?.["sso-users"]) {
|
if (resourceData.auth?.["sso-users"]) {
|
||||||
const ssoUsers = resourceData.auth?.["sso-users"];
|
|
||||||
await syncUserResources(
|
await syncUserResources(
|
||||||
existingResource.resourceId,
|
existingResource.resourceId,
|
||||||
ssoUsers,
|
resourceData.auth["sso-users"],
|
||||||
orgId,
|
orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.auth?.["whitelist-users"]) {
|
if (resourceData.auth?.["whitelist-users"]) {
|
||||||
const whitelistUsers =
|
|
||||||
resourceData.auth?.["whitelist-users"];
|
|
||||||
await syncWhitelistUsers(
|
await syncWhitelistUsers(
|
||||||
existingResource.resourceId,
|
existingResource.resourceId,
|
||||||
whitelistUsers,
|
resourceData.auth["whitelist-users"],
|
||||||
orgId,
|
orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// INLINE POLICY MODE: ensure inline policy exists
|
||||||
|
const inlinePolicyId = await ensureInlinePolicy(
|
||||||
|
existingResource.defaultResourcePolicyId,
|
||||||
|
orgId,
|
||||||
|
resourceNiceId,
|
||||||
|
adminRole.roleId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
|
||||||
|
[resource] = await trx
|
||||||
|
.update(resources)
|
||||||
|
.set({
|
||||||
|
name: resourceData.name || "Unnamed Resource",
|
||||||
|
protocol: protocol || "tcp",
|
||||||
|
http: http,
|
||||||
|
proxyPort: http ? null : resourceData["proxy-port"],
|
||||||
|
fullDomain: http
|
||||||
|
? resourceData["full-domain"]
|
||||||
|
: null,
|
||||||
|
subdomain: domain ? domain.subdomain : null,
|
||||||
|
domainId: domain ? domain.domainId : null,
|
||||||
|
wildcard: domain ? domain.wildcard : false,
|
||||||
|
enabled: resourceEnabled,
|
||||||
|
ssl: resourceSsl,
|
||||||
|
setHostHeader: resourceData["host-header"] || null,
|
||||||
|
tlsServerName:
|
||||||
|
resourceData["tls-server-name"] || null,
|
||||||
|
headers: headers || null,
|
||||||
|
maintenanceModeEnabled:
|
||||||
|
resourceData.maintenance?.enabled,
|
||||||
|
maintenanceModeType: resourceData.maintenance?.type,
|
||||||
|
maintenanceTitle: resourceData.maintenance?.title,
|
||||||
|
maintenanceMessage:
|
||||||
|
resourceData.maintenance?.message,
|
||||||
|
maintenanceEstimatedTime:
|
||||||
|
resourceData.maintenance?.["estimated-time"],
|
||||||
|
resourcePolicyId: null,
|
||||||
|
defaultResourcePolicyId: inlinePolicyId
|
||||||
|
})
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
resources.resourceId,
|
||||||
|
existingResource.resourceId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
// Update inline policy auth fields and policy-level tables
|
||||||
|
await syncInlinePolicyAuth(
|
||||||
|
inlinePolicyId,
|
||||||
|
orgId,
|
||||||
|
resourceData,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingResourceTargets = await trx
|
const existingResourceTargets = await trx
|
||||||
@@ -618,21 +721,28 @@ export async function updateProxyResources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resourceData.policy) {
|
||||||
|
// SHARED POLICY MODE: sync rules into old resourceRules table
|
||||||
const existingRules = await trx
|
const existingRules = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(resourceRules)
|
.from(resourceRules)
|
||||||
.where(
|
.where(
|
||||||
eq(resourceRules.resourceId, existingResource.resourceId)
|
eq(
|
||||||
|
resourceRules.resourceId,
|
||||||
|
existingResource.resourceId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.orderBy(resourceRules.priority);
|
.orderBy(resourceRules.priority);
|
||||||
|
|
||||||
// Sync rules
|
// Sync rules
|
||||||
for (const [index, rule] of resourceData.rules?.entries() || []) {
|
for (const [index, rule] of resourceData.rules?.entries() ||
|
||||||
|
[]) {
|
||||||
const intendedPriority = rule.priority ?? index + 1;
|
const intendedPriority = rule.priority ?? index + 1;
|
||||||
const existingRule = existingRules[index];
|
const existingRule = existingRules[index];
|
||||||
if (existingRule) {
|
if (existingRule) {
|
||||||
if (
|
if (
|
||||||
existingRule.action !== getRuleAction(rule.action) ||
|
existingRule.action !==
|
||||||
|
getRuleAction(rule.action) ||
|
||||||
existingRule.match !== rule.match.toUpperCase() ||
|
existingRule.match !== rule.match.toUpperCase() ||
|
||||||
existingRule.value !==
|
existingRule.value !==
|
||||||
getRuleValue(
|
getRuleValue(
|
||||||
@@ -654,7 +764,10 @@ export async function updateProxyResources(
|
|||||||
priority: intendedPriority
|
priority: intendedPriority
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
eq(resourceRules.ruleId, existingRule.ruleId)
|
eq(
|
||||||
|
resourceRules.ruleId,
|
||||||
|
existingRule.ruleId
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -682,6 +795,15 @@ export async function updateProxyResources(
|
|||||||
.where(eq(resourceRules.ruleId, rule.ruleId));
|
.where(eq(resourceRules.ruleId, rule.ruleId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// INLINE POLICY MODE: sync rules into policy-level table
|
||||||
|
const inlinePolicyId = resource!.defaultResourcePolicyId!;
|
||||||
|
await syncInlinePolicyRules(
|
||||||
|
inlinePolicyId,
|
||||||
|
resourceData.rules || [],
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug(`Updated resource ${existingResource.resourceId}`);
|
logger.debug(`Updated resource ${existingResource.resourceId}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -704,6 +826,58 @@ export async function updateProxyResources(
|
|||||||
resourceData.maintenance = undefined;
|
resourceData.maintenance = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look up admin role (needed for inline policy and roleResources)
|
||||||
|
const [adminRole] = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!adminRole) {
|
||||||
|
throw new Error(`Admin role not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always create an inline policy for the resource
|
||||||
|
const policyNiceId = await getUniqueResourcePolicyName(orgId);
|
||||||
|
const [inlinePolicy] = await trx
|
||||||
|
.insert(resourcePolicies)
|
||||||
|
.values({
|
||||||
|
niceId: policyNiceId,
|
||||||
|
orgId,
|
||||||
|
name: `default policy for ${resourceNiceId}`,
|
||||||
|
sso: true,
|
||||||
|
scope: "resource"
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
// Make the inline policy visible to the admin role
|
||||||
|
await trx.insert(rolePolicies).values({
|
||||||
|
roleId: adminRole.roleId,
|
||||||
|
resourcePolicyId: inlinePolicy.resourcePolicyId
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine the active shared policy (if provided)
|
||||||
|
let sharedPolicyId: number | null = null;
|
||||||
|
if (resourceData.policy) {
|
||||||
|
const [sharedPolicy] = await trx
|
||||||
|
.select()
|
||||||
|
.from(resourcePolicies)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(resourcePolicies.niceId, resourceData.policy),
|
||||||
|
eq(resourcePolicies.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!sharedPolicy) {
|
||||||
|
throw new Error(
|
||||||
|
`Shared policy not found: ${resourceData.policy} in org ${orgId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sharedPolicyId = sharedPolicy.resourcePolicyId;
|
||||||
|
}
|
||||||
|
|
||||||
// Create new resource
|
// Create new resource
|
||||||
const [newResource] = await trx
|
const [newResource] = await trx
|
||||||
.insert(resources)
|
.insert(resources)
|
||||||
@@ -719,28 +893,51 @@ export async function updateProxyResources(
|
|||||||
domainId: domain ? domain.domainId : null,
|
domainId: domain ? domain.domainId : null,
|
||||||
wildcard: domain ? domain.wildcard : false,
|
wildcard: domain ? domain.wildcard : false,
|
||||||
enabled: resourceEnabled,
|
enabled: resourceEnabled,
|
||||||
sso: resourceData.auth?.["sso-enabled"] || false,
|
|
||||||
skipToIdpId: resourceData.auth?.["auto-login-idp"] || null,
|
|
||||||
setHostHeader: resourceData["host-header"] || null,
|
setHostHeader: resourceData["host-header"] || null,
|
||||||
tlsServerName: resourceData["tls-server-name"] || null,
|
tlsServerName: resourceData["tls-server-name"] || null,
|
||||||
ssl: resourceSsl,
|
ssl: resourceSsl,
|
||||||
headers: headers || null,
|
headers: headers || null,
|
||||||
applyRules:
|
|
||||||
resourceData.rules && resourceData.rules.length > 0,
|
|
||||||
maintenanceModeEnabled: resourceData.maintenance?.enabled,
|
maintenanceModeEnabled: resourceData.maintenance?.enabled,
|
||||||
maintenanceModeType: resourceData.maintenance?.type,
|
maintenanceModeType: resourceData.maintenance?.type,
|
||||||
maintenanceTitle: resourceData.maintenance?.title,
|
maintenanceTitle: resourceData.maintenance?.title,
|
||||||
maintenanceMessage: resourceData.maintenance?.message,
|
maintenanceMessage: resourceData.maintenance?.message,
|
||||||
maintenanceEstimatedTime:
|
maintenanceEstimatedTime:
|
||||||
resourceData.maintenance?.["estimated-time"]
|
resourceData.maintenance?.["estimated-time"],
|
||||||
|
defaultResourcePolicyId: inlinePolicy.resourcePolicyId,
|
||||||
|
resourcePolicyId: sharedPolicyId,
|
||||||
|
// Only set these resource-level fields when using a shared policy
|
||||||
|
...(sharedPolicyId
|
||||||
|
? {
|
||||||
|
sso: resourceData.auth?.["sso-enabled"] || false,
|
||||||
|
skipToIdpId:
|
||||||
|
resourceData.auth?.["auto-login-idp"] || null,
|
||||||
|
emailWhitelistEnabled: resourceData.auth?.[
|
||||||
|
"whitelist-users"
|
||||||
|
]
|
||||||
|
? resourceData.auth["whitelist-users"]
|
||||||
|
.length > 0
|
||||||
|
: false,
|
||||||
|
applyRules:
|
||||||
|
resourceData.rules &&
|
||||||
|
resourceData.rules.length > 0
|
||||||
|
}
|
||||||
|
: {})
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
resource = newResource;
|
||||||
|
|
||||||
|
await trx.insert(roleResources).values({
|
||||||
|
roleId: adminRole.roleId,
|
||||||
|
resourceId: newResource.resourceId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharedPolicyId) {
|
||||||
|
// SHARED POLICY MODE: update OLD resource-level auth tables
|
||||||
if (resourceData.auth?.password) {
|
if (resourceData.auth?.password) {
|
||||||
const passwordHash = await hashPassword(
|
const passwordHash = await hashPassword(
|
||||||
resourceData.auth.password
|
resourceData.auth.password
|
||||||
);
|
);
|
||||||
|
|
||||||
await trx.insert(resourcePassword).values({
|
await trx.insert(resourcePassword).values({
|
||||||
resourceId: newResource.resourceId,
|
resourceId: newResource.resourceId,
|
||||||
passwordHash
|
passwordHash
|
||||||
@@ -751,7 +948,6 @@ export async function updateProxyResources(
|
|||||||
const pincodeHash = await hashPassword(
|
const pincodeHash = await hashPassword(
|
||||||
resourceData.auth.pincode.toString()
|
resourceData.auth.pincode.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
await trx.insert(resourcePincode).values({
|
await trx.insert(resourcePincode).values({
|
||||||
resourceId: newResource.resourceId,
|
resourceId: newResource.resourceId,
|
||||||
pincodeHash,
|
pincodeHash,
|
||||||
@@ -760,12 +956,12 @@ export async function updateProxyResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.auth?.["basic-auth"]) {
|
if (resourceData.auth?.["basic-auth"]) {
|
||||||
const headerAuthUser = resourceData.auth?.["basic-auth"]?.user;
|
const headerAuthUser =
|
||||||
|
resourceData.auth["basic-auth"]?.user;
|
||||||
const headerAuthPassword =
|
const headerAuthPassword =
|
||||||
resourceData.auth?.["basic-auth"]?.password;
|
resourceData.auth["basic-auth"]?.password;
|
||||||
const headerAuthExtendedCompatibility =
|
const headerAuthExtendedCompatibility =
|
||||||
resourceData.auth?.["basic-auth"]?.extendedCompatibility;
|
resourceData.auth["basic-auth"]?.extendedCompatibility;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
headerAuthUser &&
|
headerAuthUser &&
|
||||||
headerAuthPassword &&
|
headerAuthPassword &&
|
||||||
@@ -776,7 +972,6 @@ export async function updateProxyResources(
|
|||||||
`${headerAuthUser}:${headerAuthPassword}`
|
`${headerAuthUser}:${headerAuthPassword}`
|
||||||
).toString("base64")
|
).toString("base64")
|
||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
trx.insert(resourceHeaderAuth).values({
|
trx.insert(resourceHeaderAuth).values({
|
||||||
resourceId: newResource.resourceId,
|
resourceId: newResource.resourceId,
|
||||||
@@ -793,53 +988,65 @@ export async function updateProxyResources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource = newResource;
|
|
||||||
|
|
||||||
const [adminRole] = await trx
|
|
||||||
.select()
|
|
||||||
.from(roles)
|
|
||||||
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!adminRole) {
|
|
||||||
throw new Error(`Admin role not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await trx.insert(roleResources).values({
|
|
||||||
roleId: adminRole.roleId,
|
|
||||||
resourceId: newResource.resourceId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resourceData.auth?.["sso-roles"]) {
|
if (resourceData.auth?.["sso-roles"]) {
|
||||||
const ssoRoles = resourceData.auth?.["sso-roles"];
|
|
||||||
await syncRoleResources(
|
await syncRoleResources(
|
||||||
newResource.resourceId,
|
newResource.resourceId,
|
||||||
ssoRoles,
|
resourceData.auth["sso-roles"],
|
||||||
orgId,
|
orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.auth?.["sso-users"]) {
|
if (resourceData.auth?.["sso-users"]) {
|
||||||
const ssoUsers = resourceData.auth?.["sso-users"];
|
|
||||||
await syncUserResources(
|
await syncUserResources(
|
||||||
newResource.resourceId,
|
newResource.resourceId,
|
||||||
ssoUsers,
|
resourceData.auth["sso-users"],
|
||||||
orgId,
|
orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceData.auth?.["whitelist-users"]) {
|
if (resourceData.auth?.["whitelist-users"]) {
|
||||||
const whitelistUsers = resourceData.auth?.["whitelist-users"];
|
|
||||||
await syncWhitelistUsers(
|
await syncWhitelistUsers(
|
||||||
newResource.resourceId,
|
newResource.resourceId,
|
||||||
whitelistUsers,
|
resourceData.auth["whitelist-users"],
|
||||||
orgId,
|
orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rules into OLD resourceRules table
|
||||||
|
for (const [index, rule] of resourceData.rules?.entries() ||
|
||||||
|
[]) {
|
||||||
|
validateRule(rule);
|
||||||
|
await trx.insert(resourceRules).values({
|
||||||
|
resourceId: newResource.resourceId,
|
||||||
|
action: getRuleAction(rule.action),
|
||||||
|
match: rule.match.toUpperCase(),
|
||||||
|
value: getRuleValue(
|
||||||
|
rule.match.toUpperCase(),
|
||||||
|
rule.value
|
||||||
|
),
|
||||||
|
priority: rule.priority ?? index + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// INLINE POLICY MODE: update the inline policy auth fields
|
||||||
|
await syncInlinePolicyAuth(
|
||||||
|
inlinePolicy.resourcePolicyId,
|
||||||
|
orgId,
|
||||||
|
resourceData,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
|
||||||
|
// Rules into policy-level table
|
||||||
|
await syncInlinePolicyRules(
|
||||||
|
inlinePolicy.resourcePolicyId,
|
||||||
|
resourceData.rules || [],
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Create new targets
|
// Create new targets
|
||||||
for (const targetData of resourceData.targets) {
|
for (const targetData of resourceData.targets) {
|
||||||
if (!targetData) {
|
if (!targetData) {
|
||||||
@@ -849,17 +1056,6 @@ export async function updateProxyResources(
|
|||||||
await createTarget(newResource.resourceId, targetData);
|
await createTarget(newResource.resourceId, targetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [index, rule] of resourceData.rules?.entries() || []) {
|
|
||||||
validateRule(rule);
|
|
||||||
await trx.insert(resourceRules).values({
|
|
||||||
resourceId: newResource.resourceId,
|
|
||||||
action: getRuleAction(rule.action),
|
|
||||||
match: rule.match.toUpperCase(),
|
|
||||||
value: getRuleValue(rule.match.toUpperCase(), rule.value),
|
|
||||||
priority: rule.priority ?? index + 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`Created resource ${newResource.resourceId}`);
|
logger.debug(`Created resource ${newResource.resourceId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1097,6 +1293,399 @@ async function syncWhitelistUsers(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an inline resourcePolicy if one doesn't exist yet, and returns its ID.
|
||||||
|
* Makes the policy visible to the admin role via rolePolicies.
|
||||||
|
*/
|
||||||
|
async function ensureInlinePolicy(
|
||||||
|
existingPolicyId: number | null | undefined,
|
||||||
|
orgId: string,
|
||||||
|
resourceNiceId: string,
|
||||||
|
adminRoleId: number,
|
||||||
|
trx: Transaction
|
||||||
|
): Promise<number> {
|
||||||
|
if (existingPolicyId) {
|
||||||
|
return existingPolicyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const policyNiceId = await getUniqueResourcePolicyName(orgId);
|
||||||
|
const [newPolicy] = await trx
|
||||||
|
.insert(resourcePolicies)
|
||||||
|
.values({
|
||||||
|
niceId: policyNiceId,
|
||||||
|
orgId,
|
||||||
|
name: `default policy for ${resourceNiceId}`,
|
||||||
|
sso: true,
|
||||||
|
scope: "resource"
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
await trx.insert(rolePolicies).values({
|
||||||
|
roleId: adminRoleId,
|
||||||
|
resourcePolicyId: newPolicy.resourcePolicyId
|
||||||
|
});
|
||||||
|
|
||||||
|
return newPolicy.resourcePolicyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the inline policy's auth-related fields and policy-level tables
|
||||||
|
* (used when no shared policy is specified in the blueprint).
|
||||||
|
*/
|
||||||
|
async function syncInlinePolicyAuth(
|
||||||
|
policyId: number,
|
||||||
|
orgId: string,
|
||||||
|
resourceData: any,
|
||||||
|
trx: Transaction
|
||||||
|
) {
|
||||||
|
// Update policy-level SSO/whitelist/applyRules fields
|
||||||
|
await trx
|
||||||
|
.update(resourcePolicies)
|
||||||
|
.set({
|
||||||
|
sso: resourceData.auth?.["sso-enabled"] ?? false,
|
||||||
|
idpId: resourceData.auth?.["auto-login-idp"] || null,
|
||||||
|
emailWhitelistEnabled: resourceData.auth?.["whitelist-users"]
|
||||||
|
? resourceData.auth["whitelist-users"].length > 0
|
||||||
|
: false,
|
||||||
|
applyRules: !!(resourceData.rules && resourceData.rules.length > 0)
|
||||||
|
})
|
||||||
|
.where(eq(resourcePolicies.resourcePolicyId, policyId));
|
||||||
|
|
||||||
|
// Password
|
||||||
|
await trx
|
||||||
|
.delete(resourcePolicyPassword)
|
||||||
|
.where(eq(resourcePolicyPassword.resourcePolicyId, policyId));
|
||||||
|
if (resourceData.auth?.password) {
|
||||||
|
const passwordHash = await hashPassword(resourceData.auth.password);
|
||||||
|
await trx.insert(resourcePolicyPassword).values({
|
||||||
|
resourcePolicyId: policyId,
|
||||||
|
passwordHash
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pincode
|
||||||
|
await trx
|
||||||
|
.delete(resourcePolicyPincode)
|
||||||
|
.where(eq(resourcePolicyPincode.resourcePolicyId, policyId));
|
||||||
|
if (resourceData.auth?.pincode) {
|
||||||
|
const pincodeHash = await hashPassword(
|
||||||
|
resourceData.auth.pincode.toString()
|
||||||
|
);
|
||||||
|
await trx.insert(resourcePolicyPincode).values({
|
||||||
|
resourcePolicyId: policyId,
|
||||||
|
pincodeHash,
|
||||||
|
digitLength: 6
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header auth
|
||||||
|
await trx
|
||||||
|
.delete(resourcePolicyHeaderAuth)
|
||||||
|
.where(eq(resourcePolicyHeaderAuth.resourcePolicyId, policyId));
|
||||||
|
if (resourceData.auth?.["basic-auth"]) {
|
||||||
|
const headerAuthUser = resourceData.auth["basic-auth"]?.user;
|
||||||
|
const headerAuthPassword = resourceData.auth["basic-auth"]?.password;
|
||||||
|
const headerAuthExtendedCompatibility =
|
||||||
|
resourceData.auth["basic-auth"]?.extendedCompatibility;
|
||||||
|
if (
|
||||||
|
headerAuthUser &&
|
||||||
|
headerAuthPassword &&
|
||||||
|
headerAuthExtendedCompatibility !== null
|
||||||
|
) {
|
||||||
|
const headerAuthHash = await hashPassword(
|
||||||
|
Buffer.from(`${headerAuthUser}:${headerAuthPassword}`).toString(
|
||||||
|
"base64"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await trx.insert(resourcePolicyHeaderAuth).values({
|
||||||
|
resourcePolicyId: policyId,
|
||||||
|
headerAuthHash,
|
||||||
|
extendedCompatibility: headerAuthExtendedCompatibility
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSO roles → rolePolicies
|
||||||
|
if (resourceData.auth?.["sso-roles"] !== undefined) {
|
||||||
|
await syncRolePolicies(
|
||||||
|
policyId,
|
||||||
|
resourceData.auth["sso-roles"],
|
||||||
|
orgId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSO users → userPolicies
|
||||||
|
if (resourceData.auth?.["sso-users"] !== undefined) {
|
||||||
|
await syncUserPolicies(
|
||||||
|
policyId,
|
||||||
|
resourceData.auth["sso-users"],
|
||||||
|
orgId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whitelist → resourcePolicyWhiteList
|
||||||
|
if (resourceData.auth?.["whitelist-users"] !== undefined) {
|
||||||
|
await syncWhitelistPolicyUsers(
|
||||||
|
policyId,
|
||||||
|
resourceData.auth["whitelist-users"],
|
||||||
|
orgId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs rules into the resourcePolicyRules table (inline policy mode).
|
||||||
|
*/
|
||||||
|
async function syncInlinePolicyRules(
|
||||||
|
policyId: number,
|
||||||
|
rules: any[],
|
||||||
|
trx: Transaction
|
||||||
|
) {
|
||||||
|
const existingRules = await trx
|
||||||
|
.select()
|
||||||
|
.from(resourcePolicyRules)
|
||||||
|
.where(eq(resourcePolicyRules.resourcePolicyId, policyId))
|
||||||
|
.orderBy(resourcePolicyRules.priority);
|
||||||
|
|
||||||
|
for (const [index, rule] of rules.entries()) {
|
||||||
|
const intendedPriority = rule.priority ?? index + 1;
|
||||||
|
const existingRule = existingRules[index];
|
||||||
|
if (existingRule) {
|
||||||
|
if (
|
||||||
|
existingRule.action !== getRuleAction(rule.action) ||
|
||||||
|
existingRule.match !== rule.match.toUpperCase() ||
|
||||||
|
existingRule.value !==
|
||||||
|
getRuleValue(rule.match.toUpperCase(), rule.value) ||
|
||||||
|
existingRule.priority !== intendedPriority
|
||||||
|
) {
|
||||||
|
validateRule(rule);
|
||||||
|
await trx
|
||||||
|
.update(resourcePolicyRules)
|
||||||
|
.set({
|
||||||
|
action: getRuleAction(rule.action) as
|
||||||
|
| "ACCEPT"
|
||||||
|
| "DROP"
|
||||||
|
| "PASS",
|
||||||
|
match: rule.match.toUpperCase() as
|
||||||
|
| "CIDR"
|
||||||
|
| "IP"
|
||||||
|
| "PATH",
|
||||||
|
value: getRuleValue(
|
||||||
|
rule.match.toUpperCase(),
|
||||||
|
rule.value
|
||||||
|
),
|
||||||
|
priority: intendedPriority
|
||||||
|
})
|
||||||
|
.where(eq(resourcePolicyRules.ruleId, existingRule.ruleId));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validateRule(rule);
|
||||||
|
await trx.insert(resourcePolicyRules).values({
|
||||||
|
resourcePolicyId: policyId,
|
||||||
|
action: getRuleAction(rule.action) as
|
||||||
|
| "ACCEPT"
|
||||||
|
| "DROP"
|
||||||
|
| "PASS",
|
||||||
|
match: rule.match.toUpperCase() as "CIDR" | "IP" | "PATH",
|
||||||
|
value: getRuleValue(rule.match.toUpperCase(), rule.value),
|
||||||
|
priority: intendedPriority
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingRules.length > rules.length) {
|
||||||
|
const rulesToDelete = existingRules.slice(rules.length);
|
||||||
|
for (const rule of rulesToDelete) {
|
||||||
|
await trx
|
||||||
|
.delete(resourcePolicyRules)
|
||||||
|
.where(eq(resourcePolicyRules.ruleId, rule.ruleId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs SSO roles to the rolePolicies table (inline policy mode).
|
||||||
|
*/
|
||||||
|
async function syncRolePolicies(
|
||||||
|
policyId: number,
|
||||||
|
ssoRoles: string[],
|
||||||
|
orgId: string,
|
||||||
|
trx: Transaction
|
||||||
|
) {
|
||||||
|
const existingRolePoliciesList = await trx
|
||||||
|
.select()
|
||||||
|
.from(rolePolicies)
|
||||||
|
.where(eq(rolePolicies.resourcePolicyId, policyId));
|
||||||
|
|
||||||
|
for (const roleName of ssoRoles) {
|
||||||
|
const [role] = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(and(eq(roles.name, roleName), eq(roles.orgId, orgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
throw new Error(`Role not found: ${roleName} in org ${orgId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role.isAdmin) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingRolePolicy = existingRolePoliciesList.find(
|
||||||
|
(rp) => rp.roleId === role.roleId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!existingRolePolicy) {
|
||||||
|
await trx.insert(rolePolicies).values({
|
||||||
|
roleId: role.roleId,
|
||||||
|
resourcePolicyId: policyId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const existingRolePolicy of existingRolePoliciesList) {
|
||||||
|
const [role] = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(eq(roles.roleId, existingRolePolicy.roleId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (role?.isAdmin) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role && !ssoRoles.includes(role.name)) {
|
||||||
|
await trx
|
||||||
|
.delete(rolePolicies)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(rolePolicies.roleId, existingRolePolicy.roleId),
|
||||||
|
eq(rolePolicies.resourcePolicyId, policyId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs SSO users to the userPolicies table (inline policy mode).
|
||||||
|
*/
|
||||||
|
async function syncUserPolicies(
|
||||||
|
policyId: number,
|
||||||
|
ssoUsers: string[],
|
||||||
|
orgId: string,
|
||||||
|
trx: Transaction
|
||||||
|
) {
|
||||||
|
const existingUserPoliciesList = await trx
|
||||||
|
.select()
|
||||||
|
.from(userPolicies)
|
||||||
|
.where(eq(userPolicies.resourcePolicyId, policyId));
|
||||||
|
|
||||||
|
for (const username of ssoUsers) {
|
||||||
|
const [user] = await trx
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
or(eq(users.username, username), eq(users.email, username)),
|
||||||
|
eq(userOrgs.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error(`User not found: ${username} in org ${orgId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingUserPolicy = existingUserPoliciesList.find(
|
||||||
|
(up) => up.userId === user.user.userId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!existingUserPolicy) {
|
||||||
|
await trx.insert(userPolicies).values({
|
||||||
|
userId: user.user.userId,
|
||||||
|
resourcePolicyId: policyId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const existingUserPolicy of existingUserPoliciesList) {
|
||||||
|
const [user] = await trx
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(users.userId, existingUserPolicy.userId),
|
||||||
|
eq(userOrgs.orgId, orgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (
|
||||||
|
user &&
|
||||||
|
user.user.username &&
|
||||||
|
!ssoUsers.includes(user.user.username)
|
||||||
|
) {
|
||||||
|
await trx
|
||||||
|
.delete(userPolicies)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userPolicies.userId, existingUserPolicy.userId),
|
||||||
|
eq(userPolicies.resourcePolicyId, policyId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs whitelist emails to the resourcePolicyWhiteList table (inline policy mode).
|
||||||
|
*/
|
||||||
|
async function syncWhitelistPolicyUsers(
|
||||||
|
policyId: number,
|
||||||
|
whitelistUsers: string[],
|
||||||
|
orgId: string,
|
||||||
|
trx: Transaction
|
||||||
|
) {
|
||||||
|
const existingWhitelist = await trx
|
||||||
|
.select()
|
||||||
|
.from(resourcePolicyWhiteList)
|
||||||
|
.where(eq(resourcePolicyWhiteList.resourcePolicyId, policyId));
|
||||||
|
|
||||||
|
for (const email of whitelistUsers) {
|
||||||
|
const existingEntry = existingWhitelist.find((w) => w.email === email);
|
||||||
|
|
||||||
|
if (!existingEntry) {
|
||||||
|
await trx.insert(resourcePolicyWhiteList).values({
|
||||||
|
email,
|
||||||
|
resourcePolicyId: policyId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const existingEntry of existingWhitelist) {
|
||||||
|
if (!whitelistUsers.includes(existingEntry.email)) {
|
||||||
|
await trx
|
||||||
|
.delete(resourcePolicyWhiteList)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(
|
||||||
|
resourcePolicyWhiteList.whitelistId,
|
||||||
|
existingEntry.whitelistId
|
||||||
|
),
|
||||||
|
eq(resourcePolicyWhiteList.resourcePolicyId, policyId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function checkIfHealthcheckChanged(
|
function checkIfHealthcheckChanged(
|
||||||
existing: TargetHealthCheck | undefined,
|
existing: TargetHealthCheck | undefined,
|
||||||
incoming: TargetHealthCheck | undefined
|
incoming: TargetHealthCheck | undefined
|
||||||
|
|||||||
@@ -162,9 +162,10 @@ export const HeaderSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Schema for individual resource
|
// Schema for individual resource
|
||||||
export const ResourceSchema = z
|
export const PublicResourceSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
|
policy: z.string().optional(),
|
||||||
protocol: z.enum(["http", "tcp", "udp"]).optional(),
|
protocol: z.enum(["http", "tcp", "udp"]).optional(),
|
||||||
ssl: z.boolean().optional(),
|
ssl: z.boolean().optional(),
|
||||||
scheme: z.enum(["http", "https"]).optional(),
|
scheme: z.enum(["http", "https"]).optional(),
|
||||||
@@ -340,7 +341,8 @@ export const ResourceSchema = z
|
|||||||
if (parts.includes("*", 1)) return false; // no further wildcards
|
if (parts.includes("*", 1)) return false; // no further wildcards
|
||||||
if (parts.length < 3) return false; // need at least *.label.tld
|
if (parts.length < 3) return false; // need at least *.label.tld
|
||||||
|
|
||||||
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/;
|
const labelRegex =
|
||||||
|
/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/;
|
||||||
return parts.slice(1).every((label) => labelRegex.test(label));
|
return parts.slice(1).every((label) => labelRegex.test(label));
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -354,7 +356,7 @@ export function isTargetsOnlyResource(resource: any): boolean {
|
|||||||
return Object.keys(resource).length === 1 && resource.targets;
|
return Object.keys(resource).length === 1 && resource.targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClientResourceSchema = z
|
export const PrivateResourceSchema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
mode: z.enum(["host", "cidr", "http"]),
|
mode: z.enum(["host", "cidr", "http"]),
|
||||||
@@ -435,19 +437,19 @@ export const ClientResourceSchema = z
|
|||||||
export const ConfigSchema = z
|
export const ConfigSchema = z
|
||||||
.object({
|
.object({
|
||||||
"proxy-resources": z
|
"proxy-resources": z
|
||||||
.record(z.string(), ResourceSchema)
|
.record(z.string(), PublicResourceSchema)
|
||||||
.optional()
|
.optional()
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
"public-resources": z
|
"public-resources": z
|
||||||
.record(z.string(), ResourceSchema)
|
.record(z.string(), PublicResourceSchema)
|
||||||
.optional()
|
.optional()
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
"client-resources": z
|
"client-resources": z
|
||||||
.record(z.string(), ClientResourceSchema)
|
.record(z.string(), PrivateResourceSchema)
|
||||||
.optional()
|
.optional()
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
"private-resources": z
|
"private-resources": z
|
||||||
.record(z.string(), ClientResourceSchema)
|
.record(z.string(), PrivateResourceSchema)
|
||||||
.optional()
|
.optional()
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
sites: z.record(z.string(), SiteSchema).optional().prefault({})
|
sites: z.record(z.string(), SiteSchema).optional().prefault({})
|
||||||
@@ -472,10 +474,13 @@ export const ConfigSchema = z
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data as {
|
return data as {
|
||||||
"proxy-resources": Record<string, z.infer<typeof ResourceSchema>>;
|
"proxy-resources": Record<
|
||||||
|
string,
|
||||||
|
z.infer<typeof PublicResourceSchema>
|
||||||
|
>;
|
||||||
"client-resources": Record<
|
"client-resources": Record<
|
||||||
string,
|
string,
|
||||||
z.infer<typeof ClientResourceSchema>
|
z.infer<typeof PrivateResourceSchema>
|
||||||
>;
|
>;
|
||||||
sites: Record<string, z.infer<typeof SiteSchema>>;
|
sites: Record<string, z.infer<typeof SiteSchema>>;
|
||||||
};
|
};
|
||||||
@@ -614,5 +619,5 @@ export const ConfigSchema = z
|
|||||||
// Type inference from the schema
|
// Type inference from the schema
|
||||||
export type Site = z.infer<typeof SiteSchema>;
|
export type Site = z.infer<typeof SiteSchema>;
|
||||||
export type Target = z.infer<typeof TargetSchema>;
|
export type Target = z.infer<typeof TargetSchema>;
|
||||||
export type Resource = z.infer<typeof ResourceSchema>;
|
export type Resource = z.infer<typeof PublicResourceSchema>;
|
||||||
export type Config = z.infer<typeof ConfigSchema>;
|
export type Config = z.infer<typeof ConfigSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user