remove email verification temporarily (#6366)

## Summary

- Temporarily remove email verification. After susbcription gating was
enabled, this is less important
- Will re-add the logic back at a later time, defering requirement until
time to subscribe
- For time being, typo emails can be resolved through custom service
(https://support.comfy.org/hc/en-us/requests/new?tf_42243568391700=ccloud&tf_123456=X)
- Keep the route and redirect for deprecation. Not really needed since
the server falls back to root route anyway but generally good practice
and is more resistant to future changes + avoids a single extra routing
step in that scenario.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6366-remove-email-verification-temporarily-29b6d73d3650810095a4e7c4591b3327)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2025-10-29 00:23:48 -07:00
committed by GitHub
parent 84ce6c183d
commit 97949c61fb
18 changed files with 23 additions and 305 deletions

View File

@@ -1958,7 +1958,6 @@
"cloudClaimInvite_processingTitle": "معالجة رمز الدعوة...",
"cloudClaimInvite_claimButton": "استلام الدعوة",
"cloudSorryContactSupport_title": "عذراً، اتصل بالدعم",
"cloudVerifyEmail_title": "التحقق من البريد الإلكتروني",
"cloudPrivateBeta_title": "Cloud حالياً في مرحلة البيتا الخاصة",
"cloudPrivateBeta_desc": "سجل الدخول للانضمام إلى قائمة الانتظار. سنخطرك عندما يصبح الوصول للبيتا متاحاً. تم إخطارك بالفعل؟ سجل الدخول لبدء استخدام Comfy Cloud.",
"cloudForgotPassword_title": "نسيت كلمة المرور",

View File

@@ -2152,18 +2152,6 @@
"cloudSurvey_steps_purpose": "What will you primarily use ComfyUI for?",
"cloudSurvey_steps_industry": "What's your primary industry?",
"cloudSurvey_steps_making": "What do you plan on making?",
"cloudVerifyEmail_toast_message": "We've sent a verification email to {email}. Please check your inbox and click the link to verify your email address.",
"cloudVerifyEmail_failed_toast_message": "Failed to send verification email. Please contact support.",
"cloudVerifyEmail_title": "Check your email",
"cloudVerifyEmail_back": "Back",
"cloudVerifyEmail_sent": "A verification link was sent to:",
"cloudVerifyEmail_clickToContinue": "Click the link in that email to automatically\ncontinue onto the next steps.",
"cloudVerifyEmail_tip": "Tip: Dont forget to check your spam folder\nif you dont see it.",
"cloudVerifyEmail_didntReceive": "Didn't receive the email?",
"cloudVerifyEmail_resend": "Resend email",
"cloudVerifyEmail_toast_title": "Email sent",
"cloudVerifyEmail_toast_summary": "Check your inbox for a new verification email.",
"cloudVerifyEmail_toast_failed": "Failed to send verification email. Please try again.",
"cloudInvite_title": "YOU'RE INVITED",
"cloudInvite_subtitle": "This invite can only be used once. Double check youre signed into the account you want to use.",
"cloudInvite_switchAccounts": "Switch accounts",

View File

@@ -1955,7 +1955,6 @@
"cloudClaimInvite_processingTitle": "Procesando código de invitación...",
"cloudClaimInvite_claimButton": "Reclamar Invitación",
"cloudSorryContactSupport_title": "Lo sentimos, contacta al soporte",
"cloudVerifyEmail_title": "Verificación de Correo",
"cloudPrivateBeta_title": "Cloud está actualmente en beta privada",
"cloudPrivateBeta_desc": "Inicia sesión para unirte a la lista de espera. Te notificaremos cuando tengas acceso al beta. ¿Ya recibiste la notificación? Inicia sesión para empezar a usar Comfy Cloud.",
"cloudForgotPassword_title": "Olvidé mi Contraseña",

View File

@@ -1949,7 +1949,6 @@
"cloudClaimInvite_processingTitle": "Traitement du code d'invitation...",
"cloudClaimInvite_claimButton": "Réclamer l'Invitation",
"cloudSorryContactSupport_title": "Désolé, contactez le support",
"cloudVerifyEmail_title": "Vérification d'Email",
"cloudPrivateBeta_title": "Cloud est actuellement en bêta privée",
"cloudPrivateBeta_desc": "Connectez-vous pour rejoindre la liste d'attente. Nous vous préviendrons quand vous aurez accès à la bêta. Déjà notifié ? Connectez-vous pour commencer à utiliser Comfy Cloud.",
"cloudForgotPassword_title": "Mot de passe oublié",

View File

@@ -1955,7 +1955,6 @@
"cloudClaimInvite_processingTitle": "招待コードを処理中...",
"cloudClaimInvite_claimButton": "招待を申請",
"cloudSorryContactSupport_title": "申し訳ございません、サポートにお問い合わせください",
"cloudVerifyEmail_title": "メール確認",
"cloudPrivateBeta_title": "Cloudは現在プライベートベータ版です",
"cloudPrivateBeta_desc": "サインインしてウェイトリストに登録してください。ベータアクセスが可能になりましたらお知らせします。すでに通知を受け取りましたかサインインしてComfy Cloudを始めてください。",
"cloudForgotPassword_title": "パスワードを忘れた",

View File

@@ -1955,7 +1955,6 @@
"cloudClaimInvite_processingTitle": "초대 코드 확인중...",
"cloudClaimInvite_claimButton": "초대 요청하기",
"cloudSorryContactSupport_title": "죄송합니다, 지원팀에 문의해주세요",
"cloudVerifyEmail_title": "이메일 확인",
"cloudPrivateBeta_title": "Cloud는 현재 비공개 베타 버전입니다",
"cloudPrivateBeta_desc": "로그인하여 대기자 명단에 등록하세요. 베타 버전이 오픈될 때 알려드릴게요. 이미 알림을 받으셨다면? 로그인하여 Comfy Cloud를 시작해보세요.",
"cloudForgotPassword_title": "비밀번호 찾기",

View File

@@ -1955,7 +1955,6 @@
"cloudClaimInvite_processingTitle": "Обработка кода приглашения...",
"cloudClaimInvite_claimButton": "Получить приглашение",
"cloudSorryContactSupport_title": "Извините, свяжитесь с поддержкой",
"cloudVerifyEmail_title": "Подтверждение email",
"cloudPrivateBeta_title": "Cloud сейчас в приватной бете",
"cloudPrivateBeta_desc": "Войдите, чтобы присоединиться к списку ожидания. Мы уведомим вас, когда будет доступен бета-доступ. Уже получили уведомление? Войдите и начните работать с Comfy Cloud.",
"cloudForgotPassword_title": "Забыли пароль",

View File

@@ -1955,7 +1955,6 @@
"cloudClaimInvite_processingTitle": "處理邀請碼中...",
"cloudClaimInvite_claimButton": "領取邀請",
"cloudSorryContactSupport_title": "抱歉,請聯繫用戶支援",
"cloudVerifyEmail_title": "郵箱驗證",
"cloudPrivateBeta_title": "Cloud 目前處於內測階段",
"cloudPrivateBeta_desc": "登入即可加入等候名單,當您有內測資格時我們將及時通知。已收到通知?登入即可開始使用 Comfy Cloud。",
"cloudForgotPassword_title": "忘記密碼",

View File

@@ -1958,7 +1958,6 @@
"cloudClaimInvite_processingTitle": "处理邀请码中...",
"cloudClaimInvite_claimButton": "领取邀请",
"cloudSorryContactSupport_title": "抱歉,请联系用户支持",
"cloudVerifyEmail_title": "邮箱验证",
"cloudPrivateBeta_title": "Cloud 目前处于内测阶段",
"cloudPrivateBeta_desc": "登录即可加入等候名单,当您有内测资格时我们将及时通知。已收到通知?登录即可开始使用 Comfy Cloud。",
"cloudForgotPassword_title": "忘记密码",

View File

@@ -68,8 +68,10 @@ export const cloudOnboardingRoutes: RouteRecordRaw[] = [
{
path: 'verify-email',
name: 'cloud-verify-email',
component: () =>
import('@/platform/onboarding/cloud/CloudVerifyEmailView.vue')
redirect: (to) => ({
name: 'cloud-user-check',
query: to.query
})
},
{
path: 'sorry-contact-support',

View File

@@ -17,23 +17,16 @@ onMounted(async () => {
const inviteCode = route.params.code as string | undefined
if (firebaseAuthStore.isAuthenticated) {
const { isEmailVerified } = firebaseAuthStore
if (!isEmailVerified) {
// User is logged in but email not verified
await router.push({ name: 'cloud-verify-email', query: { inviteCode } })
// User is logged in - no email verification check needed
if (inviteCode) {
// Handle invite code flow - go to invite check
await router.push({
name: 'cloud-invite-check',
query: { inviteCode }
})
} else {
// User is logged in and verified
if (inviteCode) {
// Handle invite code flow - go to invite check
await router.push({
name: 'cloud-invite-check',
query: { inviteCode }
})
} else {
// Normal login flow - go to user check
await router.push({ name: 'cloud-user-check' })
}
// Normal login flow - go to user check
await router.push({ name: 'cloud-user-check' })
}
} else {
// User is not logged in - proceed to login page

View File

@@ -115,7 +115,6 @@ import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthAction
import CloudSignInForm from '@/platform/onboarding/cloud/components/CloudSignInForm.vue'
import { useToastStore } from '@/platform/updates/common/toastStore'
import type { SignInData } from '@/schemas/signInSchema'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { translateAuthError } from '@/utils/authErrorTranslation'
const { t } = useI18n()
@@ -140,20 +139,15 @@ const onSuccess = async () => {
})
// Check if there's an invite code
const inviteCode = route.query.inviteCode as string | undefined
const { isEmailVerified } = useFirebaseAuthStore()
if (!isEmailVerified) {
await router.push({ name: 'cloud-verify-email', query: { inviteCode } })
if (inviteCode) {
// Handle invite code flow - go to invite check
await router.push({
name: 'cloud-invite-check',
query: { inviteCode }
})
} else {
if (inviteCode) {
// Handle invite code flow - go to invite check
await router.push({
name: 'cloud-invite-check',
query: { inviteCode }
})
} else {
// Normal login flow - go to user check
await router.push({ name: 'cloud-user-check' })
}
// Normal login flow - go to user check
await router.push({ name: 'cloud-user-check' })
}
}

View File

@@ -104,7 +104,6 @@ import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useToastStore } from '@/platform/updates/common/toastStore'
import type { SignUpData } from '@/schemas/signInSchema'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
import { translateAuthError } from '@/utils/authErrorTranslation'
import { isInChina } from '@/utils/networkUtil'
@@ -127,20 +126,8 @@ const onSuccess = async () => {
summary: 'Sign up Completed',
life: 2000
})
// Check if email verification is needed
const { isEmailVerified } = useFirebaseAuthStore()
const inviteCode = route.query.inviteCode as string | undefined
if (!isEmailVerified) {
// Redirect to email verification with fromAuth flag
await router.push({
name: 'cloud-verify-email',
query: { inviteCode, fromAuth: 'true' }
})
} else {
// The invite code will be handled after the user is logged in
await router.push({ path: '/', query: route.query })
}
// Direct redirect to main app - email verification removed
await router.push({ path: '/', query: route.query })
}
// Custom error handler for inline display

View File

@@ -1,191 +0,0 @@
<template>
<div class="mx-auto max-w-[640px] px-6 py-8">
<!-- Back button -->
<button
type="button"
class="text-foreground/80 flex size-10 items-center justify-center rounded-lg border border-white bg-transparent"
aria-label="{{ t('cloudVerifyEmail_back') }}"
@click="goBack"
>
<i class="pi pi-arrow-left" />
</button>
<!-- Title -->
<h1 class="mt-8 text-2xl font-semibold">
{{ t('cloudVerifyEmail_title') }}
</h1>
<!-- Body copy -->
<p class="text-foreground/80 mt-6 mb-0 text-base">
{{ t('cloudVerifyEmail_sent') }}
</p>
<p class="mt-2 text-base font-medium">{{ authStore.userEmail }}</p>
<p class="text-foreground/80 mt-6 text-base whitespace-pre-line">
{{ t('cloudVerifyEmail_clickToContinue') }}
</p>
<p class="text-foreground/80 mt-6 text-base whitespace-pre-line">
{{ t('cloudVerifyEmail_tip') }}
</p>
<p class="text-foreground/80 mt-6 mb-0 text-base">
{{ t('cloudVerifyEmail_didntReceive') }}
</p>
<p class="text-foreground/80 mt-1 text-base">
<span class="cursor-pointer text-blue-400 no-underline" @click="onSend">
{{ t('cloudVerifyEmail_resend') }}</span
>
</p>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import { useFirebaseAuth } from 'vuefire'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { useToastStore } from '@/platform/updates/common/toastStore'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
const authStore = useFirebaseAuthStore()
const auth = useFirebaseAuth()!
const router = useRouter()
const route = useRoute()
const { t } = useI18n()
const toastStore = useToastStore()
let intervalId: number | null = null
let timeoutId: number | null = null
const redirectInProgress = ref(false)
function clearPolling(): void {
if (intervalId !== null) {
clearInterval(intervalId)
intervalId = null
}
if (timeoutId !== null) {
clearTimeout(timeoutId)
timeoutId = null
}
}
async function redirectToNextStep(): Promise<void> {
if (redirectInProgress.value) return
redirectInProgress.value = true
clearPolling()
const inviteCode = route.query.inviteCode as string | undefined
if (inviteCode) {
await router.push({
name: 'cloud-invite-check',
query: { inviteCode }
})
} else {
await router.push({ name: 'cloud-user-check' })
}
}
const goBack = async () => {
const inviteCode = route.query.inviteCode as string | undefined
const authStore = useFirebaseAuthStore()
// If the user is already verified (email link already clicked),
// continue to the next step automatically.
if (authStore.isEmailVerified) {
await router.push({
name: 'cloud-invite-check',
query: inviteCode ? { inviteCode } : {}
})
} else {
await router.push({
name: 'cloud-login',
query: {
inviteCode
}
})
}
}
async function onSend() {
try {
await authStore.verifyEmail()
// Track email verification requested
if (isCloud) {
useTelemetry()?.trackEmailVerification('requested')
}
toastStore.add({
severity: 'info',
summary: t('cloudVerifyEmail_toast_title'),
detail: t('cloudVerifyEmail_toast_summary'),
life: 2000
})
} catch (e) {
toastStore.add({
severity: 'error',
summary: t('cloudVerifyEmail_toast_failed'),
life: 2000
})
}
}
onMounted(async () => {
// Track email verification screen opened
if (isCloud) {
useTelemetry()?.trackEmailVerification('opened')
}
// If the user is already verified (email link already clicked),
// continue to the next step automatically.
if (authStore.isEmailVerified) {
return redirectToNextStep()
}
// Only send verification email automatically if coming from signup/login flow
// Check if 'fromAuth' query parameter is present
const fromAuth = route.query.fromAuth === 'true'
if (fromAuth) {
await onSend()
// Remove fromAuth query parameter after sending email to prevent re-sending on refresh
const { fromAuth: _, ...remainingQuery } = route.query
await router.replace({
name: route.name as string,
query: remainingQuery
})
}
// Start polling to check email verification status
intervalId = window.setInterval(async () => {
if (auth.currentUser && !redirectInProgress.value) {
await auth.currentUser.reload()
if (auth.currentUser?.emailVerified) {
// Track email verification completed
if (isCloud) {
useTelemetry()?.trackEmailVerification('completed')
}
void redirectToNextStep()
}
}
}, 5000) // Check every 5 seconds
// Stop polling after 5 minutes
timeoutId = window.setTimeout(
() => {
clearPolling()
},
5 * 60 * 1000
)
})
onUnmounted(() => {
clearPolling()
})
</script>

View File

@@ -347,24 +347,6 @@ export class MixpanelTelemetryProvider implements TelemetryProvider {
}
}
trackEmailVerification(stage: 'opened' | 'requested' | 'completed'): void {
let eventName: TelemetryEventName
switch (stage) {
case 'opened':
eventName = TelemetryEvents.USER_EMAIL_VERIFY_OPENED
break
case 'requested':
eventName = TelemetryEvents.USER_EMAIL_VERIFY_REQUESTED
break
case 'completed':
eventName = TelemetryEvents.USER_EMAIL_VERIFY_COMPLETED
break
}
this.trackEvent(eventName)
}
trackTemplate(metadata: TemplateMetadata): void {
this.trackEvent(TelemetryEvents.TEMPLATE_WORKFLOW_OPENED, metadata)
}

View File

@@ -104,9 +104,6 @@ export interface TelemetryProvider {
// Survey flow events
trackSurvey(stage: 'opened' | 'submitted', responses?: SurveyResponses): void
// Email verification events
trackEmailVerification(stage: 'opened' | 'requested' | 'completed'): void
// Template workflow events
trackTemplate(metadata: TemplateMetadata): void
@@ -141,11 +138,6 @@ export const TelemetryEvents = {
USER_SURVEY_OPENED: 'app:user_survey_opened',
USER_SURVEY_SUBMITTED: 'app:user_survey_submitted',
// Email Verification
USER_EMAIL_VERIFY_OPENED: 'app:user_email_verify_opened',
USER_EMAIL_VERIFY_REQUESTED: 'app:user_email_verify_requested',
USER_EMAIL_VERIFY_COMPLETED: 'app:user_email_verify_completed',
// Template Tracking
TEMPLATE_WORKFLOW_OPENED: 'app:template_workflow_opened',

View File

@@ -179,13 +179,6 @@ router.beforeEach(async (to, _from, next) => {
// For root path, check actual user status to handle waitlisted users
if (!isElectron() && isLoggedIn && to.path === '/') {
try {
// Check email verification first
const authStore = useFirebaseAuthStore()
if (!authStore.isEmailVerified) {
// Don't pass fromAuth here since this is from root navigation, not auth flow
return next({ name: 'cloud-verify-email' })
}
// Import auth functions dynamically to avoid circular dependency
const { getUserCloudStatus, getSurveyCompletedStatus } = await import(
'@/api/auth'

View File

@@ -9,7 +9,6 @@ import {
getAdditionalUserInfo,
onAuthStateChanged,
onIdTokenChanged,
sendEmailVerification,
sendPasswordResetEmail,
setPersistence,
signInWithEmailAndPassword,
@@ -82,9 +81,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
const isAuthenticated = computed(() => !!currentUser.value)
const userEmail = computed(() => currentUser.value?.email)
const userId = computed(() => currentUser.value?.uid)
const isEmailVerified = computed(
() => currentUser.value?.emailVerified ?? false
)
// Get auth from VueFire and listen for auth state changes
// From useFirebaseAuth docs:
@@ -349,14 +345,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
await updatePassword(currentUser.value, newPassword)
}
/** Send email verification to current user */
const verifyEmail = async (): Promise<void> => {
if (!currentUser.value) {
throw new FirebaseAuthStoreError(t('toastMessages.userNotAuthenticated'))
}
await sendEmailVerification(currentUser.value)
}
/** Delete the current user account */
const _deleteAccount = async (): Promise<void> => {
if (!currentUser.value) {
@@ -440,7 +428,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
// State
loading,
currentUser,
isEmailVerified,
isInitialized,
balance,
lastBalanceUpdateTime,
@@ -466,7 +453,6 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
sendPasswordReset,
updatePassword: _updatePassword,
getAuthHeader,
verifyEmail,
deleteAccount: _deleteAccount
}
})