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
10 changes: 8 additions & 2 deletions apps/sim/app/(auth)/verify/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { hasEmailService } from '@/lib/email/mailer'
import { isProd } from '@/lib/environment'
import { isEmailVerificationEnabled, isProd } from '@/lib/environment'
import { VerifyContent } from '@/app/(auth)/verify/verify-content'

export const dynamic = 'force-dynamic'

export default function VerifyPage() {
const emailServiceConfigured = hasEmailService()

return <VerifyContent hasEmailService={emailServiceConfigured} isProduction={isProd} />
return (
<VerifyContent
hasEmailService={emailServiceConfigured}
isProduction={isProd}
isEmailVerificationEnabled={isEmailVerificationEnabled}
/>
)
}
12 changes: 8 additions & 4 deletions apps/sim/app/(auth)/verify/use-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const logger = createLogger('useVerification')
interface UseVerificationParams {
hasEmailService: boolean
isProduction: boolean
isEmailVerificationEnabled: boolean
}

interface UseVerificationReturn {
Expand All @@ -22,6 +23,7 @@ interface UseVerificationReturn {
isOtpComplete: boolean
hasEmailService: boolean
isProduction: boolean
isEmailVerificationEnabled: boolean
verifyCode: () => Promise<void>
resendCode: () => void
handleOtpChange: (value: string) => void
Expand All @@ -30,6 +32,7 @@ interface UseVerificationReturn {
export function useVerification({
hasEmailService,
isProduction,
isEmailVerificationEnabled,
}: UseVerificationParams): UseVerificationReturn {
const router = useRouter()
const searchParams = useSearchParams()
Expand Down Expand Up @@ -157,7 +160,7 @@ export function useVerification({
}

function resendCode() {
if (!email || !hasEmailService) return
if (!email || !hasEmailService || !isEmailVerificationEnabled) return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Should verify email verification is enabled before attempting to send OTP in the initial setup useEffect (lines 79-83) as well, similar to how resendCode now checks this condition

Suggested change
if (!email || !hasEmailService || !isEmailVerificationEnabled) return
if (email && !isSendingInitialOtp && hasEmailService && isEmailVerificationEnabled) {


setIsLoading(true)
setErrorMessage('')
Expand Down Expand Up @@ -197,14 +200,14 @@ export function useVerification({

useEffect(() => {
if (typeof window !== 'undefined') {
if (!isProduction && !hasEmailService) {
if (!isEmailVerificationEnabled) {
setIsVerified(true)

const handleRedirect = async () => {
try {
await refetchSession()
} catch (error) {
logger.warn('Failed to refetch session during dev verification skip:', error)
logger.warn('Failed to refetch session during verification skip:', error)
}

if (isInviteFlow && redirectUrl) {
Expand All @@ -217,7 +220,7 @@ export function useVerification({
handleRedirect()
}
}
}, [isProduction, hasEmailService, router, isInviteFlow, redirectUrl])
}, [isEmailVerificationEnabled, router, isInviteFlow, redirectUrl])

return {
otp,
Expand All @@ -229,6 +232,7 @@ export function useVerification({
isOtpComplete,
hasEmailService,
isProduction,
isEmailVerificationEnabled,
verifyCode,
resendCode,
handleOtpChange,
Expand Down
31 changes: 22 additions & 9 deletions apps/sim/app/(auth)/verify/verify-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import { soehne } from '@/app/fonts/soehne/soehne'
interface VerifyContentProps {
hasEmailService: boolean
isProduction: boolean
isEmailVerificationEnabled: boolean
}

function VerificationForm({
hasEmailService,
isProduction,
isEmailVerificationEnabled,
}: {
hasEmailService: boolean
isProduction: boolean
isEmailVerificationEnabled: boolean
}) {
const {
otp,
Expand All @@ -32,7 +35,7 @@ function VerificationForm({
verifyCode,
resendCode,
handleOtpChange,
} = useVerification({ hasEmailService, isProduction })
} = useVerification({ hasEmailService, isProduction, isEmailVerificationEnabled })

const [countdown, setCountdown] = useState(0)
const [isResendDisabled, setIsResendDisabled] = useState(false)
Expand Down Expand Up @@ -93,15 +96,17 @@ function VerificationForm({
<p className={`${inter.className} font-[380] text-[16px] text-muted-foreground`}>
{isVerified
? 'Your email has been verified. Redirecting to dashboard...'
: hasEmailService
? `A verification code has been sent to ${email || 'your email'}`
: !isProduction
? 'Development mode: Check your console logs for the verification code'
: 'Error: Invalid API key configuration'}
: !isEmailVerificationEnabled
? 'Email verification is disabled. Redirecting to dashboard...'
: hasEmailService
? `A verification code has been sent to ${email || 'your email'}`
: !isProduction
? 'Development mode: Check your console logs for the verification code'
: 'Error: Email verification is enabled but no email service is configured'}
</p>
</div>

{!isVerified && (
{!isVerified && isEmailVerificationEnabled && (
<div className={`${inter.className} mt-8 space-y-8`}>
<div className='space-y-6'>
<p className='text-center text-muted-foreground text-sm'>
Expand Down Expand Up @@ -245,10 +250,18 @@ function VerificationFormFallback() {
)
}

export function VerifyContent({ hasEmailService, isProduction }: VerifyContentProps) {
export function VerifyContent({
hasEmailService,
isProduction,
isEmailVerificationEnabled,
}: VerifyContentProps) {
return (
<Suspense fallback={<VerificationFormFallback />}>
<VerificationForm hasEmailService={hasEmailService} isProduction={isProduction} />
<VerificationForm
hasEmailService={hasEmailService}
isProduction={isProduction}
isEmailVerificationEnabled={isEmailVerificationEnabled}
/>
</Suspense>
)
}
10 changes: 5 additions & 5 deletions apps/sim/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ import {
handleInvoicePaymentFailed,
handleInvoicePaymentSucceeded,
} from '@/lib/billing/webhooks/invoices'
import { hasEmailService, sendEmail } from '@/lib/email/mailer'
import { sendEmail } from '@/lib/email/mailer'
import { getFromEmailAddress } from '@/lib/email/utils'
import { quickValidateEmail } from '@/lib/email/validation'
import { env, isTruthy } from '@/lib/env'
import { isBillingEnabled, isProd } from '@/lib/environment'
import { isBillingEnabled, isEmailVerificationEnabled } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'

const logger = createLogger('Auth')
Expand Down Expand Up @@ -165,7 +165,7 @@ export const auth = betterAuth({
},
emailAndPassword: {
enabled: true,
requireEmailVerification: isProd && hasEmailService(),
requireEmailVerification: isEmailVerificationEnabled,
sendVerificationOnSignUp: false,
throwOnMissingCredentials: true,
throwOnInvalidCredentials: true,
Expand Down Expand Up @@ -240,8 +240,8 @@ export const auth = betterAuth({
otp: string
type: 'sign-in' | 'email-verification' | 'forget-password'
}) => {
if (!isProd) {
logger.info('Skipping email verification in dev/docker')
if (!isEmailVerificationEnabled) {
logger.info('Skipping email verification')
return
}
try {
Expand Down
1 change: 1 addition & 0 deletions apps/sim/lib/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const env = createEnv({
BILLING_ENABLED: z.boolean().optional(), // Enable billing enforcement and usage tracking

// Email & Communication
EMAIL_VERIFICATION_ENABLED: z.boolean().optional(), // Enable email verification for user registration and login (defaults to false)
RESEND_API_KEY: z.string().min(1).optional(), // Resend API key for transactional emails
FROM_EMAIL_ADDRESS: z.string().min(1).optional(), // Complete from address (e.g., "Sim <noreply@domain.com>" or "noreply@domain.com")
EMAIL_DOMAIN: z.string().min(1).optional(), // Domain for sending emails (fallback when FROM_EMAIL_ADDRESS not set)
Expand Down
5 changes: 5 additions & 0 deletions apps/sim/lib/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export const isHosted =
*/
export const isBillingEnabled = isTruthy(env.BILLING_ENABLED)

/**
* Is email verification enabled
*/
export const isEmailVerificationEnabled = isTruthy(env.EMAIL_VERIFICATION_ENABLED)

/**
* Get cost multiplier based on environment
*/
Expand Down
3 changes: 3 additions & 0 deletions helm/sim/examples/values-production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ app:
BETTER_AUTH_SECRET: "your-production-auth-secret-here"
ENCRYPTION_KEY: "your-production-encryption-key-here"

# Email verification (set to true if you want to require email verification)
EMAIL_VERIFICATION_ENABLED: "false"

# Optional third-party service integrations (configure as needed)
RESEND_API_KEY: "your-resend-api-key"
GOOGLE_CLIENT_ID: "your-google-client-id"
Expand Down
1 change: 1 addition & 0 deletions helm/sim/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ app:
ENCRYPTION_KEY: "" # REQUIRED - set via --set flag or external secret manager

# Email & Communication
EMAIL_VERIFICATION_ENABLED: "false" # Enable email verification for user registration and login (defaults to false)
RESEND_API_KEY: "" # Resend API key for transactional emails
FROM_EMAIL_ADDRESS: "" # Complete from address (e.g., "Sim <noreply@domain.com>" or "DoNotReply@domain.com")
EMAIL_DOMAIN: "" # Domain for sending emails (fallback when FROM_EMAIL_ADDRESS not set)
Expand Down