diff --git a/.all-contributorsrc b/.all-contributorsrc index 5381eb142..1df78fb21 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -761,6 +761,15 @@ "contributions": [ "doc" ] + }, + { + "login": "Ajen07", + "name": "Arman Kumar Jena", + "avatar_url": "https://avatars.githubusercontent.com/u/112967686?v=4", + "profile": "https://github.com/Ajen07", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index 44f8fbce3..770a16c47 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ **Digitomize**, part of **Microsoft for Startups Founders Hub**, is an open-source platform that combines two main sections: Contests and User Profiles. It allows users to explore upcoming coding contests and dynamically create developer portfolios.
-[![All Contributors](https://img.shields.io/badge/all_contributors-83-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-84-orange.svg?style=flat-square)](#contributors-) Website: [![Better Stack Badge](https://uptime.betterstack.com/status-badges/v1/monitor/zb9g.svg)](https://uptime.betterstack.com/?utm_source=status_badge) @@ -257,6 +257,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Keshav Priyam
Keshav Priyam

💻 Ramakrushna Biswal
Ramakrushna Biswal

💻 Rohith Singh
Rohith Singh

📖 + Arman Kumar Jena
Arman Kumar Jena

💻 diff --git a/backend/users/middlewares/authMiddleware.js b/backend/users/middlewares/authMiddleware.js index 7fd2c68a6..108d779fb 100644 --- a/backend/users/middlewares/authMiddleware.js +++ b/backend/users/middlewares/authMiddleware.js @@ -23,10 +23,24 @@ const addUID = async (request, response, next) => { } try { + const decodedToken = await getAuth().verifyIdToken(authToken); + const { + uid, + firebase: { sign_in_provider }, + } = decodedToken; + + if (sign_in_provider === "github.com") { + await getAuth().updateUser(uid, { + emailVerified: true, + }); + } getAuth() .verifyIdToken(authToken) .then((decTok) => { request.decodedToken = decTok; + if (decTok.firebase.sign_in_provider === "github.com") { + decTok.email_verified = true; + } // console.log("calling next"); next(); }) diff --git a/client/src/App.jsx b/client/src/App.jsx index 76d3ae67e..d43e29c16 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -34,6 +34,7 @@ import { About, Footer, MetaData, + resendLoader } from "./components/CustomComponents"; // import UserDashBoardAccount from "./user/dashboard/Account"; import UserDashboard from "./user/dashboard/UserDashboard"; @@ -117,6 +118,8 @@ import { userDashboardDetails } from "../api"; import Preferences from "./user/dashboard/Preferences/Preferences"; import Ratings from "./user/dashboard/Ratings/Ratings"; import Settings from "./user/dashboard/Settings/Settings"; +import ResendEmailVerification from "./pages/verification/ResendEmailVerification"; +import VerifyEmailPage from "./pages/verification/VerifyEmailPage"; function Logout() { const navigate = useNavigate(); @@ -177,6 +180,12 @@ const router = createBrowserRouter( } /> } loader={signupLoader} /> } loader={forgotPasswordLoader} /> + } + loader={resendLoader} + /> + } /> }> } /> } /> diff --git a/client/src/ProtectedRoute.jsx b/client/src/ProtectedRoute.jsx index f54093e9e..70b2d40e3 100644 --- a/client/src/ProtectedRoute.jsx +++ b/client/src/ProtectedRoute.jsx @@ -1,14 +1,20 @@ import React from "react"; import { Outlet, Navigate } from "react-router-dom"; import { useUserAuth } from "@context/UserAuthContext"; +import { auth } from "../firebase"; function ProtectedRoute() { const { user } = useUserAuth(); - return user ? ( - - ) : ( - - ); + + if (!user) { + return ; + } + + if (auth?.currentUser?.emailVerified) { + return ; + } + + return ; } export default ProtectedRoute; diff --git a/client/src/components/AuthButtons/GithubAuthButton.jsx b/client/src/components/AuthButtons/GithubAuthButton.jsx index b566a7917..aa2c05e06 100644 --- a/client/src/components/AuthButtons/GithubAuthButton.jsx +++ b/client/src/components/AuthButtons/GithubAuthButton.jsx @@ -34,7 +34,8 @@ export default function GithubAuthButton() { }) // .then((res) => console.log(res)) .catch((err) => console.error(err)); - navigate("/u/dashboard/account"); + await auth.currentUser.reload() + navigate("/u/dashboard"); }) .catch((error) => { toast.error(error.code, { diff --git a/client/src/components/CustomComponents.jsx b/client/src/components/CustomComponents.jsx index ed9ff5df7..852d02255 100644 --- a/client/src/components/CustomComponents.jsx +++ b/client/src/components/CustomComponents.jsx @@ -18,6 +18,7 @@ import Footer from "./globals/Footer"; import { loader as loginLoader } from "./Login"; import { loader as signupLoader } from "./globals/Signup"; import { loader as forgotPasswordLoader } from "./ForgotPassword"; +import {loader as resendLoader} from "../pages/verification/ResendEmailVerification" import NewNavbar from "./globals/Navbar/NewNavbar"; import ScrollToTop from "./globals/ScrollToTop"; import Signup from "./globals/Signup"; @@ -51,4 +52,5 @@ export { signupLoader, ForgotPassword, forgotPasswordLoader, + resendLoader, }; diff --git a/client/src/components/Login.jsx b/client/src/components/Login.jsx index 8c000b4b4..840574a5e 100644 --- a/client/src/components/Login.jsx +++ b/client/src/components/Login.jsx @@ -21,8 +21,9 @@ import { Eye, EyeOff } from "lucide-react"; export async function loader({ request }) { const message = new URL(request.url).searchParams.get("message"); const loggedIn = await isLoggedIn(); + if (loggedIn) { - return redirect("/u/dashboard"); + return redirect("/u/dashboard"); } return message; diff --git a/client/src/components/globals/Signup.jsx b/client/src/components/globals/Signup.jsx index c0e955fb6..eb4285091 100644 --- a/client/src/components/globals/Signup.jsx +++ b/client/src/components/globals/Signup.jsx @@ -26,6 +26,9 @@ export async function loader() { if (loggedIn) { return redirect("/login"); } + if (loggedIn && auth.currentUser && auth.currentUser.emailVerified) { + return redirect("/u/dashboard"); + } return null; } @@ -105,7 +108,17 @@ export default function Signup() { .catch((err) => setError(err.code)); } - navigate("/login"); + toast.success("Verification link sent to email", { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "colored", + }); + navigate("/login", { replace: true }); } catch (err) { toast.error(err.code, { position: "top-right", diff --git a/client/src/context/UserAuthContext.jsx b/client/src/context/UserAuthContext.jsx index afe1c308a..77b6635f0 100644 --- a/client/src/context/UserAuthContext.jsx +++ b/client/src/context/UserAuthContext.jsx @@ -9,6 +9,7 @@ import { signInWithPopup, GithubAuthProvider, updateProfile, + sendEmailVerification, } from "firebase/auth"; import { auth } from "../../firebase"; @@ -37,6 +38,11 @@ export function UserAuthContextProvider({ children }) { console.error("Error updating profile:", error); }); const token = await user.getIdToken(); + sendEmailVerification(user).then(() => { + /* console.log("Email verification sent."); */ + }).catch((error) => { + console.error("Error sending email verification:", error); + }); return { result, token }; }) .catch((error) => { diff --git a/client/src/pages/verification/ResendEmailVerification.jsx b/client/src/pages/verification/ResendEmailVerification.jsx new file mode 100644 index 000000000..3cd06385b --- /dev/null +++ b/client/src/pages/verification/ResendEmailVerification.jsx @@ -0,0 +1,92 @@ +import React, { useState } from "react"; +import { toast, ToastContainer } from "react-toastify"; +import { auth } from "../../../firebase"; +import { sendEmailVerification } from "firebase/auth"; +import { isLoggedIn } from "../../../api"; +import { redirect } from "react-router-dom"; + + +export async function loader() { + const loggedIn = await isLoggedIn(); + if (loggedIn && auth.currentUser.emailVerified) { + return redirect("/u/dashboard"); + }else if (!loggedIn ) { + return redirect("/login"); + } + return null; +} + +const ResendEmailVerification = () => { + const [btnState, setbtnState] = useState(false); + const handleSubmit = async (e) => { + e.preventDefault(); + try { + setbtnState(true); + await sendEmailVerification(auth.currentUser); + toast.success("Verification E-Mail send", { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "colored", + }); + setbtnState(false); + } catch (err) { + setbtnState(false); + toast.error(err.code, { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "colored", + }); + } + }; + return ( + <> + + + +
+

+ Please verify your email to continue +

+

+ Didn't receive the verification email? Click the button below to + resend it. +

+
+ +
+ +
+ +
+
+ + ); +}; + +export default ResendEmailVerification; \ No newline at end of file diff --git a/client/src/pages/verification/VerifyEmailPage.jsx b/client/src/pages/verification/VerifyEmailPage.jsx new file mode 100644 index 000000000..c7ce91b7a --- /dev/null +++ b/client/src/pages/verification/VerifyEmailPage.jsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from "react"; +import { auth } from "../../../firebase"; +import { applyActionCode } from "firebase/auth"; +import { ToastContainer, toast } from "react-toastify"; +import { Link } from "react-router-dom"; + +const VerifyEmailPage = () => { + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); + const [isSuccess, setIsSuccess] = useState(false); + let isMounted = true; + useEffect(() => { + const { oobCode } = Object.fromEntries( + new URLSearchParams(window.location.search), + ); + async function verifyEmail() { + try { + setIsLoading((prevState)=>!prevState); + + if (isMounted) { + await applyActionCode(auth, oobCode); + toast.success("Verification Successfull", { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "colored", + }); + await auth.currentUser.reload(); + setIsSuccess(true); + } + } catch (error) { + + setIsError(true); + toast.error(error.code, { + position: "top-right", + autoClose: 5000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "colored", + }); + + } finally { + + setIsLoading(false); + + } + } + verifyEmail(); + return () => { + isMounted = false; + }; + }, [isMounted]); + if (isLoading) { + return ( +
+
+
Verifying User
+
+
+ +
+
+
+
+ ); + } + if (isSuccess) { + return ( + <> +
+ +
+

+ Verification Successful , Please Login +

+ + + + + + Login + + +
+
+ + ); + } + if (isError) { + return ( + <> +
+

+ Oops!! something went wrong , Please try again later +

+ + + + + + Home + + +
+ + ); + } +}; + +export default VerifyEmailPage; \ No newline at end of file diff --git a/client/tailwind.config.js b/client/tailwind.config.js index cf35080c2..70dd30be4 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -1,4 +1,7 @@ /** @type {import('tailwindcss').Config} */ +const { + default: flattenColorPalette, +} = require("tailwindcss/lib/util/flattenColorPalette"); export default { content: [ "node_modules/daisyui/dist/**/*.js",