diff --git a/server/routers/resource/addEmailToResourceWhitelist.ts b/server/routers/resource/addEmailToResourceWhitelist.ts index 506c2caa9..6d5ef22cd 100644 --- a/server/routers/resource/addEmailToResourceWhitelist.ts +++ b/server/routers/resource/addEmailToResourceWhitelist.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"; @@ -103,40 +108,99 @@ export async function addEmailToResourceWhitelist( ); } - if (!resource.emailWhitelistEnabled) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Email whitelist is not enabled for this resource" - ) - ); + // A shared policy takes precedence over the resource's inline + // (default) policy, which takes precedence over the resource's own + // direct whitelist fields. This mirrors the precedence used at + // request time in authWithWhitelist.ts / getResourceAuthInfo.ts. + const policyId = + resource.resourcePolicyId ?? resource.defaultResourcePolicyId; + + if (policyId !== null) { + const [policy] = await db + .select() + .from(resourcePolicies) + .where(eq(resourcePolicies.resourcePolicyId, policyId)); + + if (!policy) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Resource policy not found" + ) + ); + } + + if (!policy.emailWhitelistEnabled) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email whitelist is not enabled for this resource" + ) + ); + } + + const existingEntry = await db + .select() + .from(resourcePolicyWhiteList) + .where( + and( + eq( + resourcePolicyWhiteList.resourcePolicyId, + policyId + ), + eq(resourcePolicyWhiteList.email, email) + ) + ); + + if (existingEntry.length > 0) { + return next( + createHttpError( + HttpCode.CONFLICT, + "Email already exists in whitelist" + ) + ); + } + + await db.insert(resourcePolicyWhiteList).values({ + email, + resourcePolicyId: policyId + }); + } else { + if (!resource.emailWhitelistEnabled) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email whitelist is not enabled for this resource" + ) + ); + } + + // Check if email already exists in whitelist + const existingEntry = await db + .select() + .from(resourceWhitelist) + .where( + and( + eq(resourceWhitelist.resourceId, resourceId), + eq(resourceWhitelist.email, email) + ) + ); + + if (existingEntry.length > 0) { + return next( + createHttpError( + HttpCode.CONFLICT, + "Email already exists in whitelist" + ) + ); + } + + await db.insert(resourceWhitelist).values({ + email, + resourceId + }); } - // Check if email already exists in whitelist - const existingEntry = await db - .select() - .from(resourceWhitelist) - .where( - and( - eq(resourceWhitelist.resourceId, resourceId), - eq(resourceWhitelist.email, email) - ) - ); - - if (existingEntry.length > 0) { - return next( - createHttpError( - HttpCode.CONFLICT, - "Email already exists in whitelist" - ) - ); - } - - await db.insert(resourceWhitelist).values({ - email, - resourceId - }); - return response(res, { data: {}, success: true, diff --git a/server/routers/resource/getResourceWhitelist.ts b/server/routers/resource/getResourceWhitelist.ts index 2d882d49e..0bebe6a71 100644 --- a/server/routers/resource/getResourceWhitelist.ts +++ b/server/routers/resource/getResourceWhitelist.ts @@ -96,13 +96,17 @@ export async function getResourceWhitelist( ); } - const isInlinePolicy = - resource.resourcePolicyId === null && - resource.defaultResourcePolicyId !== null; + // A shared policy takes precedence over the resource's inline + // (default) policy, which takes precedence over the resource's own + // direct whitelist fields. This mirrors the precedence used at + // request time in authWithWhitelist.ts / getResourceAuthInfo.ts. + const policyId = + resource.resourcePolicyId ?? resource.defaultResourcePolicyId; - const whitelist = isInlinePolicy - ? await queryPolicyWhitelist(resource.defaultResourcePolicyId!) - : await queryWhitelist(resourceId); + const whitelist = + policyId !== null + ? await queryPolicyWhitelist(policyId) + : await queryWhitelist(resourceId); return response(res, { data: { diff --git a/server/routers/resource/removeEmailFromResourceWhitelist.ts b/server/routers/resource/removeEmailFromResourceWhitelist.ts index 615e62438..03d961335 100644 --- a/server/routers/resource/removeEmailFromResourceWhitelist.ts +++ b/server/routers/resource/removeEmailFromResourceWhitelist.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"; @@ -102,44 +107,110 @@ export async function removeEmailFromResourceWhitelist( ); } - if (!resource.emailWhitelistEnabled) { - return next( - createHttpError( - HttpCode.BAD_REQUEST, - "Email whitelist is not enabled for this resource" - ) - ); + // A shared policy takes precedence over the resource's inline + // (default) policy, which takes precedence over the resource's own + // direct whitelist fields. This mirrors the precedence used at + // request time in authWithWhitelist.ts / getResourceAuthInfo.ts. + const policyId = + resource.resourcePolicyId ?? resource.defaultResourcePolicyId; + + if (policyId !== null) { + const [policy] = await db + .select() + .from(resourcePolicies) + .where(eq(resourcePolicies.resourcePolicyId, policyId)); + + if (!policy) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Resource policy not found" + ) + ); + } + + if (!policy.emailWhitelistEnabled) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email whitelist is not enabled for this resource" + ) + ); + } + + const existingEntry = await db + .select() + .from(resourcePolicyWhiteList) + .where( + and( + eq( + resourcePolicyWhiteList.resourcePolicyId, + policyId + ), + eq(resourcePolicyWhiteList.email, email) + ) + ); + + if (existingEntry.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Email not found in whitelist" + ) + ); + } + + await db + .delete(resourcePolicyWhiteList) + .where( + and( + eq( + resourcePolicyWhiteList.resourcePolicyId, + policyId + ), + eq(resourcePolicyWhiteList.email, email) + ) + ); + } else { + if (!resource.emailWhitelistEnabled) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Email whitelist is not enabled for this resource" + ) + ); + } + + // Check if email exists in whitelist + const existingEntry = await db + .select() + .from(resourceWhitelist) + .where( + and( + eq(resourceWhitelist.resourceId, resourceId), + eq(resourceWhitelist.email, email) + ) + ); + + if (existingEntry.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "Email not found in whitelist" + ) + ); + } + + await db + .delete(resourceWhitelist) + .where( + and( + eq(resourceWhitelist.resourceId, resourceId), + eq(resourceWhitelist.email, email) + ) + ); } - // Check if email exists in whitelist - const existingEntry = await db - .select() - .from(resourceWhitelist) - .where( - and( - eq(resourceWhitelist.resourceId, resourceId), - eq(resourceWhitelist.email, email) - ) - ); - - if (existingEntry.length === 0) { - return next( - createHttpError( - HttpCode.NOT_FOUND, - "Email not found in whitelist" - ) - ); - } - - await db - .delete(resourceWhitelist) - .where( - and( - eq(resourceWhitelist.resourceId, resourceId), - eq(resourceWhitelist.email, email) - ) - ); - return response(res, { data: {}, success: true, diff --git a/server/routers/resource/setResourceWhitelist.ts b/server/routers/resource/setResourceWhitelist.ts index 697ae4541..10bf5b480 100644 --- a/server/routers/resource/setResourceWhitelist.ts +++ b/server/routers/resource/setResourceWhitelist.ts @@ -109,13 +109,14 @@ export async function setResourceWhitelist( ); } - const isInlinePolicy = - resource.resourcePolicyId === null && - resource.defaultResourcePolicyId !== null; - - if (isInlinePolicy) { - const policyId = resource.defaultResourcePolicyId!; + // A shared policy takes precedence over the resource's inline + // (default) policy, which takes precedence over the resource's own + // direct whitelist fields. This mirrors the precedence used at + // request time in authWithWhitelist.ts / getResourceAuthInfo.ts. + const policyId = + resource.resourcePolicyId ?? resource.defaultResourcePolicyId; + if (policyId !== null) { const [policy] = await db .select() .from(resourcePolicies)