From 8e0553aae0c897a41bd68c7fc143d583d4817427 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 15 Oct 2025 12:04:05 -0700 Subject: [PATCH 1/9] fix(chat-subs): always use next public app url env --- apps/sim/lib/webhooks/webhook-helpers.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/sim/lib/webhooks/webhook-helpers.ts b/apps/sim/lib/webhooks/webhook-helpers.ts index 850e292a61..657d432389 100644 --- a/apps/sim/lib/webhooks/webhook-helpers.ts +++ b/apps/sim/lib/webhooks/webhook-helpers.ts @@ -71,11 +71,14 @@ export async function createTeamsSubscription( } // Build notification URL - const requestOrigin = new URL(request.url).origin - const effectiveOrigin = requestOrigin.includes('localhost') - ? env.NEXT_PUBLIC_APP_URL || requestOrigin - : requestOrigin - const notificationUrl = `${effectiveOrigin}/api/webhooks/trigger/${webhook.path}` + // Always use NEXT_PUBLIC_APP_URL to ensure Microsoft Graph can reach the public endpoint + if (!env.NEXT_PUBLIC_APP_URL) { + teamsLogger.error( + `[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot create Teams subscription` + ) + return false + } + const notificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${webhook.path}` // Subscribe to the specified chat const resource = `/chats/${chatId}/messages` From 0e94b8299d8bdff905d1c7ae9b814645391da5b4 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 15 Oct 2025 12:37:42 -0700 Subject: [PATCH 2/9] use getBaseUrl everywhere --- apps/sim/app/(auth)/login/login-form.tsx | 3 +- apps/sim/app/api/chat/route.ts | 4 +- apps/sim/app/api/files/download/route.ts | 3 +- .../app/api/webhooks/[id]/test-url/route.ts | 9 +--- apps/sim/app/api/webhooks/route.ts | 11 +--- apps/sim/app/api/webhooks/test/route.ts | 12 +---- .../invitations/[invitationId]/route.ts | 53 ++++--------------- .../app/api/workspaces/invitations/route.ts | 4 +- .../components/trigger-modal.tsx | 4 +- .../webhook/components/webhook-modal.tsx | 8 +-- .../components/account/account.tsx | 3 +- .../cancel-subscription.tsx | 3 +- .../components/subscription/subscription.tsx | 3 +- .../components/team-usage/team-usage.tsx | 3 +- .../emails/batch-invitation-email.tsx | 4 +- .../emails/enterprise-subscription-email.tsx | 4 +- apps/sim/components/emails/footer.tsx | 7 +-- .../emails/help-confirmation-email.tsx | 4 +- .../components/emails/invitation-email.tsx | 4 +- .../emails/otp-verification-email.tsx | 4 +- .../components/emails/plan-welcome-email.tsx | 4 +- apps/sim/components/emails/render-email.ts | 3 +- .../emails/reset-password-email.tsx | 4 +- .../emails/usage-threshold-email.tsx | 4 +- .../emails/workspace-invitation.tsx | 4 +- apps/sim/lib/auth-client.ts | 9 ++-- apps/sim/lib/auth.ts | 4 +- apps/sim/lib/billing/core/subscription.ts | 4 +- apps/sim/lib/billing/core/usage.ts | 3 +- apps/sim/lib/branding/metadata.ts | 4 +- apps/sim/lib/email/mailer.ts | 3 +- .../lib/guardrails/validate_hallucination.ts | 4 +- apps/sim/lib/urls/utils.ts | 25 ++++----- apps/sim/lib/workflows/utils.ts | 5 +- apps/sim/tools/http/utils.ts | 15 +----- 35 files changed, 90 insertions(+), 155 deletions(-) diff --git a/apps/sim/app/(auth)/login/login-form.tsx b/apps/sim/app/(auth)/login/login-form.tsx index 45877f4e8f..aa274086a8 100644 --- a/apps/sim/app/(auth)/login/login-form.tsx +++ b/apps/sim/app/(auth)/login/login-form.tsx @@ -18,6 +18,7 @@ import { client } from '@/lib/auth-client' import { quickValidateEmail } from '@/lib/email/validation' import { env, isFalsy, isTruthy } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { cn } from '@/lib/utils' import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons' import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button' @@ -322,7 +323,7 @@ export default function LoginPage({ }, body: JSON.stringify({ email: forgotPasswordEmail, - redirectTo: `${window.location.origin}/reset-password`, + redirectTo: `${getBaseUrl()}/reset-password`, }), }) diff --git a/apps/sim/app/api/chat/route.ts b/apps/sim/app/api/chat/route.ts index c546691ecc..fb51159bb2 100644 --- a/apps/sim/app/api/chat/route.ts +++ b/apps/sim/app/api/chat/route.ts @@ -5,9 +5,9 @@ import type { NextRequest } from 'next/server' import { v4 as uuidv4 } from 'uuid' import { z } from 'zod' import { getSession } from '@/lib/auth' -import { env } from '@/lib/env' import { isDev } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { encryptSecret } from '@/lib/utils' import { checkWorkflowAccessForChatCreation } from '@/app/api/chat/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' @@ -171,7 +171,7 @@ export async function POST(request: NextRequest) { // Return successful response with chat URL // Generate chat URL using path-based routing instead of subdomains - const baseUrl = env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' + const baseUrl = getBaseUrl() let chatUrl: string try { diff --git a/apps/sim/app/api/files/download/route.ts b/apps/sim/app/api/files/download/route.ts index d1429b1289..dcf0226bc9 100644 --- a/apps/sim/app/api/files/download/route.ts +++ b/apps/sim/app/api/files/download/route.ts @@ -2,6 +2,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' import { getPresignedUrl, getPresignedUrlWithConfig, isUsingCloudStorage } from '@/lib/uploads' import { BLOB_EXECUTION_FILES_CONFIG, S3_EXECUTION_FILES_CONFIG } from '@/lib/uploads/setup' +import { getBaseUrl } from '@/lib/urls/utils' import { createErrorResponse } from '@/app/api/files/utils' const logger = createLogger('FileDownload') @@ -81,7 +82,7 @@ export async function POST(request: NextRequest) { } } else { // For local storage, return the direct path - const downloadUrl = `${process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/files/serve/${key}` + const downloadUrl = `${getBaseUrl()}/api/files/serve/${key}` return NextResponse.json({ downloadUrl, diff --git a/apps/sim/app/api/webhooks/[id]/test-url/route.ts b/apps/sim/app/api/webhooks/[id]/test-url/route.ts index b4da4142d6..b844dc8d61 100644 --- a/apps/sim/app/api/webhooks/[id]/test-url/route.ts +++ b/apps/sim/app/api/webhooks/[id]/test-url/route.ts @@ -2,9 +2,9 @@ import { db, webhook, workflow } from '@sim/db' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { getBaseUrl } from '@/lib/urls/utils' import { generateRequestId } from '@/lib/utils' import { signTestWebhookToken } from '@/lib/webhooks/test-tokens' @@ -64,13 +64,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } - if (!env.NEXT_PUBLIC_APP_URL) { - logger.error(`[${requestId}] NEXT_PUBLIC_APP_URL not configured`) - return NextResponse.json({ error: 'Server configuration error' }, { status: 500 }) - } - const token = await signTestWebhookToken(id, ttlSeconds) - const url = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/test/${id}?token=${encodeURIComponent(token)}` + const url = `${getBaseUrl()}/api/webhooks/test/${id}?token=${encodeURIComponent(token)}` logger.info(`[${requestId}] Minted test URL for webhook ${id}`) return NextResponse.json({ diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index d9b39e66c5..f4fcfbe94c 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -4,9 +4,9 @@ import { and, desc, eq } from 'drizzle-orm' import { nanoid } from 'nanoid' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { getBaseUrl } from '@/lib/urls/utils' import { generateRequestId } from '@/lib/utils' import { getOAuthToken } from '@/app/api/auth/oauth/utils' @@ -467,14 +467,7 @@ async function createAirtableWebhookSubscription( ) } - if (!env.NEXT_PUBLIC_APP_URL) { - logger.error( - `[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot register Airtable webhook` - ) - throw new Error('NEXT_PUBLIC_APP_URL must be configured for Airtable webhook registration') - } - - const notificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${path}` + const notificationUrl = `${getBaseUrl()}/api/webhooks/trigger/${path}` const airtableApiUrl = `https://api.airtable.com/v0/bases/${baseId}/webhooks` diff --git a/apps/sim/app/api/webhooks/test/route.ts b/apps/sim/app/api/webhooks/test/route.ts index 5b8886bf23..8467f53063 100644 --- a/apps/sim/app/api/webhooks/test/route.ts +++ b/apps/sim/app/api/webhooks/test/route.ts @@ -2,8 +2,8 @@ import { db } from '@sim/db' import { webhook } from '@sim/db/schema' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { generateRequestId } from '@/lib/utils' const logger = createLogger('WebhookTestAPI') @@ -35,15 +35,7 @@ export async function GET(request: NextRequest) { const provider = foundWebhook.provider || 'generic' const providerConfig = (foundWebhook.providerConfig as Record) || {} - if (!env.NEXT_PUBLIC_APP_URL) { - logger.error(`[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot test webhook`) - return NextResponse.json( - { success: false, error: 'NEXT_PUBLIC_APP_URL must be configured' }, - { status: 500 } - ) - } - const baseUrl = env.NEXT_PUBLIC_APP_URL - const webhookUrl = `${baseUrl}/api/webhooks/trigger/${foundWebhook.path}` + const webhookUrl = `${getBaseUrl()}/api/webhooks/trigger/${foundWebhook.path}` logger.info(`[${requestId}] Testing webhook for provider: ${provider}`, { webhookId, diff --git a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts index 9b8a379cd5..dc6073a3f1 100644 --- a/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts +++ b/apps/sim/app/api/workspaces/invitations/[invitationId]/route.ts @@ -14,8 +14,8 @@ import { WorkspaceInvitationEmail } from '@/components/emails/workspace-invitati import { getSession } from '@/lib/auth' import { sendEmail } from '@/lib/email/mailer' import { getFromEmailAddress } from '@/lib/email/utils' -import { env } from '@/lib/env' import { hasWorkspaceAdminAccess } from '@/lib/permissions/utils' +import { getBaseUrl } from '@/lib/urls/utils' // GET /api/workspaces/invitations/[invitationId] - Get invitation details OR accept via token export async function GET( @@ -30,12 +30,7 @@ export async function GET( if (!session?.user?.id) { // For token-based acceptance flows, redirect to login if (isAcceptFlow) { - return NextResponse.redirect( - new URL( - `/invite/${invitationId}?token=${token}`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) - ) + return NextResponse.redirect(new URL(`/invite/${invitationId}?token=${token}`, getBaseUrl())) } return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } @@ -54,10 +49,7 @@ export async function GET( if (!invitation) { if (isAcceptFlow) { return NextResponse.redirect( - new URL( - `/invite/${invitationId}?error=invalid-token`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) + new URL(`/invite/${invitationId}?error=invalid-token`, getBaseUrl()) ) } return NextResponse.json({ error: 'Invitation not found or has expired' }, { status: 404 }) @@ -66,10 +58,7 @@ export async function GET( if (new Date() > new Date(invitation.expiresAt)) { if (isAcceptFlow) { return NextResponse.redirect( - new URL( - `/invite/${invitation.id}?error=expired`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) + new URL(`/invite/${invitation.id}?error=expired`, getBaseUrl()) ) } return NextResponse.json({ error: 'Invitation has expired' }, { status: 400 }) @@ -84,10 +73,7 @@ export async function GET( if (!workspaceDetails) { if (isAcceptFlow) { return NextResponse.redirect( - new URL( - `/invite/${invitation.id}?error=workspace-not-found`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) + new URL(`/invite/${invitation.id}?error=workspace-not-found`, getBaseUrl()) ) } return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) @@ -96,10 +82,7 @@ export async function GET( if (isAcceptFlow) { if (invitation.status !== ('pending' as WorkspaceInvitationStatus)) { return NextResponse.redirect( - new URL( - `/invite/${invitation.id}?error=already-processed`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) + new URL(`/invite/${invitation.id}?error=already-processed`, getBaseUrl()) ) } @@ -114,10 +97,7 @@ export async function GET( if (!userData) { return NextResponse.redirect( - new URL( - `/invite/${invitation.id}?error=user-not-found`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) + new URL(`/invite/${invitation.id}?error=user-not-found`, getBaseUrl()) ) } @@ -125,10 +105,7 @@ export async function GET( if (!isValidMatch) { return NextResponse.redirect( - new URL( - `/invite/${invitation.id}?error=email-mismatch`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) + new URL(`/invite/${invitation.id}?error=email-mismatch`, getBaseUrl()) ) } @@ -154,10 +131,7 @@ export async function GET( .where(eq(workspaceInvitation.id, invitation.id)) return NextResponse.redirect( - new URL( - `/workspace/${invitation.workspaceId}/w`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) + new URL(`/workspace/${invitation.workspaceId}/w`, getBaseUrl()) ) } @@ -181,12 +155,7 @@ export async function GET( .where(eq(workspaceInvitation.id, invitation.id)) }) - return NextResponse.redirect( - new URL( - `/workspace/${invitation.workspaceId}/w`, - env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' - ) - ) + return NextResponse.redirect(new URL(`/workspace/${invitation.workspaceId}/w`, getBaseUrl())) } return NextResponse.json({ @@ -298,7 +267,7 @@ export async function POST( .set({ token: newToken, expiresAt: newExpiresAt, updatedAt: new Date() }) .where(eq(workspaceInvitation.id, invitationId)) - const baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' + const baseUrl = getBaseUrl() const invitationLink = `${baseUrl}/invite/${invitationId}?token=${newToken}` const emailHtml = await render( diff --git a/apps/sim/app/api/workspaces/invitations/route.ts b/apps/sim/app/api/workspaces/invitations/route.ts index e15885a802..99b89f2b73 100644 --- a/apps/sim/app/api/workspaces/invitations/route.ts +++ b/apps/sim/app/api/workspaces/invitations/route.ts @@ -15,8 +15,8 @@ import { WorkspaceInvitationEmail } from '@/components/emails/workspace-invitati import { getSession } from '@/lib/auth' import { sendEmail } from '@/lib/email/mailer' import { getFromEmailAddress } from '@/lib/email/utils' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' export const dynamic = 'force-dynamic' @@ -232,7 +232,7 @@ async function sendInvitationEmail({ token: string }) { try { - const baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' + const baseUrl = getBaseUrl() // Use invitation ID in path, token in query parameter for security const invitationLink = `${baseUrl}/invite/${invitationId}?token=${token}` diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-modal.tsx index 73863f3f90..5be21dd2a6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/trigger-config/components/trigger-modal.tsx @@ -20,6 +20,7 @@ import { } from '@/components/ui/select' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { cn } from '@/lib/utils' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { getTrigger } from '@/triggers' @@ -284,8 +285,7 @@ export function TriggerModal({ } if (finalPath) { - const baseUrl = window.location.origin - setWebhookUrl(`${baseUrl}/api/webhooks/trigger/${finalPath}`) + setWebhookUrl(`${getBaseUrl()}/api/webhooks/trigger/${finalPath}`) } }, [ triggerPath, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx index 2f7ce0ab45..7363de60ab 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/webhook/components/webhook-modal.tsx @@ -9,6 +9,7 @@ import { DialogTitle, } from '@/components/ui/dialog' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { AirtableConfig, DeleteConfirmDialog, @@ -404,12 +405,7 @@ export function WebhookModal({ }, [webhookPath]) // Construct the full webhook URL - const baseUrl = - typeof window !== 'undefined' - ? `${window.location.protocol}//${window.location.host}` - : 'https://your-domain.com' - - const webhookUrl = `${baseUrl}/api/webhooks/trigger/${formattedPath}` + const webhookUrl = `${getBaseUrl()}/api/webhooks/trigger/${formattedPath}` const generateTestUrl = async () => { if (!webhookId) return diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/account/account.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/account/account.tsx index 99749d6ecf..69b38d4307 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/account/account.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/account/account.tsx @@ -12,6 +12,7 @@ import { Skeleton } from '@/components/ui/skeleton' import { signOut, useSession } from '@/lib/auth-client' import { useBrandConfig } from '@/lib/branding/branding' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { useProfilePictureUpload } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/account/hooks/use-profile-picture-upload' import { clearUserData } from '@/stores' @@ -208,7 +209,7 @@ export function Account(_props: AccountProps) { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, - redirectTo: `${window.location.origin}/reset-password`, + redirectTo: `${getBaseUrl()}/reset-password`, }), }) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx index 18565e9c08..30ca7ce4ee 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx @@ -15,6 +15,7 @@ import { Button } from '@/components/ui/button' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { useSession, useSubscription } from '@/lib/auth-client' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { cn } from '@/lib/utils' import { useOrganizationStore } from '@/stores/organization' import { useSubscriptionStore } from '@/stores/subscription/store' @@ -89,7 +90,7 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub throw new Error('Subscription management not available') } - const returnUrl = window.location.origin + window.location.pathname.split('/w/')[0] + const returnUrl = getBaseUrl() + window.location.pathname.split('/w/')[0] const cancelParams: any = { returnUrl, diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx index f7c07d051e..9ac78581ef 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { Skeleton, Switch } from '@/components/ui' import { useSession } from '@/lib/auth-client' import { useSubscriptionUpgrade } from '@/lib/subscription/upgrade' +import { getBaseUrl } from '@/lib/urls/utils' import { cn } from '@/lib/utils' import { UsageHeader } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/shared/usage-header' import { @@ -391,7 +392,7 @@ export function Subscription({ onOpenChange }: SubscriptionProps) { context: subscription.isTeam || subscription.isEnterprise ? 'organization' : 'user', organizationId: activeOrgId, - returnUrl: `${window.location.origin}/workspace?billing=updated`, + returnUrl: `${getBaseUrl()}/workspace?billing=updated`, }), }) const data = await res.json() diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-usage/team-usage.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-usage/team-usage.tsx index 432e53b429..8af2017811 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-usage/team-usage.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/components/team-usage/team-usage.tsx @@ -4,6 +4,7 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Skeleton } from '@/components/ui/skeleton' import { useActiveOrganization } from '@/lib/auth-client' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { UsageHeader } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/shared/usage-header' import { UsageLimit, @@ -122,7 +123,7 @@ export function TeamUsage({ hasAdminAccess }: TeamUsageProps) { body: JSON.stringify({ context: 'organization', organizationId: activeOrg?.id, - returnUrl: `${window.location.origin}/workspace?billing=updated`, + returnUrl: `${getBaseUrl()}/workspace?billing=updated`, }), }) const data = await res.json() diff --git a/apps/sim/components/emails/batch-invitation-email.tsx b/apps/sim/components/emails/batch-invitation-email.tsx index 8b4d948d5e..5c8d09bc0f 100644 --- a/apps/sim/components/emails/batch-invitation-email.tsx +++ b/apps/sim/components/emails/batch-invitation-email.tsx @@ -12,7 +12,7 @@ import { Text, } from '@react-email/components' import { getBrandConfig } from '@/lib/branding/branding' -import { env } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' import EmailFooter from './footer' @@ -30,7 +30,7 @@ interface BatchInvitationEmailProps { acceptUrl: string } -const baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' +const baseUrl = getBaseUrl() const getPermissionLabel = (permission: string) => { switch (permission) { diff --git a/apps/sim/components/emails/enterprise-subscription-email.tsx b/apps/sim/components/emails/enterprise-subscription-email.tsx index 763ff0e55d..47168039a2 100644 --- a/apps/sim/components/emails/enterprise-subscription-email.tsx +++ b/apps/sim/components/emails/enterprise-subscription-email.tsx @@ -13,7 +13,7 @@ import { } from '@react-email/components' import { format } from 'date-fns' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' import EmailFooter from './footer' @@ -24,7 +24,7 @@ interface EnterpriseSubscriptionEmailProps { createdDate?: Date } -const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' +const baseUrl = getBaseUrl() export const EnterpriseSubscriptionEmail = ({ userName = 'Valued User', diff --git a/apps/sim/components/emails/footer.tsx b/apps/sim/components/emails/footer.tsx index 28703d96ef..7ee919fcfb 100644 --- a/apps/sim/components/emails/footer.tsx +++ b/apps/sim/components/emails/footer.tsx @@ -1,7 +1,7 @@ import { Container, Img, Link, Section, Text } from '@react-email/components' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' import { isHosted } from '@/lib/environment' +import { getBaseUrl } from '@/lib/urls/utils' interface UnsubscribeOptions { unsubscribeToken?: string @@ -13,10 +13,7 @@ interface EmailFooterProps { unsubscribe?: UnsubscribeOptions } -export const EmailFooter = ({ - baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai', - unsubscribe, -}: EmailFooterProps) => { +export const EmailFooter = ({ baseUrl = getBaseUrl(), unsubscribe }: EmailFooterProps) => { const brand = getBrandConfig() return ( diff --git a/apps/sim/components/emails/help-confirmation-email.tsx b/apps/sim/components/emails/help-confirmation-email.tsx index 0a13cca47e..a470e923dd 100644 --- a/apps/sim/components/emails/help-confirmation-email.tsx +++ b/apps/sim/components/emails/help-confirmation-email.tsx @@ -12,7 +12,7 @@ import { } from '@react-email/components' import { format } from 'date-fns' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' import EmailFooter from './footer' @@ -23,7 +23,7 @@ interface HelpConfirmationEmailProps { submittedDate?: Date } -const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' +const baseUrl = getBaseUrl() const getTypeLabel = (type: string) => { switch (type) { diff --git a/apps/sim/components/emails/invitation-email.tsx b/apps/sim/components/emails/invitation-email.tsx index 0532455583..da2445a35c 100644 --- a/apps/sim/components/emails/invitation-email.tsx +++ b/apps/sim/components/emails/invitation-email.tsx @@ -13,8 +13,8 @@ import { } from '@react-email/components' import { format } from 'date-fns' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' import EmailFooter from './footer' @@ -26,7 +26,7 @@ interface InvitationEmailProps { updatedDate?: Date } -const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' +const baseUrl = getBaseUrl() const logger = createLogger('InvitationEmail') diff --git a/apps/sim/components/emails/otp-verification-email.tsx b/apps/sim/components/emails/otp-verification-email.tsx index bf1faed218..b0953b3faa 100644 --- a/apps/sim/components/emails/otp-verification-email.tsx +++ b/apps/sim/components/emails/otp-verification-email.tsx @@ -11,7 +11,7 @@ import { Text, } from '@react-email/components' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' import EmailFooter from './footer' @@ -22,7 +22,7 @@ interface OTPVerificationEmailProps { chatTitle?: string } -const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' +const baseUrl = getBaseUrl() const getSubjectByType = (type: string, brandName: string, chatTitle?: string) => { switch (type) { diff --git a/apps/sim/components/emails/plan-welcome-email.tsx b/apps/sim/components/emails/plan-welcome-email.tsx index 0141814036..25bd1a9fac 100644 --- a/apps/sim/components/emails/plan-welcome-email.tsx +++ b/apps/sim/components/emails/plan-welcome-email.tsx @@ -14,7 +14,7 @@ import { } from '@react-email/components' import EmailFooter from '@/components/emails/footer' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' interface PlanWelcomeEmailProps { @@ -31,7 +31,7 @@ export function PlanWelcomeEmail({ createdDate = new Date(), }: PlanWelcomeEmailProps) { const brand = getBrandConfig() - const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' + const baseUrl = getBaseUrl() const cta = loginLink || `${baseUrl}/login` const previewText = `${brand.name}: Your ${planName} plan is active` diff --git a/apps/sim/components/emails/render-email.ts b/apps/sim/components/emails/render-email.ts index 9f124b606a..8c439130cb 100644 --- a/apps/sim/components/emails/render-email.ts +++ b/apps/sim/components/emails/render-email.ts @@ -10,6 +10,7 @@ import { UsageThresholdEmail, } from '@/components/emails' import { getBrandConfig } from '@/lib/branding/branding' +import { getBaseUrl } from '@/lib/urls/utils' export async function renderOTPEmail( otp: string, @@ -89,7 +90,7 @@ export async function renderEnterpriseSubscriptionEmail( userName: string, userEmail: string ): Promise { - const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' + const baseUrl = getBaseUrl() const loginLink = `${baseUrl}/login` return await render( diff --git a/apps/sim/components/emails/reset-password-email.tsx b/apps/sim/components/emails/reset-password-email.tsx index c444114cbf..5dec2b10ee 100644 --- a/apps/sim/components/emails/reset-password-email.tsx +++ b/apps/sim/components/emails/reset-password-email.tsx @@ -13,7 +13,7 @@ import { } from '@react-email/components' import { format } from 'date-fns' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' import EmailFooter from './footer' @@ -23,7 +23,7 @@ interface ResetPasswordEmailProps { updatedDate?: Date } -const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' +const baseUrl = getBaseUrl() export const ResetPasswordEmail = ({ username = '', diff --git a/apps/sim/components/emails/usage-threshold-email.tsx b/apps/sim/components/emails/usage-threshold-email.tsx index 82ea228544..96e46f69af 100644 --- a/apps/sim/components/emails/usage-threshold-email.tsx +++ b/apps/sim/components/emails/usage-threshold-email.tsx @@ -14,7 +14,7 @@ import { } from '@react-email/components' import EmailFooter from '@/components/emails/footer' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' interface UsageThresholdEmailProps { @@ -37,7 +37,7 @@ export function UsageThresholdEmail({ updatedDate = new Date(), }: UsageThresholdEmailProps) { const brand = getBrandConfig() - const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' + const baseUrl = getBaseUrl() const previewText = `${brand.name}: You're at ${percentUsed}% of your ${planName} monthly budget` diff --git a/apps/sim/components/emails/workspace-invitation.tsx b/apps/sim/components/emails/workspace-invitation.tsx index 6277147322..c633790fb7 100644 --- a/apps/sim/components/emails/workspace-invitation.tsx +++ b/apps/sim/components/emails/workspace-invitation.tsx @@ -12,8 +12,8 @@ import { Text, } from '@react-email/components' import { getBrandConfig } from '@/lib/branding/branding' -import { getEnv } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { baseStyles } from './base-styles' import EmailFooter from './footer' @@ -25,7 +25,7 @@ interface WorkspaceInvitationEmailProps { invitationLink?: string } -const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'https://sim.ai' +const baseUrl = getBaseUrl() export const WorkspaceInvitationEmail = ({ workspaceName = 'Workspace', diff --git a/apps/sim/lib/auth-client.ts b/apps/sim/lib/auth-client.ts index 718a526c18..6b992121c9 100644 --- a/apps/sim/lib/auth-client.ts +++ b/apps/sim/lib/auth-client.ts @@ -9,16 +9,13 @@ import { } from 'better-auth/client/plugins' import { createAuthClient } from 'better-auth/react' import type { auth } from '@/lib/auth' -import { env, getEnv } from '@/lib/env' +import { env } from '@/lib/env' import { isBillingEnabled } from '@/lib/environment' import { SessionContext, type SessionHookResult } from '@/lib/session/session-context' - -export function getBaseURL() { - return getEnv('NEXT_PUBLIC_APP_URL') || 'http://localhost:3000' -} +import { getBaseUrl } from '@/lib/urls/utils' export const client = createAuthClient({ - baseURL: getBaseURL(), + baseURL: getBaseUrl(), plugins: [ emailOTPClient(), genericOAuthClient(), diff --git a/apps/sim/lib/auth.ts b/apps/sim/lib/auth.ts index 5dd170ad91..4e38aa384c 100644 --- a/apps/sim/lib/auth.ts +++ b/apps/sim/lib/auth.ts @@ -22,7 +22,6 @@ import { renderOTPEmail, renderPasswordResetEmail, } from '@/components/emails/render-email' -import { getBaseURL } from '@/lib/auth-client' import { sendPlanWelcomeEmail } from '@/lib/billing' import { authorizeSubscriptionReference } from '@/lib/billing/authorization' import { handleNewUser } from '@/lib/billing/core/usage' @@ -44,6 +43,7 @@ import { quickValidateEmail } from '@/lib/email/validation' import { env, isTruthy } from '@/lib/env' import { isBillingEnabled, isEmailVerificationEnabled } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { SSO_TRUSTED_PROVIDERS } from './sso/consts' const logger = createLogger('Auth') @@ -60,7 +60,7 @@ if (validStripeKey) { } export const auth = betterAuth({ - baseURL: getBaseURL(), + baseURL: getBaseUrl(), trustedOrigins: [ env.NEXT_PUBLIC_APP_URL, ...(env.NEXT_PUBLIC_SOCKET_URL ? [env.NEXT_PUBLIC_SOCKET_URL] : []), diff --git a/apps/sim/lib/billing/core/subscription.ts b/apps/sim/lib/billing/core/subscription.ts index ae7234e30c..dda969d114 100644 --- a/apps/sim/lib/billing/core/subscription.ts +++ b/apps/sim/lib/billing/core/subscription.ts @@ -9,9 +9,9 @@ import { getPerUserMinimumLimit, } from '@/lib/billing/subscriptions/utils' import type { UserSubscriptionState } from '@/lib/billing/types' -import { env } from '@/lib/env' import { isProd } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('SubscriptionCore') @@ -303,7 +303,7 @@ export async function sendPlanWelcomeEmail(subscription: any): Promise { ) const { sendEmail } = await import('@/lib/email/mailer') - const baseUrl = env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' + const baseUrl = getBaseUrl() const html = await renderPlanWelcomeEmail({ planName: subPlan === 'pro' ? 'Pro' : 'Team', userName: users[0].name || undefined, diff --git a/apps/sim/lib/billing/core/usage.ts b/apps/sim/lib/billing/core/usage.ts index 71d8bbd3d5..8d219a8b9e 100644 --- a/apps/sim/lib/billing/core/usage.ts +++ b/apps/sim/lib/billing/core/usage.ts @@ -13,6 +13,7 @@ import { sendEmail } from '@/lib/email/mailer' import { getEmailPreferences } from '@/lib/email/unsubscribe' import { isBillingEnabled } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('UsageManagement') @@ -617,7 +618,7 @@ export async function maybeSendUsageThresholdEmail(params: { if (!(params.percentBefore < 80 && params.percentAfter >= 80)) return if (params.limit <= 0 || params.currentUsageAfter <= 0) return - const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://sim.ai' + const baseUrl = getBaseUrl() const ctaLink = `${baseUrl}/workspace?billing=usage` const sendTo = async (email: string, name?: string) => { const prefs = await getEmailPreferences(email) diff --git a/apps/sim/lib/branding/metadata.ts b/apps/sim/lib/branding/metadata.ts index e2f4b2d119..9bd057fb54 100644 --- a/apps/sim/lib/branding/metadata.ts +++ b/apps/sim/lib/branding/metadata.ts @@ -1,6 +1,6 @@ import type { Metadata } from 'next' import { getBrandConfig } from '@/lib/branding/branding' -import { env } from '@/lib/env' +import { getBaseUrl } from '@/lib/urls/utils' /** * Generate dynamic metadata based on brand configuration @@ -63,7 +63,7 @@ export function generateBrandedMetadata(override: Partial = {}): Metad openGraph: { type: 'website', locale: 'en_US', - url: env.NEXT_PUBLIC_APP_URL || 'https://sim.ai', + url: getBaseUrl(), title: defaultTitle, description: summaryFull, siteName: brand.name, diff --git a/apps/sim/lib/email/mailer.ts b/apps/sim/lib/email/mailer.ts index ab35acc349..08f49226cc 100644 --- a/apps/sim/lib/email/mailer.ts +++ b/apps/sim/lib/email/mailer.ts @@ -4,6 +4,7 @@ import { generateUnsubscribeToken, isUnsubscribed } from '@/lib/email/unsubscrib import { getFromEmailAddress } from '@/lib/email/utils' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('Mailer') @@ -167,7 +168,7 @@ async function processEmailData(options: EmailOptions): Promise` diff --git a/apps/sim/lib/guardrails/validate_hallucination.ts b/apps/sim/lib/guardrails/validate_hallucination.ts index 49f08dca84..409b8bb2ef 100644 --- a/apps/sim/lib/guardrails/validate_hallucination.ts +++ b/apps/sim/lib/guardrails/validate_hallucination.ts @@ -1,5 +1,5 @@ -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { executeProviderRequest } from '@/providers' import { getApiKey, getProviderFromModel } from '@/providers/utils' @@ -41,7 +41,7 @@ async function queryKnowledgeBase( }) // Call the knowledge base search API directly - const searchUrl = `${env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'}/api/knowledge/search` + const searchUrl = `${getBaseUrl()}/api/knowledge/search` const response = await fetch(searchUrl, { method: 'POST', diff --git a/apps/sim/lib/urls/utils.ts b/apps/sim/lib/urls/utils.ts index 3b572a079a..0b623b1ebf 100644 --- a/apps/sim/lib/urls/utils.ts +++ b/apps/sim/lib/urls/utils.ts @@ -2,25 +2,26 @@ import { getEnv } from '@/lib/env' import { isProd } from '@/lib/environment' /** - * Returns the base URL of the application, respecting environment variables for deployment environments + * Returns the base URL of the application from NEXT_PUBLIC_APP_URL + * This ensures webhooks, callbacks, and other integrations always use the correct public URL * @returns The base URL string (e.g., 'http://localhost:3000' or 'https://example.com') + * @throws Error if NEXT_PUBLIC_APP_URL is not configured */ export function getBaseUrl(): string { - if (typeof window !== 'undefined' && window.location?.origin) { - return window.location.origin - } - const baseUrl = getEnv('NEXT_PUBLIC_APP_URL') - if (baseUrl) { - if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) { - return baseUrl - } - const protocol = isProd ? 'https://' : 'http://' - return `${protocol}${baseUrl}` + if (!baseUrl) { + throw new Error( + 'NEXT_PUBLIC_APP_URL must be configured for webhooks and callbacks to work correctly' + ) + } + + if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) { + return baseUrl } - return 'http://localhost:3000' + const protocol = isProd ? 'https://' : 'http://' + return `${protocol}${baseUrl}` } /** diff --git a/apps/sim/lib/workflows/utils.ts b/apps/sim/lib/workflows/utils.ts index b412b6e5e5..58ee581ac7 100644 --- a/apps/sim/lib/workflows/utils.ts +++ b/apps/sim/lib/workflows/utils.ts @@ -178,9 +178,8 @@ export async function updateWorkflowRunCounts(workflowId: string, runs = 1) { throw new Error(`Workflow ${workflowId} not found`) } - // Get the origin from the environment or use direct DB update as fallback - const origin = - getEnv('NEXT_PUBLIC_APP_URL') || (typeof window !== 'undefined' ? window.location.origin : '') + // Get the origin from the environment + const origin = getEnv('NEXT_PUBLIC_APP_URL') if (origin) { // Use absolute URL with origin diff --git a/apps/sim/tools/http/utils.ts b/apps/sim/tools/http/utils.ts index 0d2809bca1..d252010125 100644 --- a/apps/sim/tools/http/utils.ts +++ b/apps/sim/tools/http/utils.ts @@ -1,4 +1,3 @@ -import { getEnv } from '@/lib/env' import { isTest } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' import { getBaseUrl } from '@/lib/urls/utils' @@ -6,18 +5,6 @@ import type { TableRow } from '@/tools/types' const logger = createLogger('HTTPRequestUtils') -export const getReferer = (): string => { - if (typeof window !== 'undefined') { - return window.location.origin - } - - try { - return getBaseUrl() - } catch (_error) { - return getEnv('NEXT_PUBLIC_APP_URL') || 'http://localhost:3000' - } -} - /** * Creates a set of default headers used in HTTP requests * @param customHeaders Additional user-provided headers to include @@ -35,7 +22,7 @@ export const getDefaultHeaders = ( 'Accept-Encoding': 'gzip, deflate, br', 'Cache-Control': 'no-cache', Connection: 'keep-alive', - Referer: getReferer(), + Referer: getBaseUrl(), 'Sec-Ch-Ua': 'Chromium;v=91, Not-A.Brand;v=99', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"macOS"', From 880a5baf54c7f8d454aacb1db5fb225c526433b2 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 15 Oct 2025 12:46:54 -0700 Subject: [PATCH 3/9] move remaining uses --- apps/sim/app/api/billing/portal/route.ts | 5 ++--- .../organizations/[id]/invitations/route.ts | 6 +++--- .../api/organizations/[id]/members/route.ts | 4 ++-- apps/sim/app/api/webhooks/[id]/route.ts | 10 ++-------- .../settings-modal/components/sso/sso.tsx | 8 ++++---- apps/sim/lib/branding/metadata.ts | 4 +--- apps/sim/lib/webhooks/webhook-helpers.ts | 19 +++---------------- 7 files changed, 17 insertions(+), 39 deletions(-) diff --git a/apps/sim/app/api/billing/portal/route.ts b/apps/sim/app/api/billing/portal/route.ts index f7a980cb2f..017fbb8bd7 100644 --- a/apps/sim/app/api/billing/portal/route.ts +++ b/apps/sim/app/api/billing/portal/route.ts @@ -4,8 +4,8 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { requireStripeClient } from '@/lib/billing/stripe-client' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('BillingPortal') @@ -21,8 +21,7 @@ export async function POST(request: NextRequest) { const context: 'user' | 'organization' = body?.context === 'organization' ? 'organization' : 'user' const organizationId: string | undefined = body?.organizationId || undefined - const returnUrl: string = - body?.returnUrl || `${env.NEXT_PUBLIC_APP_URL}/workspace?billing=updated` + const returnUrl: string = body?.returnUrl || `${getBaseUrl()}/workspace?billing=updated` const stripe = requireStripeClient() diff --git a/apps/sim/app/api/organizations/[id]/invitations/route.ts b/apps/sim/app/api/organizations/[id]/invitations/route.ts index 0da3668d9e..85fa235c57 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/route.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/route.ts @@ -23,9 +23,9 @@ import { } from '@/lib/billing/validation/seat-management' import { sendEmail } from '@/lib/email/mailer' import { quickValidateEmail } from '@/lib/email/validation' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { hasWorkspaceAdminAccess } from '@/lib/permissions/utils' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('OrganizationInvitations') @@ -339,7 +339,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ organizationEntry[0]?.name || 'organization', role, workspaceInvitationsWithNames, - `${env.NEXT_PUBLIC_APP_URL}/invite/${orgInvitation.id}` + `${getBaseUrl()}/invite/${orgInvitation.id}` ) emailResult = await sendEmail({ @@ -352,7 +352,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const emailHtml = await renderInvitationEmail( inviter[0]?.name || 'Someone', organizationEntry[0]?.name || 'organization', - `${env.NEXT_PUBLIC_APP_URL}/invite/${orgInvitation.id}`, + `${getBaseUrl()}/invite/${orgInvitation.id}`, email ) diff --git a/apps/sim/app/api/organizations/[id]/members/route.ts b/apps/sim/app/api/organizations/[id]/members/route.ts index 193ace3699..43e69e4813 100644 --- a/apps/sim/app/api/organizations/[id]/members/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/route.ts @@ -9,8 +9,8 @@ import { getUserUsageData } from '@/lib/billing/core/usage' import { validateSeatAvailability } from '@/lib/billing/validation/seat-management' import { sendEmail } from '@/lib/email/mailer' import { quickValidateEmail } from '@/lib/email/validation' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('OrganizationMembersAPI') @@ -260,7 +260,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const emailHtml = await renderInvitationEmail( inviter[0]?.name || 'Someone', organizationEntry[0]?.name || 'organization', - `${env.NEXT_PUBLIC_APP_URL}/invite/organization?id=${invitationId}`, + `${getBaseUrl()}/invite/organization?id=${invitationId}`, normalizedEmail ) diff --git a/apps/sim/app/api/webhooks/[id]/route.ts b/apps/sim/app/api/webhooks/[id]/route.ts index 1154ef1d89..a8119d9e1d 100644 --- a/apps/sim/app/api/webhooks/[id]/route.ts +++ b/apps/sim/app/api/webhooks/[id]/route.ts @@ -3,9 +3,9 @@ import { webhook, workflow } from '@sim/db/schema' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' -import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { getBaseUrl } from '@/lib/urls/utils' import { generateRequestId } from '@/lib/utils' import { getOAuthToken } from '@/app/api/auth/oauth/utils' @@ -282,13 +282,7 @@ export async function DELETE( if (!resolvedExternalId) { try { - if (!env.NEXT_PUBLIC_APP_URL) { - logger.error( - `[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot match Airtable webhook` - ) - throw new Error('NEXT_PUBLIC_APP_URL must be configured') - } - const expectedNotificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${foundWebhook.path}` + const expectedNotificationUrl = `${getBaseUrl()}/api/webhooks/trigger/${foundWebhook.path}` const listUrl = `https://api.airtable.com/v0/bases/${baseId}/webhooks` const listResp = await fetch(listUrl, { 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 4c10951f6d..985f8217ef 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 @@ -5,9 +5,9 @@ import { Check, ChevronDown, Copy, Eye, EyeOff } from 'lucide-react' import { Alert, AlertDescription, Button, Input, Label } from '@/components/ui' import { Skeleton } from '@/components/ui/skeleton' import { useSession } from '@/lib/auth-client' -import { env } from '@/lib/env' import { isBillingEnabled } from '@/lib/environment' import { createLogger } from '@/lib/logs/console/logger' +import { getBaseUrl } from '@/lib/urls/utils' import { cn } from '@/lib/utils' import { useOrganizationStore } from '@/stores/organization' @@ -441,7 +441,7 @@ export function SSO() { }) } - const callbackUrl = `${env.NEXT_PUBLIC_APP_URL}/api/auth/sso/callback/${formData.providerId}` + const callbackUrl = `${getBaseUrl()}/api/auth/sso/callback/${formData.providerId}` const copyCallback = async () => { try { @@ -551,14 +551,14 @@ export function SSO() {
(e.target as HTMLInputElement).select()} />