Skip to content

Commit

Permalink
Fix token exp (#235)
Browse files Browse the repository at this point in the history
* fix token expired

* Update tokenExpirationHandler.ts

* update token

* time expandation

* update token

* update token

* Stop tracking yarn.lock and ignore it

* Update .gitignore

* update token

* update token

* time expandation

* fix token expired

* Update tokenExpirationHandler.ts

* Update .gitignore

* Update .gitignore to ignore yarn.lock

* Restore yarn.lock to the repository

* Update .gitignore to ignore yarn.lock in future changes

* Update .gitignore to ignore yarn.lock

* Update .gitignore to ignore yarn.lock

* Update .gitignore to ignore yarn.lock

* signup button

* signup button
  • Loading branch information
ManziPatrick authored Nov 5, 2024
1 parent 6bf831a commit 4e2a670
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 19 deletions.
45 changes: 28 additions & 17 deletions src/components/form/RegisterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { registerSchema } from "../validation/Register";
import { useForm } from "react-hook-form";
import InputField from "./InputField";
import Button from "./Button";
// import {Header} from "../sidebar/Header"
import { fetchCountries } from "../country/country";
import { zodResolver } from "@hookform/resolvers/zod";
import { formData } from "../validation/Register";
Expand Down Expand Up @@ -169,7 +170,9 @@ function SignupForm() {
};
return (
<>

<div className="flex items-center justify-center mx-auto bg-white dark:bg-[#374151] h-screen">

{isAnError && (
<Toasty message={isAnError} type="error" onClose={() => setError(null)} />
)}
Expand Down Expand Up @@ -284,23 +287,31 @@ function SignupForm() {
</datalist>

</div>
<div className="w-[50%] ">
<InputField
placeholder="Gender"
type="text"
{...register("gender")}
list="gender"
error={errors?.gender}
/>
<Datalist
id="gender"
options={[
{ value: "female" },
{ value: "male" },
{ value: "other" },
]}
/>
</div>
<div className="relative w-full">
<select
{...register("gender")}
className="w-full rounded-md px-2 py-2 border border-white
placeholder:text-gray-400 text-white sm:text-[12px]
outline-none bg-[#1F2A37] appearance-none"
>
<option value="" disabled selected>Gender</option>
<option value="female">female</option>
<option value="male">male</option>
<option value="other">other</option>
</select>

{/* Dropdown arrow */}
<div className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
<svg
className="w-4 h-4 text-white"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</div>

<div className="flex items-center w-[25vw] sm:w-5/6 lg:w-[25vw] justify-between">
Expand Down
11 changes: 9 additions & 2 deletions src/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import { Icon } from "@iconify/react";
import { Link, useNavigate, useLocation } from "react-router-dom";
import {
Expand All @@ -8,13 +8,20 @@ import {
applicantSidebarItems,
} from "./sidebarItems";
import "./navslide.css";
import TokenExpirationHandler from "../../utils/tokenExpirationHandler";

const Sidebar = ({ expanded, setExpanded }) => {
const navigate = useNavigate();
const location = useLocation();
const roleName = localStorage.getItem("roleName");

// Select items based on the role
useEffect(() => {
const isTokenValid = TokenExpirationHandler.validateToken();
if (!isTokenValid) {
navigate("/login");
}
}, [location.pathname, navigate]);

const items =
roleName === "applicant"
? applicantSidebarItems
Expand Down
103 changes: 103 additions & 0 deletions src/utils/tokenExpirationHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import jwtDecode from 'jwt-decode';
import { toast } from 'react-toastify';

interface DecodedToken {
exp?: number;
iat?: number;
data?: {
email: string;
};
email?: string;
}

class TokenExpirationHandler {
private static readonly STORAGE_KEYS = {
ACCESS_TOKEN: 'access_token',
TOKEN_ISSUED_TIME: 'token_issued_time',
} as const;

private static readonly LOGIN_ROUTE = '/login';

private static showToast(message: string) {
toast.error(message);
}

private static clearAuthData() {
localStorage.clear();
}

private static redirect() {
window.location.href = this.LOGIN_ROUTE;
}

private static handleRedirection(message: string) {
this.showToast(message);
this.clearAuthData();
setTimeout(() => this.redirect(), 400);
}

public static isTokenExpired(token: string): boolean {
try {
const decoded = jwtDecode<DecodedToken>(token);
const tokenIssuedTime = localStorage.getItem(this.STORAGE_KEYS.TOKEN_ISSUED_TIME);
const issuedTime = tokenIssuedTime ? parseInt(tokenIssuedTime, 10) : null;


const currentTime = Date.now() / 1000;


const tokenIat = issuedTime || decoded.iat;
if (!tokenIat) return false;

const timeElapsedSinceIssue = currentTime - tokenIat;
const ExTime = 60 * 60;


return timeElapsedSinceIssue > ExTime;
} catch {
return true;
}
}

public static validateToken(): boolean {
const token = localStorage.getItem(this.STORAGE_KEYS.ACCESS_TOKEN);

if (!token) {
this.handleRedirection('Please login to continue');
return false;
}

if (this.isTokenExpired(token)) {
this.handleRedirection('Your session has expired after 1 hour. Please login again');
return false;
}

return true;
}

public static handleAuthError(error: any): void {
const errorMessage = error?.message?.toLowerCase() || '';

if (errorMessage.includes('jwt expired')) {
this.handleRedirection('Your session has expired. Please login again');
} else if (errorMessage.includes('invalid token')) {
this.handleRedirection('Invalid authentication. Please login again');
} else if (errorMessage.includes('unauthorized')) {
this.handleRedirection('Unauthorized access. Please login again');
} else {
this.handleRedirection('Authentication error. Please login again');
}
}

public static forceLogout(message?: string): void {
this.handleRedirection(message || 'You have been logged out');
}

public static storeToken(token: string): void {
localStorage.setItem(this.STORAGE_KEYS.ACCESS_TOKEN, token);

localStorage.setItem(this.STORAGE_KEYS.TOKEN_ISSUED_TIME, Math.floor(Date.now() / 1000).toString());
}
}

export default TokenExpirationHandler;

0 comments on commit 4e2a670

Please sign in to comment.