diff --git a/backend/src/config/isAuthorized.ts b/backend/src/config/isAuthorized.ts new file mode 100644 index 0000000..681ac00 --- /dev/null +++ b/backend/src/config/isAuthorized.ts @@ -0,0 +1,60 @@ +import jwt from "jsonwebtoken"; +import dotenv from "dotenv"; +import { Response, NextFunction } from "express"; +import { + AuthenticatedUserRequest, + DecodedJwtPayload, +} from "../common/types.js"; +import { getUserById } from "../controllers/userController.js"; + +// Load environment variables +dotenv.config(); + +// Define environment variables +const TOKEN_SECRET = process.env.TOKEN_SECRET; + +async function isAuthorized( + req: AuthenticatedUserRequest, + res: Response, + next: NextFunction +): Promise { + // Get the token from the request body + const token = req.body.token; + + // If the token is not provided, return an error message + if (!token) { + return res.status(400).json({ + error: "Missing required parameter: 'token'", + }); + } + + if (!TOKEN_SECRET) { + return res.status(500).json({ + error: "Server configuration error: TOKEN_SECRET is not defined", + }); + } + + try { + // Verify the token + const decoded = jwt.verify(token, TOKEN_SECRET) as DecodedJwtPayload; + + try { + const result = await getUserById(decoded.user_id); + + // Attach the user to the request + req.user = result; + // Call the next function + next(); + } catch (error: any) { + return res.status(error.status).json({ + error: error.message, + }); + } + } catch (err) { + return res.status(400).json({ + error: "The token is either invalid or has expired", + }); + } +} + +export { isAuthorized }; diff --git a/backend/src/controllers/userController.ts b/backend/src/controllers/userController.ts index 00091f9..8c4684f 100644 --- a/backend/src/controllers/userController.ts +++ b/backend/src/controllers/userController.ts @@ -290,7 +290,8 @@ async function loginUser(req: Request, res: Response): Promise { const volunteer = await getVolunteerByUserId(user.user_id); if (volunteer.active == 0 || volunteer.active == null) { return res.status(400).json({ - error: "Your account is not verified yet", + //verifyError: "Your account is not verified yet", + error: "verifyError", }); } diff --git a/backend/src/routes/authRouter.ts b/backend/src/routes/authRouter.ts new file mode 100644 index 0000000..c8b059f --- /dev/null +++ b/backend/src/routes/authRouter.ts @@ -0,0 +1,37 @@ +import { Router, Request, Response } from "express"; +import { + registerUser, + loginUser, + resetPassword, + verifyAndRedirect, + sendResetPasswordEmail, + updatePassword, +} from "../controllers/userController.js"; +import { isAuthorized } from "../config/isAuthorized.js"; +import { AuthenticatedUserRequest } from "../common/types.js"; + +export const authRouter = Router(); + +authRouter.post("/register", (req: Request, res: Response) => + registerUser(req, res) +); + +authRouter.post("/login", (req: Request, res: Response) => loginUser(req, res)); + +authRouter.post("/send-reset-password-email", (req: Request, res: Response) => + sendResetPasswordEmail(req, res) +); + +authRouter.get("/forgot-password/:id/:token", (req: Request, res: Response) => + verifyAndRedirect(req, res) +); + +authRouter.post("/reset-password/:id/:token", (req: Request, res: Response) => + resetPassword(req, res) +); + +authRouter.post( + "/update-password", + isAuthorized, + (req: AuthenticatedUserRequest, res: Response) => updatePassword(req, res) +); diff --git a/frontend/package.json b/frontend/package.json index fd3d7b5..62b047c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,4 +58,4 @@ "ajv": "^8.17.1", "@babel/plugin-proposal-private-property-in-object": "^7.21.11" } -} +} \ No newline at end of file diff --git a/frontend/src/App.js b/frontend/src/App.js index 0749f73..5d733cb 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -10,6 +10,7 @@ import Classes from "./pages/classes"; import VolunteerSchedule from "./pages/schedule"; import VolunteerProfile from "./pages/volunteerProfile"; import AdminVerify from "./pages/AdminVerify"; +import AccountNotVerified from './pages/account_not_verified'; function App() { const [isVolunteer, setIsVolunteer] = useState(false); @@ -35,7 +36,7 @@ function App() { }; checkAuth(); }, []); - + return (
@@ -44,6 +45,7 @@ function App() { } /> }/> }/> + } /> : } /> : } /> : }/> diff --git a/frontend/src/components/LoginForm/index.js b/frontend/src/components/LoginForm/index.js index 3ef1d5d..ecc090c 100644 --- a/frontend/src/components/LoginForm/index.js +++ b/frontend/src/components/LoginForm/index.js @@ -31,12 +31,21 @@ const LoginForm = () => { localStorage.setItem("neuronAuthToken", response.token); setTimeout(() => { window.location.href = "/"; - }, 1500); + }, 1500); setSubmitting(false); }) .catch((error) => { - notyf.error(error.response.data.error); - setSubmitting(false); + if (error.response.data.error == "verifyError") { + // Redirect to account not verified + window.location.href = "/auth/account-not-verified"; + setSubmitting(false); + } + else { + notyf.error(error.response.data.error); + setSubmitting(false); + } + + }); }}> {({ diff --git a/frontend/src/pages/account_not_verified/index.css b/frontend/src/pages/account_not_verified/index.css new file mode 100644 index 0000000..7322d16 --- /dev/null +++ b/frontend/src/pages/account_not_verified/index.css @@ -0,0 +1,37 @@ +@import '../../styles.css'; + +.account-not-verified-page { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; + padding: 20px; + max-width: 500px; + margin: 0 auto; + height: 100vh; +} + +.account-not-verified-page h2 { + font-size: var(--title-text, 24px); + font-weight: 500; + color: var(--primary-blue); + margin-bottom: 15px; +} + +.account-not-verified-button { + margin-top: 20px; + padding: 10px 20px; + background-color: var(--white); + color: var(--dark); + border: 1px solid var(--primary-blue); + border-radius: 5px; + cursor: pointer; + font-size: var(--font-primary, 16px); + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3); + width: auto; +} + +.account-not-verified-button:hover { + background-color: var(--secondary-blue); +} diff --git a/frontend/src/pages/account_not_verified/index.js b/frontend/src/pages/account_not_verified/index.js new file mode 100644 index 0000000..cebd328 --- /dev/null +++ b/frontend/src/pages/account_not_verified/index.js @@ -0,0 +1,30 @@ +import React, { useState } from 'react'; +import './index.css'; +import logoutIcon from "./logout.png"; + +function AccountNotVerified() { + //const [data, setData] = useState(null); + + const logOut = () => { + // Implement log-out logic here + console.log('Logged out'); + }; + + return ( +
+

Waiting for an admin to verify your account.

+
You can reach out to us at bwp@gmail.com.
+ + +
+ ); +} + +export default AccountNotVerified; \ No newline at end of file diff --git a/frontend/src/pages/account_not_verified/logout.png b/frontend/src/pages/account_not_verified/logout.png new file mode 100644 index 0000000..e8df8ff Binary files /dev/null and b/frontend/src/pages/account_not_verified/logout.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4ea2a21 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "neuron", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}