Merge branch 'resource-policies' into dev

This commit is contained in:
Owen
2026-05-28 15:30:16 -07:00
81 changed files with 13114 additions and 1618 deletions

View File

@@ -1,15 +1,19 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, domainNamespaces, loginPage } from "@server/db";
import { build } from "@server/build";
import {
domains,
orgDomains,
db,
loginPage,
orgs,
Resource,
resources,
resourcePolicies,
roleResources,
rolePolicies,
roles,
userResources
userPolicies,
userResources,
domainNamespaces
} from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -20,9 +24,7 @@ import logger from "@server/logger";
import { subdomainSchema, wildcardSubdomainSchema } from "@server/lib/schemas";
import config from "@server/lib/config";
import { OpenAPITags, registry } from "@server/openApi";
import { build } from "@server/build";
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
import { getUniqueResourceName } from "@server/db/names";
import {
validateAndConstructDomain,
checkWildcardDomainConflict
@@ -30,6 +32,10 @@ import {
import { isSubscribed } from "#dynamic/lib/isSubscribed";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import {
getUniqueResourceName,
getUniqueResourcePolicyName
} from "@server/db/names";
const createResourceParamsSchema = z.strictObject({
orgId: z.string()
@@ -391,8 +397,46 @@ async function createHttpResource(
let resource: Resource | undefined;
const niceId = await getUniqueResourceName(orgId);
const policyNiceId = await getUniqueResourcePolicyName(orgId);
await db.transaction(async (trx) => {
const adminRole = await trx
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1);
if (adminRole.length === 0) {
return next(
createHttpError(HttpCode.NOT_FOUND, `Admin role not found`)
);
}
const [defaultPolicy] = await trx
.insert(resourcePolicies)
.values({
niceId: policyNiceId,
orgId,
name: `default policy for ${niceId}`,
sso: true,
scope: "resource"
})
.returning();
// make this policy visible by the admin role
await trx.insert(rolePolicies).values({
roleId: adminRole[0].roleId,
resourcePolicyId: defaultPolicy.resourcePolicyId
});
// make this policy visible by the current user
if (req.user && !req.userOrgRoleIds?.includes(adminRole[0].roleId)) {
await trx.insert(userPolicies).values({
userId: req.user?.userId!,
resourcePolicyId: defaultPolicy.resourcePolicyId
});
}
const newResource = await trx
.insert(resources)
.values({
@@ -410,22 +454,11 @@ async function createHttpResource(
stickySession: stickySession,
postAuthPath: postAuthPath,
wildcard,
health: "unknown"
health: "unknown",
defaultResourcePolicyId: defaultPolicy.resourcePolicyId
})
.returning();
const adminRole = await db
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1);
if (adminRole.length === 0) {
return next(
createHttpError(HttpCode.NOT_FOUND, `Admin role not found`)
);
}
await trx.insert(roleResources).values({
roleId: adminRole[0].roleId,
resourceId: newResource[0].resourceId
@@ -451,7 +484,7 @@ async function createHttpResource(
);
}
if (build != "oss") {
if (build !== "oss") {
await createCertificate(domainId, fullDomain, db);
}
@@ -502,21 +535,10 @@ async function createRawResource(
let resource: Resource | undefined;
const niceId = await getUniqueResourceName(orgId);
const policyNiceId = await getUniqueResourcePolicyName(orgId);
await db.transaction(async (trx) => {
const newResource = await trx
.insert(resources)
.values({
niceId,
orgId,
name,
proxyPort,
mode: resolvedMode.mode
// enableProxy
})
.returning();
const adminRole = await db
const adminRole = await trx
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
@@ -528,6 +550,43 @@ async function createRawResource(
);
}
const [defaultPolicy] = await trx
.insert(resourcePolicies)
.values({
niceId: policyNiceId,
orgId,
name: `default policy for ${niceId}`,
sso: true,
scope: "resource"
})
.returning();
// make this policy visible by the admin role
await trx.insert(rolePolicies).values({
roleId: adminRole[0].roleId,
resourcePolicyId: defaultPolicy.resourcePolicyId
});
// make this policy visible by the current user
if (req.user && !req.userOrgRoleIds?.includes(adminRole[0].roleId)) {
await trx.insert(userPolicies).values({
userId: req.user?.userId!,
resourcePolicyId: defaultPolicy.resourcePolicyId
});
}
const newResource = await trx
.insert(resources)
.values({
niceId,
orgId,
name,
mode: resolvedMode.mode,
proxyPort,
defaultResourcePolicyId: defaultPolicy.resourcePolicyId
})
.returning();
await trx.insert(roleResources).values({
roleId: adminRole[0].roleId,
resourceId: newResource[0].resourceId

View File

@@ -1,17 +1,22 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, targetHealthCheck } from "@server/db";
import { newts, resources, sites, targets } from "@server/db";
import { eq, inArray } from "drizzle-orm";
import {
db,
newts,
resourcePolicies,
resources,
sites,
targetHealthCheck,
targets
} from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { addPeer } from "../gerbil/peers";
import { removeTargets } from "../newt/targets";
import { getAllowedIps } from "../target/helpers";
import { OpenAPITags, registry } from "@server/openApi";
import HttpCode from "@server/types/HttpCode";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
import { removeTargets } from "../newt/targets";
// Define Zod schema for request parameters validation
const deleteResourceSchema = z.strictObject({
@@ -113,6 +118,18 @@ export async function deleteResource(
}
}
// Also delete default resource policy
if (deletedResource.defaultResourcePolicyId) {
await db
.delete(resourcePolicies)
.where(
eq(
resourcePolicies.resourcePolicyId,
deletedResource.defaultResourcePolicyId
)
);
}
return response(res, {
data: null,
success: true,

View File

@@ -2,13 +2,13 @@ import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
db,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility,
resourcePassword,
resourcePincode,
resourcePolicies,
resourcePolicyHeaderAuth,
resourcePolicyPassword,
resourcePolicyPincode,
resources
} from "@server/db";
import { eq } from "drizzle-orm";
import { eq, or } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
@@ -60,64 +60,53 @@ export async function getResourceAuthInfo(
const isGuidInteger = /^\d+$/.test(resourceGuid);
const buildQuery = (whereClause: ReturnType<typeof eq>) =>
db
.select()
.from(resources)
.leftJoin(
resourcePolicies,
or(
eq(
resourcePolicies.resourcePolicyId,
resources.resourcePolicyId
),
eq(
resourcePolicies.resourcePolicyId,
resources.defaultResourcePolicyId
)
)
)
.leftJoin(
resourcePolicyPincode,
eq(
resourcePolicyPincode.resourcePolicyId,
resourcePolicies.resourcePolicyId
)
)
.leftJoin(
resourcePolicyPassword,
eq(
resourcePolicyPassword.resourcePolicyId,
resourcePolicies.resourcePolicyId
)
)
.leftJoin(
resourcePolicyHeaderAuth,
eq(
resourcePolicyHeaderAuth.resourcePolicyId,
resourcePolicies.resourcePolicyId
)
)
.where(whereClause)
.limit(1);
const [result] =
isGuidInteger && build === "saas"
? await db
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceId, Number(resourceGuid)))
.limit(1)
: await db
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceGuid, resourceGuid))
.limit(1);
? await buildQuery(
eq(resources.resourceId, Number(resourceGuid))
)
: await buildQuery(eq(resources.resourceGuid, resourceGuid));
const resource = result?.resources;
if (!resource) {
@@ -126,11 +115,10 @@ export async function getResourceAuthInfo(
);
}
const pincode = result?.resourcePincode;
const password = result?.resourcePassword;
const headerAuth = result?.resourceHeaderAuth;
const headerAuthExtendedCompatibility =
result?.resourceHeaderAuthExtendedCompatibility;
const policy = result?.resourcePolicies;
const pincode = result?.resourcePolicyPincode;
const password = result?.resourcePolicyPassword;
const headerAuth = result?.resourcePolicyHeaderAuth;
const url = resource.fullDomain
? `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`
@@ -146,13 +134,13 @@ export async function getResourceAuthInfo(
pincode: pincode !== null,
headerAuth: headerAuth !== null,
headerAuthExtendedCompatibility:
headerAuthExtendedCompatibility !== null,
sso: resource.sso,
headerAuth?.extendedCompatibility ?? false,
sso: policy?.sso ?? false,
blockAccess: resource.blockAccess,
url: url ?? "",
wildcard: resource.wildcard ?? false,
fullDomain: resource.fullDomain,
whitelist: resource.emailWhitelistEnabled,
whitelist: policy?.emailWhitelistEnabled ?? false,
skipToIdpId: resource.skipToIdpId,
orgId: resource.orgId,
postAuthPath: resource.postAuthPath ?? null

View File

@@ -0,0 +1,109 @@
import { db, resources } from "@server/db";
import {
queryResourcePolicy,
type GetResourcePolicyResponse
} from "@server/routers/policy/getResourcePolicy";
import response from "@server/lib/response";
import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
import HttpCode from "@server/types/HttpCode";
import { eq } from "drizzle-orm";
import type { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import z from "zod";
import { fromError } from "zod-validation-error";
const getResourcePoliciesParamsSchema = z.strictObject({
resourceId: z.string().transform(Number).pipe(z.int().positive())
});
export type GetResourcePoliciesResponse = {
defaultPolicy: GetResourcePolicyResponse;
sharedPolicy: GetResourcePolicyResponse | null;
};
registry.registerPath({
method: "get",
path: "/resource/{resourceId}/policies",
description: "Get the inline and shared policies associated with a resource.",
tags: [OpenAPITags.PublicResource, OpenAPITags.Policy],
request: {
params: getResourcePoliciesParamsSchema
},
responses: {}
});
export async function getResourcePolicies(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = getResourcePoliciesParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { resourceId } = parsedParams.data;
const [resource] = await db
.select({
defaultResourcePolicyId: resources.defaultResourcePolicyId,
resourcePolicyId: resources.resourcePolicyId
})
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (!resource) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
);
}
if (!resource.defaultResourcePolicyId) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"Resource has no default policy"
)
);
}
const [defaultPolicy, sharedPolicy] = await Promise.all([
queryResourcePolicy({
resourcePolicyId: resource.defaultResourcePolicyId
}),
resource.resourcePolicyId
? queryResourcePolicy({
resourcePolicyId: resource.resourcePolicyId
})
: null
]);
return response<GetResourcePoliciesResponse>(res, {
data: {
defaultPolicy:
// the policy will always be non nullable
defaultPolicy as unknown as GetResourcePolicyResponse,
sharedPolicy
},
success: true,
error: false,
message: "Resource policies retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -34,3 +34,4 @@ export * from "./listAllResourceNames";
export * from "./removeEmailFromResourceWhitelist";
export * from "./getStatusHistory";
export * from "./getBrowserTarget";
export * from "./getResourcePolicies";

View File

@@ -7,6 +7,10 @@ import {
resourceLabels,
resourcePassword,
resourcePincode,
resourcePolicies,
resourcePolicyHeaderAuth,
resourcePolicyPassword,
resourcePolicyPincode,
resources,
roleResources,
sites,
@@ -189,39 +193,55 @@ function queryResourcesBase() {
name: resources.name,
ssl: resources.ssl,
fullDomain: resources.fullDomain,
passwordId: resourcePassword.passwordId,
sso: resources.sso,
pincodeId: resourcePincode.pincodeId,
whitelist: resources.emailWhitelistEnabled,
passwordId: resourcePolicyPassword.passwordId,
sso: resourcePolicies.sso,
pincodeId: resourcePolicyPincode.pincodeId,
whitelist: resourcePolicies.emailWhitelistEnabled,
proxyPort: resources.proxyPort,
enabled: resources.enabled,
domainId: resources.domainId,
niceId: resources.niceId,
wildcard: resources.wildcard,
headerAuthId: resourceHeaderAuth.headerAuthId,
headerAuthExtendedCompatibilityId:
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
mode: resources.mode,
health: resources.health,
mode: resources.mode
headerAuthId: resourcePolicyHeaderAuth.headerAuthId,
headerAuthExtendedCompatibility:
resourcePolicyHeaderAuth.extendedCompatibility
})
.from(resources)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
resourcePolicies,
or(
eq(
resourcePolicies.resourcePolicyId,
resources.resourcePolicyId
),
eq(
resourcePolicies.resourcePolicyId,
resources.defaultResourcePolicyId
)
)
)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(resourceHeaderAuth.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
resourcePolicyPassword,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
resourcePolicyPassword.resourcePolicyId,
resourcePolicies.resourcePolicyId
)
)
.leftJoin(
resourcePolicyPincode,
eq(
resourcePolicyPincode.resourcePolicyId,
resourcePolicies.resourcePolicyId
)
)
.leftJoin(
resourcePolicyHeaderAuth,
eq(
resourcePolicyHeaderAuth.resourcePolicyId,
resourcePolicies.resourcePolicyId
)
)
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
@@ -231,10 +251,10 @@ function queryResourcesBase() {
)
.groupBy(
resources.resourceId,
resourcePassword.passwordId,
resourcePincode.pincodeId,
resourceHeaderAuth.headerAuthId,
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId
resourcePolicies.resourcePolicyId,
resourcePolicyPassword.passwordId,
resourcePolicyPincode.pincodeId,
resourcePolicyHeaderAuth.headerAuthId
);
}
@@ -370,21 +390,21 @@ export async function listResources(
case "protected":
conditions.push(
or(
eq(resources.sso, true),
eq(resources.emailWhitelistEnabled, true),
not(isNull(resourceHeaderAuth.headerAuthId)),
not(isNull(resourcePincode.pincodeId)),
not(isNull(resourcePassword.passwordId))
eq(resourcePolicies.sso, true),
eq(resourcePolicies.emailWhitelistEnabled, true),
not(isNull(resourcePolicyHeaderAuth.headerAuthId)),
not(isNull(resourcePolicyPincode.pincodeId)),
not(isNull(resourcePolicyPassword.passwordId))
)
);
break;
case "not_protected":
conditions.push(
not(eq(resources.sso, true)),
not(eq(resources.emailWhitelistEnabled, true)),
isNull(resourceHeaderAuth.headerAuthId),
isNull(resourcePincode.pincodeId),
isNull(resourcePassword.passwordId)
not(eq(resourcePolicies.sso, true)),
not(eq(resourcePolicies.emailWhitelistEnabled, true)),
isNull(resourcePolicyHeaderAuth.headerAuthId),
isNull(resourcePolicyPincode.pincodeId),
isNull(resourcePolicyPassword.passwordId)
);
break;
}
@@ -558,9 +578,9 @@ export async function listResources(
ssl: row.ssl,
fullDomain: row.fullDomain,
passwordId: row.passwordId,
sso: row.sso,
sso: row.sso ?? false,
pincodeId: row.pincodeId,
whitelist: row.whitelist,
whitelist: row.whitelist ?? false,
proxyPort: row.proxyPort,
wildcard: row.wildcard,
mode: row.mode,

View File

@@ -1,3 +1,6 @@
import type { Resource, ResourcePolicy } from "@server/db";
import type { PaginatedResponse } from "@server/types/Pagination";
export type GetMaintenanceInfoResponse = {
resourceId: number;
name: string;
@@ -8,3 +11,19 @@ export type GetMaintenanceInfoResponse = {
maintenanceMessage: string | null;
maintenanceEstimatedTime: string | null;
};
export type AttachedResource = Pick<
Resource,
"resourceId" | "name" | "fullDomain"
>;
export type ResourcePolicyWithResources = Pick<
ResourcePolicy,
"resourcePolicyId" | "niceId" | "name" | "orgId"
> & {
resources: Array<AttachedResource>;
};
export type ListResourcePoliciesResponse = PaginatedResponse<{
policies: Array<ResourcePolicyWithResources>;
}>;

View File

@@ -1,12 +1,23 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, domainNamespaces, loginPage } from "@server/db";
import {
db,
domainNamespaces,
loginPage,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility,
resourcePassword,
resourcePincode,
resourceRules,
resourceWhitelist
} from "@server/db";
import {
domains,
Org,
orgDomains,
orgs,
Resource,
resourcePolicies,
resources
} from "@server/db";
import { eq, and, ne } from "drizzle-orm";
@@ -75,7 +86,8 @@ const updateHttpResourceBodySchema = z
// SSH settings
pamMode: z.enum(["passthrough", "push"]).optional(),
authDaemonMode: z.enum(["site", "remote", "native"]).optional(),
authDaemonPort: z.int().min(1).max(65535).nullable().optional()
authDaemonPort: z.int().min(1).max(65535).nullable().optional(),
resourcePolicyId: z.number().nullable().optional()
})
.refine((data) => Object.keys(data).length > 0, {
error: "At least one field must be provided for update"
@@ -172,7 +184,8 @@ const updateRawResourceBodySchema = z
stickySession: z.boolean().optional(),
enabled: z.boolean().optional(),
proxyProtocol: z.boolean().optional(),
proxyProtocolVersion: z.int().min(1).optional()
proxyProtocolVersion: z.int().min(1).optional(),
resourcePolicyId: z.number().nullable().optional()
})
.refine((data) => Object.keys(data).length > 0, {
error: "At least one field must be provided for update"
@@ -308,6 +321,42 @@ async function updateHttpResource(
const updateData = parsedBody.data;
const isLicensed = await isLicensedOrSubscribed(
resource.orgId,
tierMatrix.wildcardSubdomain
);
if (updateData.resourcePolicyId != null) {
if (!isLicensed) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Resource policies are not supported on your current plan. Please upgrade to access this feature."
)
);
}
const [existingPolicy] = await db
.select()
.from(resourcePolicies)
.where(
eq(
resourcePolicies.resourcePolicyId,
updateData.resourcePolicyId
)
)
.limit(1);
if (!existingPolicy) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource policy with ID ${updateData.resourcePolicyId} not found`
)
);
}
}
if (updateData.niceId) {
const [existingResource] = await db
.select()
@@ -333,10 +382,6 @@ async function updateHttpResource(
// Wildcard subdomains are a paid feature
if (updateData.subdomain && updateData.subdomain.includes("*")) {
const isLicensed = await isLicensedOrSubscribed(
resource.orgId,
tierMatrix.wildcardSubdomain
);
if (!isLicensed) {
return next(
createHttpError(
@@ -481,10 +526,6 @@ async function updateHttpResource(
headers = null;
}
const isLicensed = await isLicensedOrSubscribed(
resource.orgId,
tierMatrix.maintencePage
);
if (!isLicensed) {
updateData.maintenanceModeEnabled = undefined;
updateData.maintenanceModeType = undefined;
@@ -542,38 +583,122 @@ async function updateRawResource(
}
const updateData = parsedBody.data;
let updatedResource: Resource | null = null;
if (updateData.niceId) {
const [existingResource] = await db
.select()
.from(resources)
.where(
and(
eq(resources.niceId, updateData.niceId),
eq(resources.orgId, resource.orgId)
)
);
if (
existingResource &&
existingResource.resourceId !== resource.resourceId
) {
return next(
createHttpError(
HttpCode.CONFLICT,
`A resource with niceId "${updateData.niceId}" already exists`
)
);
}
}
const updatedResource = await db
.update(resources)
.set(updateData)
const [existingResource] = await db
.select()
.from(resources)
.where(eq(resources.resourceId, resource.resourceId))
.returning();
.limit(1);
if (updatedResource.length === 0) {
await db.transaction(async (trx) => {
if (updateData.resourcePolicyId != null) {
const [existingPolicy] = await trx
.select()
.from(resourcePolicies)
.where(
eq(
resourcePolicies.resourcePolicyId,
updateData.resourcePolicyId
)
)
.limit(1);
if (!existingPolicy) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource policy with ID ${updateData.resourcePolicyId} not found`
)
);
}
} else {
// we are in an inline policy and we need to clear out the old tables
await Promise.all([
trx
.delete(resourcePassword)
.where(
eq(
resourcePassword.resourceId,
existingResource.resourceId
)
),
trx
.delete(resourcePincode)
.where(
eq(
resourcePincode.resourceId,
existingResource.resourceId
)
),
trx
.delete(resourceHeaderAuth)
.where(
eq(
resourceHeaderAuth.resourceId,
existingResource.resourceId
)
),
trx
.delete(resourceHeaderAuthExtendedCompatibility)
.where(
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
existingResource.resourceId
)
),
trx
.delete(resourceWhitelist)
.where(
eq(
resourceWhitelist.resourceId,
existingResource.resourceId
)
),
trx
.delete(resourceRules)
.where(
eq(
resourceRules.resourceId,
existingResource.resourceId
)
)
]);
}
if (updateData.niceId) {
const [existingResourceConflict] = await trx
.select()
.from(resources)
.where(
and(
eq(resources.niceId, updateData.niceId),
eq(resources.orgId, resource.orgId)
)
);
if (
existingResourceConflict &&
existingResourceConflict.resourceId !== resource.resourceId
) {
return next(
createHttpError(
HttpCode.CONFLICT,
`A resource with niceId "${updateData.niceId}" already exists`
)
);
}
}
[updatedResource] = await trx
.update(resources)
.set(updateData)
.where(eq(resources.resourceId, resource.resourceId))
.returning();
});
if (!updatedResource) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
@@ -583,7 +708,7 @@ async function updateRawResource(
}
return response(res, {
data: updatedResource[0],
data: updatedResource,
success: true,
error: false,
message: "Non-http Resource updated successfully",