From 9e4f552e7c51aa324660fec5d3eb0bf792e5e395 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Sun, 15 Jun 2025 15:05:38 +0000 Subject: [PATCH 1/3] feat: disable registration + handle env booleans --- apps/sim/app/(auth)/signup/page.tsx | 5 +++ .../sim/app/(auth)/verify/use-verification.ts | 17 +++++++-- apps/sim/lib/auth.ts | 25 +++++++++--- apps/sim/lib/env.ts | 38 +++++++++++++------ apps/sim/next.config.ts | 13 ++++--- 5 files changed, 72 insertions(+), 26 deletions(-) diff --git a/apps/sim/app/(auth)/signup/page.tsx b/apps/sim/app/(auth)/signup/page.tsx index c469074e30f..c415382ef2b 100644 --- a/apps/sim/app/(auth)/signup/page.tsx +++ b/apps/sim/app/(auth)/signup/page.tsx @@ -1,3 +1,4 @@ +import { env, isTruthy } from '@/lib/env' import { getOAuthProviderStatus } from '../components/oauth-provider-checker' import SignupForm from './signup-form' @@ -7,6 +8,10 @@ export const dynamic = 'force-dynamic' export default async function SignupPage() { const { githubAvailable, googleAvailable, isProduction } = await getOAuthProviderStatus() + if (isTruthy(env.DISABLE_REGISTRATION)) { + return
Registration is disabled, please contact your admin.
+ } + return ( { - logger.info('Notification store state:', { addNotification: !!addNotification }) + logger.info('Notification store state:', { + addNotification: !!addNotification, + }) }, [addNotification]) useEffect(() => { @@ -154,7 +157,10 @@ export function useVerification({ // Set both state variables to ensure the error shows setIsInvalidOtp(true) setErrorMessage(message) - logger.info('Error state after API error:', { isInvalidOtp: true, errorMessage: message }) + logger.info('Error state after API error:', { + isInvalidOtp: true, + errorMessage: message, + }) // Clear the OTP input on invalid code setOtp('') } @@ -173,7 +179,10 @@ export function useVerification({ // Set both state variables to ensure the error shows setIsInvalidOtp(true) setErrorMessage(message) - logger.info('Error state after caught error:', { isInvalidOtp: true, errorMessage: message }) + logger.info('Error state after caught error:', { + isInvalidOtp: true, + errorMessage: message, + }) // Clear the OTP input on error setOtp('') @@ -218,7 +227,7 @@ export function useVerification({ logger.info('Auto-verifying user', { email: storedEmail }) } - const isDevOrDocker = !isProduction || process.env.DOCKER_BUILD === 'true' + const isDevOrDocker = !isProduction || isTruthy(env.DOCKER_BUILD) // Auto-verify and redirect in development/docker environments if (isDevOrDocker || !hasResendKey) { diff --git a/apps/sim/lib/auth.ts b/apps/sim/lib/auth.ts index 6b0be3a33cf..8031024697e 100644 --- a/apps/sim/lib/auth.ts +++ b/apps/sim/lib/auth.ts @@ -2,7 +2,7 @@ import { stripe } from '@better-auth/stripe' import { betterAuth } from 'better-auth' import { drizzleAdapter } from 'better-auth/adapters/drizzle' import { nextCookies } from 'better-auth/next-js' -import { emailOTP, genericOAuth, organization } from 'better-auth/plugins' +import { createAuthMiddleware, emailOTP, genericOAuth, organization } from 'better-auth/plugins' import { and, eq } from 'drizzle-orm' import { headers } from 'next/headers' import { Resend } from 'resend' @@ -16,7 +16,7 @@ import { import { createLogger } from '@/lib/logs/console-logger' import { db } from '@/db' import * as schema from '@/db/schema' -import { env } from './env' +import { env, isTruthy } from './env' import { getEmailDomain } from './urls/utils' const logger = createLogger('Auth') @@ -93,10 +93,15 @@ export const auth = betterAuth({ }, } } - logger.info('No organizations found for user', { userId: session.userId }) + logger.info('No organizations found for user', { + userId: session.userId, + }) return { data: session } } catch (error) { - logger.error('Error setting active organization', { error, userId: session.userId }) + logger.error('Error setting active organization', { + error, + userId: session.userId, + }) return { data: session } } }, @@ -158,6 +163,14 @@ export const auth = betterAuth({ } }, }, + hooks: { + before: createAuthMiddleware(async (ctx) => { + if (ctx.path.startsWith('/sign-up') && isTruthy(env.DISABLE_REGISTRATION)) + throw new Error('Registration is disabled, please contact your admin.') + + return + }), + }, plugins: [ nextCookies(), emailOTP({ @@ -469,7 +482,9 @@ export const auth = betterAuth({ userId = decodedToken.sub } } catch (e) { - logger.warn('Failed to decode Supabase ID token', { error: e }) + logger.warn('Failed to decode Supabase ID token', { + error: e, + }) } } diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index 2212dac5e39..62ee85ef1c6 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -1,8 +1,9 @@ -import { createEnv } from '@t3-oss/env-nextjs' -import { env as runtimeEnv } from 'next-runtime-env' -import { z } from 'zod' +import { createEnv } from "@t3-oss/env-nextjs"; +import { env as runtimeEnv } from "next-runtime-env"; +import { z } from "zod"; -const getEnv = (variable: string) => runtimeEnv(variable) ?? process.env[variable] +const getEnv = (variable: string) => + runtimeEnv(variable) ?? process.env[variable]; export const env = createEnv({ skipValidation: true, @@ -11,6 +12,9 @@ export const env = createEnv({ DATABASE_URL: z.string().url(), BETTER_AUTH_URL: z.string().url(), BETTER_AUTH_SECRET: z.string().min(32), + DISABLE_REGISTRATION: z + .preprocess((v) => v === "true" || v === "1", z.boolean()) + .optional(), ENCRYPTION_KEY: z.string().min(32), POSTGRES_URL: z.string().url().optional(), @@ -84,7 +88,9 @@ export const env = createEnv({ MICROSOFT_CLIENT_SECRET: z.string().optional(), HUBSPOT_CLIENT_ID: z.string().optional(), HUBSPOT_CLIENT_SECRET: z.string().optional(), - DOCKER_BUILD: z.boolean().optional(), + DOCKER_BUILD: z + .preprocess((v) => v === "true" || v === "1", z.boolean()) + .optional(), LINEAR_CLIENT_ID: z.string().optional(), LINEAR_CLIENT_SECRET: z.string().optional(), SLACK_CLIENT_ID: z.string().optional(), @@ -102,11 +108,19 @@ export const env = createEnv({ // Only need to define client variables, server variables are automatically handled experimental__runtimeEnv: { - NEXT_PUBLIC_APP_URL: getEnv('NEXT_PUBLIC_APP_URL'), - NEXT_PUBLIC_VERCEL_URL: getEnv('NEXT_PUBLIC_VERCEL_URL'), - NEXT_PUBLIC_SENTRY_DSN: getEnv('NEXT_PUBLIC_SENTRY_DSN'), - NEXT_PUBLIC_GOOGLE_CLIENT_ID: getEnv('NEXT_PUBLIC_GOOGLE_CLIENT_ID'), - NEXT_PUBLIC_GOOGLE_API_KEY: getEnv('NEXT_PUBLIC_GOOGLE_API_KEY'), - NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: getEnv('NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER'), + NEXT_PUBLIC_APP_URL: getEnv("NEXT_PUBLIC_APP_URL"), + NEXT_PUBLIC_VERCEL_URL: getEnv("NEXT_PUBLIC_VERCEL_URL"), + NEXT_PUBLIC_SENTRY_DSN: getEnv("NEXT_PUBLIC_SENTRY_DSN"), + NEXT_PUBLIC_GOOGLE_CLIENT_ID: getEnv("NEXT_PUBLIC_GOOGLE_CLIENT_ID"), + NEXT_PUBLIC_GOOGLE_API_KEY: getEnv("NEXT_PUBLIC_GOOGLE_API_KEY"), + NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: getEnv( + "NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER" + ), }, -}) +}); + +// Needing this utility because t3-env is returning string for boolean values. +export const isTruthy = (value: string | boolean | number | undefined) => + typeof value === "string" + ? value === "true" || value === "1" + : Boolean(value); diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index ec1968ce8c5..8c39a1f4493 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -1,7 +1,7 @@ import path from 'path' import { withSentryConfig } from '@sentry/nextjs' import type { NextConfig } from 'next' -import { env } from './lib/env' +import { env, isTruthy } from './lib/env' const nextConfig: NextConfig = { devIndicators: false, @@ -13,12 +13,12 @@ const nextConfig: NextConfig = { ], }, typescript: { - ignoreBuildErrors: env.DOCKER_BUILD, + ignoreBuildErrors: isTruthy(env.DOCKER_BUILD), }, eslint: { - ignoreDuringBuilds: env.DOCKER_BUILD, + ignoreDuringBuilds: isTruthy(env.DOCKER_BUILD), }, - output: env.DOCKER_BUILD ? 'standalone' : undefined, + output: isTruthy(env.DOCKER_BUILD) ? 'standalone' : undefined, turbopack: { resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.json'], }, @@ -99,7 +99,10 @@ const nextConfig: NextConfig = { source: '/api/workflows/:id/execute', headers: [ { key: 'Access-Control-Allow-Origin', value: '*' }, - { key: 'Access-Control-Allow-Methods', value: 'GET,POST,OPTIONS,PUT' }, + { + key: 'Access-Control-Allow-Methods', + value: 'GET,POST,OPTIONS,PUT', + }, { key: 'Access-Control-Allow-Headers', value: From cdb54651f42bf3162cb8e24558ed8a95243e014a Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Sun, 15 Jun 2025 15:12:30 +0000 Subject: [PATCH 2/3] chore: removing pre-process because we need to use util --- apps/sim/lib/env.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index 62ee85ef1c6..a472ab43ad6 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -12,9 +12,7 @@ export const env = createEnv({ DATABASE_URL: z.string().url(), BETTER_AUTH_URL: z.string().url(), BETTER_AUTH_SECRET: z.string().min(32), - DISABLE_REGISTRATION: z - .preprocess((v) => v === "true" || v === "1", z.boolean()) - .optional(), + DISABLE_REGISTRATION: z.boolean().optional(), ENCRYPTION_KEY: z.string().min(32), POSTGRES_URL: z.string().url().optional(), @@ -88,9 +86,7 @@ export const env = createEnv({ MICROSOFT_CLIENT_SECRET: z.string().optional(), HUBSPOT_CLIENT_ID: z.string().optional(), HUBSPOT_CLIENT_SECRET: z.string().optional(), - DOCKER_BUILD: z - .preprocess((v) => v === "true" || v === "1", z.boolean()) - .optional(), + DOCKER_BUILD: z.boolean().optional(), LINEAR_CLIENT_ID: z.string().optional(), LINEAR_CLIENT_SECRET: z.string().optional(), SLACK_CLIENT_ID: z.string().optional(), From 2189c0927a9f69f9002e93be422f7ca80a6aedd7 Mon Sep 17 00:00:00 2001 From: Aditya Tripathi Date: Sun, 15 Jun 2025 15:13:29 +0000 Subject: [PATCH 3/3] chore: format --- apps/sim/lib/env.ts | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index a472ab43ad6..91b7195fca6 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -1,9 +1,8 @@ -import { createEnv } from "@t3-oss/env-nextjs"; -import { env as runtimeEnv } from "next-runtime-env"; -import { z } from "zod"; +import { createEnv } from '@t3-oss/env-nextjs' +import { env as runtimeEnv } from 'next-runtime-env' +import { z } from 'zod' -const getEnv = (variable: string) => - runtimeEnv(variable) ?? process.env[variable]; +const getEnv = (variable: string) => runtimeEnv(variable) ?? process.env[variable] export const env = createEnv({ skipValidation: true, @@ -104,19 +103,15 @@ export const env = createEnv({ // Only need to define client variables, server variables are automatically handled experimental__runtimeEnv: { - NEXT_PUBLIC_APP_URL: getEnv("NEXT_PUBLIC_APP_URL"), - NEXT_PUBLIC_VERCEL_URL: getEnv("NEXT_PUBLIC_VERCEL_URL"), - NEXT_PUBLIC_SENTRY_DSN: getEnv("NEXT_PUBLIC_SENTRY_DSN"), - NEXT_PUBLIC_GOOGLE_CLIENT_ID: getEnv("NEXT_PUBLIC_GOOGLE_CLIENT_ID"), - NEXT_PUBLIC_GOOGLE_API_KEY: getEnv("NEXT_PUBLIC_GOOGLE_API_KEY"), - NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: getEnv( - "NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER" - ), + NEXT_PUBLIC_APP_URL: getEnv('NEXT_PUBLIC_APP_URL'), + NEXT_PUBLIC_VERCEL_URL: getEnv('NEXT_PUBLIC_VERCEL_URL'), + NEXT_PUBLIC_SENTRY_DSN: getEnv('NEXT_PUBLIC_SENTRY_DSN'), + NEXT_PUBLIC_GOOGLE_CLIENT_ID: getEnv('NEXT_PUBLIC_GOOGLE_CLIENT_ID'), + NEXT_PUBLIC_GOOGLE_API_KEY: getEnv('NEXT_PUBLIC_GOOGLE_API_KEY'), + NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: getEnv('NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER'), }, -}); +}) // Needing this utility because t3-env is returning string for boolean values. export const isTruthy = (value: string | boolean | number | undefined) => - typeof value === "string" - ? value === "true" || value === "1" - : Boolean(value); + typeof value === 'string' ? value === 'true' || value === '1' : Boolean(value)