diff --git a/server/db/queries/verifySessionQueries.ts b/server/db/queries/verifySessionQueries.ts index 0a22e8df3..a2ae61046 100644 --- a/server/db/queries/verifySessionQueries.ts +++ b/server/db/queries/verifySessionQueries.ts @@ -33,7 +33,9 @@ import { resourcePolicyPassword, ResourcePolicyPassword, resourcePolicyHeaderAuth, - ResourcePolicyHeaderAuth + ResourcePolicyHeaderAuth, + resourceWhitelist, + resourcePolicyWhiteList } from "@server/db"; import { alias } from "@server/db"; import { and, eq, inArray, isNull, or, sql } from "drizzle-orm"; @@ -448,6 +450,36 @@ export async function getResourceRules( return [...directRules, ...offsetPolicyRules] as ResourceRule[]; } +/** + * Get the whitelisted email associated with a resource session's whitelist + * match (either a direct resource whitelist entry or a resource policy + * whitelist entry). + */ +export async function getWhitelistEmail( + whitelistId?: number | null, + policyWhitelistId?: number | null +): Promise { + if (whitelistId) { + const [row] = await db + .select({ email: resourceWhitelist.email }) + .from(resourceWhitelist) + .where(eq(resourceWhitelist.whitelistId, whitelistId)) + .limit(1); + return row?.email ?? null; + } + + if (policyWhitelistId) { + const [row] = await db + .select({ email: resourcePolicyWhiteList.email }) + .from(resourcePolicyWhiteList) + .where(eq(resourcePolicyWhiteList.whitelistId, policyWhitelistId)) + .limit(1); + return row?.email ?? null; + } + + return null; +} + /** * Get organization login page */ diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index a2b219651..a6ae91dea 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -15,7 +15,8 @@ import { getRoleResourceAccess, getUserResourceAccess, getOrgLoginPage, - getUserSessionWithUser + getUserSessionWithUser, + getWhitelistEmail } from "@server/db/queries/verifySessionQueries"; import { getUserOrgRoles } from "@server/lib/userOrgRoles"; import { @@ -94,6 +95,13 @@ type BasicUserData = { role: string | null; }; +// Some auth methods (e.g. email whitelist) only know the remote email and +// have no associated user record to attach userId/username/name/role to. +type EmailOnlyUserData = { + dontStripSession?: boolean; + email: string; +}; + export type { ClientErrorResponse }; export type VerifyUserResponse = { @@ -782,6 +790,18 @@ export async function verifyResourceSession( "Resource allowed because whitelist session is valid" ); + const whitelistCacheKey = `whitelistEmail:${resourceSession.whitelistId}:${resourceSession.policyWhitelistId}`; + let whitelistEmail: string | null | undefined = + localCache.get(whitelistCacheKey); + + if (whitelistEmail === undefined) { + whitelistEmail = await getWhitelistEmail( + resourceSession.whitelistId, + resourceSession.policyWhitelistId + ); + localCache.set(whitelistCacheKey, whitelistEmail, 12); + } + logRequestAudit( { action: true, @@ -793,7 +813,11 @@ export async function verifyResourceSession( parsedBody.data ); - return allowed(res, undefined, dontStripSession); + return allowed( + res, + whitelistEmail ? { email: whitelistEmail } : undefined, + dontStripSession + ); } if (resourceSession.accessTokenId) { @@ -1022,7 +1046,7 @@ async function notAllowed( function allowed( res: Response, - userData?: BasicUserData, + userData?: BasicUserData | EmailOnlyUserData, dontStripSession?: boolean, virtualApiKeyId?: string ) {