diff --git a/backend/models/user.py b/backend/models/user.py index 676c185..4ea37f5 100644 --- a/backend/models/user.py +++ b/backend/models/user.py @@ -114,6 +114,7 @@ class UserOut(BaseModel): organization_id: Optional[int] role: Optional[str] requires_password_update: bool + email_verified: bool class UserUpdate(BaseModel): diff --git a/backend/routes/user_routes.py b/backend/routes/user_routes.py index e303162..d501594 100644 --- a/backend/routes/user_routes.py +++ b/backend/routes/user_routes.py @@ -58,6 +58,7 @@ async def read_users_me(current_user: User = Depends(get_current_user)): organization_id=current_user.organization_id, role=current_user.role, requires_password_update=current_user.requires_password_update, + email_verified=current_user.email_verified, ) return user_out diff --git a/backend/utils/email.py b/backend/utils/email.py index 5ebe617..d693979 100644 --- a/backend/utils/email.py +++ b/backend/utils/email.py @@ -34,10 +34,9 @@ async def send_verification_email_with_sendgrid(email: List[str], token: str): html_content=f'Click on the link to verify your email: https://{APP_HOST}/verify-email', ) # Disable click tracking in development - # if APP_ENV == "dev": - print("Disabling click tracking") - message.tracking_settings = TrackingSettings() - message.tracking_settings.click_tracking = ClickTracking(False, False) + if APP_ENV == "dev": + message.tracking_settings = TrackingSettings() + message.tracking_settings.click_tracking = ClickTracking(False, False) try: sg = SendGridAPIClient(SENDGRID_API_KEY) response = await sg.send(message) diff --git a/frontend/src/components/auth/RequireAuth.jsx b/frontend/src/components/auth/RequireAuth.jsx index b8a410e..1590012 100644 --- a/frontend/src/components/auth/RequireAuth.jsx +++ b/frontend/src/components/auth/RequireAuth.jsx @@ -2,8 +2,10 @@ import { Navigate } from "react-router-dom"; import { useAuth } from "../../contexts/AuthContext"; function RequireAuth({ children }) { - const { isAuthenticated, isLoading, isEmailVerified } = useAuth(); + const { isAuthenticated, isLoading, isEmailVerified, loginProcessCompleted } = + useAuth(); + // if (isLoading || !loginProcessCompleted) { if (isLoading) { return
Loading...
; // Or your preferred loading indicator/component } @@ -13,8 +15,13 @@ function RequireAuth({ children }) { return ; } - if (!isEmailVerified) { - console.log("from require auth"); + if (isAuthenticated && !isEmailVerified) { + // Redirect to the verify-email page if email is not verified + console.log("triggered"); + return ; + } + + if (loginProcessCompleted && !isEmailVerified) { // Redirect to the verify-email page if email is not verified return ; } diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index 9f4aa22..feca594 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -17,11 +17,11 @@ export const AuthProvider = ({ children }) => { const [isEmailVerified, setIsEmailVerified] = useState(false); // Add a state for email verification const [userRole, setUserRole] = useState(null); const [isLoading, setIsLoading] = useState(true); // Add a loading state + const [loginProcessCompleted, setLoginProcessCompleted] = useState(false); useEffect(() => { const verifyToken = async () => { try { - // Replace '/api/verifyToken' with your actual API endpoint const response = await axios.get(`${API_URL}verify-token/`); // Update based on the response message if (response.data.message === "User is authenticated") { @@ -29,11 +29,7 @@ export const AuthProvider = ({ children }) => { const userResponse = await axios.get(`${API_URL}users/me/`); setUserRole(userResponse.data.role); - - const emailResponse = await axios.get( - `${API_URL}users/is-email-verified/`, - ); - setIsEmailVerified(emailResponse.data.email_verified); + setIsEmailVerified(userResponse.data.email_verified); } else { setIsAuthenticated(false); } @@ -57,14 +53,21 @@ export const AuthProvider = ({ children }) => { setIsAuthenticated(newAuthState); }; - const updateEmailVerification = (newEmailVerificationState) => { + const updateEmailVerification = (newEmailVerificationState, callback) => { setIsEmailVerified(newEmailVerificationState); + if (callback) { + callback(); + } }; const updateUserRole = (newUserRole) => { setUserRole(newUserRole); }; + const markLoginProcessCompleted = (newState) => { + setLoginProcessCompleted(newState); + }; + // The Provider component from our created context is used here. // It makes the `isAuthenticated` state and `updateAuth` function available to any descendants of this component return ( @@ -77,6 +80,8 @@ export const AuthProvider = ({ children }) => { updateUserRole, userRole, isLoading, + loginProcessCompleted, + markLoginProcessCompleted, }} > {children} diff --git a/frontend/src/pages/login/LoginPage.jsx b/frontend/src/pages/login/LoginPage.jsx index 086e5f7..6de3887 100644 --- a/frontend/src/pages/login/LoginPage.jsx +++ b/frontend/src/pages/login/LoginPage.jsx @@ -27,16 +27,19 @@ function LoginPage({ onLogin }) { updateEmailVerification, updateUserRole, isAuthenticated, + isEmailVerified, + markLoginProcessCompleted, } = useAuth(); const [errorMessage, setErrorMessage] = useState(""); const location = useLocation(); const [emailVerifiedMessage, setEmailVerifiedMessage] = useState(""); useEffect(() => { - if (isAuthenticated) { + if (isAuthenticated && isEmailVerified) { + console.log("Navigating to dashboards"); navigate("/dashboards"); } - }, [isAuthenticated, navigate]); + }, [isAuthenticated, isEmailVerified, navigate]); useEffect(() => { if (location.state?.emailVerified) { @@ -70,24 +73,24 @@ function LoginPage({ onLogin }) { ); if (response.status === 200) { - updateAuth(true); + markLoginProcessCompleted(true); const userResponse = await axios.get(`${API_URL}users/me/`, { headers: { Authorization: `Bearer ${response.data.access_token}`, }, }); - console.log(userResponse.data.role); updateUserRole(userResponse.data.role); + updateAuth(true); if (userResponse.data.requires_password_update) { navigate("/change-password"); - } else if (userResponse.data.email_verified == false) { - console.log("email_verified is false"); - navigate("/verify-email"); } else { - console.log("email_verified is true"); - updateEmailVerification(true); - navigate("/dashboards"); + updateEmailVerification(userResponse.data.email_verified); + if (userResponse.data.email_verified) { + navigate("/dashboards"); + } else { + navigate("/verify-email"); + } } } } catch (error) { diff --git a/frontend/src/pages/logout/LogoutPage.jsx b/frontend/src/pages/logout/LogoutPage.jsx index 40fcd6d..b4feab4 100644 --- a/frontend/src/pages/logout/LogoutPage.jsx +++ b/frontend/src/pages/logout/LogoutPage.jsx @@ -6,7 +6,12 @@ import { useAuth } from "../../contexts/AuthContext"; function Logout() { const navigate = useNavigate(); - const { updateAuth } = useAuth(); + const { + updateAuth, + updateEmailVerification, + updateUserRole, + markLoginProcessCompleted, + } = useAuth(); useEffect(() => { // Clear the authentication cookie @@ -15,10 +20,19 @@ function Logout() { // Update authentication state updateAuth(false); + updateEmailVerification(false); + updateUserRole(null); + markLoginProcessCompleted(false); // Redirect to the login page navigate("/"); - }, [navigate, updateAuth]); + }, [ + navigate, + updateAuth, + updateEmailVerification, + updateUserRole, + markLoginProcessCompleted, + ]); // Optionally, you can render a message or a spinner here return
Logging out...
; diff --git a/frontend/src/pages/register/RegisterPage.jsx b/frontend/src/pages/register/RegisterPage.jsx index a8ec5a1..b91eb8e 100644 --- a/frontend/src/pages/register/RegisterPage.jsx +++ b/frontend/src/pages/register/RegisterPage.jsx @@ -40,7 +40,7 @@ function RegisterPage() { }); // Navigate to the verify-email page instead of login - navigate("/verify-email", { state: { email } }); + navigate("/verify-email"); } } catch (error) { if (error.response) { diff --git a/frontend/src/pages/verify-email/VerifyEmailPage.jsx b/frontend/src/pages/verify-email/VerifyEmailPage.jsx index e1ef0cc..6f21850 100644 --- a/frontend/src/pages/verify-email/VerifyEmailPage.jsx +++ b/frontend/src/pages/verify-email/VerifyEmailPage.jsx @@ -1,16 +1,26 @@ import React, { useState, useEffect } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import { Box, Button, Container, Typography } from "@mui/material"; +import { + Alert, + Box, + Button, + Container, + Snackbar, + Typography, +} from "@mui/material"; import { useAuth } from "../../contexts/AuthContext"; import { API_URL } from "../../utils/constants"; import axios from "axios"; +import { set } from "date-fns"; const VerifyEmailPage = () => { const location = useLocation(); const navigate = useNavigate(); - const email = location.state?.email || null; + const [userResponse, setUserResponse] = useState(null); const [token, setToken] = useState(null); - const { updateAuth, updateEmailVerification } = useAuth(); + const { updateAuth, updateEmailVerification, loginProcessCompleted } = + useAuth(); + const [openAlert, setOpenAlert] = useState(false); useEffect(() => { const params = new URLSearchParams(location.search); @@ -18,6 +28,25 @@ const VerifyEmailPage = () => { setToken(token); }, [location]); + useEffect(() => { + if (!token) { + axios + .get(`${API_URL}users/me/`) + .then((response) => { + if (response.data) { + setUserResponse(response.data); + } else { + // If no user data is returned, navigate to the login page + navigate("/login"); + } + }) + .catch((error) => { + // If an error occurs, navigate to the login page + navigate("/login"); + }); + } + }, [token]); + useEffect(() => { if (token) { console.log("token", token); @@ -38,7 +67,7 @@ const VerifyEmailPage = () => { .catch((error) => { // Handle failed token verification console.log("Failed to verify token"); - navigate("/login", { state: { emailVerified: true } }); + navigate("/login"); }); }) .catch((error) => { @@ -51,10 +80,13 @@ const VerifyEmailPage = () => { const handleResendEmail = async (event) => { event.preventDefault(); axios - .post(`${API_URL}users/send-verification-email/`, { email }) + .post(`${API_URL}users/send-verification-email/`, { + email: userResponse.email, + }) .then((response) => { // Handle successful email resend console.log("Verification email sent successfully"); + setOpenAlert(true); }) .catch((error) => { // Handle failed email resend @@ -62,6 +94,13 @@ const VerifyEmailPage = () => { }); }; + const handleCloseAlert = (event, reason) => { + if (reason === "clickaway") { + return; + } + setOpenAlert(false); + }; + return ( { > Resend Verification Email + + + Verification email has been resent! + + );