Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/sim/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(auth)/components/oauth-provider-checker.tsx
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
8 changes: 4 additions & 4 deletions apps/sim/app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -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 <div>Registration is disabled, please contact your admin.</div>
}

const { githubAvailable, googleAvailable, isProduction } = await getOAuthProviderStatus()

return (
<SignupForm
githubAvailable={githubAvailable}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(auth)/verify/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isEmailVerificationEnabled, isProd } from '@/lib/core/config/environment'
import { isEmailVerificationEnabled, isProd } from '@/lib/core/config/feature-flags'
import { hasEmailService } from '@/lib/messaging/email/mailer'
import { VerifyContent } from '@/app/(auth)/verify/verify-content'

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/careers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
SelectValue,
} from '@/components/ui/select'
import { Textarea } from '@/components/ui/textarea'
import { isHosted } from '@/lib/core/config/environment'
import { isHosted } from '@/lib/core/config/feature-flags'
import { cn } from '@/lib/core/utils/cn'
import { createLogger } from '@/lib/logs/console/logger'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/components/legal-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { isHosted } from '@/lib/core/config/environment'
import { isHosted } from '@/lib/core/config/feature-flags'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import Footer from '@/app/(landing)/components/footer/footer'
import Nav from '@/app/(landing)/components/nav/nav'
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/components/nav/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { GithubIcon } from '@/components/icons'
import { useBrandConfig } from '@/lib/branding/branding'
import { isHosted } from '@/lib/core/config/environment'
import { isHosted } from '@/lib/core/config/feature-flags'
import { createLogger } from '@/lib/logs/console/logger'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { getFormattedGitHubStars } from '@/app/(landing)/actions/github'
Expand Down
19 changes: 18 additions & 1 deletion apps/sim/app/api/auth/[...all]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import { toNextJsHandler } from 'better-auth/next-js'
import { type NextRequest, NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import { createAnonymousSession, ensureAnonymousUserExists } from '@/lib/auth/anonymous'
import { isAuthDisabled } from '@/lib/core/config/feature-flags'

export const dynamic = 'force-dynamic'

export const { GET, POST } = toNextJsHandler(auth.handler)
const { GET: betterAuthGET, POST: betterAuthPOST } = toNextJsHandler(auth.handler)

export async function GET(request: NextRequest) {
const url = new URL(request.url)
const path = url.pathname.replace('/api/auth/', '')

if (path === 'get-session' && isAuthDisabled) {
await ensureAnonymousUserExists()
return NextResponse.json(createAnonymousSession())
}

return betterAuthGET(request)
}

export const POST = betterAuthPOST
5 changes: 5 additions & 0 deletions apps/sim/app/api/auth/socket-token/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { headers } from 'next/headers'
import { NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import { isAuthDisabled } from '@/lib/core/config/feature-flags'

export async function POST() {
try {
if (isAuthDisabled) {
return NextResponse.json({ token: 'anonymous-socket-token' })
}

const hdrs = await headers()
const response = await auth.api.generateOneTimeToken({
headers: hdrs,
Expand Down
10 changes: 4 additions & 6 deletions apps/sim/app/api/auth/sso/providers/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { db, ssoProvider } from '@sim/db'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
import { NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console/logger'

const logger = createLogger('SSO-Providers')

export async function GET(req: NextRequest) {
export async function GET() {
try {
const session = await auth.api.getSession({ headers: req.headers })
const session = await getSession()

let providers
if (session?.user?.id) {
Expand Down Expand Up @@ -38,8 +38,6 @@ export async function GET(req: NextRequest) {
: ('oidc' as 'oidc' | 'saml'),
}))
} else {
// Unauthenticated users can only see basic info (domain only)
// This is needed for SSO login flow to check if a domain has SSO enabled
const results = await db
.select({
domain: ssoProvider.domain,
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/billing/update-cost/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
import { checkInternalApiKey } from '@/lib/copilot/utils'
import { isBillingEnabled } from '@/lib/core/config/environment'
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'

Expand Down
18 changes: 8 additions & 10 deletions apps/sim/app/api/chat/manage/[id]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { NextRequest } from 'next/server'
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

vi.mock('@/lib/core/config/feature-flags', () => ({
isDev: true,
isHosted: false,
isProd: false,
}))

describe('Chat Edit API Route', () => {
const mockSelect = vi.fn()
const mockFrom = vi.fn()
Expand All @@ -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 })
Expand Down Expand Up @@ -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,
}))
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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' }) })
Expand All @@ -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',
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/chat/manage/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
11 changes: 6 additions & 5 deletions apps/sim/app/api/chat/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => ({
Expand All @@ -62,11 +68,6 @@ describe('Chat API Utils', () => {
NODE_ENV: 'development',
},
})

vi.doMock('@/lib/core/config/environment', () => ({
isDev: true,
isHosted: false,
}))
})

afterEach(() => {
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/api/chat/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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' }
Expand Down
15 changes: 5 additions & 10 deletions apps/sim/app/api/copilot/auto-allowed-tools/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ 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')

/**
* 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 })
Expand All @@ -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,
Expand All @@ -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 })
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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 })
Expand All @@ -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) {
Expand Down
Loading