diff --git a/backend/app/api/endpoints/auth.py b/backend/app/api/endpoints/auth.py index 4b3f0295..83de7f89 100644 --- a/backend/app/api/endpoints/auth.py +++ b/backend/app/api/endpoints/auth.py @@ -7,6 +7,7 @@ from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.core.config import settings +from app.core.settings_manager import SettingsManager from app.core.database import get_db from app.core.rate_limit import limiter from app.core.security import ( @@ -595,6 +596,15 @@ async def reset_password( return {"message": "Password has been reset successfully"} +@router.get("/email/verification-status") +async def get_verification_status( + db: Annotated[AsyncSession, Depends(get_db)] +): + """Check if email verification is enabled on the platform.""" + enabled = await SettingsManager.get("email_verification_enabled", db, default=True) + return {"enabled": enabled} + + @router.post("/email/send-verification") @limiter.limit("3/minute") async def send_verification_email( @@ -603,6 +613,13 @@ async def send_verification_email( db: Annotated[AsyncSession, Depends(get_db)] ): """Send an email verification link to the current user.""" + verification_enabled = await SettingsManager.get("email_verification_enabled", db, default=True) + if not verification_enabled: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Email verification is currently disabled" + ) + if current_user.email_verified_at is not None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index e4d53bcf..afc3fec3 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -59,6 +59,11 @@ export const authApi = { return response.data }, + async getVerificationStatus(): Promise<{ enabled: boolean }> { + const response = await apiClient.get<{ enabled: boolean }>('/auth/email/verification-status') + return response.data + }, + async sendVerificationEmail(): Promise { await apiClient.post('/auth/email/send-verification') }, diff --git a/frontend/src/components/layout/EmailVerificationBanner.tsx b/frontend/src/components/layout/EmailVerificationBanner.tsx index 878a2ccb..903a811a 100644 --- a/frontend/src/components/layout/EmailVerificationBanner.tsx +++ b/frontend/src/components/layout/EmailVerificationBanner.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { AlertTriangle, X, Loader2 } from 'lucide-react' import { authApi } from '@/api/auth' import { useAuthStore } from '@/store/authStore' @@ -9,8 +9,15 @@ export function EmailVerificationBanner() { const user = useAuthStore((s) => s.user) const [dismissed, setDismissed] = useState(false) const [isSending, setIsSending] = useState(false) + const [verificationEnabled, setVerificationEnabled] = useState(true) - if (!user || user.email_verified_at || dismissed) return null + useEffect(() => { + authApi.getVerificationStatus() + .then((data) => setVerificationEnabled(data.enabled)) + .catch(() => {}) + }, []) + + if (!user || user.email_verified_at || dismissed || !verificationEnabled) return null const handleResend = async () => { setIsSending(true) diff --git a/frontend/src/pages/admin/SettingsPage.tsx b/frontend/src/pages/admin/SettingsPage.tsx index 23290bd6..236f2d97 100644 --- a/frontend/src/pages/admin/SettingsPage.tsx +++ b/frontend/src/pages/admin/SettingsPage.tsx @@ -18,6 +18,7 @@ export function SettingsPage() { const maintenanceMode = Boolean(settings.maintenance_mode) const maintenanceMessage = String(settings.maintenance_message || '') + const emailVerificationEnabled = settings.email_verification_enabled !== false const handleSave = async () => { setSaving(true) @@ -46,6 +47,27 @@ export function SettingsPage() {
+
+
+

Email Verification

+

+ When enabled, unverified users see a banner prompting them to verify their email. +

+
+ +
+

Maintenance Mode