Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HER-32 remove jwt at unauthorized response #32

Merged
merged 26 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9dceab9
Trying to fix retained token bug
Dec 10, 2024
7ca8f42
Trying to fix retained token bug
Dec 10, 2024
f9dd3cf
Handle jwt auth and state management with AuthContext
Dec 19, 2024
d264f79
Add AuthContext
Dec 19, 2024
a083693
Merge branch 'HER-32-remove-jwt-at-unauthorized-response' of https://…
Dec 19, 2024
57a911b
Corrected bug from rebase and conflict merge
Dec 19, 2024
cc13f7e
Remove debug code
Dec 19, 2024
1831ee7
Merge branch 'main' into HER-32-remove-jwt-at-unauthorized-response
mhope21 Dec 19, 2024
35e4b4b
Trying to fix retained token bug
Dec 10, 2024
36eb2d3
Handle jwt auth and state management with AuthContext
Dec 19, 2024
4b85182
Add AuthContext
Dec 19, 2024
e6c2713
Trying to fix retained token bug
Dec 10, 2024
611e20d
Corrected bug from rebase and conflict merge
Dec 19, 2024
0e5f8bd
Remove debug code
Dec 19, 2024
57aff13
Merge branch 'HER-32-remove-jwt-at-unauthorized-response' of https://…
Dec 19, 2024
45a873f
Trying to fix retained token bug
Dec 10, 2024
0d38ad9
Handle jwt auth and state management with AuthContext
Dec 19, 2024
f1d0ce8
Add AuthContext
Dec 19, 2024
44ca80b
Trying to fix retained token bug
Dec 10, 2024
50db65e
Corrected bug from rebase and conflict merge
Dec 19, 2024
da2572c
Remove debug code
Dec 19, 2024
fc8be45
Trying to fix retained token bug
Dec 10, 2024
137ea8b
Handle jwt auth and state management with AuthContext
Dec 19, 2024
7946095
Trying to fix retained token bug
Dec 10, 2024
46825e1
Modified auth context error handling for better ux
Dec 19, 2024
db64024
Merge branch 'HER-32-remove-jwt-at-unauthorized-response' of https://…
Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/current_user_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class CurrentUserController < ApplicationController
before_action :authenticate_user!

