mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-06 21:54:01 -05:00
fix(verification): add OTP dev skip (#1395)
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import { env } from '@/lib/env'
|
import { hasEmailService } from '@/lib/email/mailer'
|
||||||
import { isProd } from '@/lib/environment'
|
import { isProd } from '@/lib/environment'
|
||||||
import { VerifyContent } from '@/app/(auth)/verify/verify-content'
|
import { VerifyContent } from '@/app/(auth)/verify/verify-content'
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic'
|
export const dynamic = 'force-dynamic'
|
||||||
|
|
||||||
export default function VerifyPage() {
|
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} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { createLogger } from '@/lib/logs/console/logger'
|
|||||||
const logger = createLogger('useVerification')
|
const logger = createLogger('useVerification')
|
||||||
|
|
||||||
interface UseVerificationParams {
|
interface UseVerificationParams {
|
||||||
hasResendKey: boolean
|
hasEmailService: boolean
|
||||||
isProduction: boolean
|
isProduction: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ interface UseVerificationReturn {
|
|||||||
isInvalidOtp: boolean
|
isInvalidOtp: boolean
|
||||||
errorMessage: string
|
errorMessage: string
|
||||||
isOtpComplete: boolean
|
isOtpComplete: boolean
|
||||||
hasResendKey: boolean
|
hasEmailService: boolean
|
||||||
isProduction: boolean
|
isProduction: boolean
|
||||||
verifyCode: () => Promise<void>
|
verifyCode: () => Promise<void>
|
||||||
resendCode: () => void
|
resendCode: () => void
|
||||||
@@ -28,7 +28,7 @@ interface UseVerificationReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useVerification({
|
export function useVerification({
|
||||||
hasResendKey,
|
hasEmailService,
|
||||||
isProduction,
|
isProduction,
|
||||||
}: UseVerificationParams): UseVerificationReturn {
|
}: UseVerificationParams): UseVerificationReturn {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -74,10 +74,10 @@ export function useVerification({
|
|||||||
}, [searchParams])
|
}, [searchParams])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (email && !isSendingInitialOtp && hasResendKey) {
|
if (email && !isSendingInitialOtp && hasEmailService) {
|
||||||
setIsSendingInitialOtp(true)
|
setIsSendingInitialOtp(true)
|
||||||
}
|
}
|
||||||
}, [email, isSendingInitialOtp, hasResendKey])
|
}, [email, isSendingInitialOtp, hasEmailService])
|
||||||
|
|
||||||
const isOtpComplete = otp.length === 6
|
const isOtpComplete = otp.length === 6
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ export function useVerification({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resendCode() {
|
function resendCode() {
|
||||||
if (!email || !hasResendKey) return
|
if (!email || !hasEmailService) return
|
||||||
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setErrorMessage('')
|
setErrorMessage('')
|
||||||
@@ -197,17 +197,27 @@ export function useVerification({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
if (!isProduction || !hasResendKey) {
|
if (!isProduction && !hasEmailService) {
|
||||||
setIsVerified(true)
|
setIsVerified(true)
|
||||||
|
|
||||||
const timeoutId = setTimeout(() => {
|
const handleRedirect = async () => {
|
||||||
window.location.href = '/workspace'
|
try {
|
||||||
}, 1000)
|
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 {
|
return {
|
||||||
otp,
|
otp,
|
||||||
@@ -217,7 +227,7 @@ export function useVerification({
|
|||||||
isInvalidOtp,
|
isInvalidOtp,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
isOtpComplete,
|
isOtpComplete,
|
||||||
hasResendKey,
|
hasEmailService,
|
||||||
isProduction,
|
isProduction,
|
||||||
verifyCode,
|
verifyCode,
|
||||||
resendCode,
|
resendCode,
|
||||||
|
|||||||
@@ -10,15 +10,15 @@ import { inter } from '@/app/fonts/inter'
|
|||||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||||
|
|
||||||
interface VerifyContentProps {
|
interface VerifyContentProps {
|
||||||
hasResendKey: boolean
|
hasEmailService: boolean
|
||||||
isProduction: boolean
|
isProduction: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function VerificationForm({
|
function VerificationForm({
|
||||||
hasResendKey,
|
hasEmailService,
|
||||||
isProduction,
|
isProduction,
|
||||||
}: {
|
}: {
|
||||||
hasResendKey: boolean
|
hasEmailService: boolean
|
||||||
isProduction: boolean
|
isProduction: boolean
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
@@ -32,7 +32,7 @@ function VerificationForm({
|
|||||||
verifyCode,
|
verifyCode,
|
||||||
resendCode,
|
resendCode,
|
||||||
handleOtpChange,
|
handleOtpChange,
|
||||||
} = useVerification({ hasResendKey, isProduction })
|
} = useVerification({ hasEmailService, isProduction })
|
||||||
|
|
||||||
const [countdown, setCountdown] = useState(0)
|
const [countdown, setCountdown] = useState(0)
|
||||||
const [isResendDisabled, setIsResendDisabled] = useState(false)
|
const [isResendDisabled, setIsResendDisabled] = useState(false)
|
||||||
@@ -93,7 +93,7 @@ function VerificationForm({
|
|||||||
<p className={`${inter.className} font-[380] text-[16px] text-muted-foreground`}>
|
<p className={`${inter.className} font-[380] text-[16px] text-muted-foreground`}>
|
||||||
{isVerified
|
{isVerified
|
||||||
? 'Your email has been verified. Redirecting to dashboard...'
|
? 'Your email has been verified. Redirecting to dashboard...'
|
||||||
: hasResendKey
|
: hasEmailService
|
||||||
? `A verification code has been sent to ${email || 'your email'}`
|
? `A verification code has been sent to ${email || 'your email'}`
|
||||||
: !isProduction
|
: !isProduction
|
||||||
? 'Development mode: Check your console logs for the verification code'
|
? 'Development mode: Check your console logs for the verification code'
|
||||||
@@ -106,7 +106,7 @@ function VerificationForm({
|
|||||||
<div className='space-y-6'>
|
<div className='space-y-6'>
|
||||||
<p className='text-center text-muted-foreground text-sm'>
|
<p className='text-center text-muted-foreground text-sm'>
|
||||||
Enter the 6-digit code to verify your account.
|
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>
|
</p>
|
||||||
|
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
@@ -192,7 +192,7 @@ function VerificationForm({
|
|||||||
{isLoading ? 'Verifying...' : 'Verify Email'}
|
{isLoading ? 'Verifying...' : 'Verify Email'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{hasResendKey && (
|
{hasEmailService && (
|
||||||
<div className='text-center'>
|
<div className='text-center'>
|
||||||
<p className='text-muted-foreground text-sm'>
|
<p className='text-muted-foreground text-sm'>
|
||||||
Didn't receive a code?{' '}
|
Didn't receive a code?{' '}
|
||||||
@@ -245,10 +245,10 @@ function VerificationFormFallback() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VerifyContent({ hasResendKey, isProduction }: VerifyContentProps) {
|
export function VerifyContent({ hasEmailService, isProduction }: VerifyContentProps) {
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<VerificationFormFallback />}>
|
<Suspense fallback={<VerificationFormFallback />}>
|
||||||
<VerificationForm hasResendKey={hasResendKey} isProduction={isProduction} />
|
<VerificationForm hasEmailService={hasEmailService} isProduction={isProduction} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import {
|
|||||||
handleInvoicePaymentFailed,
|
handleInvoicePaymentFailed,
|
||||||
handleInvoicePaymentSucceeded,
|
handleInvoicePaymentSucceeded,
|
||||||
} from '@/lib/billing/webhooks/invoices'
|
} 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 { getFromEmailAddress } from '@/lib/email/utils'
|
||||||
import { quickValidateEmail } from '@/lib/email/validation'
|
import { quickValidateEmail } from '@/lib/email/validation'
|
||||||
import { env, isTruthy } from '@/lib/env'
|
import { env, isTruthy } from '@/lib/env'
|
||||||
@@ -147,7 +147,7 @@ export const auth = betterAuth({
|
|||||||
},
|
},
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
requireEmailVerification: isProd,
|
requireEmailVerification: isProd && hasEmailService(),
|
||||||
sendVerificationOnSignUp: false,
|
sendVerificationOnSignUp: false,
|
||||||
throwOnMissingCredentials: true,
|
throwOnMissingCredentials: true,
|
||||||
throwOnInvalidCredentials: true,
|
throwOnInvalidCredentials: true,
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ const azureEmailClient =
|
|||||||
? new EmailClient(azureConnectionString)
|
? new EmailClient(azureConnectionString)
|
||||||
: null
|
: 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> {
|
export async function sendEmail(options: EmailOptions): Promise<SendEmailResult> {
|
||||||
try {
|
try {
|
||||||
// Check if user has unsubscribed (skip for critical transactional emails)
|
// Check if user has unsubscribed (skip for critical transactional emails)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ services:
|
|||||||
limits:
|
limits:
|
||||||
memory: 8G
|
memory: 8G
|
||||||
environment:
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
|
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
|
||||||
- BETTER_AUTH_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
- BETTER_AUTH_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
||||||
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ services:
|
|||||||
limits:
|
limits:
|
||||||
memory: 8G
|
memory: 8G
|
||||||
environment:
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
|
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-simstudio}
|
||||||
- BETTER_AUTH_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
- BETTER_AUTH_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
||||||
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
|
||||||
|
|||||||
Reference in New Issue
Block a user