From e640102797b6b628576efad3ed78659f574a0f4d Mon Sep 17 00:00:00 2001 From: Waleed Date: Mon, 22 Sep 2025 11:04:47 -0700 Subject: [PATCH] feat(otp): added environemnt variable to control enforcement of verified accounts (#1411) * feat(otp): added environemnt variable to control enforcement of verified accounts * added to helm --- apps/sim/app/(auth)/verify/page.tsx | 10 ++++-- .../sim/app/(auth)/verify/use-verification.ts | 12 ++++--- apps/sim/app/(auth)/verify/verify-content.tsx | 31 +++++++++++++------ apps/sim/lib/auth.ts | 10 +++--- apps/sim/lib/env.ts | 1 + apps/sim/lib/environment.ts | 5 +++ helm/sim/examples/values-production.yaml | 3 ++ helm/sim/values.yaml | 1 + 8 files changed, 53 insertions(+), 20 deletions(-) diff --git a/apps/sim/app/(auth)/verify/page.tsx b/apps/sim/app/(auth)/verify/page.tsx index 2c6176a02..c841e42c3 100644 --- a/apps/sim/app/(auth)/verify/page.tsx +++ b/apps/sim/app/(auth)/verify/page.tsx @@ -1,5 +1,5 @@ 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' @@ -7,5 +7,11 @@ export const dynamic = 'force-dynamic' export default function VerifyPage() { const emailServiceConfigured = hasEmailService() - return + return ( + + ) } diff --git a/apps/sim/app/(auth)/verify/use-verification.ts b/apps/sim/app/(auth)/verify/use-verification.ts index 9a3db164f..af88eb428 100644 --- a/apps/sim/app/(auth)/verify/use-verification.ts +++ b/apps/sim/app/(auth)/verify/use-verification.ts @@ -10,6 +10,7 @@ const logger = createLogger('useVerification') interface UseVerificationParams { hasEmailService: boolean isProduction: boolean + isEmailVerificationEnabled: boolean } interface UseVerificationReturn { @@ -22,6 +23,7 @@ interface UseVerificationReturn { isOtpComplete: boolean hasEmailService: boolean isProduction: boolean + isEmailVerificationEnabled: boolean verifyCode: () => Promise resendCode: () => void handleOtpChange: (value: string) => void @@ -30,6 +32,7 @@ interface UseVerificationReturn { export function useVerification({ hasEmailService, isProduction, + isEmailVerificationEnabled, }: UseVerificationParams): UseVerificationReturn { const router = useRouter() const searchParams = useSearchParams() @@ -157,7 +160,7 @@ export function useVerification({ } function resendCode() { - if (!email || !hasEmailService) return + if (!email || !hasEmailService || !isEmailVerificationEnabled) return setIsLoading(true) setErrorMessage('') @@ -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) { @@ -217,7 +220,7 @@ export function useVerification({ handleRedirect() } } - }, [isProduction, hasEmailService, router, isInviteFlow, redirectUrl]) + }, [isEmailVerificationEnabled, router, isInviteFlow, redirectUrl]) return { otp, @@ -229,6 +232,7 @@ export function useVerification({ isOtpComplete, hasEmailService, isProduction, + isEmailVerificationEnabled, verifyCode, resendCode, handleOtpChange, diff --git a/apps/sim/app/(auth)/verify/verify-content.tsx b/apps/sim/app/(auth)/verify/verify-content.tsx index cb20fdd36..a635796cc 100644 --- a/apps/sim/app/(auth)/verify/verify-content.tsx +++ b/apps/sim/app/(auth)/verify/verify-content.tsx @@ -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, @@ -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) @@ -93,15 +96,17 @@ function VerificationForm({

{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'}

- {!isVerified && ( + {!isVerified && isEmailVerificationEnabled && (

@@ -245,10 +250,18 @@ function VerificationFormFallback() { ) } -export function VerifyContent({ hasEmailService, isProduction }: VerifyContentProps) { +export function VerifyContent({ + hasEmailService, + isProduction, + isEmailVerificationEnabled, +}: VerifyContentProps) { return ( }> - + ) } diff --git a/apps/sim/lib/auth.ts b/apps/sim/lib/auth.ts index 6d57f54fa..82cd29c30 100644 --- a/apps/sim/lib/auth.ts +++ b/apps/sim/lib/auth.ts @@ -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') @@ -165,7 +165,7 @@ export const auth = betterAuth({ }, emailAndPassword: { enabled: true, - requireEmailVerification: isProd && hasEmailService(), + requireEmailVerification: isEmailVerificationEnabled, sendVerificationOnSignUp: false, throwOnMissingCredentials: true, throwOnInvalidCredentials: true, @@ -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 { diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index d3e94c5e6..832ad9cf9 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -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 " or "noreply@domain.com") EMAIL_DOMAIN: z.string().min(1).optional(), // Domain for sending emails (fallback when FROM_EMAIL_ADDRESS not set) diff --git a/apps/sim/lib/environment.ts b/apps/sim/lib/environment.ts index eff95cd78..a74ab24eb 100644 --- a/apps/sim/lib/environment.ts +++ b/apps/sim/lib/environment.ts @@ -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 */ diff --git a/helm/sim/examples/values-production.yaml b/helm/sim/examples/values-production.yaml index a2df754aa..ac307b14a 100644 --- a/helm/sim/examples/values-production.yaml +++ b/helm/sim/examples/values-production.yaml @@ -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" diff --git a/helm/sim/values.yaml b/helm/sim/values.yaml index 90b6bd025..72b7a4832 100644 --- a/helm/sim/values.yaml +++ b/helm/sim/values.yaml @@ -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 " or "DoNotReply@domain.com") EMAIL_DOMAIN: "" # Domain for sending emails (fallback when FROM_EMAIL_ADDRESS not set)