# If a user is authenticated, they are set as the current user.
def index
def show
render json: UserSerializer.new(current_user).serializable_hash[:data][:attributes], status: :ok
end
end
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Rails.application.routes.draw do
get "/current_user", to: "current_user#index"
get "/current_user", to: "current_user#show"
devise_for :users, path: "", path_names: {
sign_in: "login",
sign_out: "logout",
Expand Down
87 changes: 12 additions & 75 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import './App.css';
import {BrowserRouter as Router, Routes, Route, Navigate} from "react-router-dom";
import { useContext } from 'react';
import { Routes, Route, Navigate } from "react-router-dom";
import PageWrapper from './components/PageWrapper';
import Home from './components/pages/Home';
import About from './components/pages/About';
Expand All @@ -9,11 +10,9 @@ import RequestKit from './components/pages/RequestKit'
import Registration from './components/auth/Registration';
import Login from './components/auth/Login';
import ScrollToHash from './components/ScrollToHash';
import { useState, useEffect } from 'react';
import Confirmation from './components/pages/Confirmation';
import Donation from './components/pages/Donation';
import RequestSpeaker from './components/pages/RequestSpeaker';
import { jwtDecode } from 'jwt-decode';
import AdminDashboard from './components/pages/AdminDashboard';
import NewForms from './components/NewForms';
import NewKit from './components/NewKit';
Expand All @@ -22,95 +21,34 @@ import AddNew from './components/pages/AddNew';
import NewKitItem from './components/NewKitItem';
import AddItemToKit from './components/AddItemToKit';
import UserProfile from './components/pages/UserProfile';
import { AuthContext } from './components/auth/AuthContext';





function App() {
const [loggedIn, setLoggedIn] = useState();
const [user, setUser] = useState(null);
const [tokenExpiration, setTokenExpiration] = useState(null);
const { loggedIn, user } = useContext(AuthContext);

// Method handles login state and token, checking for existence or expiration
useEffect(() => {
const token = localStorage.getItem('jwt');

if (token) {
try {
const decoded = jwtDecode(token);
const now = Date.now() / 1000; // Current time in seconds

if (decoded.exp > now) {
setLoggedIn(true); // Token is valid
setUser(decoded.user ? decoded.user : null); // Set user data

// Calculate remaining time until token expiration
const timeUntilExpiration = (decoded.exp - now) * 1000;

// Notify 5 minutes before expiration
if (timeUntilExpiration < 300000) {
alert("Your session is about to expire. Please save your work.");
}

// Set token expiration time in state
setTokenExpiration(timeUntilExpiration);

} else {
// Token is expired, clear it
console.log("Token has expired, clearing JWT.");
localStorage.removeItem('jwt');
setLoggedIn(false);
setUser(null);
alert("Your session has expired. Please log in again.");
}
} catch (error) {
console.error('Token decoding failed:', error);
localStorage.removeItem('jwt');
setLoggedIn(false);
setUser(null);
}
} else {
// No token, set to logged out state
setLoggedIn(false);
setUser(null);
}
}, []);

// Logs out the user when the token expires
useEffect(() => {
if (tokenExpiration) {
const timer = setTimeout(() => {
console.log("Token has expired, logging out.");
localStorage.removeItem('jwt');
setLoggedIn(false);
setUser(null);
alert("Your session has expired. Please log in again.");
}, tokenExpiration);

return () => clearTimeout(timer);
}
}, [tokenExpiration]);

return (
// Sets routes for app navigation and passes props to the necessary components
<>
<div className="App">
<Router>
<PageWrapper loggedIn={loggedIn} setLoggedIn={setLoggedIn} setUser={setUser} user={user}>
<PageWrapper>
<ScrollToHash/>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact user={user} />} />
<Route path="/kits" element={<Kits user={user} />} />
<Route path="/orders" element={<RequestKit user={user} setUser={setUser} />} />
<Route path="/contact" element={<Contact />} />
<Route path="/kits" element={<Kits />} />
<Route path="/orders" element={<RequestKit />} />
<Route path="/registration" element={<Registration />} />
<Route path="/login" element={<Login setLoggedIn={setLoggedIn} />}/>
<Route path="/confirmation" element={<Confirmation user={user}/> } />
<Route path="/donation" element={<Donation user={user}/>} />
<Route path="/login" element={<Login />}/>
<Route path="/confirmation" element={<Confirmation /> } />
<Route path="/donation" element={<Donation />} />
<Route path="/speaker" element={<RequestSpeaker/>}/>
<Route path="/admin" element={user && user.role === 'admin' ? <AdminDashboard user={user} /> : <Navigate to="/" />} />
<Route path="/admin" element={user && user.role === 'admin' ? <AdminDashboard /> : <Navigate to="/" />} />
<Route path="/new_forms" element={<NewForms/>} >
<Route path="add_user" element={<AddNew header="Add New User"><NewUser /></AddNew>} />
<Route path="add_kit" element={<AddNew header="Add New Kit"><NewKit /></AddNew>} />
Expand All @@ -120,7 +58,6 @@ function App() {
<Route path="/profile/:id" element={<UserProfile/>}/>
</Routes>
</PageWrapper>
</Router>
</div>
</>
);
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/components/DashCardSet.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import DashboardCard from './DashboardCard';
import { API_URL } from '../constants';
import { AuthContext } from './auth/AuthContext';

const DashCardSet = () => {
const { logout } = useContext(AuthContext);
const [userCount, setUserCount] = useState(0);
const [orderCount, setOrderCount] = useState(0);
const [totalDonations, setTotalDonations] = useState(0);
Expand All @@ -26,14 +28,17 @@ const DashCardSet = () => {
setTotalDonations(parseFloat(data.total_donations));
} else {
console.error('Failed to fetch data:', response.statusText);
logout();
}
} catch (error) {
console.error('Error fetching dashboard data:', error);
alert("An error occurred.")
logout();
}
};

fetchDashboardData();
}, []);
}, [dashUrl, logout]);

