diff --git a/apps/sim/.env.example b/apps/sim/.env.example index 7c6ca476da..f8e926f885 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -4,10 +4,13 @@ DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres" # PostgreSQL Port (Optional) - defaults to 5432 if not specified # POSTGRES_PORT=5432 -# Authentication (Required) +# Authentication (Required unless DISABLE_AUTH=true) BETTER_AUTH_SECRET=your_secret_key # Use `openssl rand -hex 32` to generate, or visit https://www.better-auth.com/docs/installation BETTER_AUTH_URL=http://localhost:3000 +# Authentication Bypass (Optional - for self-hosted deployments behind private networks) +# DISABLE_AUTH=true # Uncomment to bypass authentication entirely. Creates an anonymous session for all requests. + # NextJS (Required) NEXT_PUBLIC_APP_URL=http://localhost:3000 diff --git a/apps/sim/app/(auth)/components/oauth-provider-checker.tsx b/apps/sim/app/(auth)/components/oauth-provider-checker.tsx index 5651e1d5ec..c7eba7af1b 100644 --- a/apps/sim/app/(auth)/components/oauth-provider-checker.tsx +++ b/apps/sim/app/(auth)/components/oauth-provider-checker.tsx @@ -1,7 +1,7 @@ 'use server' import { env } from '@/lib/core/config/env' -import { isProd } from '@/lib/core/config/environment' +import { isProd } from '@/lib/core/config/feature-flags' export async function getOAuthProviderStatus() { const githubAvailable = !!(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET) diff --git a/apps/sim/app/(auth)/login/page.tsx b/apps/sim/app/(auth)/login/page.tsx index 91648a5e00..d0173542b8 100644 --- a/apps/sim/app/(auth)/login/page.tsx +++ b/apps/sim/app/(auth)/login/page.tsx @@ -1,7 +1,6 @@ import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker' import LoginForm from '@/app/(auth)/login/login-form' -// Force dynamic rendering to avoid prerender errors with search params export const dynamic = 'force-dynamic' export default async function LoginPage() { diff --git a/apps/sim/app/(auth)/signup/page.tsx b/apps/sim/app/(auth)/signup/page.tsx index f8878e8aa8..b267716e35 100644 --- a/apps/sim/app/(auth)/signup/page.tsx +++ b/apps/sim/app/(auth)/signup/page.tsx @@ -1,16 +1,16 @@ -import { env, isTruthy } from '@/lib/core/config/env' +import { isRegistrationDisabled } from '@/lib/core/config/feature-flags' import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker' import SignupForm from '@/app/(auth)/signup/signup-form' export const dynamic = 'force-dynamic' export default async function SignupPage() { - const { githubAvailable, googleAvailable, isProduction } = await getOAuthProviderStatus() - - if (isTruthy(env.DISABLE_REGISTRATION)) { + if (isRegistrationDisabled) { return
Registration is disabled, please contact your admin.
} + const { githubAvailable, googleAvailable, isProduction } = await getOAuthProviderStatus() + return ( ({ + isDev: true, + isHosted: false, + isProd: false, +})) + describe('Chat Edit API Route', () => { const mockSelect = vi.fn() const mockFrom = vi.fn() @@ -24,7 +30,6 @@ describe('Chat Edit API Route', () => { beforeEach(() => { vi.resetModules() - // Set default return values mockLimit.mockResolvedValue([]) mockSelect.mockReturnValue({ from: mockFrom }) mockFrom.mockReturnValue({ where: mockWhere }) @@ -77,10 +82,6 @@ describe('Chat Edit API Route', () => { getEmailDomain: vi.fn().mockReturnValue('localhost:3000'), })) - vi.doMock('@/lib/core/config/environment', () => ({ - isDev: true, - })) - vi.doMock('@/app/api/chat/utils', () => ({ checkChatAccess: mockCheckChatAccess, })) @@ -254,7 +255,6 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - // Reset and reconfigure mockLimit to return the conflict mockLimit.mockReset() mockLimit.mockResolvedValue([{ id: 'other-chat-id', identifier: 'new-identifier' }]) mockWhere.mockReturnValue({ limit: mockLimit }) @@ -291,7 +291,7 @@ describe('Chat Edit API Route', () => { const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', - body: JSON.stringify({ authType: 'password' }), // No password provided + body: JSON.stringify({ authType: 'password' }), }) const { PATCH } = await import('@/app/api/chat/manage/[id]/route') const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) @@ -316,9 +316,8 @@ describe('Chat Edit API Route', () => { workflowId: 'workflow-123', } - // User doesn't own chat but has workspace admin access mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - mockLimit.mockResolvedValueOnce([]) // No identifier conflict + mockLimit.mockResolvedValueOnce([]) const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', @@ -399,7 +398,6 @@ describe('Chat Edit API Route', () => { }), })) - // User doesn't own chat but has workspace admin access mockCheckChatAccess.mockResolvedValue({ hasAccess: true }) mockWhere.mockResolvedValue(undefined) diff --git a/apps/sim/app/api/chat/manage/[id]/route.ts b/apps/sim/app/api/chat/manage/[id]/route.ts index c624582e97..d7141aa2e0 100644 --- a/apps/sim/app/api/chat/manage/[id]/route.ts +++ b/apps/sim/app/api/chat/manage/[id]/route.ts @@ -4,7 +4,7 @@ import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { isDev } from '@/lib/core/config/environment' +import { isDev } from '@/lib/core/config/feature-flags' import { encryptSecret } from '@/lib/core/security/encryption' import { getEmailDomain } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' diff --git a/apps/sim/app/api/chat/route.ts b/apps/sim/app/api/chat/route.ts index a2476424ce..3a49f32cf0 100644 --- a/apps/sim/app/api/chat/route.ts +++ b/apps/sim/app/api/chat/route.ts @@ -5,7 +5,7 @@ import type { NextRequest } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { isDev } from '@/lib/core/config/environment' +import { isDev } from '@/lib/core/config/feature-flags' import { encryptSecret } from '@/lib/core/security/encryption' import { getBaseUrl } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' diff --git a/apps/sim/app/api/chat/utils.test.ts b/apps/sim/app/api/chat/utils.test.ts index 9faa65b1ea..188c03b110 100644 --- a/apps/sim/app/api/chat/utils.test.ts +++ b/apps/sim/app/api/chat/utils.test.ts @@ -44,6 +44,12 @@ vi.mock('@/lib/core/utils/request', () => ({ generateRequestId: vi.fn(), })) +vi.mock('@/lib/core/config/feature-flags', () => ({ + isDev: true, + isHosted: false, + isProd: false, +})) + describe('Chat API Utils', () => { beforeEach(() => { vi.doMock('@/lib/logs/console/logger', () => ({ @@ -62,11 +68,6 @@ describe('Chat API Utils', () => { NODE_ENV: 'development', }, }) - - vi.doMock('@/lib/core/config/environment', () => ({ - isDev: true, - isHosted: false, - })) }) afterEach(() => { diff --git a/apps/sim/app/api/chat/utils.ts b/apps/sim/app/api/chat/utils.ts index c8b76d92fc..94cc1ec300 100644 --- a/apps/sim/app/api/chat/utils.ts +++ b/apps/sim/app/api/chat/utils.ts @@ -3,7 +3,7 @@ import { db } from '@sim/db' import { chat, workflow } from '@sim/db/schema' import { eq } from 'drizzle-orm' import type { NextRequest, NextResponse } from 'next/server' -import { isDev } from '@/lib/core/config/environment' +import { isDev } from '@/lib/core/config/feature-flags' import { decryptSecret } from '@/lib/core/security/encryption' import { createLogger } from '@/lib/logs/console/logger' import { hasAdminPermission } from '@/lib/workspaces/permissions/utils' @@ -282,8 +282,8 @@ export async function validateChatAuth( return { authorized: false, error: 'Email not authorized for SSO access' } } - const { auth } = await import('@/lib/auth') - const session = await auth.api.getSession({ headers: request.headers }) + const { getSession } = await import('@/lib/auth') + const session = await getSession() if (!session || !session.user) { return { authorized: false, error: 'auth_required_sso' } diff --git a/apps/sim/app/api/copilot/auto-allowed-tools/route.ts b/apps/sim/app/api/copilot/auto-allowed-tools/route.ts index d2e4fd0756..13a2d2e9e7 100644 --- a/apps/sim/app/api/copilot/auto-allowed-tools/route.ts +++ b/apps/sim/app/api/copilot/auto-allowed-tools/route.ts @@ -2,7 +2,7 @@ import { db } from '@sim/db' import { settings } from '@sim/db/schema' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { auth } from '@/lib/auth' +import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotAutoAllowedToolsAPI') @@ -10,9 +10,9 @@ const logger = createLogger('CopilotAutoAllowedToolsAPI') /** * GET - Fetch user's auto-allowed integration tools */ -export async function GET(request: NextRequest) { +export async function GET() { try { - const session = await auth.api.getSession({ headers: request.headers }) + const session = await getSession() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) @@ -31,7 +31,6 @@ export async function GET(request: NextRequest) { return NextResponse.json({ autoAllowedTools }) } - // If no settings record exists, create one with empty array await db.insert(settings).values({ id: userId, userId, @@ -50,7 +49,7 @@ export async function GET(request: NextRequest) { */ export async function POST(request: NextRequest) { try { - const session = await auth.api.getSession({ headers: request.headers }) + const session = await getSession() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) @@ -65,13 +64,11 @@ export async function POST(request: NextRequest) { const toolId = body.toolId - // Get existing settings const [existing] = await db.select().from(settings).where(eq(settings.userId, userId)).limit(1) if (existing) { const currentTools = (existing.copilotAutoAllowedTools as string[]) || [] - // Add tool if not already present if (!currentTools.includes(toolId)) { const updatedTools = [...currentTools, toolId] await db @@ -89,7 +86,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ success: true, autoAllowedTools: currentTools }) } - // Create new settings record with the tool await db.insert(settings).values({ id: userId, userId, @@ -109,7 +105,7 @@ export async function POST(request: NextRequest) { */ export async function DELETE(request: NextRequest) { try { - const session = await auth.api.getSession({ headers: request.headers }) + const session = await getSession() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) @@ -123,7 +119,6 @@ export async function DELETE(request: NextRequest) { return NextResponse.json({ error: 'toolId query parameter is required' }, { status: 400 }) } - // Get existing settings const [existing] = await db.select().from(settings).where(eq(settings.userId, userId)).limit(1) if (existing) { diff --git a/apps/sim/app/api/copilot/user-models/route.ts b/apps/sim/app/api/copilot/user-models/route.ts index fcbfd471a0..5708b3f602 100644 --- a/apps/sim/app/api/copilot/user-models/route.ts +++ b/apps/sim/app/api/copilot/user-models/route.ts @@ -1,6 +1,6 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { auth } from '@/lib/auth' +import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console/logger' import { db } from '@/../../packages/db' import { settings } from '@/../../packages/db/schema' @@ -32,7 +32,7 @@ const DEFAULT_ENABLED_MODELS: Record = { // GET - Fetch user's enabled models export async function GET(request: NextRequest) { try { - const session = await auth.api.getSession({ headers: request.headers }) + const session = await getSession() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) @@ -40,7 +40,6 @@ export async function GET(request: NextRequest) { const userId = session.user.id - // Try to fetch existing settings record const [userSettings] = await db .select() .from(settings) @@ -50,13 +49,11 @@ export async function GET(request: NextRequest) { if (userSettings) { const userModelsMap = (userSettings.copilotEnabledModels as Record) || {} - // Merge: start with defaults, then override with user's existing preferences const mergedModels = { ...DEFAULT_ENABLED_MODELS } for (const [modelId, enabled] of Object.entries(userModelsMap)) { mergedModels[modelId] = enabled } - // If we added any new models, update the database const hasNewModels = Object.keys(DEFAULT_ENABLED_MODELS).some( (key) => !(key in userModelsMap) ) @@ -76,7 +73,6 @@ export async function GET(request: NextRequest) { }) } - // If no settings record exists, create one with defaults await db.insert(settings).values({ id: userId, userId, @@ -97,7 +93,7 @@ export async function GET(request: NextRequest) { // PUT - Update user's enabled models export async function PUT(request: NextRequest) { try { - const session = await auth.api.getSession({ headers: request.headers }) + const session = await getSession() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) @@ -110,11 +106,9 @@ export async function PUT(request: NextRequest) { return NextResponse.json({ error: 'enabledModels must be an object' }, { status: 400 }) } - // Check if settings record exists const [existing] = await db.select().from(settings).where(eq(settings.userId, userId)).limit(1) if (existing) { - // Update existing record await db .update(settings) .set({ @@ -123,7 +117,6 @@ export async function PUT(request: NextRequest) { }) .where(eq(settings.userId, userId)) } else { - // Create new settings record await db.insert(settings).values({ id: userId, userId, diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 5a6e5d8719..a7835c72df 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -1,6 +1,6 @@ import { createContext, Script } from 'vm' import { type NextRequest, NextResponse } from 'next/server' -import { env, isTruthy } from '@/lib/core/config/env' +import { isE2bEnabled } from '@/lib/core/config/feature-flags' import { validateProxyUrl } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { executeInE2B } from '@/lib/execution/e2b' @@ -701,7 +701,6 @@ export async function POST(req: NextRequest) { resolvedCode = codeResolution.resolvedCode const contextVariables = codeResolution.contextVariables - const e2bEnabled = isTruthy(env.E2B_ENABLED) const lang = isValidCodeLanguage(language) ? language : DEFAULT_CODE_LANGUAGE // Extract imports once for JavaScript code (reuse later to avoid double extraction) @@ -722,14 +721,14 @@ export async function POST(req: NextRequest) { } // Python always requires E2B - if (lang === CodeLanguage.Python && !e2bEnabled) { + if (lang === CodeLanguage.Python && !isE2bEnabled) { throw new Error( 'Python execution requires E2B to be enabled. Please contact your administrator to enable E2B, or use JavaScript instead.' ) } // JavaScript with imports requires E2B - if (lang === CodeLanguage.JavaScript && hasImports && !e2bEnabled) { + if (lang === CodeLanguage.JavaScript && hasImports && !isE2bEnabled) { throw new Error( 'JavaScript code with import statements requires E2B to be enabled. Please remove the import statements, or contact your administrator to enable E2B.' ) @@ -740,13 +739,13 @@ export async function POST(req: NextRequest) { // - Not a custom tool AND // - (Python OR JavaScript with imports) const useE2B = - e2bEnabled && + isE2bEnabled && !isCustomTool && (lang === CodeLanguage.Python || (lang === CodeLanguage.JavaScript && hasImports)) if (useE2B) { logger.info(`[${requestId}] E2B status`, { - enabled: e2bEnabled, + enabled: isE2bEnabled, hasApiKey: Boolean(process.env.E2B_API_KEY), language: lang, }) diff --git a/apps/sim/app/api/organizations/[id]/seats/route.ts b/apps/sim/app/api/organizations/[id]/seats/route.ts index ecc0ba3b12..9f877e3b36 100644 --- a/apps/sim/app/api/organizations/[id]/seats/route.ts +++ b/apps/sim/app/api/organizations/[id]/seats/route.ts @@ -6,7 +6,7 @@ import { z } from 'zod' import { getSession } from '@/lib/auth' import { getPlanPricing } from '@/lib/billing/core/billing' import { requireStripeClient } from '@/lib/billing/stripe-client' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('OrganizationSeatsAPI') diff --git a/apps/sim/app/api/proxy/route.ts b/apps/sim/app/api/proxy/route.ts index 08b9a0758d..cb223aebd7 100644 --- a/apps/sim/app/api/proxy/route.ts +++ b/apps/sim/app/api/proxy/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from 'next/server' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' import { generateInternalToken } from '@/lib/auth/internal' -import { isDev } from '@/lib/core/config/environment' +import { isDev } from '@/lib/core/config/feature-flags' import { createPinnedUrl, validateUrlWithDNS } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { getBaseUrl } from '@/lib/core/utils/urls' diff --git a/apps/sim/app/api/schedules/execute/route.test.ts b/apps/sim/app/api/schedules/execute/route.test.ts index 43d5367a2b..6feddfe7a2 100644 --- a/apps/sim/app/api/schedules/execute/route.test.ts +++ b/apps/sim/app/api/schedules/execute/route.test.ts @@ -42,11 +42,11 @@ describe('Scheduled Workflow Execution API Route', () => { executeScheduleJob: mockExecuteScheduleJob, })) - vi.doMock('@/lib/core/config/env', () => ({ - env: { - TRIGGER_DEV_ENABLED: false, - }, - isTruthy: vi.fn(() => false), + vi.doMock('@/lib/core/config/feature-flags', () => ({ + isTriggerDevEnabled: false, + isHosted: false, + isProd: false, + isDev: true, })) vi.doMock('drizzle-orm', () => ({ @@ -119,11 +119,11 @@ describe('Scheduled Workflow Execution API Route', () => { }, })) - vi.doMock('@/lib/core/config/env', () => ({ - env: { - TRIGGER_DEV_ENABLED: true, - }, - isTruthy: vi.fn(() => true), + vi.doMock('@/lib/core/config/feature-flags', () => ({ + isTriggerDevEnabled: true, + isHosted: false, + isProd: false, + isDev: true, })) vi.doMock('drizzle-orm', () => ({ @@ -191,11 +191,11 @@ describe('Scheduled Workflow Execution API Route', () => { executeScheduleJob: vi.fn().mockResolvedValue(undefined), })) - vi.doMock('@/lib/core/config/env', () => ({ - env: { - TRIGGER_DEV_ENABLED: false, - }, - isTruthy: vi.fn(() => false), + vi.doMock('@/lib/core/config/feature-flags', () => ({ + isTriggerDevEnabled: false, + isHosted: false, + isProd: false, + isDev: true, })) vi.doMock('drizzle-orm', () => ({ @@ -250,11 +250,11 @@ describe('Scheduled Workflow Execution API Route', () => { executeScheduleJob: vi.fn().mockResolvedValue(undefined), })) - vi.doMock('@/lib/core/config/env', () => ({ - env: { - TRIGGER_DEV_ENABLED: false, - }, - isTruthy: vi.fn(() => false), + vi.doMock('@/lib/core/config/feature-flags', () => ({ + isTriggerDevEnabled: false, + isHosted: false, + isProd: false, + isDev: true, })) vi.doMock('drizzle-orm', () => ({ diff --git a/apps/sim/app/api/schedules/execute/route.ts b/apps/sim/app/api/schedules/execute/route.ts index 4368ab192e..5254028d61 100644 --- a/apps/sim/app/api/schedules/execute/route.ts +++ b/apps/sim/app/api/schedules/execute/route.ts @@ -3,7 +3,7 @@ import { tasks } from '@trigger.dev/sdk' import { and, eq, isNull, lt, lte, not, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' -import { env, isTruthy } from '@/lib/core/config/env' +import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' import { createLogger } from '@/lib/logs/console/logger' import { executeScheduleJob } from '@/background/schedule-execution' @@ -54,9 +54,7 @@ export async function GET(request: NextRequest) { logger.debug(`[${requestId}] Successfully queried schedules: ${dueSchedules.length} found`) logger.info(`[${requestId}] Processing ${dueSchedules.length} due scheduled workflows`) - const useTrigger = isTruthy(env.TRIGGER_DEV_ENABLED) - - if (useTrigger) { + if (isTriggerDevEnabled) { const triggerPromises = dueSchedules.map(async (schedule) => { const queueTime = schedule.lastQueuedAt ?? queuedAt diff --git a/apps/sim/app/api/telemetry/route.ts b/apps/sim/app/api/telemetry/route.ts index f711d3d0c2..e7bc3bc893 100644 --- a/apps/sim/app/api/telemetry/route.ts +++ b/apps/sim/app/api/telemetry/route.ts @@ -1,6 +1,6 @@ import { type NextRequest, NextResponse } from 'next/server' import { env } from '@/lib/core/config/env' -import { isProd } from '@/lib/core/config/environment' +import { isProd } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('TelemetryAPI') diff --git a/apps/sim/app/api/v1/auth.ts b/apps/sim/app/api/v1/auth.ts index b9e5b59c21..30bf8d8e51 100644 --- a/apps/sim/app/api/v1/auth.ts +++ b/apps/sim/app/api/v1/auth.ts @@ -1,5 +1,7 @@ import type { NextRequest } from 'next/server' import { authenticateApiKeyFromHeader, updateApiKeyLastUsed } from '@/lib/api-key/service' +import { ANONYMOUS_USER_ID } from '@/lib/auth/constants' +import { isAuthDisabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('V1Auth') @@ -13,6 +15,14 @@ export interface AuthResult { } export async function authenticateV1Request(request: NextRequest): Promise { + if (isAuthDisabled) { + return { + authenticated: true, + userId: ANONYMOUS_USER_ID, + keyType: 'personal', + } + } + const apiKey = request.headers.get('x-api-key') if (!apiKey) { diff --git a/apps/sim/app/api/wand/route.ts b/apps/sim/app/api/wand/route.ts index 48f0dd7820..b3925663d6 100644 --- a/apps/sim/app/api/wand/route.ts +++ b/apps/sim/app/api/wand/route.ts @@ -5,7 +5,7 @@ import { type NextRequest, NextResponse } from 'next/server' import OpenAI, { AzureOpenAI } from 'openai' import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing' import { env } from '@/lib/core/config/env' -import { getCostMultiplier, isBillingEnabled } from '@/lib/core/config/environment' +import { getCostMultiplier, isBillingEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' import { createLogger } from '@/lib/logs/console/logger' import { getModelPricing } from '@/providers/utils' diff --git a/apps/sim/app/api/workflows/[id]/execute/route.ts b/apps/sim/app/api/workflows/[id]/execute/route.ts index a330dd624c..2b9cd8beaf 100644 --- a/apps/sim/app/api/workflows/[id]/execute/route.ts +++ b/apps/sim/app/api/workflows/[id]/execute/route.ts @@ -3,7 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { validate as uuidValidate, v4 as uuidv4 } from 'uuid' import { z } from 'zod' import { checkHybridAuth } from '@/lib/auth/hybrid' -import { env, isTruthy } from '@/lib/core/config/env' +import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' import { SSE_HEADERS } from '@/lib/core/utils/sse' import { getBaseUrl } from '@/lib/core/utils/urls' @@ -236,9 +236,8 @@ type AsyncExecutionParams = { */ async function handleAsyncExecution(params: AsyncExecutionParams): Promise { const { requestId, workflowId, userId, input, triggerType } = params - const useTrigger = isTruthy(env.TRIGGER_DEV_ENABLED) - if (!useTrigger) { + if (!isTriggerDevEnabled) { logger.warn(`[${requestId}] Async mode requested but TRIGGER_DEV_ENABLED is false`) return NextResponse.json( { error: 'Async execution is not enabled. Set TRIGGER_DEV_ENABLED=true to use async mode.' }, diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx index 320002ec2d..c3d9aa7dbc 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/general/general.tsx @@ -16,6 +16,7 @@ import { } from '@/components/emcn' import { Input, Skeleton } from '@/components/ui' import { signOut, useSession } from '@/lib/auth/auth-client' +import { ANONYMOUS_USER_ID } from '@/lib/auth/constants' import { useBrandConfig } from '@/lib/branding/branding' import { getEnv, isTruthy } from '@/lib/core/config/env' import { getBaseUrl } from '@/lib/core/utils/urls' @@ -59,6 +60,7 @@ export function General({ onOpenChange }: GeneralProps) { const isLoading = isProfileLoading || isSettingsLoading const isTrainingEnabled = isTruthy(getEnv('NEXT_PUBLIC_COPILOT_TRAINING_ENABLED')) + const isAuthDisabled = session?.user?.id === ANONYMOUS_USER_ID const [isSuperUser, setIsSuperUser] = useState(false) const [loadingSuperUser, setLoadingSuperUser] = useState(true) @@ -461,10 +463,12 @@ export function General({ onOpenChange }: GeneralProps) { )} -
- - -
+ {!isAuthDisabled && ( +
+ + +
+ )} {/* Password Reset Confirmation Modal */} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/sso/sso.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/sso/sso.tsx index 6bb09130a8..d01fb51d84 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/sso/sso.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/sso/sso.tsx @@ -6,7 +6,7 @@ import { Button, Combobox, Input, Switch, Textarea } from '@/components/emcn' import { Skeleton } from '@/components/ui' import { useSession } from '@/lib/auth/auth-client' import { getSubscriptionStatus } from '@/lib/billing/client/utils' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { cn } from '@/lib/core/utils/cn' import { getBaseUrl } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx index 1d1997c663..d1debd8d21 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/settings-modal.tsx @@ -26,7 +26,7 @@ import { McpIcon } from '@/components/icons' import { useSession } from '@/lib/auth/auth-client' import { getSubscriptionStatus } from '@/lib/billing/client' import { getEnv, isTruthy } from '@/lib/core/config/env' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { getUserRole } from '@/lib/workspaces/organization' import { ApiKeys, diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts index d57b7d3021..3e321d2cd0 100644 --- a/apps/sim/blocks/blocks/agent.ts +++ b/apps/sim/blocks/blocks/agent.ts @@ -1,5 +1,5 @@ import { AgentIcon } from '@/components/icons' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' import type { BlockConfig } from '@/blocks/types' import { AuthMode } from '@/blocks/types' diff --git a/apps/sim/blocks/blocks/evaluator.ts b/apps/sim/blocks/blocks/evaluator.ts index fa291521c9..e809ed0476 100644 --- a/apps/sim/blocks/blocks/evaluator.ts +++ b/apps/sim/blocks/blocks/evaluator.ts @@ -1,5 +1,5 @@ import { ChartBarIcon } from '@/components/icons' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' import type { BlockConfig, ParamType } from '@/blocks/types' import type { ProviderId } from '@/providers/types' diff --git a/apps/sim/blocks/blocks/guardrails.ts b/apps/sim/blocks/blocks/guardrails.ts index 5af165f3ee..af9f7a7d46 100644 --- a/apps/sim/blocks/blocks/guardrails.ts +++ b/apps/sim/blocks/blocks/guardrails.ts @@ -1,5 +1,5 @@ import { ShieldCheckIcon } from '@/components/icons' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import type { BlockConfig } from '@/blocks/types' import { getHostedModels, getProviderIcon } from '@/providers/utils' import { useProvidersStore } from '@/stores/providers/store' diff --git a/apps/sim/blocks/blocks/router.ts b/apps/sim/blocks/blocks/router.ts index 31daa27e2d..744aa53950 100644 --- a/apps/sim/blocks/blocks/router.ts +++ b/apps/sim/blocks/blocks/router.ts @@ -1,5 +1,5 @@ import { ConnectIcon } from '@/components/icons' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { AuthMode, type BlockConfig } from '@/blocks/types' import type { ProviderId } from '@/providers/types' import { diff --git a/apps/sim/blocks/blocks/translate.ts b/apps/sim/blocks/blocks/translate.ts index 5f7c954a1e..bd984b8601 100644 --- a/apps/sim/blocks/blocks/translate.ts +++ b/apps/sim/blocks/blocks/translate.ts @@ -1,5 +1,5 @@ import { TranslateIcon } from '@/components/icons' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { AuthMode, type BlockConfig } from '@/blocks/types' import { getHostedModels, getProviderIcon, providers } from '@/providers/utils' import { useProvidersStore } from '@/stores/providers/store' diff --git a/apps/sim/components/emails/footer.tsx b/apps/sim/components/emails/footer.tsx index 6b45e7e011..f6eb4044d2 100644 --- a/apps/sim/components/emails/footer.tsx +++ b/apps/sim/components/emails/footer.tsx @@ -1,6 +1,6 @@ import { Container, Img, Link, Section, Text } from '@react-email/components' import { getBrandConfig } from '@/lib/branding/branding' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { getBaseUrl } from '@/lib/core/utils/urls' interface UnsubscribeOptions { diff --git a/apps/sim/executor/handlers/agent/agent-handler.test.ts b/apps/sim/executor/handlers/agent/agent-handler.test.ts index 1e366c4014..449dd82441 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.test.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest' -import { isHosted } from '@/lib/core/config/environment' import { getAllBlocks } from '@/blocks' import { BlockType } from '@/executor/constants' import { AgentBlockHandler } from '@/executor/handlers/agent/agent-handler' @@ -11,11 +10,11 @@ import { executeTool } from '@/tools' process.env.NEXT_PUBLIC_APP_URL = 'http://localhost:3000' -vi.mock('@/lib/core/config/environment', () => ({ - isHosted: vi.fn().mockReturnValue(false), - isProd: vi.fn().mockReturnValue(false), - isDev: vi.fn().mockReturnValue(true), - isTest: vi.fn().mockReturnValue(false), +vi.mock('@/lib/core/config/feature-flags', () => ({ + isHosted: false, + isProd: false, + isDev: true, + isTest: false, getCostMultiplier: vi.fn().mockReturnValue(1), isEmailVerificationEnabled: false, isBillingEnabled: false, @@ -65,7 +64,6 @@ global.fetch = Object.assign(vi.fn(), { preconnect: vi.fn() }) as typeof fetch const mockGetAllBlocks = getAllBlocks as Mock const mockExecuteTool = executeTool as Mock -const mockIsHosted = isHosted as unknown as Mock const mockGetProviderFromModel = getProviderFromModel as Mock const mockTransformBlockTool = transformBlockTool as Mock const mockFetch = global.fetch as unknown as Mock @@ -120,7 +118,6 @@ describe('AgentBlockHandler', () => { loops: {}, } as SerializedWorkflow, } - mockIsHosted.mockReturnValue(false) mockGetProviderFromModel.mockReturnValue('mock-provider') mockFetch.mockImplementation(() => { @@ -552,8 +549,6 @@ describe('AgentBlockHandler', () => { }) it('should not require API key for gpt-4o on hosted version', async () => { - mockIsHosted.mockReturnValue(true) - const inputs = { model: 'gpt-4o', systemPrompt: 'You are a helpful assistant.', diff --git a/apps/sim/hooks/queries/copilot-keys.ts b/apps/sim/hooks/queries/copilot-keys.ts index ada3c84801..3354a0f70e 100644 --- a/apps/sim/hooks/queries/copilot-keys.ts +++ b/apps/sim/hooks/queries/copilot-keys.ts @@ -1,5 +1,5 @@ import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('CopilotKeysQuery') diff --git a/apps/sim/lib/auth/anonymous.ts b/apps/sim/lib/auth/anonymous.ts new file mode 100644 index 0000000000..30ee4e94a4 --- /dev/null +++ b/apps/sim/lib/auth/anonymous.ts @@ -0,0 +1,104 @@ +import { db } from '@sim/db' +import * as schema from '@sim/db/schema' +import { eq } from 'drizzle-orm' +import { createLogger } from '@/lib/logs/console/logger' +import { ANONYMOUS_USER, ANONYMOUS_USER_ID } from './constants' + +const logger = createLogger('AnonymousAuth') + +let anonymousUserEnsured = false + +/** + * Ensures the anonymous user and their stats record exist in the database. + * Called when DISABLE_AUTH is enabled to ensure DB operations work. + */ +export async function ensureAnonymousUserExists(): Promise { + if (anonymousUserEnsured) return + + try { + const existingUser = await db.query.user.findFirst({ + where: eq(schema.user.id, ANONYMOUS_USER_ID), + }) + + if (!existingUser) { + const now = new Date() + await db.insert(schema.user).values({ + ...ANONYMOUS_USER, + createdAt: now, + updatedAt: now, + }) + logger.info('Created anonymous user for DISABLE_AUTH mode') + } + + const existingStats = await db.query.userStats.findFirst({ + where: eq(schema.userStats.userId, ANONYMOUS_USER_ID), + }) + + if (!existingStats) { + await db.insert(schema.userStats).values({ + id: crypto.randomUUID(), + userId: ANONYMOUS_USER_ID, + currentUsageLimit: '10000000000', + }) + logger.info('Created anonymous user stats for DISABLE_AUTH mode') + } + + anonymousUserEnsured = true + } catch (error) { + if ( + error instanceof Error && + (error.message.includes('unique') || error.message.includes('duplicate')) + ) { + anonymousUserEnsured = true + return + } + logger.error('Failed to ensure anonymous user exists', { error }) + throw error + } +} + +export interface AnonymousSession { + user: { + id: string + name: string + email: string + emailVerified: boolean + image: null + createdAt: Date + updatedAt: Date + } + session: { + id: string + userId: string + expiresAt: Date + createdAt: Date + updatedAt: Date + token: string + ipAddress: null + userAgent: null + } +} + +/** + * Creates an anonymous session for when auth is disabled. + */ +export function createAnonymousSession(): AnonymousSession { + const now = new Date() + return { + user: { + ...ANONYMOUS_USER, + createdAt: now, + updatedAt: now, + }, + session: { + id: 'anonymous-session', + userId: ANONYMOUS_USER_ID, + expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1 year + createdAt: now, + updatedAt: now, + token: 'anonymous-token', + ipAddress: null, + userAgent: null, + }, + } +} diff --git a/apps/sim/lib/auth/auth-client.ts b/apps/sim/lib/auth/auth-client.ts index 817902a0b4..5c61e5bff4 100644 --- a/apps/sim/lib/auth/auth-client.ts +++ b/apps/sim/lib/auth/auth-client.ts @@ -10,7 +10,7 @@ import { import { createAuthClient } from 'better-auth/react' import type { auth } from '@/lib/auth' import { env } from '@/lib/core/config/env' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { getBaseUrl } from '@/lib/core/utils/urls' import { SessionContext, type SessionHookResult } from '@/app/_shell/providers/session-provider' diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index eeec34fcc8..fb9287e908 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -38,13 +38,19 @@ import { handleSubscriptionCreated, handleSubscriptionDeleted, } from '@/lib/billing/webhooks/subscription' -import { env, isTruthy } from '@/lib/core/config/env' -import { isBillingEnabled, isEmailVerificationEnabled } from '@/lib/core/config/environment' +import { env } from '@/lib/core/config/env' +import { + isAuthDisabled, + isBillingEnabled, + isEmailVerificationEnabled, + isRegistrationDisabled, +} from '@/lib/core/config/feature-flags' import { getBaseUrl } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' import { getFromEmailAddress } from '@/lib/messaging/email/utils' import { quickValidateEmail } from '@/lib/messaging/email/validation' +import { createAnonymousSession, ensureAnonymousUserExists } from './anonymous' import { SSO_TRUSTED_PROVIDERS } from './sso/constants' const logger = createLogger('Auth') @@ -270,7 +276,7 @@ export const auth = betterAuth({ }, hooks: { before: createAuthMiddleware(async (ctx) => { - if (ctx.path.startsWith('/sign-up') && isTruthy(env.DISABLE_REGISTRATION)) + if (ctx.path.startsWith('/sign-up') && isRegistrationDisabled) throw new Error('Registration is disabled, please contact your admin.') if ( @@ -2185,6 +2191,11 @@ export const auth = betterAuth({ }) export async function getSession() { + if (isAuthDisabled) { + await ensureAnonymousUserExists() + return createAnonymousSession() + } + const hdrs = await headers() return await auth.api.getSession({ headers: hdrs, diff --git a/apps/sim/lib/auth/constants.ts b/apps/sim/lib/auth/constants.ts new file mode 100644 index 0000000000..46eca038cf --- /dev/null +++ b/apps/sim/lib/auth/constants.ts @@ -0,0 +1,10 @@ +/** Anonymous user ID used when DISABLE_AUTH is enabled */ +export const ANONYMOUS_USER_ID = '00000000-0000-0000-0000-000000000000' + +export const ANONYMOUS_USER = { + id: ANONYMOUS_USER_ID, + name: 'Anonymous', + email: 'anonymous@localhost', + emailVerified: true, + image: null, +} as const diff --git a/apps/sim/lib/auth/index.ts b/apps/sim/lib/auth/index.ts index 84c42e215b..d997017e19 100644 --- a/apps/sim/lib/auth/index.ts +++ b/apps/sim/lib/auth/index.ts @@ -1 +1,4 @@ +export type { AnonymousSession } from './anonymous' +export { createAnonymousSession, ensureAnonymousUserExists } from './anonymous' export { auth, getSession, signIn, signUp } from './auth' +export { ANONYMOUS_USER, ANONYMOUS_USER_ID } from './constants' diff --git a/apps/sim/lib/billing/calculations/usage-monitor.ts b/apps/sim/lib/billing/calculations/usage-monitor.ts index 66cf8fbe44..219f9e2f30 100644 --- a/apps/sim/lib/billing/calculations/usage-monitor.ts +++ b/apps/sim/lib/billing/calculations/usage-monitor.ts @@ -2,7 +2,7 @@ import { db } from '@sim/db' import { member, organization, userStats } from '@sim/db/schema' import { and, eq, inArray } from 'drizzle-orm' import { getUserUsageLimit } from '@/lib/billing/core/usage' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('UsageMonitor') diff --git a/apps/sim/lib/billing/core/subscription.ts b/apps/sim/lib/billing/core/subscription.ts index 4aa977abe9..74a2fa9ee3 100644 --- a/apps/sim/lib/billing/core/subscription.ts +++ b/apps/sim/lib/billing/core/subscription.ts @@ -9,7 +9,7 @@ import { getPerUserMinimumLimit, } from '@/lib/billing/subscriptions/utils' import type { UserSubscriptionState } from '@/lib/billing/types' -import { isProd } from '@/lib/core/config/environment' +import { isProd } from '@/lib/core/config/feature-flags' import { getBaseUrl } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' diff --git a/apps/sim/lib/billing/core/usage.ts b/apps/sim/lib/billing/core/usage.ts index f784151cf5..f32ac38bff 100644 --- a/apps/sim/lib/billing/core/usage.ts +++ b/apps/sim/lib/billing/core/usage.ts @@ -14,7 +14,7 @@ import { getPlanPricing, } from '@/lib/billing/subscriptions/utils' import type { BillingData, UsageData, UsageLimitInfo } from '@/lib/billing/types' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { getBaseUrl } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' import { sendEmail } from '@/lib/messaging/email/mailer' diff --git a/apps/sim/lib/billing/storage/limits.ts b/apps/sim/lib/billing/storage/limits.ts index 8f15ec2237..9e7dd5efc7 100644 --- a/apps/sim/lib/billing/storage/limits.ts +++ b/apps/sim/lib/billing/storage/limits.ts @@ -13,7 +13,7 @@ import { import { organization, subscription, userStats } from '@sim/db/schema' import { eq } from 'drizzle-orm' import { getEnv } from '@/lib/core/config/env' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('StorageLimits') diff --git a/apps/sim/lib/billing/storage/tracking.ts b/apps/sim/lib/billing/storage/tracking.ts index b094769e70..704a4ae6ab 100644 --- a/apps/sim/lib/billing/storage/tracking.ts +++ b/apps/sim/lib/billing/storage/tracking.ts @@ -7,7 +7,7 @@ import { db } from '@sim/db' import { organization, userStats } from '@sim/db/schema' import { eq, sql } from 'drizzle-orm' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('StorageTracking') diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index ab289468e3..2fc2ad1a09 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -20,6 +20,7 @@ export const env = createEnv({ BETTER_AUTH_URL: z.string().url(), // Base URL for Better Auth service BETTER_AUTH_SECRET: z.string().min(32), // Secret key for Better Auth JWT signing DISABLE_REGISTRATION: z.boolean().optional(), // Flag to disable new user registration + DISABLE_AUTH: z.boolean().optional(), // Bypass authentication entirely (self-hosted only, creates anonymous session) ALLOWED_LOGIN_EMAILS: z.string().optional(), // Comma-separated list of allowed email addresses for login ALLOWED_LOGIN_DOMAINS: z.string().optional(), // Comma-separated list of allowed email domains for login ENCRYPTION_KEY: z.string().min(32), // Key for encrypting sensitive data diff --git a/apps/sim/lib/core/config/environment.ts b/apps/sim/lib/core/config/feature-flags.ts similarity index 63% rename from apps/sim/lib/core/config/environment.ts rename to apps/sim/lib/core/config/feature-flags.ts index 835f54c8bc..cac10d296d 100644 --- a/apps/sim/lib/core/config/environment.ts +++ b/apps/sim/lib/core/config/feature-flags.ts @@ -35,6 +35,31 @@ export const isBillingEnabled = isTruthy(env.BILLING_ENABLED) */ export const isEmailVerificationEnabled = isTruthy(env.EMAIL_VERIFICATION_ENABLED) +/** + * Is authentication disabled (for self-hosted deployments behind private networks) + */ +export const isAuthDisabled = isTruthy(env.DISABLE_AUTH) + +/** + * Is user registration disabled + */ +export const isRegistrationDisabled = isTruthy(env.DISABLE_REGISTRATION) + +/** + * Is Trigger.dev enabled for async job processing + */ +export const isTriggerDevEnabled = isTruthy(env.TRIGGER_DEV_ENABLED) + +/** + * Is SSO enabled for enterprise authentication + */ +export const isSsoEnabled = isTruthy(env.SSO_ENABLED) + +/** + * Is E2B enabled for remote code execution + */ +export const isE2bEnabled = isTruthy(env.E2B_ENABLED) + /** * Get cost multiplier based on environment */ diff --git a/apps/sim/lib/core/utils/urls.ts b/apps/sim/lib/core/utils/urls.ts index e740ac52fc..22e164cf1d 100644 --- a/apps/sim/lib/core/utils/urls.ts +++ b/apps/sim/lib/core/utils/urls.ts @@ -1,5 +1,5 @@ import { getEnv } from '@/lib/core/config/env' -import { isProd } from '@/lib/core/config/environment' +import { isProd } from '@/lib/core/config/feature-flags' /** * Returns the base URL of the application from NEXT_PUBLIC_APP_URL diff --git a/apps/sim/lib/logs/events.ts b/apps/sim/lib/logs/events.ts index a1b0cc2023..14e2ceee8b 100644 --- a/apps/sim/lib/logs/events.ts +++ b/apps/sim/lib/logs/events.ts @@ -6,7 +6,7 @@ import { } from '@sim/db/schema' import { and, eq, or, sql } from 'drizzle-orm' import { v4 as uuidv4 } from 'uuid' -import { env, isTruthy } from '@/lib/core/config/env' +import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' import type { WorkflowExecutionLog } from '@/lib/logs/types' import { @@ -140,9 +140,7 @@ export async function emitWorkflowExecutionCompleted(log: WorkflowExecutionLog): alertConfig: alertConfig || undefined, } - const useTrigger = isTruthy(env.TRIGGER_DEV_ENABLED) - - if (useTrigger) { + if (isTriggerDevEnabled) { await workspaceNotificationDeliveryTask.trigger(payload) logger.info( `Enqueued ${subscription.notificationType} notification ${deliveryId} via Trigger.dev` diff --git a/apps/sim/lib/logs/execution/logger.ts b/apps/sim/lib/logs/execution/logger.ts index 3bf8496313..390a604567 100644 --- a/apps/sim/lib/logs/execution/logger.ts +++ b/apps/sim/lib/logs/execution/logger.ts @@ -15,7 +15,7 @@ import { maybeSendUsageThresholdEmail, } from '@/lib/billing/core/usage' import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing' -import { isBillingEnabled } from '@/lib/core/config/environment' +import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { redactApiKeys } from '@/lib/core/security/redaction' import { filterForDisplay } from '@/lib/core/utils/display-filters' import { createLogger } from '@/lib/logs/console/logger' diff --git a/apps/sim/lib/mcp/service.ts b/apps/sim/lib/mcp/service.ts index cc04f2e6de..8d1483f522 100644 --- a/apps/sim/lib/mcp/service.ts +++ b/apps/sim/lib/mcp/service.ts @@ -5,7 +5,7 @@ import { db } from '@sim/db' import { mcpServers } from '@sim/db/schema' import { and, eq, isNull } from 'drizzle-orm' -import { isTest } from '@/lib/core/config/environment' +import { isTest } from '@/lib/core/config/feature-flags' import { generateRequestId } from '@/lib/core/utils/request' import { getEffectiveDecryptedEnv } from '@/lib/environment/utils' import { createLogger } from '@/lib/logs/console/logger' diff --git a/apps/sim/lib/notifications/inactivity-polling.ts b/apps/sim/lib/notifications/inactivity-polling.ts index f5558088fc..4d4395faa4 100644 --- a/apps/sim/lib/notifications/inactivity-polling.ts +++ b/apps/sim/lib/notifications/inactivity-polling.ts @@ -7,7 +7,7 @@ import { } from '@sim/db/schema' import { and, eq, gte, sql } from 'drizzle-orm' import { v4 as uuidv4 } from 'uuid' -import { env, isTruthy } from '@/lib/core/config/env' +import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' import { executeNotificationDelivery, @@ -118,9 +118,7 @@ async function checkWorkflowInactivity( alertConfig, } - const useTrigger = isTruthy(env.TRIGGER_DEV_ENABLED) - - if (useTrigger) { + if (isTriggerDevEnabled) { await workspaceNotificationDeliveryTask.trigger(payload) } else { void executeNotificationDelivery(payload).catch((error) => { diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 8f0fd8cad8..d7cb0f65f6 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -3,7 +3,7 @@ import { tasks } from '@trigger.dev/sdk' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v4 as uuidv4 } from 'uuid' -import { env, isTruthy } from '@/lib/core/config/env' +import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags' import { preprocessExecution } from '@/lib/execution/preprocessing' import { createLogger } from '@/lib/logs/console/logger' import { convertSquareBracketsToTwiML } from '@/lib/webhooks/utils' @@ -707,9 +707,7 @@ export async function queueWebhookExecution( ...(credentialId ? { credentialId } : {}), } - const useTrigger = isTruthy(env.TRIGGER_DEV_ENABLED) - - if (useTrigger) { + if (isTriggerDevEnabled) { const handle = await tasks.trigger('webhook-execution', payload) logger.info( `[${options.requestId}] Queued ${options.testMode ? 'TEST ' : ''}webhook execution task ${ diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index 3e3e22bc89..a007db01b0 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -1,6 +1,6 @@ import type { NextConfig } from 'next' import { env, getEnv, isTruthy } from './lib/core/config/env' -import { isDev, isHosted } from './lib/core/config/environment' +import { isDev, isHosted } from './lib/core/config/feature-flags' import { getMainCSPPolicy, getWorkflowExecutionCSPPolicy } from './lib/core/security/csp' const nextConfig: NextConfig = { diff --git a/apps/sim/providers/index.ts b/apps/sim/providers/index.ts index 0b6d7b1ac7..72d1423e15 100644 --- a/apps/sim/providers/index.ts +++ b/apps/sim/providers/index.ts @@ -1,4 +1,4 @@ -import { getCostMultiplier } from '@/lib/core/config/environment' +import { getCostMultiplier } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' import type { StreamingExecution } from '@/executor/types' import type { ProviderRequest, ProviderResponse } from '@/providers/types' diff --git a/apps/sim/providers/utils.test.ts b/apps/sim/providers/utils.test.ts index 13977f37d9..003119c86c 100644 --- a/apps/sim/providers/utils.test.ts +++ b/apps/sim/providers/utils.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import * as environmentModule from '@/lib/core/config/environment' +import * as environmentModule from '@/lib/core/config/feature-flags' import { calculateCost, extractAndParseJSON, diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index ca0b6db77f..972cdf8156 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -1,5 +1,5 @@ import { getEnv, isTruthy } from '@/lib/core/config/env' -import { isHosted } from '@/lib/core/config/environment' +import { isHosted } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' import { anthropicProvider } from '@/providers/anthropic' import { azureOpenAIProvider } from '@/providers/azure-openai' diff --git a/apps/sim/proxy.ts b/apps/sim/proxy.ts index 0d0c2cb647..bb1b23bfad 100644 --- a/apps/sim/proxy.ts +++ b/apps/sim/proxy.ts @@ -1,6 +1,6 @@ import { getSessionCookie } from 'better-auth/cookies' import { type NextRequest, NextResponse } from 'next/server' -import { isHosted } from './lib/core/config/environment' +import { isAuthDisabled, isHosted } from './lib/core/config/feature-flags' import { generateRuntimeCSP } from './lib/core/security/csp' import { createLogger } from './lib/logs/console/logger' @@ -135,7 +135,7 @@ export async function proxy(request: NextRequest) { const url = request.nextUrl const sessionCookie = getSessionCookie(request) - const hasActiveSession = !!sessionCookie + const hasActiveSession = isAuthDisabled || !!sessionCookie const redirect = handleRootPathRedirects(request, hasActiveSession) if (redirect) return redirect diff --git a/apps/sim/scripts/process-docs.ts b/apps/sim/scripts/process-docs.ts index 2a53e78fc5..8657e3b6fc 100644 --- a/apps/sim/scripts/process-docs.ts +++ b/apps/sim/scripts/process-docs.ts @@ -5,7 +5,7 @@ import { db } from '@sim/db' import { docsEmbeddings } from '@sim/db/schema' import { sql } from 'drizzle-orm' import { type DocChunk, DocsChunker } from '@/lib/chunkers' -import { isDev } from '@/lib/core/config/environment' +import { isDev } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('ProcessDocs') diff --git a/apps/sim/socket-server/config/socket.ts b/apps/sim/socket-server/config/socket.ts index 2eab72f588..6015dc971f 100644 --- a/apps/sim/socket-server/config/socket.ts +++ b/apps/sim/socket-server/config/socket.ts @@ -1,7 +1,7 @@ import type { Server as HttpServer } from 'http' import { Server } from 'socket.io' import { env } from '@/lib/core/config/env' -import { isProd } from '@/lib/core/config/environment' +import { isProd } from '@/lib/core/config/feature-flags' import { getBaseUrl } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' diff --git a/apps/sim/socket-server/middleware/auth.ts b/apps/sim/socket-server/middleware/auth.ts index 56de4676ca..3b3a6cc548 100644 --- a/apps/sim/socket-server/middleware/auth.ts +++ b/apps/sim/socket-server/middleware/auth.ts @@ -1,10 +1,14 @@ import type { Socket } from 'socket.io' import { auth } from '@/lib/auth' +import { ANONYMOUS_USER, ANONYMOUS_USER_ID } from '@/lib/auth/constants' +import { isAuthDisabled } from '@/lib/core/config/feature-flags' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('SocketAuth') -// Extend Socket interface to include user data +/** + * Authenticated socket with user data attached. + */ export interface AuthenticatedSocket extends Socket { userId?: string userName?: string @@ -13,9 +17,21 @@ export interface AuthenticatedSocket extends Socket { userImage?: string | null } -// Enhanced authentication middleware +/** + * Socket.IO authentication middleware. + * Handles both anonymous mode (DISABLE_AUTH=true) and normal token-based auth. + */ export async function authenticateSocket(socket: AuthenticatedSocket, next: any) { try { + if (isAuthDisabled) { + socket.userId = ANONYMOUS_USER_ID + socket.userName = ANONYMOUS_USER.name + socket.userEmail = ANONYMOUS_USER.email + socket.userImage = ANONYMOUS_USER.image + logger.debug(`Socket ${socket.id} authenticated as anonymous`) + return next() + } + // Extract authentication data from socket handshake const token = socket.handshake.auth?.token const origin = socket.handshake.headers.origin diff --git a/apps/sim/tools/http/utils.ts b/apps/sim/tools/http/utils.ts index 1c99a162db..9e8248d3e3 100644 --- a/apps/sim/tools/http/utils.ts +++ b/apps/sim/tools/http/utils.ts @@ -1,4 +1,4 @@ -import { isTest } from '@/lib/core/config/environment' +import { isTest } from '@/lib/core/config/feature-flags' import { getBaseUrl } from '@/lib/core/utils/urls' import { createLogger } from '@/lib/logs/console/logger' import type { TableRow } from '@/tools/types'