From 580bc7caf87202761b6d26f5458ff1e048805d17 Mon Sep 17 00:00:00 2001 From: basil-ahmed Date: Mon, 20 Nov 2023 18:28:39 -0500 Subject: [PATCH 1/7] POST test update --- .../test/studentIssueDetailsUpdate.test.js | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 back-end/test/studentIssueDetailsUpdate.test.js diff --git a/back-end/test/studentIssueDetailsUpdate.test.js b/back-end/test/studentIssueDetailsUpdate.test.js new file mode 100644 index 0000000..84b61f9 --- /dev/null +++ b/back-end/test/studentIssueDetailsUpdate.test.js @@ -0,0 +1,88 @@ +import chai, { expect } from 'chai'; +import chaiHttp from 'chai-http'; +import sinon from 'sinon'; +import axios from 'axios'; +import { promises as fs } from 'fs'; +import { studentIssueUpdateHandler } from '../src/controllers/studentIssueUpdateHandler.js'; +import { publicpath } from "../app.js"; + +let server = "http://localhost:5000/"; + +chai.use(chaiHttp); + +describe('Unit Tests for studentIssueUpdateHandler', () => { + let req, res, axiosGetStub, sendSpy, jsonSpy, statusSpy; + + beforeEach(() => { + req = { + params: { paramName: '123', studentNetID: 's123456' }, + body: { issueindex: '1', comments: 'Test comment', currentStatus: 'open' } + }; + res = { + json: sinon.spy(), + status: sinon.stub().returns({ send: sinon.spy() }) + }; + sendSpy = res.status().send; + jsonSpy = res.json; + + // Stub axios.get method + axiosGetStub = sinon.stub(axios, 'get'); + }); + + afterEach(() => { + // Restore the stub after each test + axiosGetStub.restore(); + }); + + it('should update the issue and send the correct response when the request is valid', async () => { + // Set up the stub to return a specific value + axiosGetStub.resolves({ data: [{ index: '1', comments: [] }] }); + + await studentIssueUpdateHandler(req, res); + expect(jsonSpy.calledOnce).to.be.false; + expect(sendSpy.calledOnce).to.be.true; + }); + + it('should send an error response when the request is invalid', async () => { + // Set up the stub to return a specific value + axiosGetStub.resolves({ data: [{ index: '2', comments: [] }] }); + + await studentIssueUpdateHandler(req, res); + expect(jsonSpy.calledOnce).to.be.false; + expect(sendSpy.calledOnce).to.be.true; + }); +}); + +chai.use(chaiHttp); + +describe('Integration Tests for studentIssueUpdateHandler', () => { + + // const studentNetID = "tm2005"; + // const paramName = "6"; + + // it('should update the issue and return the correct response when the request is valid', async () => { + + // // Execute the handler + // // await studentIssueUpdateHandler(req, res); + + // // axiosStub.resolves(mockResponse); + + // const res = await chai.request(server) + // .post(`api/actions/student/${studentNetID}/${paramName}`) + // .send({ + // "index": 6 + // }); + + // expect(res.status).to.equal(200); + // expect(res.body).to.have.property('message').that.equals('Issue updated successfully'); + // }); + + it('should return an error response when the request is invalid', async () => { + const res = await chai.request(server) + .post('api/actions/student/tm2005/9999') + .send({ issueindex: '2', studentNetID: 's123456', comments: 'Test comment', currentStatus: 'open' }); + + expect(res.status).to.equal(500); + // expect(res.body).to.have.property('message').that.equals('Invalid request'); + }); +}); \ No newline at end of file From 74155fd44c21b2c74c55637bd027009dc0854d4a Mon Sep 17 00:00:00 2001 From: hasiburratul Date: Tue, 21 Nov 2023 13:06:21 -0500 Subject: [PATCH 2/7] logout works --- back-end/app.js | 7 +++++++ front-end/src/App.js | 12 ++++++++++-- .../src/components/admin/AdminNavbar/AdminNavbar.js | 13 ++++++++++++- .../student/StudentNavbar/StudentNavbar.js | 13 ++++++++++++- .../src/layouts/AdminDashboard/AdminDashboard.js | 4 ++-- .../layouts/StudentDashboard/StudentDashboard.js | 4 ++-- 6 files changed, 45 insertions(+), 8 deletions(-) diff --git a/back-end/app.js b/back-end/app.js index f53a4bc..d0fee7c 100644 --- a/back-end/app.js +++ b/back-end/app.js @@ -65,6 +65,13 @@ app.use(passport.initialize()); // protected routes setup app.use(checkJWT); +// Logout endpoint +app.get('/api/logout', (req, res) => { + res.cookie('jwt', '', { maxAge: 0 }); // Clear the cookie + console.log('Logged out successfully'); + res.json({ message: 'Logged out successfully' }); +}); + // ROUTE HANDLERS // login diff --git a/front-end/src/App.js b/front-end/src/App.js index 32a06a9..9458ff0 100644 --- a/front-end/src/App.js +++ b/front-end/src/App.js @@ -1,6 +1,9 @@ // import { useState, useEffect } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; + /* eslint-disable */ +import { useEffect, useState } from "react"; +/* eslint-enable */ import "./App.css"; import StudentDashboard from "./layouts/StudentDashboard/StudentDashboard"; import IssueDetails from "./components/student/StudentIssueOverlay/DesktopIssueDetails"; @@ -9,15 +12,20 @@ import AdminDashboard from "./layouts/AdminDashboard/AdminDashboard"; import AdminIssueDetails from "./components/admin/AdminIssueDetails/AdminIssueDetails"; const App = (props) => { + /* eslint-disable */ + const [isAuthenticated, setIsAuthenticated] = useState(""); + /* eslint-enable */ + // const BASE_URL = process.env.REACT_APP_BACKEND_URL; + return (
} /> - } /> + } /> } /> - } /> + } /> } />
diff --git a/front-end/src/components/admin/AdminNavbar/AdminNavbar.js b/front-end/src/components/admin/AdminNavbar/AdminNavbar.js index 7636d25..7cfa483 100644 --- a/front-end/src/components/admin/AdminNavbar/AdminNavbar.js +++ b/front-end/src/components/admin/AdminNavbar/AdminNavbar.js @@ -1,12 +1,23 @@ import { useNavigate } from 'react-router-dom'; import logoutImage from "../../../assets/images/logout-icon.png"; import "./AdminNavbar.css"; +import axios from "axios"; -export default function AdminNavbar({ adminName, unresolvedIssues }) { +export default function AdminNavbar({ adminName, unresolvedIssues, setIsAuthenticated }) { // const [showNotification, setShowNotification] = useState(false); const navigate = useNavigate(); + const BASE_URL = process.env.REACT_APP_BACKEND_URL; const handleLogout = () => { + axios.get(`${BASE_URL}/api/logout`, { withCredentials: true }) + .then(() => { + setIsAuthenticated(false); // Update state to reflect that user is logged out + // Redirect to login page or perform other actions as needed + }) + .catch(error => { + console.error("Logout error:", error); + // Handle error + }); navigate('/'); }; diff --git a/front-end/src/components/student/StudentNavbar/StudentNavbar.js b/front-end/src/components/student/StudentNavbar/StudentNavbar.js index 0cf8c2a..8146b3b 100644 --- a/front-end/src/components/student/StudentNavbar/StudentNavbar.js +++ b/front-end/src/components/student/StudentNavbar/StudentNavbar.js @@ -1,14 +1,16 @@ import { useState } from "react"; import { useNavigate } from 'react-router-dom'; +import axios from "axios"; import logoutImage from "../../../assets/images/logout-icon.png"; import notificationIcon from "../../../assets/images/notification-icon.png"; import "./StudentNavbar.css"; -export default function StudentNavbar({ studentName }) { +export default function StudentNavbar({ studentName, setIsAuthenticated }) { const [notificationTimer, setNotificationTimer] = useState(null); const [showNotificationOverlay, setShowNotificationOverlay] = useState(false); // const [showNotification, setShowNotification] = useState(false); const navigate = useNavigate(); + const BASE_URL = process.env.REACT_APP_BACKEND_URL; // Handles notification click to display a notification overlay const handleNotificationClick = () => { @@ -37,6 +39,15 @@ export default function StudentNavbar({ studentName }) { }; const handleLogout = () => { + axios.get(`${BASE_URL}/api/logout`, { withCredentials: true }) + .then(() => { + setIsAuthenticated(false); // Update state to reflect that user is logged out + // Redirect to login page or perform other actions as needed + }) + .catch(error => { + console.error("Logout error:", error); + // Handle error + }); navigate('/'); }; diff --git a/front-end/src/layouts/AdminDashboard/AdminDashboard.js b/front-end/src/layouts/AdminDashboard/AdminDashboard.js index fd28d02..fcd01af 100644 --- a/front-end/src/layouts/AdminDashboard/AdminDashboard.js +++ b/front-end/src/layouts/AdminDashboard/AdminDashboard.js @@ -13,7 +13,7 @@ import SiteWideFooter from '../../components/general/SiteWideFooter/SiteWideFoot import axios from "axios"; export const currentSetDepartment = "IT"; -function AdminDashboard() { +function AdminDashboard({ setIsAuthenticated }) { const [searchText, setSearchText] = useState(''); const [issues, setIssues] = useState([]); const [activeOptionsOverlay, setActiveOptionsOverlay] = useState(null); @@ -138,7 +138,7 @@ function AdminDashboard() { return ( <> - +
{/*

Issue Board

*/} diff --git a/front-end/src/layouts/StudentDashboard/StudentDashboard.js b/front-end/src/layouts/StudentDashboard/StudentDashboard.js index 3d3ba52..8e91679 100644 --- a/front-end/src/layouts/StudentDashboard/StudentDashboard.js +++ b/front-end/src/layouts/StudentDashboard/StudentDashboard.js @@ -7,7 +7,7 @@ import SiteWideFooter from "../../components/general/SiteWideFooter/SiteWideFoot import { CreateRequest } from "../../components/student/CreateRequest/CreateRequest.js"; import axios from "axios"; -const StudentDashboard = () => { +const StudentDashboard = ({ setIsAuthenticated }) => { // State initialization for holding requests and their display variant const [allRequests, setAllRequests] = useState([]); const [displayedRequests, setDisplayedRequests] = useState([]); @@ -517,7 +517,7 @@ const StudentDashboard = () => {
- +

Your Requests

From fbbfc0f6c4fb1cd9219b49fe5dc255015abc8ded Mon Sep 17 00:00:00 2001 From: hasiburratul Date: Tue, 21 Nov 2023 13:09:19 -0500 Subject: [PATCH 3/7] added logout endpoint --- back-end/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back-end/app.js b/back-end/app.js index d0fee7c..f637fe3 100644 --- a/back-end/app.js +++ b/back-end/app.js @@ -65,7 +65,7 @@ app.use(passport.initialize()); // protected routes setup app.use(checkJWT); -// Logout endpoint +// Logout endpoint to remove token from cookie app.get('/api/logout', (req, res) => { res.cookie('jwt', '', { maxAge: 0 }); // Clear the cookie console.log('Logged out successfully'); From eaa27eb808f4c25e74607db9f15bf1660702f0b3 Mon Sep 17 00:00:00 2001 From: hasiburratul Date: Tue, 21 Nov 2023 15:43:49 -0500 Subject: [PATCH 4/7] updated authentication and logout --- back-end/app.js | 12 ++- back-end/src/middlewares/checkJWT.js | 19 +++-- front-end/src/App.js | 63 ++++++++-------- front-end/src/AuthContext.js | 34 +++++++++ .../admin/AdminNavbar/AdminNavbar.js | 11 +-- .../student/StudentNavbar/StudentNavbar.js | 13 ++-- .../layouts/AdminDashboard/AdminDashboard.js | 2 +- front-end/src/layouts/LoginPage/LoginPage.js | 74 ++++++++----------- .../StudentDashboard/StudentDashboard.js | 2 +- 9 files changed, 136 insertions(+), 94 deletions(-) create mode 100644 front-end/src/AuthContext.js diff --git a/back-end/app.js b/back-end/app.js index f637fe3..30dd689 100644 --- a/back-end/app.js +++ b/back-end/app.js @@ -63,7 +63,7 @@ app.use(cookieParser()); app.use(passport.initialize()); // protected routes setup -app.use(checkJWT); +// app.use(checkJWT); // Logout endpoint to remove token from cookie app.get('/api/logout', (req, res) => { @@ -72,6 +72,16 @@ app.get('/api/logout', (req, res) => { res.json({ message: 'Logged out successfully' }); }); +app.get("/api/check-auth", checkJWT, (req, res) => { + if (req.user) { + console.log("User authenticated!!!!!!!"); + res.status(200).json({ authenticated: true, user: req.user }); + } else { + // User is not authenticated + res.status(401).json({ authenticated: false, message: "User not authenticated" }); + } +}); + // ROUTE HANDLERS // login diff --git a/back-end/src/middlewares/checkJWT.js b/back-end/src/middlewares/checkJWT.js index 9ca0188..80ac962 100644 --- a/back-end/src/middlewares/checkJWT.js +++ b/back-end/src/middlewares/checkJWT.js @@ -2,20 +2,23 @@ import passport from "../../config/passportConfig.js"; export default function checkJWT(req, res, next) { passport.authenticate("jwt", { session: false }, (err, user, info) => { - if (err || !user) { + if (err) { + console.error("Error during authentication:", err); + return res.status(500).json({ message: "Internal server error" }); + } + + if (!user) { if (req.path !== "/") { - console.log("User not authenticated. Redirecting to login page."); - next(); - // return res.redirect("/"); - // redirecting won't work as the routing is managed by react in the frontend, not express - // we would need to send some json data here rather that indicates a redirect in the frontend + console.log("User not authenticated. Sending response."); + // Send a 401 Unauthorized response with a message + return res.status(401).json({ authenticated: false, message: "User not authenticated" }); } else { return next(); } } else { - req.user = user; // Forward user information to the next middleware + req.user = user; console.log("User authenticated."); next(); } })(req, res, next); -} +} \ No newline at end of file diff --git a/front-end/src/App.js b/front-end/src/App.js index 9458ff0..b491161 100644 --- a/front-end/src/App.js +++ b/front-end/src/App.js @@ -1,36 +1,41 @@ -// import { useState, useEffect } from "react"; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; +import { AuthProvider, AuthContext } from './AuthContext'; // Import AuthProvider and AuthContext +import { useContext } from 'react'; -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; - /* eslint-disable */ -import { useEffect, useState } from "react"; -/* eslint-enable */ -import "./App.css"; -import StudentDashboard from "./layouts/StudentDashboard/StudentDashboard"; -import IssueDetails from "./components/student/StudentIssueOverlay/DesktopIssueDetails"; -import LoginPage from "./layouts/LoginPage/LoginPage.js"; -import AdminDashboard from "./layouts/AdminDashboard/AdminDashboard"; -import AdminIssueDetails from "./components/admin/AdminIssueDetails/AdminIssueDetails"; +import StudentDashboard from './layouts/StudentDashboard/StudentDashboard'; +import IssueDetails from './components/student/StudentIssueOverlay/DesktopIssueDetails'; +import LoginPage from './layouts/LoginPage/LoginPage'; +import AdminDashboard from './layouts/AdminDashboard/AdminDashboard'; +import AdminIssueDetails from './components/admin/AdminIssueDetails/AdminIssueDetails'; -const App = (props) => { - /* eslint-disable */ - const [isAuthenticated, setIsAuthenticated] = useState(""); - /* eslint-enable */ - // const BASE_URL = process.env.REACT_APP_BACKEND_URL; +// ProtectedRoute component +const ProtectedRoute = ({ component: Component }) => { + const { isAuthenticated } = useContext(AuthContext); // Use AuthContext to get isAuthenticated return ( -
- -
- - } /> - } /> - } /> - } /> - } /> - -
-
-
+ isAuthenticated + ? + : + ); +}; + +const App = () => { + return ( + {/* Wrap the application in AuthProvider */} +
+ +
+ + } /> + } /> + } /> + } /> + } /> + +
+
+
+
); }; diff --git a/front-end/src/AuthContext.js b/front-end/src/AuthContext.js new file mode 100644 index 0000000..342e855 --- /dev/null +++ b/front-end/src/AuthContext.js @@ -0,0 +1,34 @@ +import { createContext, useState, useEffect } from 'react'; +import axios from 'axios'; + +export const AuthContext = createContext(); +export const AuthProvider = ({ children }) => { + const [isAuthenticated, setIsAuthenticated] = useState( + localStorage.getItem('isAuthenticated') === 'true' + ); + + const BASE_URL = process.env.REACT_APP_BACKEND_URL; + // Function to check authentication + const checkAuthentication = () => { + axios.get(`${BASE_URL}/api/check-auth`, { withCredentials: true }) + .then((response) => { + setIsAuthenticated(response.data.authenticated); + localStorage.setItem('isAuthenticated', 'true'); + }) + .catch((error) => { + console.error("Authentication error:", error); + setIsAuthenticated(false); + localStorage.setItem('isAuthenticated', 'false'); + }); + }; + + useEffect(() => { + checkAuthentication(); + }, []); + + return ( + + {children} + + ); +}; diff --git a/front-end/src/components/admin/AdminNavbar/AdminNavbar.js b/front-end/src/components/admin/AdminNavbar/AdminNavbar.js index 7cfa483..a422003 100644 --- a/front-end/src/components/admin/AdminNavbar/AdminNavbar.js +++ b/front-end/src/components/admin/AdminNavbar/AdminNavbar.js @@ -1,24 +1,26 @@ +import { useContext } from 'react'; // Import useContext import { useNavigate } from 'react-router-dom'; +import { AuthContext } from '../../../AuthContext'; // Import AuthContext import logoutImage from "../../../assets/images/logout-icon.png"; import "./AdminNavbar.css"; import axios from "axios"; -export default function AdminNavbar({ adminName, unresolvedIssues, setIsAuthenticated }) { - // const [showNotification, setShowNotification] = useState(false); +export default function AdminNavbar({ adminName, unresolvedIssues }) { const navigate = useNavigate(); + const { setIsAuthenticated } = useContext(AuthContext); // Use AuthContext const BASE_URL = process.env.REACT_APP_BACKEND_URL; const handleLogout = () => { axios.get(`${BASE_URL}/api/logout`, { withCredentials: true }) .then(() => { setIsAuthenticated(false); // Update state to reflect that user is logged out - // Redirect to login page or perform other actions as needed + localStorage.removeItem('isAuthenticated'); // Clear the authentication flag from browser storage + navigate('/'); // Redirect to login page }) .catch(error => { console.error("Logout error:", error); // Handle error }); - navigate('/'); }; return ( @@ -36,6 +38,5 @@ export default function AdminNavbar({ adminName, unresolvedIssues, setIsAuthenti />
- ); } diff --git a/front-end/src/components/student/StudentNavbar/StudentNavbar.js b/front-end/src/components/student/StudentNavbar/StudentNavbar.js index 8146b3b..ae72156 100644 --- a/front-end/src/components/student/StudentNavbar/StudentNavbar.js +++ b/front-end/src/components/student/StudentNavbar/StudentNavbar.js @@ -1,14 +1,16 @@ -import { useState } from "react"; +import { useContext, useState } from "react"; import { useNavigate } from 'react-router-dom'; import axios from "axios"; import logoutImage from "../../../assets/images/logout-icon.png"; import notificationIcon from "../../../assets/images/notification-icon.png"; +import { AuthContext } from "../../../AuthContext"; import "./StudentNavbar.css"; -export default function StudentNavbar({ studentName, setIsAuthenticated }) { +export default function StudentNavbar({ studentName }) { const [notificationTimer, setNotificationTimer] = useState(null); const [showNotificationOverlay, setShowNotificationOverlay] = useState(false); // const [showNotification, setShowNotification] = useState(false); + const { setIsAuthenticated } = useContext(AuthContext); // Use AuthContext const navigate = useNavigate(); const BASE_URL = process.env.REACT_APP_BACKEND_URL; @@ -41,14 +43,13 @@ export default function StudentNavbar({ studentName, setIsAuthenticated }) { const handleLogout = () => { axios.get(`${BASE_URL}/api/logout`, { withCredentials: true }) .then(() => { - setIsAuthenticated(false); // Update state to reflect that user is logged out - // Redirect to login page or perform other actions as needed + setIsAuthenticated(false); + localStorage.removeItem('isAuthenticated'); // Clear the authentication flag from browser storage + navigate('/'); }) .catch(error => { console.error("Logout error:", error); - // Handle error }); - navigate('/'); }; return ( diff --git a/front-end/src/layouts/AdminDashboard/AdminDashboard.js b/front-end/src/layouts/AdminDashboard/AdminDashboard.js index fcd01af..9e67596 100644 --- a/front-end/src/layouts/AdminDashboard/AdminDashboard.js +++ b/front-end/src/layouts/AdminDashboard/AdminDashboard.js @@ -138,7 +138,7 @@ function AdminDashboard({ setIsAuthenticated }) { return ( <> - +
{/*

Issue Board

*/} diff --git a/front-end/src/layouts/LoginPage/LoginPage.js b/front-end/src/layouts/LoginPage/LoginPage.js index 0497daf..1078ffe 100644 --- a/front-end/src/layouts/LoginPage/LoginPage.js +++ b/front-end/src/layouts/LoginPage/LoginPage.js @@ -1,85 +1,73 @@ -import { useState } from "react"; -import axios from "axios"; -import { useNavigate } from "react-router-dom"; -import "./LoginPage.css"; -import logo from "../../assets/images/nyu-logo.png"; -import LoginPageNavbar from "../../components/general/LoginPageNavbar/LoginPageNavbar"; +import { useState, useContext } from 'react'; +import axios from 'axios'; +import { useNavigate } from 'react-router-dom'; +import { AuthContext } from '../../AuthContext'; // Import AuthContext +import './LoginPage.css'; +import logo from '../../assets/images/nyu-logo.png'; +import LoginPageNavbar from '../../components/general/LoginPageNavbar/LoginPageNavbar'; const LoginPage = () => { - // state variable to keep track of the user type - const [userType, setUserType] = useState("student"); - const BASE_URL = process.env.REACT_APP_BACKEND_URL; // base url for the backend - - // useNavigate hook later to be used to redirect to the student dashboard + const [userType, setUserType] = useState('student'); + const { setIsAuthenticated } = useContext(AuthContext); // Use useContext to access AuthContext const navigate = useNavigate(); + const BASE_URL = process.env.REACT_APP_BACKEND_URL; - // handleFormSubmit function to handle form submission const handleFormSubmit = async (event) => { - // preventing reload of the page event.preventDefault(); - - // creating a new FormData object(key-value pairs representing form fields and values const formData = new FormData(event.target); const urlEncodedData = new URLSearchParams(formData); let auth = false; try { - // making a POST request to the backend const response = await axios.post( `${BASE_URL}/api/login/${userType}`, urlEncodedData, - { - withCredentials: true - } + { withCredentials: true } ); - // The response data from the server auth = response.data.authenticated; + + if (auth) { + setIsAuthenticated(true); // Update the authentication state + localStorage.setItem('isAuthenticated', 'true'); + if (userType === 'student') { + navigate('/student/dashboard'); + } else if (userType === 'admin') { + navigate('/admin/dashboard'); + } + } } catch (error) { - console.error("Error during form submission:", error); - // In case of error, if you need to access the response provided by the server (if any) + console.error('Error during form submission:', error); if (error.response) { const errorData = error.response.data; - console.error("Error data from server:", errorData); + console.error('Error data from server:', errorData); } - } - - if (userType === "student" && auth) { - // Redirect to the student dashboard - navigate("/student/dashboard"); - } - - if (userType === "admin" && auth) { - navigate("/admin/dashboard"); + setIsAuthenticated(false); + localStorage.setItem('isAuthenticated', 'false'); } }; return (
- {/*

NYU Abu Dhabi Issue Resolution Portal

*/} -
Logo

Log In to Your NYU Account

- - {/* Form to take the username and password as input - calls the handleFormSubmit function on form submission */}
+ {/* Form fields */}