return (
// Displays data using the dashboard card component
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/DashTable.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { AuthContext } from './auth/AuthContext';

// Passed in api endpoint, headers for tables, and the event handler for showing the Edit Modal from Admin Dashboard component
const DashTable = ({ apiEndpoint, headers, handleShow }) => {
const { logout } = useContext(AuthContext);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Expand All @@ -28,6 +30,7 @@ const DashTable = ({ apiEndpoint, headers, handleShow }) => {
} catch (err) {
setError(err.message);
console.error("Error fetching data:", err);
alert("A network error occurred.")
} finally {
setLoading(false);
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/EditModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ if (recordType === 'user') {
console.log(`${recordType} updated successfully!`);
alert(`${recordType} updated successfully!`);
handleClose(); // Close the modal after success
} else {
alert("An error occurred with the update.")
}
};

Expand Down
13 changes: 8 additions & 5 deletions frontend/src/components/Navigation.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React from "react"
import React, { useContext } from "react"
import { Link, useNavigate } from "react-router-dom";
import CurrentUser from "./auth/CurrentUser";
import { AuthContext } from "./auth/AuthContext";
import Logout from "./auth/Logout";
import { AuthContext } from "./auth/AuthContext";

// Passed in logged in and user state
function Navigation({ loggedIn, setLoggedIn, setUser, user }) {
function Navigation() {
const { loggedIn, user } = useContext(AuthContext);
const navigate = useNavigate();
const handleDonateClick = (e) => {
e.preventDefault();
Expand All @@ -13,7 +16,7 @@ function Navigation({ loggedIn, setLoggedIn, setUser, user }) {
if (!user) {
// If user not logged in, navigate to login page
alert("You must be logged in to make a donation. Please log in or register if you haven't already.");
navigate("/login")
navigate("/login");
} else {
// If user is logged in, navigate to the donation page
navigate("/donation");
Expand All @@ -32,7 +35,7 @@ function Navigation({ loggedIn, setLoggedIn, setUser, user }) {
<ul className="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
{loggedIn && (
<>
<li className="nav-item"><span className="nav-link"><CurrentUser setLoggedIn={setLoggedIn} setUser={setUser} user={user} /></span></li>
<li className="nav-item"><span className="nav-link"><CurrentUser /></span></li>

</>
)}
Expand All @@ -48,7 +51,7 @@ function Navigation({ loggedIn, setLoggedIn, setUser, user }) {

{loggedIn ? (
<>
<li><Logout setLoggedIn={setLoggedIn} setUser={setUser} /></li>
<li><Logout /></li>
</>
) : (
<>
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/components/NewKit.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { API_URL } from '../constants';
import { AuthContext } from './auth/AuthContext';

// Component for creating a new kit
const NewKit = () => {
const { logout } = useContext(AuthContext);
const [image, setImage] = useState('');
const [name, setName] = useState('');
const [description, setDescription] = useState('');
Expand Down Expand Up @@ -50,12 +52,6 @@ const NewKit = () => {

console.log("New Kit added successfully!");
alert("New Kit added successfully!");

// Clear input fields
setName("");
setDescription("");
setGradeLevel("");
setImage('');
// Redirect to admin page
navigate("/admin");
} else {
Expand All @@ -68,6 +64,7 @@ const NewKit = () => {
} catch (error) {

setMessages("An error occurred: " + error.message);
logout();
console.log(error.message);
}
};
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/NewKitItem.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { API_URL } from '../constants';
import { AuthContext } from './auth/AuthContext';

const NewKitItem = () => {

const { logout } = useContext(AuthContext);
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [image, setImage] = useState(null);
Expand Down Expand Up @@ -60,6 +61,7 @@ const NewKitItem = () => {

setMessages("An error occurred: " + error.message);
console.log(error.message);
logout();
}
};

Expand Down
5 changes: 4 additions & 1 deletion frontend/src/components/NewUser.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { API_URL2 } from '../constants';
import { AuthContext } from './auth/AuthContext';

const NewUser = () => {
const { logout } = useContext(AuthContext);
const [userData, setUserData] = useState("");
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
Expand Down Expand Up @@ -65,6 +67,7 @@ const NewUser = () => {

setRegistrationMessages("An error occurred: " + error.message);
console.log(error.message);
logout();
}
};

Expand Down
11 changes: 6 additions & 5 deletions frontend/src/components/PageWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import React from 'react'
import Navigation from './Navigation';
import Footer from './Footer';

// Passed in the content for the middle of the page as children and the logged in and user state. Navbar and footer displayed on every page.
const PageWrapper = ({children, loggedIn, setLoggedIn, setUser, user }) => (
// Passed in the content for the middle of the page as children. Navbar and footer displayed on every page.
const PageWrapper = ({children }) => {


return (
<div id="page-top">
<Navigation loggedIn={loggedIn} setLoggedIn={setLoggedIn} setUser={setUser} user={user} />
<Navigation />
{children}
<Footer/>
</div>
);

);
};

export default PageWrapper;
Loading
Loading