From 61b34c8b16550d06dfe7e193c08716e62dd2f32d Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Sun, 26 Jan 2025 18:12:30 -0500 Subject: [PATCH] allow wildcard emails in email whitelist --- server/routers/resource/authWithWhitelist.ts | 52 ++++++++++++++----- .../routers/resource/setResourceWhitelist.ts | 12 ++++- .../[resourceId]/authentication/page.tsx | 18 ++++++- src/components/ui/info-popup.tsx | 38 ++++++++++++++ 4 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 src/components/ui/info-popup.tsx diff --git a/server/routers/resource/authWithWhitelist.ts b/server/routers/resource/authWithWhitelist.ts index c58c7efd..f97b3035 100644 --- a/server/routers/resource/authWithWhitelist.ts +++ b/server/routers/resource/authWithWhitelist.ts @@ -13,9 +13,7 @@ import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; import { fromError } from "zod-validation-error"; -import { - createResourceSession, -} from "@server/auth/sessions/resource"; +import { createResourceSession } from "@server/auth/sessions/resource"; import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp"; import logger from "@server/logger"; @@ -90,20 +88,48 @@ export async function authWithWhitelist( .leftJoin(orgs, eq(orgs.orgId, resources.orgId)) .limit(1); - const resource = result?.resources; - const org = result?.orgs; - const whitelistedEmail = result?.resourceWhitelist; + let resource = result?.resources; + let org = result?.orgs; + let whitelistedEmail = result?.resourceWhitelist; if (!whitelistedEmail) { - return next( - createHttpError( - HttpCode.UNAUTHORIZED, - createHttpError( - HttpCode.BAD_REQUEST, - "Email is not whitelisted" + // if email is not found, check for wildcard email + const wildcard = "*@" + email.split("@")[1]; + + logger.debug("Checking for wildcard email: " + wildcard) + + const [result] = await db + .select() + .from(resourceWhitelist) + .where( + and( + eq(resourceWhitelist.resourceId, resourceId), + eq(resourceWhitelist.email, wildcard) ) ) - ); + .leftJoin( + resources, + eq(resources.resourceId, resourceWhitelist.resourceId) + ) + .leftJoin(orgs, eq(orgs.orgId, resources.orgId)) + .limit(1); + + resource = result?.resources; + org = result?.orgs; + whitelistedEmail = result?.resourceWhitelist; + + // if wildcard is still not found, return unauthorized + if (!whitelistedEmail) { + return next( + createHttpError( + HttpCode.UNAUTHORIZED, + createHttpError( + HttpCode.BAD_REQUEST, + "Email is not whitelisted" + ) + ) + ); + } } if (!org) { diff --git a/server/routers/resource/setResourceWhitelist.ts b/server/routers/resource/setResourceWhitelist.ts index 8931d4ff..d60d079d 100644 --- a/server/routers/resource/setResourceWhitelist.ts +++ b/server/routers/resource/setResourceWhitelist.ts @@ -12,7 +12,17 @@ import { and, eq } from "drizzle-orm"; const setResourceWhitelistBodySchema = z .object({ emails: z - .array(z.string().email()) + .array( + z + .string() + .email() + .or( + z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, { + message: + "Invalid email address. Wildcard (*) must be the entire local part." + }) + ) + ) .max(50) .transform((v) => v.map((e) => e.toLowerCase())) }) diff --git a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx index c59806b5..64464d60 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/authentication/page.tsx @@ -48,6 +48,7 @@ import { SettingsSectionFooter } from "@app/components/Settings"; import { SwitchInput } from "@app/components/SwitchInput"; +import { InfoPopup } from "@app/components/ui/info-popup"; const UsersRolesFormSchema = z.object({ roles: z.array( @@ -665,10 +666,12 @@ export default function ResourceAuthenticationPage() { render={({ field }) => ( - Whitelisted Emails + - {/* @ts-ignore */} {/* @ts-ignore */} + {text} + + + + + +

{info}

+
+
+ + ); +}