From 84fef5f1d686c7476e04bfb7f0c0c8837c72ab94 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 4 Jun 2026 21:59:34 -0700 Subject: [PATCH] Resource policy api backward compatability --- server/routers/resource/addRoleToResource.ts | 79 +++++--- server/routers/resource/addUserToResource.ts | 79 +++++--- server/routers/resource/createResourceRule.ts | 30 ++- server/routers/resource/deleteResourceRule.ts | 44 ++++- .../routers/resource/getResourceWhitelist.ts | 35 +++- server/routers/resource/listResourceRoles.ts | 35 +++- server/routers/resource/listResourceRules.ts | 51 ++++- .../resource/removeRoleFromResource.ts | 90 ++++++--- .../resource/removeUserFromResource.ts | 90 ++++++--- .../routers/resource/setResourceHeaderAuth.ts | 95 +++++++--- .../routers/resource/setResourcePassword.ts | 56 +++++- server/routers/resource/setResourcePincode.ts | 54 +++++- server/routers/resource/setResourceRoles.ts | 69 +++++-- server/routers/resource/setResourceUsers.ts | 56 ++++-- .../routers/resource/setResourceWhitelist.ts | 175 +++++++++++++----- server/routers/resource/updateResource.ts | 52 ++++++ 16 files changed, 851 insertions(+), 239 deletions(-) diff --git a/server/routers/resource/addRoleToResource.ts b/server/routers/resource/addRoleToResource.ts index 8192f779c..5637cf8f8 100644 --- a/server/routers/resource/addRoleToResource.ts +++ b/server/routers/resource/addRoleToResource.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db, resources } from "@server/db"; -import { roleResources, roles } from "@server/db"; +import { roleResources, roles, rolePolicies } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -131,31 +131,64 @@ export async function addRoleToResource( ); } - // Check if role already exists in resource - const existingEntry = await db - .select() - .from(roleResources) - .where( - and( - eq(roleResources.resourceId, resourceId), - eq(roleResources.roleId, roleId) - ) - ); + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; - if (existingEntry.length > 0) { - return next( - createHttpError( - HttpCode.CONFLICT, - "Role already assigned to resource" - ) - ); + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + + // Check if role already exists in the inline policy + const existingEntry = await db + .select() + .from(rolePolicies) + .where( + and( + eq(rolePolicies.resourcePolicyId, policyId), + eq(rolePolicies.roleId, roleId) + ) + ); + + if (existingEntry.length > 0) { + return next( + createHttpError( + HttpCode.CONFLICT, + "Role already assigned to resource" + ) + ); + } + + await db.insert(rolePolicies).values({ + roleId, + resourcePolicyId: policyId + }); + } else { + // Check if role already exists in resource + const existingEntry = await db + .select() + .from(roleResources) + .where( + and( + eq(roleResources.resourceId, resourceId), + eq(roleResources.roleId, roleId) + ) + ); + + if (existingEntry.length > 0) { + return next( + createHttpError( + HttpCode.CONFLICT, + "Role already assigned to resource" + ) + ); + } + + await db.insert(roleResources).values({ + roleId, + resourceId + }); } - await db.insert(roleResources).values({ - roleId, - resourceId - }); - return response(res, { data: {}, success: true, diff --git a/server/routers/resource/addUserToResource.ts b/server/routers/resource/addUserToResource.ts index 3a75b0043..0b749e04f 100644 --- a/server/routers/resource/addUserToResource.ts +++ b/server/routers/resource/addUserToResource.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db, resources } from "@server/db"; -import { userResources } from "@server/db"; +import { userResources, userPolicies } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -103,31 +103,64 @@ export async function addUserToResource( ); } - // Check if user already exists in resource - const existingEntry = await db - .select() - .from(userResources) - .where( - and( - eq(userResources.resourceId, resourceId), - eq(userResources.userId, userId) - ) - ); + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; - if (existingEntry.length > 0) { - return next( - createHttpError( - HttpCode.CONFLICT, - "User already assigned to resource" - ) - ); + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + + // Check if user already exists in the inline policy + const existingEntry = await db + .select() + .from(userPolicies) + .where( + and( + eq(userPolicies.resourcePolicyId, policyId), + eq(userPolicies.userId, userId) + ) + ); + + if (existingEntry.length > 0) { + return next( + createHttpError( + HttpCode.CONFLICT, + "User already assigned to resource" + ) + ); + } + + await db.insert(userPolicies).values({ + userId, + resourcePolicyId: policyId + }); + } else { + // Check if user already exists in resource + const existingEntry = await db + .select() + .from(userResources) + .where( + and( + eq(userResources.resourceId, resourceId), + eq(userResources.userId, userId) + ) + ); + + if (existingEntry.length > 0) { + return next( + createHttpError( + HttpCode.CONFLICT, + "User already assigned to resource" + ) + ); + } + + await db.insert(userResources).values({ + userId, + resourceId + }); } - await db.insert(userResources).values({ - userId, - resourceId - }); - return response(res, { data: {}, success: true, diff --git a/server/routers/resource/createResourceRule.ts b/server/routers/resource/createResourceRule.ts index f55eb4112..14ac69fcc 100644 --- a/server/routers/resource/createResourceRule.ts +++ b/server/routers/resource/createResourceRule.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resourceRules, resources } from "@server/db"; +import { resourceRules, resourcePolicyRules, resources } from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -153,6 +153,34 @@ export async function createResourceRule( } } + // Create the new resource rule + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + const [newRule] = await db + .insert(resourcePolicyRules) + .values({ + resourcePolicyId: policyId, + action, + match, + value, + priority, + enabled + }) + .returning(); + + return response(res, { + data: newRule, + success: true, + error: false, + message: "Resource rule created successfully", + status: HttpCode.CREATED + }); + } + // Create the new resource rule const [newRule] = await db .insert(resourceRules) diff --git a/server/routers/resource/deleteResourceRule.ts b/server/routers/resource/deleteResourceRule.ts index ef40ecaab..a3a1543f8 100644 --- a/server/routers/resource/deleteResourceRule.ts +++ b/server/routers/resource/deleteResourceRule.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resourceRules, resources } from "@server/db"; +import { resourceRules, resourcePolicyRules, resources } from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -59,6 +59,48 @@ export async function deleteResourceRule( const { ruleId } = parsedParams.data; + // Look up resource to determine which table to use + const { resourceId } = parsedParams.data; + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!resource) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") + ); + } + + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + + if (isInlinePolicy) { + const [deletedRule] = await db + .delete(resourcePolicyRules) + .where(eq(resourcePolicyRules.ruleId, ruleId)) + .returning(); + + if (!deletedRule) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource rule with ID ${ruleId} not found` + ) + ); + } + + return response(res, { + data: null, + success: true, + error: false, + message: "Resource rule deleted successfully", + status: HttpCode.OK + }); + } + // Delete the rule and return the deleted record const [deletedRule] = await db .delete(resourceRules) diff --git a/server/routers/resource/getResourceWhitelist.ts b/server/routers/resource/getResourceWhitelist.ts index bb6105b0b..c773f89b9 100644 --- a/server/routers/resource/getResourceWhitelist.ts +++ b/server/routers/resource/getResourceWhitelist.ts @@ -1,7 +1,11 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resourceWhitelist, users } from "@server/db"; // Assuming these are the correct tables +import { + resourceWhitelist, + resourcePolicyWhiteList, + resources +} from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -23,6 +27,15 @@ async function queryWhitelist(resourceId: number) { .where(eq(resourceWhitelist.resourceId, resourceId)); } +async function queryPolicyWhitelist(policyId: number) { + return await db + .select({ + email: resourcePolicyWhiteList.email + }) + .from(resourcePolicyWhiteList) + .where(eq(resourcePolicyWhiteList.resourcePolicyId, policyId)); +} + export type GetResourceWhitelistResponse = { whitelist: NonNullable>>; }; @@ -71,7 +84,25 @@ export async function getResourceWhitelist( const { resourceId } = parsedParams.data; - const whitelist = await queryWhitelist(resourceId); + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!resource) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") + ); + } + + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + + const whitelist = isInlinePolicy + ? await queryPolicyWhitelist(resource.defaultResourcePolicyId!) + : await queryWhitelist(resourceId); return response(res, { data: { diff --git a/server/routers/resource/listResourceRoles.ts b/server/routers/resource/listResourceRoles.ts index ffff8c602..4ee3f9535 100644 --- a/server/routers/resource/listResourceRoles.ts +++ b/server/routers/resource/listResourceRoles.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { roleResources, roles } from "@server/db"; +import { roleResources, roles, rolePolicies, resources } from "@server/db"; import { eq } from "drizzle-orm"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; @@ -27,6 +27,19 @@ async function query(resourceId: number) { .where(eq(roleResources.resourceId, resourceId)); } +async function queryInlinePolicy(policyId: number) { + return await db + .select({ + roleId: roles.roleId, + name: roles.name, + description: roles.description, + isAdmin: roles.isAdmin + }) + .from(rolePolicies) + .innerJoin(roles, eq(rolePolicies.roleId, roles.roleId)) + .where(eq(rolePolicies.resourcePolicyId, policyId)); +} + export type ListResourceRolesResponse = { roles: NonNullable>>; }; @@ -75,7 +88,25 @@ export async function listResourceRoles( const { resourceId } = parsedParams.data; - const resourceRolesList = await query(resourceId); + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!resource) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") + ); + } + + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + + const resourceRolesList = isInlinePolicy + ? await queryInlinePolicy(resource.defaultResourcePolicyId!) + : await query(resourceId); return response(res, { data: { diff --git a/server/routers/resource/listResourceRules.ts b/server/routers/resource/listResourceRules.ts index b1b2581ed..76d6fb97e 100644 --- a/server/routers/resource/listResourceRules.ts +++ b/server/routers/resource/listResourceRules.ts @@ -1,5 +1,5 @@ import { db } from "@server/db"; -import { resourceRules, resources } from "@server/db"; +import { resourceRules, resourcePolicyRules, resources } from "@server/db"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; import { eq, sql } from "drizzle-orm"; @@ -47,6 +47,21 @@ function queryResourceRules(resourceId: number) { return baseQuery; } +function queryPolicyRules(policyId: number) { + return db + .select({ + ruleId: resourcePolicyRules.ruleId, + resourceId: sql`null`, + action: resourcePolicyRules.action, + match: resourcePolicyRules.match, + value: resourcePolicyRules.value, + priority: resourcePolicyRules.priority, + enabled: resourcePolicyRules.enabled + }) + .from(resourcePolicyRules) + .where(eq(resourcePolicyRules.resourcePolicyId, policyId)); +} + export type ListResourceRulesResponse = { rules: Awaited>; pagination: { total: number; limit: number; offset: number }; @@ -125,16 +140,34 @@ export async function listResourceRules( ); } - const baseQuery = queryResourceRules(resourceId); + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; - const countQuery = db - .select({ count: sql`cast(count(*) as integer)` }) - .from(resourceRules) - .where(eq(resourceRules.resourceId, resourceId)); + let rulesList: Awaited>; + let totalCount: number; - let rulesList = await baseQuery.limit(limit).offset(offset); - const totalCountResult = await countQuery; - const totalCount = totalCountResult[0].count; + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + const policyRules = await queryPolicyRules(policyId) + .limit(limit) + .offset(offset); + const countResult = await db + .select({ count: sql`cast(count(*) as integer)` }) + .from(resourcePolicyRules) + .where(eq(resourcePolicyRules.resourcePolicyId, policyId)); + rulesList = policyRules as typeof rulesList; + totalCount = countResult[0].count; + } else { + const baseQuery = queryResourceRules(resourceId); + const countQuery = db + .select({ count: sql`cast(count(*) as integer)` }) + .from(resourceRules) + .where(eq(resourceRules.resourceId, resourceId)); + rulesList = await baseQuery.limit(limit).offset(offset); + const totalCountResult = await countQuery; + totalCount = totalCountResult[0].count; + } // sort rules list by the priority in ascending order rulesList = rulesList.sort((a, b) => a.priority - b.priority); diff --git a/server/routers/resource/removeRoleFromResource.ts b/server/routers/resource/removeRoleFromResource.ts index 66da1d377..9f1323cca 100644 --- a/server/routers/resource/removeRoleFromResource.ts +++ b/server/routers/resource/removeRoleFromResource.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db, resources } from "@server/db"; -import { roleResources, roles } from "@server/db"; +import { roleResources, roles, rolePolicies } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -130,35 +130,71 @@ export async function removeRoleFromResource( ); } - // Check if role exists in resource - const existingEntry = await db - .select() - .from(roleResources) - .where( - and( - eq(roleResources.resourceId, resourceId), - eq(roleResources.roleId, roleId) - ) - ); + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; - if (existingEntry.length === 0) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "Role not found in resource" - ) - ); + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + + const existingEntry = await db + .select() + .from(rolePolicies) + .where( + and( + eq(rolePolicies.resourcePolicyId, policyId), + eq(rolePolicies.roleId, roleId) + ) + ); + + if (existingEntry.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Role not found in resource" + ) + ); + } + + await db + .delete(rolePolicies) + .where( + and( + eq(rolePolicies.resourcePolicyId, policyId), + eq(rolePolicies.roleId, roleId) + ) + ); + } else { + // Check if role exists in resource + const existingEntry = await db + .select() + .from(roleResources) + .where( + and( + eq(roleResources.resourceId, resourceId), + eq(roleResources.roleId, roleId) + ) + ); + + if (existingEntry.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Role not found in resource" + ) + ); + } + + await db + .delete(roleResources) + .where( + and( + eq(roleResources.resourceId, resourceId), + eq(roleResources.roleId, roleId) + ) + ); } - await db - .delete(roleResources) - .where( - and( - eq(roleResources.resourceId, resourceId), - eq(roleResources.roleId, roleId) - ) - ); - return response(res, { data: {}, success: true, diff --git a/server/routers/resource/removeUserFromResource.ts b/server/routers/resource/removeUserFromResource.ts index 17f2380a2..234ef642d 100644 --- a/server/routers/resource/removeUserFromResource.ts +++ b/server/routers/resource/removeUserFromResource.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db, resources } from "@server/db"; -import { userResources } from "@server/db"; +import { userResources, userPolicies } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -103,35 +103,71 @@ export async function removeUserFromResource( ); } - // Check if user exists in resource - const existingEntry = await db - .select() - .from(userResources) - .where( - and( - eq(userResources.resourceId, resourceId), - eq(userResources.userId, userId) - ) - ); + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; - if (existingEntry.length === 0) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "User not found in resource" - ) - ); + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + + const existingEntry = await db + .select() + .from(userPolicies) + .where( + and( + eq(userPolicies.resourcePolicyId, policyId), + eq(userPolicies.userId, userId) + ) + ); + + if (existingEntry.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "User not found in resource" + ) + ); + } + + await db + .delete(userPolicies) + .where( + and( + eq(userPolicies.resourcePolicyId, policyId), + eq(userPolicies.userId, userId) + ) + ); + } else { + // Check if user exists in resource + const existingEntry = await db + .select() + .from(userResources) + .where( + and( + eq(userResources.resourceId, resourceId), + eq(userResources.userId, userId) + ) + ); + + if (existingEntry.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "User not found in resource" + ) + ); + } + + await db + .delete(userResources) + .where( + and( + eq(userResources.resourceId, resourceId), + eq(userResources.userId, userId) + ) + ); } - await db - .delete(userResources) - .where( - and( - eq(userResources.resourceId, resourceId), - eq(userResources.userId, userId) - ) - ); - return response(res, { data: {}, success: true, diff --git a/server/routers/resource/setResourceHeaderAuth.ts b/server/routers/resource/setResourceHeaderAuth.ts index cec9ad96a..44571eb6a 100644 --- a/server/routers/resource/setResourceHeaderAuth.ts +++ b/server/routers/resource/setResourceHeaderAuth.ts @@ -3,7 +3,9 @@ import { z } from "zod"; import { db, resourceHeaderAuth, - resourceHeaderAuthExtendedCompatibility + resourceHeaderAuthExtendedCompatibility, + resourcePolicyHeaderAuth, + resources } from "@server/db"; import { eq } from "drizzle-orm"; import HttpCode from "@server/types/HttpCode"; @@ -89,36 +91,73 @@ export async function setResourceHeaderAuth( const { resourceId } = parsedParams.data; const { user, password, extendedCompatibility } = parsedBody.data; + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!resource) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") + ); + } + + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + await db.transaction(async (trx) => { - await trx - .delete(resourceHeaderAuth) - .where(eq(resourceHeaderAuth.resourceId, resourceId)); - await trx - .delete(resourceHeaderAuthExtendedCompatibility) - .where( - eq( - resourceHeaderAuthExtendedCompatibility.resourceId, - resourceId - ) - ); + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + await trx + .delete(resourcePolicyHeaderAuth) + .where( + eq(resourcePolicyHeaderAuth.resourcePolicyId, policyId) + ); - if (user && password && extendedCompatibility !== null) { - const headerAuthHash = await hashPassword( - Buffer.from(`${user}:${password}`).toString("base64") - ); + if (user && password && extendedCompatibility !== null) { + const headerAuthHash = await hashPassword( + Buffer.from(`${user}:${password}`).toString("base64") + ); - await Promise.all([ - trx - .insert(resourceHeaderAuth) - .values({ resourceId, headerAuthHash }), - trx - .insert(resourceHeaderAuthExtendedCompatibility) - .values({ - resourceId, - extendedCompatibilityIsActivated: - extendedCompatibility - }) - ]); + await trx.insert(resourcePolicyHeaderAuth).values({ + resourcePolicyId: policyId, + headerAuthHash, + extendedCompatibility: extendedCompatibility! + }); + } + } else { + await trx + .delete(resourceHeaderAuth) + .where(eq(resourceHeaderAuth.resourceId, resourceId)); + await trx + .delete(resourceHeaderAuthExtendedCompatibility) + .where( + eq( + resourceHeaderAuthExtendedCompatibility.resourceId, + resourceId + ) + ); + + if (user && password && extendedCompatibility !== null) { + const headerAuthHash = await hashPassword( + Buffer.from(`${user}:${password}`).toString("base64") + ); + + await Promise.all([ + trx + .insert(resourceHeaderAuth) + .values({ resourceId, headerAuthHash }), + trx + .insert(resourceHeaderAuthExtendedCompatibility) + .values({ + resourceId, + extendedCompatibilityIsActivated: + extendedCompatibility + }) + ]); + } } }); diff --git a/server/routers/resource/setResourcePassword.ts b/server/routers/resource/setResourcePassword.ts index 0f89bccd4..355e20a49 100644 --- a/server/routers/resource/setResourcePassword.ts +++ b/server/routers/resource/setResourcePassword.ts @@ -1,7 +1,11 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resourcePassword } from "@server/db"; +import { + resourcePassword, + resourcePolicyPassword, + resources +} from "@server/db"; import { eq } from "drizzle-orm"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -85,17 +89,49 @@ export async function setResourcePassword( const { resourceId } = parsedParams.data; const { password } = parsedBody.data; + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!resource) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") + ); + } + + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + await db.transaction(async (trx) => { - await trx - .delete(resourcePassword) - .where(eq(resourcePassword.resourceId, resourceId)); - - if (password) { - const passwordHash = await hashPassword(password); - + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; await trx - .insert(resourcePassword) - .values({ resourceId, passwordHash }); + .delete(resourcePolicyPassword) + .where( + eq(resourcePolicyPassword.resourcePolicyId, policyId) + ); + + if (password) { + const passwordHash = await hashPassword(password); + await trx + .insert(resourcePolicyPassword) + .values({ resourcePolicyId: policyId, passwordHash }); + } + } else { + await trx + .delete(resourcePassword) + .where(eq(resourcePassword.resourceId, resourceId)); + + if (password) { + const passwordHash = await hashPassword(password); + + await trx + .insert(resourcePassword) + .values({ resourceId, passwordHash }); + } } }); diff --git a/server/routers/resource/setResourcePincode.ts b/server/routers/resource/setResourcePincode.ts index 9135529fb..67651fc9e 100644 --- a/server/routers/resource/setResourcePincode.ts +++ b/server/routers/resource/setResourcePincode.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resourcePincode } from "@server/db"; +import { resourcePincode, resourcePolicyPincode, resources } from "@server/db"; import { eq } from "drizzle-orm"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -89,17 +89,51 @@ export async function setResourcePincode( const { resourceId } = parsedParams.data; const { pincode } = parsedBody.data; + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (!resource) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") + ); + } + + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + await db.transaction(async (trx) => { - await trx - .delete(resourcePincode) - .where(eq(resourcePincode.resourceId, resourceId)); - - if (pincode) { - const pincodeHash = await hashPassword(pincode); - + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; await trx - .insert(resourcePincode) - .values({ resourceId, pincodeHash, digitLength: 6 }); + .delete(resourcePolicyPincode) + .where( + eq(resourcePolicyPincode.resourcePolicyId, policyId) + ); + + if (pincode) { + const pincodeHash = await hashPassword(pincode); + await trx.insert(resourcePolicyPincode).values({ + resourcePolicyId: policyId, + pincodeHash, + digitLength: 6 + }); + } + } else { + await trx + .delete(resourcePincode) + .where(eq(resourcePincode.resourceId, resourceId)); + + if (pincode) { + const pincodeHash = await hashPassword(pincode); + + await trx + .insert(resourcePincode) + .values({ resourceId, pincodeHash, digitLength: 6 }); + } } }); diff --git a/server/routers/resource/setResourceRoles.ts b/server/routers/resource/setResourceRoles.ts index 0015e373a..e091bfa85 100644 --- a/server/routers/resource/setResourceRoles.ts +++ b/server/routers/resource/setResourceRoles.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db, resources } from "@server/db"; -import { apiKeys, roleResources, roles } from "@server/db"; +import { apiKeys, roleResources, roles, rolePolicies } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -129,28 +129,61 @@ export async function setResourceRoles( ); const adminRoleIds = adminRoles.map((role) => role.roleId); + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + await db.transaction(async (trx) => { - if (adminRoleIds.length > 0) { - await trx.delete(roleResources).where( - and( - eq(roleResources.resourceId, resourceId), - ne(roleResources.roleId, adminRoleIds[0]) // delete all but the admin role + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + + // For inline policy, preserve admin roles by only deleting non-admin entries + if (adminRoleIds.length > 0) { + await trx + .delete(rolePolicies) + .where( + and( + eq(rolePolicies.resourcePolicyId, policyId), + ne(rolePolicies.roleId, adminRoleIds[0]) + ) + ); + } else { + await trx + .delete(rolePolicies) + .where(eq(rolePolicies.resourcePolicyId, policyId)); + } + + await Promise.all( + roleIds.map((roleId) => + trx + .insert(rolePolicies) + .values({ roleId, resourcePolicyId: policyId }) + .returning() ) ); } else { - await trx - .delete(roleResources) - .where(eq(roleResources.resourceId, resourceId)); - } + if (adminRoleIds.length > 0) { + await trx.delete(roleResources).where( + and( + eq(roleResources.resourceId, resourceId), + ne(roleResources.roleId, adminRoleIds[0]) // delete all but the admin role + ) + ); + } else { + await trx + .delete(roleResources) + .where(eq(roleResources.resourceId, resourceId)); + } - const newRoleResources = await Promise.all( - roleIds.map((roleId) => - trx - .insert(roleResources) - .values({ roleId, resourceId }) - .returning() - ) - ); + await Promise.all( + roleIds.map((roleId) => + trx + .insert(roleResources) + .values({ roleId, resourceId }) + .returning() + ) + ); + } return response(res, { data: {}, diff --git a/server/routers/resource/setResourceUsers.ts b/server/routers/resource/setResourceUsers.ts index 4c2b7457a..86532c6e2 100644 --- a/server/routers/resource/setResourceUsers.ts +++ b/server/routers/resource/setResourceUsers.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { userResources } from "@server/db"; +import { userResources, userPolicies, resources } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -82,19 +82,51 @@ export async function setResourceUsers( const { resourceId } = parsedParams.data; - await db.transaction(async (trx) => { - await trx - .delete(userResources) - .where(eq(userResources.resourceId, resourceId)); + const [resource] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); - const newUserResources = await Promise.all( - userIds.map((userId) => - trx - .insert(userResources) - .values({ userId, resourceId }) - .returning() - ) + if (!resource) { + return next( + createHttpError(HttpCode.NOT_FOUND, "Resource not found") ); + } + + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + + await db.transaction(async (trx) => { + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + await trx + .delete(userPolicies) + .where(eq(userPolicies.resourcePolicyId, policyId)); + + await Promise.all( + userIds.map((userId) => + trx + .insert(userPolicies) + .values({ userId, resourcePolicyId: policyId }) + .returning() + ) + ); + } else { + await trx + .delete(userResources) + .where(eq(userResources.resourceId, resourceId)); + + await Promise.all( + userIds.map((userId) => + trx + .insert(userResources) + .values({ userId, resourceId }) + .returning() + ) + ); + } return response(res, { data: {}, diff --git a/server/routers/resource/setResourceWhitelist.ts b/server/routers/resource/setResourceWhitelist.ts index ff6c9fd02..b228d66ed 100644 --- a/server/routers/resource/setResourceWhitelist.ts +++ b/server/routers/resource/setResourceWhitelist.ts @@ -1,7 +1,12 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; import { db } from "@server/db"; -import { resources, resourceWhitelist } from "@server/db"; +import { + resources, + resourceWhitelist, + resourcePolicies, + resourcePolicyWhiteList +} from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -104,57 +109,135 @@ export async function setResourceWhitelist( ); } - if (!resource.emailWhitelistEnabled) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Email whitelist is not enabled for this resource" - ) - ); - } + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; - const whitelist = await db - .select() - .from(resourceWhitelist) - .where(eq(resourceWhitelist.resourceId, resourceId)); + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; - await db.transaction(async (trx) => { - // diff the emails - const existingEmails = whitelist.map((w) => w.email); + const [policy] = await db + .select() + .from(resourcePolicies) + .where(eq(resourcePolicies.resourcePolicyId, policyId)); - const emailsToAdd = emails.filter( - (e) => !existingEmails.includes(e) - ); - const emailsToRemove = existingEmails.filter( - (e) => !emails.includes(e) - ); + if (!policy) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Resource policy not found" + ) + ); + } - for (const email of emailsToAdd) { - await trx.insert(resourceWhitelist).values({ - email, - resourceId + if (!policy.emailWhitelistEnabled) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email whitelist is not enabled for this resource" + ) + ); + } + + const existingPolicyWhitelist = await db + .select() + .from(resourcePolicyWhiteList) + .where(eq(resourcePolicyWhiteList.resourcePolicyId, policyId)); + + await db.transaction(async (trx) => { + const existingEmails = existingPolicyWhitelist.map( + (w) => w.email + ); + + const emailsToAdd = emails.filter( + (e) => !existingEmails.includes(e) + ); + const emailsToRemove = existingEmails.filter( + (e) => !emails.includes(e) + ); + + for (const email of emailsToAdd) { + await trx.insert(resourcePolicyWhiteList).values({ + email, + resourcePolicyId: policyId + }); + } + + for (const email of emailsToRemove) { + await trx + .delete(resourcePolicyWhiteList) + .where( + and( + eq( + resourcePolicyWhiteList.resourcePolicyId, + policyId + ), + eq(resourcePolicyWhiteList.email, email) + ) + ); + } + + return response(res, { + data: {}, + success: true, + error: false, + message: "Whitelist set for resource successfully", + status: HttpCode.CREATED }); - } - - for (const email of emailsToRemove) { - await trx - .delete(resourceWhitelist) - .where( - and( - eq(resourceWhitelist.resourceId, resourceId), - eq(resourceWhitelist.email, email) - ) - ); - } - - return response(res, { - data: {}, - success: true, - error: false, - message: "Whitelist set for resource successfully", - status: HttpCode.CREATED }); - }); + } else { + if (!resource.emailWhitelistEnabled) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email whitelist is not enabled for this resource" + ) + ); + } + + const whitelist = await db + .select() + .from(resourceWhitelist) + .where(eq(resourceWhitelist.resourceId, resourceId)); + + await db.transaction(async (trx) => { + // diff the emails + const existingEmails = whitelist.map((w) => w.email); + + const emailsToAdd = emails.filter( + (e) => !existingEmails.includes(e) + ); + const emailsToRemove = existingEmails.filter( + (e) => !emails.includes(e) + ); + + for (const email of emailsToAdd) { + await trx.insert(resourceWhitelist).values({ + email, + resourceId + }); + } + + for (const email of emailsToRemove) { + await trx + .delete(resourceWhitelist) + .where( + and( + eq(resourceWhitelist.resourceId, resourceId), + eq(resourceWhitelist.email, email) + ) + ); + } + + return response(res, { + data: {}, + success: true, + error: false, + message: "Whitelist set for resource successfully", + status: HttpCode.CREATED + }); + }); + } } catch (error) { logger.error(error); return next( diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 9647bb68e..ea1ae66d7 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -549,6 +549,58 @@ async function updateHttpResource( updateData.maintenanceEstimatedTime = undefined; } + const isInlinePolicy = + resource.resourcePolicyId === null && + resource.defaultResourcePolicyId !== null; + + if (isInlinePolicy) { + const policyId = resource.defaultResourcePolicyId!; + const { + sso, + emailWhitelistEnabled, + applyRules, + skipToIdpId, + ...resourceOnlyData + } = updateData; + + const policyUpdate: Record = {}; + if (sso !== undefined) policyUpdate.sso = sso; + if (emailWhitelistEnabled !== undefined) + policyUpdate.emailWhitelistEnabled = emailWhitelistEnabled; + if (applyRules !== undefined) policyUpdate.applyRules = applyRules; + if (skipToIdpId !== undefined) policyUpdate.idpId = skipToIdpId; + + if (Object.keys(policyUpdate).length > 0) { + await db + .update(resourcePolicies) + .set(policyUpdate) + .where(eq(resourcePolicies.resourcePolicyId, policyId)); + } + + const updatedResource = await db + .update(resources) + .set({ ...resourceOnlyData, headers }) + .where(eq(resources.resourceId, resource.resourceId)) + .returning(); + + if (updatedResource.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Resource with ID ${resource.resourceId} not found` + ) + ); + } + + return response(res, { + data: updatedResource[0], + success: true, + error: false, + message: "HTTP resource updated successfully", + status: HttpCode.OK + }); + } + const updatedResource = await db .update(resources) .set({ ...updateData, headers })