diff --git a/.env.example b/.env.example index 48170e8f365..25645d52092 100644 --- a/.env.example +++ b/.env.example @@ -24,6 +24,11 @@ NODE_ENV=development # FROM_EMAIL= # REPLY_TO_EMAIL= +# OPTIONAL VARIABLES +WHITELISTED_EMAILS="matt@gmail\.com|jane@yahoo\.com" +# Description: This environment variable defines a regex pattern for allowed email addresses. +# Only emails that match this pattern will be able to sign up or log in. + # CLOUD VARIABLES POSTHOG_PROJECT_KEY= PLAIN_API_KEY= diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts index 4e229834c42..29674276f2d 100644 --- a/apps/webapp/app/env.server.ts +++ b/apps/webapp/app/env.server.ts @@ -11,6 +11,7 @@ const EnvironmentSchema = z.object({ MAGIC_LINK_SECRET: z.string(), ENCRYPTION_KEY: z.string(), REMIX_APP_PORT: z.string().optional(), + WHITELISTED_EMAILS: z.string().optional(), LOGIN_ORIGIN: z.string().default("http://localhost:3030"), APP_ORIGIN: z.string().default("http://localhost:3030"), APP_ENV: z diff --git a/apps/webapp/app/models/user.server.ts b/apps/webapp/app/models/user.server.ts index 31e8621edb4..42e250ebbee 100644 --- a/apps/webapp/app/models/user.server.ts +++ b/apps/webapp/app/models/user.server.ts @@ -1,8 +1,13 @@ import type { Prisma, User } from "@trigger.dev/database"; import type { GitHubProfile } from "remix-auth-github"; import { prisma } from "~/db.server"; +import { env } from "~/env.server"; +import { authenticator } from "~/services/auth.server"; +import { addEmailLinkStrategy } from "~/services/emailAuth.server"; export type { User } from "@trigger.dev/database"; +addEmailLinkStrategy(authenticator); + type FindOrCreateMagicLink = { authenticationMethod: "MAGIC_LINK"; email: string; @@ -22,7 +27,12 @@ type LoggedInUser = { isNewUser: boolean; }; +class EmailWhitelistError extends Error {} + export async function findOrCreateUser(input: FindOrCreateUser): Promise { + if (!isEmailWhitelisted(input.email, env.WHITELISTED_EMAILS)) { + throw new EmailWhitelistError("Access to this instance is restricted."); + } switch (input.authenticationMethod) { case "GITHUB": { return findOrCreateGithubUser(input); @@ -178,3 +188,11 @@ export async function grantUserCloudAccess({ id, inviteCode }: { id: string; inv }, }); } + +function isEmailWhitelisted(email: string, whitelist: string | undefined) { + if (whitelist) { + const regex = new RegExp(whitelist); + return regex.test(email); + } + return true; // No whitelist means all emails are allowed +} \ No newline at end of file diff --git a/apps/webapp/app/routes/magic.tsx b/apps/webapp/app/routes/magic.tsx index 6a65b5cc9d2..d137cbf9137 100644 --- a/apps/webapp/app/routes/magic.tsx +++ b/apps/webapp/app/routes/magic.tsx @@ -7,6 +7,6 @@ export async function loader({ request }: LoaderArgs) { await authenticator.authenticate("email-link", request, { successRedirect: redirectTo ?? "/", - failureRedirect: "/login", + failureRedirect: "/login/magic", }); }