From 19d557155e55a8c27e43dba3ec6c0b430ad37b7a Mon Sep 17 00:00:00 2001 From: waleedlatif1 Date: Sat, 6 Sep 2025 13:27:40 -0700 Subject: [PATCH 1/4] update infra and remove railway --- .github/workflows/build.yml | 39 ++++++++++++++++++++++++++++++++----- .github/workflows/ci.yml | 22 --------------------- railway.json | 21 -------------------- 3 files changed, 34 insertions(+), 48 deletions(-) delete mode 100644 railway.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30d2eb2608..518add6b14 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -55,7 +55,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Log in to the Container registry - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') uses: docker/login-action@v3 with: registry: ghcr.io @@ -69,7 +69,7 @@ jobs: images: ${{ matrix.image }} tags: | type=raw,value=latest-${{ matrix.arch }},enable=${{ github.ref == 'refs/heads/main' }} - type=raw,value=staging-${{ github.sha }}-${{ matrix.arch }},enable=${{ github.ref == 'refs/heads/staging' }} + type=raw,value=staging-${{ matrix.arch }},enable=${{ github.ref == 'refs/heads/staging' }} type=sha,format=long,suffix=-${{ matrix.arch }} - name: Build and push Docker image @@ -78,7 +78,7 @@ jobs: context: . file: ${{ matrix.dockerfile }} platforms: ${{ matrix.platform }} - push: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/main' }} + push: ${{ github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=build-v3 @@ -89,7 +89,7 @@ jobs: create-manifests: runs-on: ubuntu-latest needs: build-and-push - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') strategy: matrix: include: @@ -115,6 +115,7 @@ jobs: images: ${{ matrix.image }} tags: | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=staging,enable=${{ github.ref == 'refs/heads/staging' }} type=sha,format=long - name: Create and push manifest @@ -148,4 +149,32 @@ jobs: docker manifest inspect "$arm64_image" || echo "ARM64 image not found" exit 1 fi - done \ No newline at end of file + done + + trigger-infrastructure-deploy: + runs-on: ubuntu-latest + needs: create-manifests + if: github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/main' + permissions: + contents: read + + steps: + - name: Trigger staging deployment + if: github.ref == 'refs/heads/staging' + run: | + curl -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ secrets.INFRA_DEPLOY_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ secrets.INFRA_REPO }}/dispatches \ + -d '{"event_type":"staging-deploy","client_payload":{"sha":"${{ github.sha }}","ref":"${{ github.ref }}"}}' + + - name: Trigger production deployment + if: github.ref == 'refs/heads/main' + run: | + curl -X POST \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ secrets.INFRA_DEPLOY_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ secrets.INFRA_REPO }}/dispatches \ + -d '{"event_type":"production-deploy","client_payload":{"sha":"${{ github.sha }}","ref":"${{ github.ref }}"}}' \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffd68d8b87..0f64e4f960 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,25 +53,3 @@ jobs: fail_ci_if_error: false verbose: true - migrations: - name: Apply Database Migrations - runs-on: ubuntu-latest - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging') - needs: test - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Apply migrations - working-directory: ./apps/sim - env: - DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }} - run: bunx drizzle-kit migrate diff --git a/railway.json b/railway.json deleted file mode 100644 index 62d6da767e..0000000000 --- a/railway.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://railway.app/railway.schema.json", - "build": { - "builder": "NIXPACKS", - "buildCommand": "cd apps/sim && bun install --frozen-lockfile && bun run build" - }, - "deploy": { - "startCommand": "cd apps/sim && NODE_ENV=production bun run socket-server/index.ts", - "healthcheckPath": "/health", - "healthcheckTimeout": 300, - "restartPolicyType": "ON_FAILURE", - "restartPolicyMaxRetries": 10 - }, - "environments": { - "production": { - "variables": { - "NODE_ENV": "production" - } - } - } -} From e3233a98643f12dadfc0c2ff79b74d50416f45db Mon Sep 17 00:00:00 2001 From: waleedlatif1 Date: Wed, 17 Sep 2025 14:55:58 -0700 Subject: [PATCH 2/4] feat(signup): added back to login functionalityfrom OTP page --- apps/sim/app/(auth)/login/login-form.tsx | 25 +++------ apps/sim/app/(auth)/signup/signup-form.tsx | 21 +++----- .../sim/app/(auth)/verify/use-verification.ts | 54 +++++++------------ apps/sim/app/(auth)/verify/verify-content.tsx | 16 ++++-- apps/sim/lib/auth.ts | 3 +- apps/sim/middleware.ts | 7 +-- 6 files changed, 46 insertions(+), 80 deletions(-) diff --git a/apps/sim/app/(auth)/login/login-form.tsx b/apps/sim/app/(auth)/login/login-form.tsx index f48b599bab..b977efda45 100644 --- a/apps/sim/app/(auth)/login/login-form.tsx +++ b/apps/sim/app/(auth)/login/login-form.tsx @@ -210,7 +210,8 @@ export default function LoginPage({ setIsLoading(true) const formData = new FormData(e.currentTarget) - const email = formData.get('email') as string + const emailRaw = formData.get('email') as string + const email = emailRaw.trim().toLowerCase() // Validate email on submit const emailValidationErrors = validateEmailField(email) @@ -295,29 +296,15 @@ export default function LoginPage({ // Mark that the user has previously logged in if (typeof window !== 'undefined') { localStorage.setItem('has_logged_in_before', 'true') - document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry } } catch (err: any) { // Handle only the special verification case that requires a redirect if (err.message?.includes('not verified') || err.code?.includes('EMAIL_NOT_VERIFIED')) { - try { - await client.emailOtp.sendVerificationOtp({ - email, - type: 'email-verification', - }) - - if (typeof window !== 'undefined') { - sessionStorage.setItem('verificationEmail', email) - } - - router.push('/verify') - return - } catch (_verifyErr) { - setPasswordErrors(['Failed to send verification code. Please try again later.']) - setShowValidationError(true) - setIsLoading(false) - return + if (typeof window !== 'undefined') { + sessionStorage.setItem('verificationEmail', email) } + router.push('/verify') + return } console.error('Uncaught login error:', err) diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index f72a40558c..83d93279cf 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -244,7 +244,8 @@ function SignupFormContent({ setIsLoading(true) const formData = new FormData(e.currentTarget) - const emailValue = formData.get('email') as string + const emailValueRaw = formData.get('email') as string + const emailValue = emailValueRaw.trim().toLowerCase() const passwordValue = formData.get('password') as string const nameValue = formData.get('name') as string @@ -356,34 +357,26 @@ function SignupFormContent({ logger.error('Failed to refresh session after signup:', sessionError) } - // For new signups, always require verification + // Store verification context client-side, but rely on server to enforce verification if (typeof window !== 'undefined') { sessionStorage.setItem('verificationEmail', emailValue) localStorage.setItem('has_logged_in_before', 'true') - - // Set cookie flag for middleware check - document.cookie = 'requiresEmailVerification=true; path=/; max-age=900; SameSite=Lax' // 15 min expiry - document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' - - // Store invitation flow state if applicable if (isInviteFlow && redirectUrl) { sessionStorage.setItem('inviteRedirectUrl', redirectUrl) sessionStorage.setItem('isInviteFlow', 'true') } } - // Send verification OTP manually + // Send a sign-in OTP and redirect to verify try { await client.emailOtp.sendVerificationOtp({ email: emailValue, - type: 'email-verification', + type: 'sign-in', }) - } catch (otpError) { - logger.error('Failed to send OTP:', otpError) - // Continue anyway - user can use resend button + } catch (otpErr) { + logger.warn('Failed to send sign-in OTP after signup; user can press Resend', otpErr) } - // Always redirect to verification for new signups router.push('/verify?fromSignup=true') } catch (error) { logger.error('Signup error:', error) diff --git a/apps/sim/app/(auth)/verify/use-verification.ts b/apps/sim/app/(auth)/verify/use-verification.ts index ecaf68036f..797bc58886 100644 --- a/apps/sim/app/(auth)/verify/use-verification.ts +++ b/apps/sim/app/(auth)/verify/use-verification.ts @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react' import { useRouter, useSearchParams } from 'next/navigation' import { client, useSession } from '@/lib/auth-client' -import { env, isTruthy } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' const logger = createLogger('useVerification') @@ -79,27 +78,12 @@ export function useVerification({ } }, [searchParams]) - // Send initial OTP code if this is the first load + // Do not auto-send OTP on mount; allow user to trigger resend explicitly useEffect(() => { if (email && !isSendingInitialOtp && hasResendKey) { setIsSendingInitialOtp(true) - - // Only send verification OTP if we're coming from login page - // Skip this if coming from signup since the OTP is already sent - if (!searchParams.get('fromSignup')) { - client.emailOtp - .sendVerificationOtp({ - email, - type: 'email-verification', - }) - .then(() => {}) - .catch((error) => { - logger.error('Failed to send initial verification code:', error) - setErrorMessage('Failed to send verification code. Please use the resend button.') - }) - } } - }, [email, isSendingInitialOtp, searchParams, hasResendKey]) + }, [email, isSendingInitialOtp, hasResendKey]) // Enable the verify button when all 6 digits are entered const isOtpComplete = otp.length === 6 @@ -112,9 +96,11 @@ export function useVerification({ setErrorMessage('') try { - // Call the verification API with the OTP code - const response = await client.emailOtp.verifyEmail({ - email, + // Normalize email before verifying + const normalizedEmail = email.trim().toLowerCase() + // Use sign-in OTP verification to create a session directly + const response = await client.signIn.emailOtp({ + email: normalizedEmail, otp, }) @@ -122,14 +108,17 @@ export function useVerification({ if (response && !response.error) { setIsVerified(true) + // Refresh session after verification to avoid redirect bounce + try { + await refetchSession() + } catch (e) { + logger.warn('Failed to refetch session after verification', e) + } + // Clear verification requirements and session storage if (typeof window !== 'undefined') { sessionStorage.removeItem('verificationEmail') - // Clear the verification requirement flag - document.cookie = - 'requiresEmailVerification=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' - // Also clear invite-related items if (isInviteFlow) { sessionStorage.removeItem('inviteRedirectUrl') @@ -192,10 +181,11 @@ export function useVerification({ setIsLoading(true) setErrorMessage('') + const normalizedEmail = email.trim().toLowerCase() client.emailOtp .sendVerificationOtp({ - email, - type: 'email-verification', + email: normalizedEmail, + type: 'sign-in', }) .then(() => {}) .catch(() => { @@ -228,18 +218,10 @@ export function useVerification({ useEffect(() => { if (typeof window !== 'undefined') { + // Dev-only skip (do not skip in production even if running in Docker) if (!isProduction || !hasResendKey) { - const storedEmail = sessionStorage.getItem('verificationEmail') - } - - const isDevOrDocker = !isProduction || isTruthy(env.DOCKER_BUILD) - - if (isDevOrDocker || !hasResendKey) { setIsVerified(true) - document.cookie = - 'requiresEmailVerification=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' - const timeoutId = setTimeout(() => { window.location.href = '/workspace' }, 1000) diff --git a/apps/sim/app/(auth)/verify/verify-content.tsx b/apps/sim/app/(auth)/verify/verify-content.tsx index b6dd4e6c53..4e030cb8ae 100644 --- a/apps/sim/app/(auth)/verify/verify-content.tsx +++ b/apps/sim/app/(auth)/verify/verify-content.tsx @@ -232,14 +232,22 @@ function VerificationForm({ )} - {/*
+
-
*/} +
)} diff --git a/apps/sim/lib/auth.ts b/apps/sim/lib/auth.ts index cf8ac7be29..aa91c22b37 100644 --- a/apps/sim/lib/auth.ts +++ b/apps/sim/lib/auth.ts @@ -147,7 +147,7 @@ export const auth = betterAuth({ }, emailAndPassword: { enabled: true, - requireEmailVerification: false, + requireEmailVerification: isProd, sendVerificationOnSignUp: false, throwOnMissingCredentials: true, throwOnInvalidCredentials: true, @@ -281,6 +281,7 @@ export const auth = betterAuth({ throw error } }, + // We will explicitly send a 'sign-in' OTP after signup on the client sendVerificationOnSignUp: false, otpLength: 6, // Explicitly set the OTP length expiresIn: 15 * 60, // 15 minutes in seconds diff --git a/apps/sim/middleware.ts b/apps/sim/middleware.ts index a4b330b12c..de4a800044 100644 --- a/apps/sim/middleware.ts +++ b/apps/sim/middleware.ts @@ -138,12 +138,7 @@ export async function middleware(request: NextRequest) { return NextResponse.redirect(new URL('/login', request.url)) } - // Check if user needs email verification - const requiresVerification = request.cookies.get('requiresEmailVerification') - if (requiresVerification?.value === 'true') { - return NextResponse.redirect(new URL('/verify', request.url)) - } - + // Email verification is enforced by Better Auth (server-side). No cookie gating here. return NextResponse.next() } From 5863266d40e226f175aa375d2871c0131cc66426 Mon Sep 17 00:00:00 2001 From: waleedlatif1 Date: Wed, 17 Sep 2025 15:27:51 -0700 Subject: [PATCH 3/4] remove placeholders from docker commands, simplified login flow --- .../components/oauth-provider-checker.tsx | 14 +----- .../components/social-login-buttons.tsx | 13 ----- apps/sim/app/(auth)/login/login-form.tsx | 26 +--------- apps/sim/app/(auth)/reset-password/page.tsx | 2 - .../reset-password/reset-password-form.tsx | 7 --- apps/sim/app/(auth)/signup/page.tsx | 1 - apps/sim/app/(auth)/signup/signup-form.tsx | 20 +------- apps/sim/app/(auth)/verify/page.tsx | 7 +-- .../sim/app/(auth)/verify/use-verification.ts | 24 +-------- apps/sim/app/(auth)/verify/verify-content.tsx | 23 +-------- apps/sim/lib/auth.ts | 49 +++---------------- docker-compose.local.yml | 5 -- docker-compose.prod.yml | 5 -- 13 files changed, 18 insertions(+), 178 deletions(-) diff --git a/apps/sim/app/(auth)/components/oauth-provider-checker.tsx b/apps/sim/app/(auth)/components/oauth-provider-checker.tsx index 8aff925c33..43766112bb 100644 --- a/apps/sim/app/(auth)/components/oauth-provider-checker.tsx +++ b/apps/sim/app/(auth)/components/oauth-provider-checker.tsx @@ -4,19 +4,9 @@ import { env } from '@/lib/env' import { isProd } from '@/lib/environment' export async function getOAuthProviderStatus() { - const githubAvailable = !!( - env.GITHUB_CLIENT_ID && - env.GITHUB_CLIENT_SECRET && - env.GITHUB_CLIENT_ID !== 'placeholder' && - env.GITHUB_CLIENT_SECRET !== 'placeholder' - ) + const githubAvailable = !!(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET) - const googleAvailable = !!( - env.GOOGLE_CLIENT_ID && - env.GOOGLE_CLIENT_SECRET && - env.GOOGLE_CLIENT_ID !== 'placeholder' && - env.GOOGLE_CLIENT_SECRET !== 'placeholder' - ) + const googleAvailable = !!(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) return { githubAvailable, googleAvailable, isProduction: isProd } } diff --git a/apps/sim/app/(auth)/components/social-login-buttons.tsx b/apps/sim/app/(auth)/components/social-login-buttons.tsx index 4cb19691f2..7c76325b6f 100644 --- a/apps/sim/app/(auth)/components/social-login-buttons.tsx +++ b/apps/sim/app/(auth)/components/social-login-buttons.tsx @@ -37,12 +37,6 @@ export function SocialLoginButtons({ setIsGithubLoading(true) try { await client.signIn.social({ provider: 'github', callbackURL }) - - // Mark that the user has previously logged in - if (typeof window !== 'undefined') { - localStorage.setItem('has_logged_in_before', 'true') - document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry - } } catch (err: any) { let errorMessage = 'Failed to sign in with GitHub' @@ -66,13 +60,6 @@ export function SocialLoginButtons({ setIsGoogleLoading(true) try { await client.signIn.social({ provider: 'google', callbackURL }) - - // Mark that the user has previously logged in - if (typeof window !== 'undefined') { - localStorage.setItem('has_logged_in_before', 'true') - // Also set a cookie to enable middleware to check login status - document.cookie = 'has_logged_in_before=true; path=/; max-age=31536000; SameSite=Lax' // 1 year expiry - } } catch (err: any) { let errorMessage = 'Failed to sign in with Google' diff --git a/apps/sim/app/(auth)/login/login-form.tsx b/apps/sim/app/(auth)/login/login-form.tsx index b977efda45..d30b03b634 100644 --- a/apps/sim/app/(auth)/login/login-form.tsx +++ b/apps/sim/app/(auth)/login/login-form.tsx @@ -74,12 +74,12 @@ const validatePassword = (passwordValue: string): string[] => { if (!PASSWORD_VALIDATIONS.required.test(passwordValue)) { errors.push(PASSWORD_VALIDATIONS.required.message) - return errors // Return early for required field + return errors } if (!PASSWORD_VALIDATIONS.notEmpty.test(passwordValue)) { errors.push(PASSWORD_VALIDATIONS.notEmpty.message) - return errors // Return early for empty field + return errors } return errors @@ -104,11 +104,9 @@ export default function LoginPage({ const [showValidationError, setShowValidationError] = useState(false) const [buttonClass, setButtonClass] = useState('auth-button-gradient') - // Initialize state for URL parameters const [callbackUrl, setCallbackUrl] = useState('/workspace') const [isInviteFlow, setIsInviteFlow] = useState(false) - // Forgot password states const [forgotPasswordOpen, setForgotPasswordOpen] = useState(false) const [forgotPasswordEmail, setForgotPasswordEmail] = useState('') const [isSubmittingReset, setIsSubmittingReset] = useState(false) @@ -117,25 +115,20 @@ export default function LoginPage({ message: string }>({ type: null, message: '' }) - // Email validation state const [email, setEmail] = useState('') const [emailErrors, setEmailErrors] = useState([]) const [showEmailValidationError, setShowEmailValidationError] = useState(false) - // Extract URL parameters after component mounts to avoid SSR issues useEffect(() => { setMounted(true) - // Only access search params on the client side if (searchParams) { const callback = searchParams.get('callbackUrl') if (callback) { - // Validate the callbackUrl before setting it if (validateCallbackUrl(callback)) { setCallbackUrl(callback) } else { logger.warn('Invalid callback URL detected and blocked:', { url: callback }) - // Keep the default safe value ('/workspace') } } @@ -143,12 +136,10 @@ export default function LoginPage({ setIsInviteFlow(inviteFlow) } - // Check if CSS variable has been customized const checkCustomBrand = () => { const computedStyle = getComputedStyle(document.documentElement) const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim() - // Check if the CSS variable exists and is different from the default if (brandAccent && brandAccent !== '#6f3dfa') { setButtonClass('auth-button-custom') } else { @@ -158,7 +149,6 @@ export default function LoginPage({ checkCustomBrand() - // Also check on window resize or theme changes window.addEventListener('resize', checkCustomBrand) const observer = new MutationObserver(checkCustomBrand) observer.observe(document.documentElement, { @@ -189,7 +179,6 @@ export default function LoginPage({ const newEmail = e.target.value setEmail(newEmail) - // Silently validate but don't show errors until submit const errors = validateEmailField(newEmail) setEmailErrors(errors) setShowEmailValidationError(false) @@ -199,7 +188,6 @@ export default function LoginPage({ const newPassword = e.target.value setPassword(newPassword) - // Silently validate but don't show errors until submit const errors = validatePassword(newPassword) setPasswordErrors(errors) setShowValidationError(false) @@ -213,24 +201,20 @@ export default function LoginPage({ const emailRaw = formData.get('email') as string const email = emailRaw.trim().toLowerCase() - // Validate email on submit const emailValidationErrors = validateEmailField(email) setEmailErrors(emailValidationErrors) setShowEmailValidationError(emailValidationErrors.length > 0) - // Validate password on submit const passwordValidationErrors = validatePassword(password) setPasswordErrors(passwordValidationErrors) setShowValidationError(passwordValidationErrors.length > 0) - // If there are validation errors, stop submission if (emailValidationErrors.length > 0 || passwordValidationErrors.length > 0) { setIsLoading(false) return } try { - // Final validation before submission const safeCallbackUrl = validateCallbackUrl(callbackUrl) ? callbackUrl : '/workspace' const result = await client.signIn.email( @@ -292,13 +276,7 @@ export default function LoginPage({ setIsLoading(false) return } - - // Mark that the user has previously logged in - if (typeof window !== 'undefined') { - localStorage.setItem('has_logged_in_before', 'true') - } } catch (err: any) { - // Handle only the special verification case that requires a redirect if (err.message?.includes('not verified') || err.code?.includes('EMAIL_NOT_VERIFIED')) { if (typeof window !== 'undefined') { sessionStorage.setItem('verificationEmail', email) diff --git a/apps/sim/app/(auth)/reset-password/page.tsx b/apps/sim/app/(auth)/reset-password/page.tsx index 478ea777a8..8474d054e0 100644 --- a/apps/sim/app/(auth)/reset-password/page.tsx +++ b/apps/sim/app/(auth)/reset-password/page.tsx @@ -24,7 +24,6 @@ function ResetPasswordContent() { text: '', }) - // Validate token presence useEffect(() => { if (!token) { setStatusMessage({ @@ -60,7 +59,6 @@ function ResetPasswordContent() { text: 'Password reset successful! Redirecting to login...', }) - // Redirect to login page after 1.5 seconds setTimeout(() => { router.push('/login?resetSuccess=true') }, 1500) diff --git a/apps/sim/app/(auth)/reset-password/reset-password-form.tsx b/apps/sim/app/(auth)/reset-password/reset-password-form.tsx index ff6e39f455..fe1377e1bc 100644 --- a/apps/sim/app/(auth)/reset-password/reset-password-form.tsx +++ b/apps/sim/app/(auth)/reset-password/reset-password-form.tsx @@ -30,12 +30,10 @@ export function RequestResetForm({ const [buttonClass, setButtonClass] = useState('auth-button-gradient') useEffect(() => { - // Check if CSS variable has been customized const checkCustomBrand = () => { const computedStyle = getComputedStyle(document.documentElement) const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim() - // Check if the CSS variable exists and is different from the default if (brandAccent && brandAccent !== '#6f3dfa') { setButtonClass('auth-button-custom') } else { @@ -45,7 +43,6 @@ export function RequestResetForm({ checkCustomBrand() - // Also check on window resize or theme changes window.addEventListener('resize', checkCustomBrand) const observer = new MutationObserver(checkCustomBrand) observer.observe(document.documentElement, { @@ -132,12 +129,10 @@ export function SetNewPasswordForm({ const [buttonClass, setButtonClass] = useState('auth-button-gradient') useEffect(() => { - // Check if CSS variable has been customized const checkCustomBrand = () => { const computedStyle = getComputedStyle(document.documentElement) const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim() - // Check if the CSS variable exists and is different from the default if (brandAccent && brandAccent !== '#6f3dfa') { setButtonClass('auth-button-custom') } else { @@ -147,7 +142,6 @@ export function SetNewPasswordForm({ checkCustomBrand() - // Also check on window resize or theme changes window.addEventListener('resize', checkCustomBrand) const observer = new MutationObserver(checkCustomBrand) observer.observe(document.documentElement, { @@ -164,7 +158,6 @@ export function SetNewPasswordForm({ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - // Simple validation if (password.length < 8) { setValidationMessage('Password must be at least 8 characters long') return diff --git a/apps/sim/app/(auth)/signup/page.tsx b/apps/sim/app/(auth)/signup/page.tsx index 3fededdd14..6e0734a1f9 100644 --- a/apps/sim/app/(auth)/signup/page.tsx +++ b/apps/sim/app/(auth)/signup/page.tsx @@ -2,7 +2,6 @@ import { env, isTruthy } from '@/lib/env' import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker' import SignupForm from '@/app/(auth)/signup/signup-form' -// Force dynamic rendering to avoid prerender errors with search params export const dynamic = 'force-dynamic' export default async function SignupPage() { diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 83d93279cf..0dbba30255 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -95,7 +95,6 @@ function SignupFormContent({ const [isInviteFlow, setIsInviteFlow] = useState(false) const [buttonClass, setButtonClass] = useState('auth-button-gradient') - // Name validation state const [name, setName] = useState('') const [nameErrors, setNameErrors] = useState([]) const [showNameValidationError, setShowNameValidationError] = useState(false) @@ -107,29 +106,24 @@ function SignupFormContent({ setEmail(emailParam) } - // Handle redirection for invitation flow const redirectParam = searchParams.get('redirect') if (redirectParam) { setRedirectUrl(redirectParam) - // Check if this is part of an invitation flow if (redirectParam.startsWith('/invite/')) { setIsInviteFlow(true) } } - // Explicitly check for invite_flow parameter const inviteFlowParam = searchParams.get('invite_flow') if (inviteFlowParam === 'true') { setIsInviteFlow(true) } - // Check if CSS variable has been customized const checkCustomBrand = () => { const computedStyle = getComputedStyle(document.documentElement) const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim() - // Check if the CSS variable exists and is different from the default if (brandAccent && brandAccent !== '#6f3dfa') { setButtonClass('auth-button-custom') } else { @@ -139,7 +133,6 @@ function SignupFormContent({ checkCustomBrand() - // Also check on window resize or theme changes window.addEventListener('resize', checkCustomBrand) const observer = new MutationObserver(checkCustomBrand) observer.observe(document.documentElement, { @@ -153,7 +146,6 @@ function SignupFormContent({ } }, [searchParams]) - // Validate password and return array of error messages const validatePassword = (passwordValue: string): string[] => { const errors: string[] = [] @@ -180,18 +172,17 @@ function SignupFormContent({ return errors } - // Validate name and return array of error messages const validateName = (nameValue: string): string[] => { const errors: string[] = [] if (!NAME_VALIDATIONS.required.test(nameValue)) { errors.push(NAME_VALIDATIONS.required.message) - return errors // Return early for required field + return errors } if (!NAME_VALIDATIONS.notEmpty.test(nameValue)) { errors.push(NAME_VALIDATIONS.notEmpty.message) - return errors // Return early for empty field + return errors } if (!NAME_VALIDATIONS.validCharacters.regex.test(nameValue.trim())) { @@ -209,7 +200,6 @@ function SignupFormContent({ const newPassword = e.target.value setPassword(newPassword) - // Silently validate but don't show errors const errors = validatePassword(newPassword) setPasswordErrors(errors) setShowValidationError(false) @@ -228,12 +218,10 @@ function SignupFormContent({ const newEmail = e.target.value setEmail(newEmail) - // Silently validate but don't show errors until submit const errors = validateEmailField(newEmail) setEmailErrors(errors) setShowEmailValidationError(false) - // Clear any previous server-side email errors when the user starts typing if (emailError) { setEmailError('') } @@ -349,7 +337,6 @@ function SignupFormContent({ return } - // Refresh session to get the new user data immediately after signup try { await refetchSession() logger.info('Session refreshed after successful signup') @@ -357,17 +344,14 @@ function SignupFormContent({ logger.error('Failed to refresh session after signup:', sessionError) } - // Store verification context client-side, but rely on server to enforce verification if (typeof window !== 'undefined') { sessionStorage.setItem('verificationEmail', emailValue) - localStorage.setItem('has_logged_in_before', 'true') if (isInviteFlow && redirectUrl) { sessionStorage.setItem('inviteRedirectUrl', redirectUrl) sessionStorage.setItem('isInviteFlow', 'true') } } - // Send a sign-in OTP and redirect to verify try { await client.emailOtp.sendVerificationOtp({ email: emailValue, diff --git a/apps/sim/app/(auth)/verify/page.tsx b/apps/sim/app/(auth)/verify/page.tsx index 55c185675d..7e169b8760 100644 --- a/apps/sim/app/(auth)/verify/page.tsx +++ b/apps/sim/app/(auth)/verify/page.tsx @@ -1,14 +1,11 @@ import { env } from '@/lib/env' import { isProd } from '@/lib/environment' -import { getBaseUrl } from '@/lib/urls/utils' import { VerifyContent } from '@/app/(auth)/verify/verify-content' -// Force dynamic rendering to avoid prerender errors with search params export const dynamic = 'force-dynamic' export default function VerifyPage() { - const baseUrl = getBaseUrl() - const hasResendKey = Boolean(env.RESEND_API_KEY && env.RESEND_API_KEY !== 'placeholder') + const hasResendKey = Boolean(env.RESEND_API_KEY) - return + return } diff --git a/apps/sim/app/(auth)/verify/use-verification.ts b/apps/sim/app/(auth)/verify/use-verification.ts index 797bc58886..3d4b1d32a0 100644 --- a/apps/sim/app/(auth)/verify/use-verification.ts +++ b/apps/sim/app/(auth)/verify/use-verification.ts @@ -46,46 +46,39 @@ export function useVerification({ useEffect(() => { if (typeof window !== 'undefined') { - // Get stored email const storedEmail = sessionStorage.getItem('verificationEmail') if (storedEmail) { setEmail(storedEmail) } - // Check for redirect information const storedRedirectUrl = sessionStorage.getItem('inviteRedirectUrl') if (storedRedirectUrl) { setRedirectUrl(storedRedirectUrl) } - // Check if this is an invite flow const storedIsInviteFlow = sessionStorage.getItem('isInviteFlow') if (storedIsInviteFlow === 'true') { setIsInviteFlow(true) } } - // Also check URL parameters for redirect information const redirectParam = searchParams.get('redirectAfter') if (redirectParam) { setRedirectUrl(redirectParam) } - // Check for invite_flow parameter const inviteFlowParam = searchParams.get('invite_flow') if (inviteFlowParam === 'true') { setIsInviteFlow(true) } }, [searchParams]) - // Do not auto-send OTP on mount; allow user to trigger resend explicitly useEffect(() => { if (email && !isSendingInitialOtp && hasResendKey) { setIsSendingInitialOtp(true) } }, [email, isSendingInitialOtp, hasResendKey]) - // Enable the verify button when all 6 digits are entered const isOtpComplete = otp.length === 6 async function verifyCode() { @@ -96,30 +89,24 @@ export function useVerification({ setErrorMessage('') try { - // Normalize email before verifying const normalizedEmail = email.trim().toLowerCase() - // Use sign-in OTP verification to create a session directly const response = await client.signIn.emailOtp({ email: normalizedEmail, otp, }) - // Check if verification was successful if (response && !response.error) { setIsVerified(true) - // Refresh session after verification to avoid redirect bounce try { await refetchSession() } catch (e) { logger.warn('Failed to refetch session after verification', e) } - // Clear verification requirements and session storage if (typeof window !== 'undefined') { sessionStorage.removeItem('verificationEmail') - // Also clear invite-related items if (isInviteFlow) { sessionStorage.removeItem('inviteRedirectUrl') sessionStorage.removeItem('isInviteFlow') @@ -128,24 +115,20 @@ export function useVerification({ setTimeout(() => { if (isInviteFlow && redirectUrl) { - // For invitation flow, redirect to the invitation page window.location.href = redirectUrl } else { - // Default redirect to dashboard window.location.href = '/workspace' } }, 1000) } else { logger.info('Setting invalid OTP state - API error response') const message = 'Invalid verification code. Please check and try again.' - // Set both state variables to ensure the error shows setIsInvalidOtp(true) setErrorMessage(message) logger.info('Error state after API error:', { isInvalidOtp: true, errorMessage: message, }) - // Clear the OTP input on invalid code setOtp('') } } catch (error: any) { @@ -160,7 +143,6 @@ export function useVerification({ message = 'Too many failed attempts. Please request a new code.' } - // Set both state variables to ensure the error shows setIsInvalidOtp(true) setErrorMessage(message) logger.info('Error state after caught error:', { @@ -168,7 +150,6 @@ export function useVerification({ errorMessage: message, }) - // Clear the OTP input on error setOtp('') } finally { setIsLoading(false) @@ -197,7 +178,6 @@ export function useVerification({ } function handleOtpChange(value: string) { - // Only clear error when user is actively typing a new code if (value.length === 6) { setIsInvalidOtp(false) setErrorMessage('') @@ -205,12 +185,11 @@ export function useVerification({ setOtp(value) } - // Auto-submit when OTP is complete useEffect(() => { if (otp.length === 6 && email && !isLoading && !isVerified) { const timeoutId = setTimeout(() => { verifyCode() - }, 300) // Small delay to ensure UI is ready + }, 300) return () => clearTimeout(timeoutId) } @@ -218,7 +197,6 @@ export function useVerification({ useEffect(() => { if (typeof window !== 'undefined') { - // Dev-only skip (do not skip in production even if running in Docker) if (!isProduction || !hasResendKey) { setIsVerified(true) diff --git a/apps/sim/app/(auth)/verify/verify-content.tsx b/apps/sim/app/(auth)/verify/verify-content.tsx index 4e030cb8ae..c402bd9015 100644 --- a/apps/sim/app/(auth)/verify/verify-content.tsx +++ b/apps/sim/app/(auth)/verify/verify-content.tsx @@ -11,7 +11,6 @@ import { soehne } from '@/app/fonts/soehne/soehne' interface VerifyContentProps { hasResendKey: boolean - baseUrl: string isProduction: boolean } @@ -56,30 +55,13 @@ function VerificationForm({ setCountdown(30) } - const handleCancelVerification = () => { - // Clear verification data - if (typeof window !== 'undefined') { - sessionStorage.removeItem('verificationEmail') - sessionStorage.removeItem('inviteRedirectUrl') - sessionStorage.removeItem('isInviteFlow') - - // Clear the verification requirement cookie - document.cookie = 'requiresEmailVerification=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' - } - - // Redirect to login - router.push('/login') - } - const [buttonClass, setButtonClass] = useState('auth-button-gradient') useEffect(() => { - // Check if CSS variable has been customized const checkCustomBrand = () => { const computedStyle = getComputedStyle(document.documentElement) const brandAccent = computedStyle.getPropertyValue('--brand-accent-hex').trim() - // Check if the CSS variable exists and is different from the default if (brandAccent && brandAccent !== '#6f3dfa') { setButtonClass('auth-button-custom') } else { @@ -89,7 +71,6 @@ function VerificationForm({ checkCustomBrand() - // Also check on window resize or theme changes window.addEventListener('resize', checkCustomBrand) const observer = new MutationObserver(checkCustomBrand) observer.observe(document.documentElement, { @@ -235,7 +216,6 @@ function VerificationForm({