From dcc4cb1d0fe3639278e82fcf9623dd49f0895714 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 21 May 2025 17:26:55 +0100 Subject: [PATCH 1/5] make github auth respect email whitelist --- apps/webapp/app/models/user.server.ts | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/webapp/app/models/user.server.ts b/apps/webapp/app/models/user.server.ts index 2e16f2e956..603e12f584 100644 --- a/apps/webapp/app/models/user.server.ts +++ b/apps/webapp/app/models/user.server.ts @@ -38,31 +38,31 @@ export async function findOrCreateUser(input: FindOrCreateUser): Promise { - if (env.WHITELISTED_EMAILS && !new RegExp(env.WHITELISTED_EMAILS).test(input.email)) { +export async function findOrCreateMagicLinkUser({ + email, +}: FindOrCreateMagicLink): Promise { + if (env.WHITELISTED_EMAILS && !new RegExp(env.WHITELISTED_EMAILS).test(email)) { throw new Error("This email is unauthorized"); } const existingUser = await prisma.user.findFirst({ where: { - email: input.email, + email, }, }); const adminEmailRegex = env.ADMIN_EMAILS ? new RegExp(env.ADMIN_EMAILS) : undefined; - const makeAdmin = adminEmailRegex ? adminEmailRegex.test(input.email) : false; + const makeAdmin = adminEmailRegex ? adminEmailRegex.test(email) : false; const user = await prisma.user.upsert({ where: { - email: input.email, + email, }, update: { - email: input.email, + email, }, create: { - email: input.email, + email, authenticationMethod: "MAGIC_LINK", admin: makeAdmin, // only on create, to prevent automatically removing existing admins }, @@ -79,6 +79,10 @@ export async function findOrCreateGithubUser({ authenticationProfile, authenticationExtraParams, }: FindOrCreateGithub): Promise { + if (env.WHITELISTED_EMAILS && !new RegExp(env.WHITELISTED_EMAILS).test(email)) { + throw new Error("This email is unauthorized"); + } + const name = authenticationProfile._json.name; let avatarUrl: string | undefined = undefined; if (authenticationProfile.photos[0]) { From e0adc2bc8b10d8bf3f522ccd9fd543f870715bfd Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 21 May 2025 18:48:25 +0100 Subject: [PATCH 2/5] display github login errors --- apps/webapp/app/routes/login._index/route.tsx | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/routes/login._index/route.tsx b/apps/webapp/app/routes/login._index/route.tsx index 8511a889eb..b92c1bb39e 100644 --- a/apps/webapp/app/routes/login._index/route.tsx +++ b/apps/webapp/app/routes/login._index/route.tsx @@ -6,12 +6,14 @@ import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson"; import { LoginPageLayout } from "~/components/LoginPageLayout"; import { Button, LinkButton } from "~/components/primitives/Buttons"; import { Fieldset } from "~/components/primitives/Fieldset"; +import { FormError } from "~/components/primitives/FormError"; import { Header1 } from "~/components/primitives/Headers"; import { Paragraph } from "~/components/primitives/Paragraph"; import { TextLink } from "~/components/primitives/TextLink"; import { isGithubAuthSupported } from "~/services/auth.server"; import { commitSession, setRedirectTo } from "~/services/redirectTo.server"; import { getUserId } from "~/services/session.server"; +import { getUserSession } from "~/services/sessionStorage.server"; import { requestUrl } from "~/utils/requestUrl.server"; export const meta: MetaFunction = ({ matches }) => { @@ -48,7 +50,11 @@ export async function loader({ request }: LoaderFunctionArgs) { const session = await setRedirectTo(request, redirectTo); return typedjson( - { redirectTo, showGithubAuth: isGithubAuthSupported }, + { + redirectTo, + showGithubAuth: isGithubAuthSupported, + authError: null, + }, { headers: { "Set-Cookie": await commitSession(session), @@ -56,9 +62,22 @@ export async function loader({ request }: LoaderFunctionArgs) { } ); } else { + const session = await getUserSession(request); + const error = session.get("auth:error"); + + let authError: string | undefined; + if (error) { + if ("message" in error) { + authError = error.message; + } else { + authError = JSON.stringify(error, null, 2); + } + } + return typedjson({ redirectTo: null, showGithubAuth: isGithubAuthSupported, + authError, }); } } @@ -81,7 +100,7 @@ export default function LoginPage() { Create an account or login
-
+
{data.showGithubAuth && (
By signing up you agree to our{" "} From cd7a30803aae70e61103b650307c2faa18d39c06 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 21 May 2025 19:32:55 +0100 Subject: [PATCH 3/5] fix smtp secure env vars --- apps/webapp/app/env.server.ts | 5 +++-- apps/webapp/app/utils/boolEnv.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 apps/webapp/app/utils/boolEnv.ts diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 884f31e03d..fc0d994a51 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { SecretStoreOptionsSchema } from "./services/secrets/secretStoreOptionsSchema.server"; import { isValidDatabaseUrl } from "./utils/db"; import { isValidRegex } from "./utils/regex"; +import { BoolEnv } from "./utils/boolEnv"; const EnvironmentSchema = z.object({ NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]), @@ -50,7 +51,7 @@ const EnvironmentSchema = z.object({ RESEND_API_KEY: z.string().optional(), SMTP_HOST: z.string().optional(), SMTP_PORT: z.coerce.number().optional(), - SMTP_SECURE: z.coerce.boolean().optional(), + SMTP_SECURE: BoolEnv.optional(), SMTP_USER: z.string().optional(), SMTP_PASSWORD: z.string().optional(), @@ -338,7 +339,7 @@ const EnvironmentSchema = z.object({ ALERT_RESEND_API_KEY: z.string().optional(), ALERT_SMTP_HOST: z.string().optional(), ALERT_SMTP_PORT: z.coerce.number().optional(), - ALERT_SMTP_SECURE: z.coerce.boolean().optional(), + ALERT_SMTP_SECURE: BoolEnv.optional(), ALERT_SMTP_USER: z.string().optional(), ALERT_SMTP_PASSWORD: z.string().optional(), ALERT_RATE_LIMITER_EMISSION_INTERVAL: z.coerce.number().int().default(2_500), diff --git a/apps/webapp/app/utils/boolEnv.ts b/apps/webapp/app/utils/boolEnv.ts new file mode 100644 index 0000000000..a2609034e3 --- /dev/null +++ b/apps/webapp/app/utils/boolEnv.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const BoolEnv = z.preprocess((val) => { + if (typeof val !== "string") { + return val; + } + + return ["true", "1"].includes(val.toLowerCase().trim()); +}, z.boolean()); From 04c36011c61aeaf81cb1f68fa67fe3c58d6e3ff3 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 21 May 2025 20:51:05 +0100 Subject: [PATCH 4/5] prevent sending magic link if email not allowed --- apps/webapp/app/models/user.server.ts | 10 +++------- apps/webapp/app/services/email.server.ts | 4 +++- apps/webapp/app/utils/email.ts | 13 +++++++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 apps/webapp/app/utils/email.ts diff --git a/apps/webapp/app/models/user.server.ts b/apps/webapp/app/models/user.server.ts index 603e12f584..8a381a8394 100644 --- a/apps/webapp/app/models/user.server.ts +++ b/apps/webapp/app/models/user.server.ts @@ -7,7 +7,7 @@ import { getDashboardPreferences, } from "~/services/dashboardPreferences.server"; export type { User } from "@trigger.dev/database"; - +import { assertEmailAllowed } from "~/utils/email"; type FindOrCreateMagicLink = { authenticationMethod: "MAGIC_LINK"; email: string; @@ -41,9 +41,7 @@ export async function findOrCreateUser(input: FindOrCreateUser): Promise { - if (env.WHITELISTED_EMAILS && !new RegExp(env.WHITELISTED_EMAILS).test(email)) { - throw new Error("This email is unauthorized"); - } + assertEmailAllowed(email); const existingUser = await prisma.user.findFirst({ where: { @@ -79,9 +77,7 @@ export async function findOrCreateGithubUser({ authenticationProfile, authenticationExtraParams, }: FindOrCreateGithub): Promise { - if (env.WHITELISTED_EMAILS && !new RegExp(env.WHITELISTED_EMAILS).test(email)) { - throw new Error("This email is unauthorized"); - } + assertEmailAllowed(email); const name = authenticationProfile._json.name; let avatarUrl: string | undefined = undefined; diff --git a/apps/webapp/app/services/email.server.ts b/apps/webapp/app/services/email.server.ts index 0f14fb28b0..425e2d4497 100644 --- a/apps/webapp/app/services/email.server.ts +++ b/apps/webapp/app/services/email.server.ts @@ -3,11 +3,11 @@ import { EmailClient, MailTransportOptions } from "emails"; import type { SendEmailOptions } from "remix-auth-email-link"; import { redirect } from "remix-typedjson"; import { env } from "~/env.server"; -import type { User } from "~/models/user.server"; import type { AuthUser } from "./authUser"; import { workerQueue } from "./worker.server"; import { logger } from "./logger.server"; import { singleton } from "~/utils/singleton"; +import { assertEmailAllowed } from "~/utils/email"; const client = singleton( "email-client", @@ -66,6 +66,8 @@ function buildTransportOptions(alerts?: boolean): MailTransportOptions { } export async function sendMagicLinkEmail(options: SendEmailOptions): Promise { + assertEmailAllowed(options.emailAddress); + // Auto redirect when in development mode if (env.NODE_ENV === "development") { throw redirect(options.magicLink); diff --git a/apps/webapp/app/utils/email.ts b/apps/webapp/app/utils/email.ts new file mode 100644 index 0000000000..de41ae592a --- /dev/null +++ b/apps/webapp/app/utils/email.ts @@ -0,0 +1,13 @@ +import { env } from "~/env.server"; + +export function assertEmailAllowed(email: string) { + if (!env.WHITELISTED_EMAILS) { + return; + } + + const regexp = new RegExp(env.WHITELISTED_EMAILS); + + if (!regexp.test(email)) { + throw new Error("This email is unauthorized"); + } +} From 72691602d4a069ecb1ae201f28d85079e6a65996 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Wed, 21 May 2025 21:01:07 +0100 Subject: [PATCH 5/5] fix MARQS_DISABLE_REBALANCING and RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS --- apps/webapp/app/env.server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index fc0d994a51..38360c93c3 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -379,7 +379,7 @@ const EnvironmentSchema = z.object({ MAX_SEQUENTIAL_INDEX_FAILURE_COUNT: z.coerce.number().default(96), LOOPS_API_KEY: z.string().optional(), - MARQS_DISABLE_REBALANCING: z.coerce.boolean().default(false), + MARQS_DISABLE_REBALANCING: BoolEnv.default(false), MARQS_VISIBILITY_TIMEOUT_MS: z.coerce .number() .int() @@ -457,7 +457,7 @@ const EnvironmentSchema = z.object({ .number() .int() .default(60_000 * 10), - RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: z.coerce.boolean().default(false), + RUN_ENGINE_DEBUG_WORKER_NOTIFICATIONS: BoolEnv.default(false), RUN_ENGINE_PARENT_QUEUE_LIMIT: z.coerce.number().int().default(1000), RUN_ENGINE_CONCURRENCY_LIMIT_BIAS: z.coerce.number().default(0.75), RUN_ENGINE_AVAILABLE_CAPACITY_BIAS: z.coerce.number().default(0.3),