token refresher

This commit is contained in:
amNobodyyy
2025-01-29 00:21:08 +05:30
parent 16db1725f3
commit 597e47c2d0
10 changed files with 265 additions and 100 deletions

View File

@@ -13,9 +13,12 @@ import CourseTable from "./Pages/CourseTable";
import ConsolidatedTable from "./Pages/ConsolidatedTable";
import CourseConsolidated from "./Pages/courseConsolidated";
import PrivateRoute from "./components/PrivateRoute";
import TokenRefresher from "./components/TokenRefresher";
function App() {
return (
<>
<TokenRefresher />
<Routes>
<Route path="/" element={<AuthPage />}></Route>
<Route path="/course-form/:id" element={<PrivateRoute element={<CourseForm />} />} />
@@ -28,6 +31,7 @@ function App() {
<Route path="/consolidated" element={<PrivateRoute element={<ConsolidatedTable />} />} />
<Route path="/courseConsolidated" element={<PrivateRoute element={<CourseConsolidated />} />} />
</Routes>
</>
);
}

View File

@@ -6,39 +6,39 @@ import Navbar from "./Navbar";
const styles = {
header: {
background: '#003366',
color: 'white',
padding: '20px 0',
textAlign: 'center',
fontSize: '24px',
marginBottom: '0',
background: "#003366",
color: "white",
padding: "20px 0",
textAlign: "center",
fontSize: "24px",
marginBottom: "0",
},
buttonRow: {
display: 'flex',
justifyContent: 'space-around',
alignItems: 'center',
padding: '10px 0',
margin: '0',
backgroundColor: '#003366',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
display: "flex",
justifyContent: "space-around",
alignItems: "center",
padding: "10px 0",
margin: "0",
backgroundColor: "#003366",
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
},
button: {
padding: '10px 20px',
borderRadius: '5px',
color: 'white',
border: 'none',
cursor: 'pointer',
fontSize: '16px',
flex: '1',
padding: "10px 20px",
borderRadius: "5px",
color: "white",
border: "none",
cursor: "pointer",
fontSize: "16px",
flex: "1",
},
bulkDownloadButton: {
backgroundColor: '#3fb5a3',
backgroundColor: "#3fb5a3",
},
downloadButton: {
backgroundColor: '#28a745',
backgroundColor: "#28a745",
},
emailButton: {
backgroundColor: '#ff6f61',
backgroundColor: "#ff6f61",
},
tableContainer: {
maxHeight: "70vh",
@@ -77,11 +77,9 @@ const styles = {
},
main: {
width: "100%",
}
},
};
const ConsolidatedTable = () => {
const [searchQuery, setSearchQuery] = useState(""); // State for search input
const [data, setData] = useState([]);
@@ -94,7 +92,8 @@ const ConsolidatedTable = () => {
const fetchData = async () => {
try {
const response = await axios.get(
"http://localhost:8080/api/table/consolidated-table"
"http://localhost:8080/api/table/consolidated-table",
{ withCredentials: true }
);
setData(response.data);
setLoading(false);
@@ -113,8 +112,7 @@ const ConsolidatedTable = () => {
// Extract unique faculty names and filter based on the search query
const filteredTeachers = [...new Set(data.map((row) => row.Name))].filter(
(teacher) =>
teacher.toLowerCase().includes(searchQuery.toLowerCase()) // Filter by search query
(teacher) => teacher.toLowerCase().includes(searchQuery.toLowerCase()) // Filter by search query
);
// Pagination logic applied to filtered teachers
@@ -151,7 +149,8 @@ const ConsolidatedTable = () => {
const facultyId = teacherData[0].facultyId;
try {
const response = await axios.get(
`http://localhost:8080/api/faculty/${facultyId}`
`http://localhost:8080/api/faculty/${facultyId}`,
{ withCredentials: true }
);
const facultyEmail = response.data.email;
const workbook = createExcelBook(teacherData, teacher);
@@ -259,9 +258,7 @@ const ConsolidatedTable = () => {
)
}
>
<h2
style={{ color: "black", margin: 0, fontSize: "1.5rem" }}
>
<h2 style={{ color: "black", margin: 0, fontSize: "1.5rem" }}>
{teacher}'s Table
</h2>
<div>

View File

@@ -1,4 +1,4 @@
import React, { useEffect , useState } from "react";
import React, { useEffect, useState } from "react";
import { Container, Col, Row } from "react-bootstrap";
import { FcGoogle } from "react-icons/fc";
import axios from "axios";
@@ -18,7 +18,6 @@ function AuthPage() {
const notifyError = (message) => {
toast.error(message);
};
function ToggleSign(event) {
event.preventDefault();
@@ -40,38 +39,39 @@ function AuthPage() {
async function handleSubmit(event) {
event.preventDefault();
if (!formData.username.trim() && signin) {
notifyError("Username cannot be empty");
return;
}
if (!formData.username.trim() && signin) {
notifyError("Username cannot be empty");
return;
}
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(formData.email)) {
notifyError("Enter a valid email address");
return;
}
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(formData.email)) {
notifyError("Enter a valid email address");
return;
}
// Check password length
if (formData.password.length < 8) {
notifyError("Password must be at least 8 characters long");
return;
}
// Check password length
if (formData.password.length < 8) {
notifyError("Password must be at least 8 characters long");
return;
}
try {
const response = await axios.post(
`http://localhost:8080/api/${
!signin ? "login" : "register"
}`,
formData
`http://localhost:8080/api/${!signin ? "login" : "register"}`,
formData,
{ withCredentials: true }
);
const { user } = response.data;
delete user.password;
const gravatarUrl = `https://www.gravatar.com/avatar/${md5(
user.email
)}?d=identicon`;
user.profilePicture = gravatarUrl;
localStorage.setItem("user", JSON.stringify(user));
window.location.href = "/Welcom";
if (response.status === 200) {
const { user } = response.data;
delete user.password;
const gravatarUrl = `https://www.gravatar.com/avatar/${md5(
user.email
)}?d=identicon`;
user.profilePicture = gravatarUrl;
window.location.href = "/Welcome";
}
} catch (error) {
console.error("Authentication error:", error);
if (
@@ -88,11 +88,9 @@ function AuthPage() {
const handleGoogleLogin = (event) => {
event.preventDefault();
window.location.href =
"http://localhost:8080/auth/google";
window.location.href = "http://localhost:8080/auth/google";
};
return (
<>
<ToastContainer />

View File

@@ -1,9 +1,26 @@
import React from "react";
import { FaUserCircle } from "react-icons/fa";
import { NavLink } from "react-router-dom"; // Import NavLink for navigation
import { NavLink, useNavigate } from "react-router-dom"; // Import NavLink for navigation
import "./Navbar.css"; // Navbar-specific styles
import axios from "axios";
const Navbar = () => {
const navigate = useNavigate();
// Handle logout functionality
const handleLogout = async () => {
try {
// Call the logout API
await axios.get("http://localhost:8080/auth/logout", { withCredentials: true });
// Redirect to the login page after successful logout
navigate("/");
} catch (error) {
console.error("Error during logout:", error);
alert("Failed to log out. Please try again.");
}
};
return (
<header className="navbar">
<div className="navbar-container">
@@ -21,6 +38,11 @@ const Navbar = () => {
Course Consolidated
</NavLink>
</div>
<div>
<button className="logout-button" onClick={handleLogout}>
Logout
</button>
</div>
{/* User icon at the right */}
<NavLink to="/accounts" className="user-icon-link">

View File

@@ -17,7 +17,8 @@ const CourseConsolidated = () => {
const fetchData = async () => {
try {
const response = await axios.get(
"http://localhost:8080/api/table/course-consolidated"
"http://localhost:8080/api/table/course-consolidated",
{ withCredentials: true }
);
setData(response.data);
setLoading(false);

View File

@@ -1,12 +1,36 @@
import React from 'react';
import { Navigate } from 'react-router-dom'; // Use Navigate for redirect
import Cookies from "js-cookie";
import React, { useEffect, useState } from "react";
import { Navigate } from "react-router-dom";
import axios from "axios";
const PrivateRoute = ({ element: Element, ...rest }) => {
const token = Cookies.get("token");
const [isAuthenticated, setIsAuthenticated] = useState(null); // Track authentication status
// If token exists, render the element. Otherwise, redirect to the login page
return token ? Element : <Navigate to="/" />;
useEffect(() => {
// Call the server to validate the token
const checkAuth = async () => {
try {
const response = await axios.get("http://localhost:8080/api/auth-check", {
withCredentials: true, // Include cookies in the request
});
if (response.status === 200) {
setIsAuthenticated(true); // User is authenticated
}
} catch (error) {
setIsAuthenticated(false); // User is not authenticated
}
};
checkAuth();
}, []);
// If still checking authentication, render nothing or a loading spinner
if (isAuthenticated === null) {
return <div>Loading...</div>;
}
// If authenticated, render the component. Otherwise, redirect to the login page
return isAuthenticated ? Element : <Navigate to="/" />;
};
export default PrivateRoute;

View File

@@ -0,0 +1,37 @@
import { useEffect } from "react";
import axios from "axios";
import Cookies from "js-cookie";
import { useNavigate } from "react-router-dom";
const TokenRefresher = () => {
const navigate = useNavigate();
useEffect(() => {
// Function to refresh the token
const refreshToken = async () => {
try {
const response = await axios.post("http://localhost:8080/api/refresh", {}, {
withCredentials: true, // Make sure cookies are sent
});
} catch (error) {
console.error("Failed to refresh token", error);
// If refreshing the token fails (e.g., due to expiration), log out the user
Cookies.remove("token"); // Optionally, clear any existing cookies
navigate("/"); // Redirect to login page
}
};
// Set the interval to call refresh every 55 minutes (3300000 ms)
const refreshInterval = setInterval(() => {
refreshToken();
}, 3300000);
// Clean up the interval on component unmount
return () => clearInterval(refreshInterval);
}, [navigate]); // Empty dependency array to run once when component mounts
return null; // This component doesn't render anything
};
export default TokenRefresher;