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'