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

export const dynamic = 'force-dynamic'

export default function VerifyPage() {
const hasResendKey = Boolean(env.RESEND_API_KEY)
const emailServiceConfigured = hasEmailService()

return <VerifyContent hasResendKey={hasResendKey} isProduction={isProd} />
return <VerifyContent hasEmailService={emailServiceConfigured} isProduction={isProd} />
}
36 changes: 23 additions & 13 deletions apps/sim/app/(auth)/verify/use-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('useVerification')

interface UseVerificationParams {
hasResendKey: boolean
hasEmailService: boolean
isProduction: boolean
}

Expand All @@ -20,15 +20,15 @@ interface UseVerificationReturn {
isInvalidOtp: boolean
errorMessage: string
isOtpComplete: boolean
hasResendKey: boolean
hasEmailService: boolean
isProduction: boolean
verifyCode: () => Promise<void>
resendCode: () => void
handleOtpChange: (value: string) => void
}

export function useVerification({
hasResendKey,
hasEmailService,
isProduction,
}: UseVerificationParams): UseVerificationReturn {
const router = useRouter()
Expand Down Expand Up @@ -74,10 +74,10 @@ export function useVerification({
}, [searchParams])

useEffect(() => {
if (email && !isSendingInitialOtp && hasResendKey) {
if (email && !isSendingInitialOtp && hasEmailService) {
setIsSendingInitialOtp(true)
}
}, [email, isSendingInitialOtp, hasResendKey])
}, [email, isSendingInitialOtp, hasEmailService])

const isOtpComplete = otp.length === 6

Expand Down Expand Up @@ -157,7 +157,7 @@ export function useVerification({
}

function resendCode() {
if (!email || !hasResendKey) return
if (!email || !hasEmailService) return

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

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

const timeoutId = setTimeout(() => {
window.location.href = '/workspace'
}, 1000)
const handleRedirect = async () => {
try {
await refetchSession()
} catch (error) {
logger.warn('Failed to refetch session during dev verification skip:', error)
}

if (isInviteFlow && redirectUrl) {
window.location.href = redirectUrl
} else {
router.push('/workspace')
}
}

return () => clearTimeout(timeoutId)
handleRedirect()
}
}
}, [isProduction, hasResendKey, router])
}, [isProduction, hasEmailService, router, isInviteFlow, redirectUrl])

return {
otp,
Expand All @@ -217,7 +227,7 @@ export function useVerification({
isInvalidOtp,
errorMessage,
isOtpComplete,
hasResendKey,
hasEmailService,
isProduction,
verifyCode,
resendCode,
Expand Down
18 changes: 9 additions & 9 deletions apps/sim/app/(auth)/verify/verify-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import { inter } from '@/app/fonts/inter'
import { soehne } from '@/app/fonts/soehne/soehne'

interface VerifyContentProps {
hasResendKey: boolean
hasEmailService: boolean
isProduction: boolean
}

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

const [countdown, setCountdown] = useState(0)
const [isResendDisabled, setIsResendDisabled] = useState(false)
Expand Down Expand Up @@ -93,7 +93,7 @@ function VerificationForm({
<p className={`${inter.className} font-[380] text-[16px] text-muted-foreground`}>
{isVerified
? 'Your email has been verified. Redirecting to dashboard...'
: hasResendKey
: hasEmailService
? `A verification code has been sent to ${email || 'your email'}`
: !isProduction
? 'Development mode: Check your console logs for the verification code'
Expand All @@ -106,7 +106,7 @@ function VerificationForm({
<div className='space-y-6'>
<p className='text-center text-muted-foreground text-sm'>
Enter the 6-digit code to verify your account.
{hasResendKey ? " If you don't see it in your inbox, check your spam folder." : ''}
{hasEmailService ? " If you don't see it in your inbox, check your spam folder." : ''}
</p>

<div className='flex justify-center'>
Expand Down Expand Up @@ -192,7 +192,7 @@ function VerificationForm({
{isLoading ? 'Verifying...' : 'Verify Email'}
</Button>

{hasResendKey && (
{hasEmailService && (
<div className='text-center'>
<p className='text-muted-foreground text-sm'>
Didn't receive a code?{' '}
Expand Down Expand Up @@ -245,10 +245,10 @@ function VerificationFormFallback() {
)
}

export function VerifyContent({ hasResendKey, isProduction }: VerifyContentProps) {
export function VerifyContent({ hasEmailService, isProduction }: VerifyContentProps) {
return (
<Suspense fallback={<VerificationFormFallback />}>
<VerificationForm hasResendKey={hasResendKey} isProduction={isProduction} />
<VerificationForm hasEmailService={hasEmailService} isProduction={isProduction} />
</Suspense>
)
}
4 changes: 2 additions & 2 deletions apps/sim/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
handleInvoicePaymentFailed,
handleInvoicePaymentSucceeded,
} from '@/lib/billing/webhooks/invoices'
import { sendEmail } from '@/lib/email/mailer'
import { hasEmailService, sendEmail } from '@/lib/email/mailer'
import { getFromEmailAddress } from '@/lib/email/utils'
import { quickValidateEmail } from '@/lib/email/validation'
import { env, isTruthy } from '@/lib/env'
Expand Down Expand Up @@ -147,7 +147,7 @@ export const auth = betterAuth({
},
emailAndPassword: {
enabled: true,
requireEmailVerification: isProd,
requireEmailVerification: isProd && hasEmailService(),
sendVerificationOnSignUp: false,
throwOnMissingCredentials: true,
throwOnInvalidCredentials: true,
Expand Down
7 changes: 7 additions & 0 deletions apps/sim/lib/email/mailer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ const azureEmailClient =
? new EmailClient(azureConnectionString)
: null

/**
* Check if any email service is configured and available
*/
export function hasEmailService(): boolean {
return !!(resend || azureEmailClient)
}

export async function sendEmail(options: EmailOptions): Promise<SendEmailResult> {
try {
// Check if user has unsubscribed (skip for critical transactional emails)
Expand Down
1 change: 1 addition & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ services:
limits:
memory: 8G
environment:
- NODE_ENV=development
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
- BETTER_AUTH_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
Expand Down
1 change: 1 addition & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ services:
limits:
memory: 8G
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
- BETTER_AUTH_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
Expand Down