fix(verification): add OTP dev skip (#1395)

This commit is contained in:
Waleed
2025-09-20 11:31:41 -07:00
committed by GitHub
parent b5570c1c0e
commit 93f9293f2c
7 changed files with 46 additions and 27 deletions

View File

@@ -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} />
}

View File

@@ -8,7 +8,7 @@ import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('useVerification')
interface UseVerificationParams {
hasResendKey: boolean
hasEmailService: boolean
isProduction: boolean
}
@@ -20,7 +20,7 @@ interface UseVerificationReturn {
isInvalidOtp: boolean
errorMessage: string
isOtpComplete: boolean
hasResendKey: boolean
hasEmailService: boolean
isProduction: boolean
verifyCode: () => Promise<void>
resendCode: () => void
@@ -28,7 +28,7 @@ interface UseVerificationReturn {
}
export function useVerification({
hasResendKey,
hasEmailService,
isProduction,
}: UseVerificationParams): UseVerificationReturn {
const router = useRouter()
@@ -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
@@ -157,7 +157,7 @@ export function useVerification({
}
function resendCode() {
if (!email || !hasResendKey) return
if (!email || !hasEmailService) return
setIsLoading(true)
setErrorMessage('')
@@ -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)
}
return () => clearTimeout(timeoutId)
if (isInviteFlow && redirectUrl) {
window.location.href = redirectUrl
} else {
router.push('/workspace')
}
}
handleRedirect()
}
}
}, [isProduction, hasResendKey, router])
}, [isProduction, hasEmailService, router, isInviteFlow, redirectUrl])
return {
otp,
@@ -217,7 +227,7 @@ export function useVerification({
isInvalidOtp,
errorMessage,
isOtpComplete,
hasResendKey,
hasEmailService,
isProduction,
verifyCode,
resendCode,

View File

@@ -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 {
@@ -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)
@@ -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'
@@ -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'>
@@ -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?{' '}
@@ -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>
)
}

View File

@@ -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'
@@ -147,7 +147,7 @@ export const auth = betterAuth({
},
emailAndPassword: {
enabled: true,
requireEmailVerification: isProd,
requireEmailVerification: isProd && hasEmailService(),
sendVerificationOnSignUp: false,
throwOnMissingCredentials: true,
throwOnInvalidCredentials: true,

View File

@@ -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)

View File

@@ -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}

View File

@@ -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}