From 8418520d287c39fbc62ee572d51f4d7fa03f0189 Mon Sep 17 00:00:00 2001
From: a2-imeri <Alfret2.imeri@live.uwe.ac.uk>
Date: Fri, 5 Jul 2024 09:44:57 +0100
Subject: [PATCH] Update Token Authentication API

---
 .../__pycache__/settings.cpython-310.pyc      | Bin 4147 -> 4219 bytes
 MisplaceAI/MisplaceAI/settings.py             |  12 +-
 .../__pycache__/urls.cpython-310.pyc          | Bin 600 -> 656 bytes
 .../__pycache__/views.cpython-310.pyc         | Bin 2185 -> 2576 bytes
 MisplaceAI/authentication/forms.py            |  31 -----
 MisplaceAI/authentication/serializers.py      |   1 -
 MisplaceAI/authentication/urls.py             |   3 +-
 MisplaceAI/authentication/views.py            |  11 +-
 frontend/package-lock.json                    |  10 ++
 frontend/package.json                         |   1 +
 frontend/src/App.js                           |   8 +-
 frontend/src/constants.js                     |   2 +
 frontend/src/firewall/ProtectedRoute.js       |  54 +++++++-
 frontend/src/firewall/RouteProtection.js      |  17 ++-
 frontend/src/layouts/Navbar/Navbar.js         |   8 +-
 frontend/src/services/api.js                  | 121 ++++++++++++++----
 frontend/src/services/auth.js                 | 116 ++++++++---------
 17 files changed, 253 insertions(+), 142 deletions(-)
 delete mode 100644 MisplaceAI/authentication/forms.py
 create mode 100644 frontend/src/constants.js

diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc
index 78bd7a0ef7bd7019670610a6016177d89c9a0e2d..02813be6beff5c02754f819d73ecadb2a17f005a 100644
GIT binary patch
delta 330
zcmdn2@LPd5pO=@5fq{YHU~^mA+=;xhjH@PU-;?BsQc96akxx-bQA|-vQBF}wQJuq<
z$rz<PS&mV$UL{2@N;O44N-c#WN<D=mN+ZP}N;8!+#Za0dl{3XinjxJjN-M=UN;{Rm
z=yD2U3UiK5lungms?Gx46qALF3-lH;q?k%Fq%cY{q?k!EMCqsMFEB_kU&xqZA<3}7
zAjNVaBS<dFFjZiIQL5oWMn;Bkh7{&t1{Q`?oh)OPER)Jhn-4MGXJi!Kyoi~XNwZ3$
zD7CmGKCLJ*H?=&!C_BD5Gq)foHLJWtuOvS^H7`CXCowrYC$o6-I@ZT*jHa6>@jPIZ
zh!S)54+@TV^zrcz2a)mqL7wiOe!-iI_@o&bmrU;Cmy#4`;$ivE#LDuYg_VVgg^`7k
Wg&9mTPCm%5!otGF#5(yozYhRt3R^Y+

delta 306
zcmeyZuvvjOpO=@5fq{XcAi5=O#zbCO#$^+=@72qt$fqczD5fZ-D5t2TsLo-_WQ<Zu
zQA<(JWK7XW(M-|GWK7YX!<3?yqLay(qL!kY$(YU(rJSM{rIMl_rJBMKrIx}GrJiCC
zrIE^+Vkpg!%9&y$&5+I%rI}(JrIpHGbTNf7g*it%O1nxiReOO>ipfI81-c6vQcNWo
zQW&iy8B)w78KU%3^%m%-m@i~Zv5;g~pr2y7kTHc(k|D|<RbYW(s=-1=Muu>P6y{(C
z7KT*qETb&r$_ty1G2Uln6xzIunU`tvUDiizj3%28@H}ALyn|1g(PR+=0|N^K4=)EJ
z52FAx3mXd)A0rDh8w&>uhZqwN%YP<Tmj5iQEKDqnEQ~BnEX*J{nO#7IiHU2nzJLz^
D*;7Z_

diff --git a/MisplaceAI/MisplaceAI/settings.py b/MisplaceAI/MisplaceAI/settings.py
index 83a2b6a..0319f3b 100644
--- a/MisplaceAI/MisplaceAI/settings.py
+++ b/MisplaceAI/MisplaceAI/settings.py
@@ -87,14 +87,9 @@ ROOT_URLCONF = 'MisplaceAI.urls'
 
 
 SESSION_COOKIE_AGE = 1209600  # 2 weeks in seconds
-SESSION_EXPIRE_AT_BROWSER_CLOSE = False# Do not expire the session when the browser closes
-
-
-# Optionally, secure the session cookie (recommended for production)
-SESSION_COOKIE_SECURE = False  # Set to True if using HTTPS
-
-# Save the session cookie on every request (optional, based on your needs)
-SESSION_SAVE_EVERY_REQUEST = True
+SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Do not expire the session when the browser closes
+SESSION_COOKIE_SECURE = False   # Set to True if using HTTPS
+SESSION_SAVE_EVERY_REQUEST = True # Save the session cookie on every request (optional, based on your needs)
 
 # Configuration of the session engine 
 SESSION_ENGINE = 'django.contrib.sessions.backends.db'
@@ -135,6 +130,7 @@ SIMPLE_JWT = {
     'ROTATE_REFRESH_TOKENS': True,
     'BLACKLIST_AFTER_ROTATION': True,
 }
+CORS_ALLOW_ALL_ORIGINS = True
 # Database
 # https://docs.djangoproject.com/en/4.0/ref/settings/#databases
 
diff --git a/MisplaceAI/authentication/__pycache__/urls.cpython-310.pyc b/MisplaceAI/authentication/__pycache__/urls.cpython-310.pyc
index 1008e47ecfc3b133b5260926e8c9ad6097420ed4..b79f85e6c3da732f08f20763d8d9dd4f9f58c0c9 100644
GIT binary patch
delta 268
zcmcb?GJ#b)pO=@5fq{Wx{i4>iUM2>H#~=<e2FW@wFfbI)nW$}8pUWA=$;gn(n8KRM
zwt#CPLke38doOb;V+uzqJDAT2<#VNSfce}|K2It$n9mF4^QE$Y`TS76Kq@DgF9_nN
zvqf>I2n91}3cmz7MU(LsOF?2uh9=X*<GPG26TfP5+~V@dPtPwcnJmTFDpJLs1Lo-8
zVuLXJG<ha3Vhmy`;+*`EQHD*3fq|h&XtFSqzJv$^0|O7E0HXw>0J8ui408Me061AZ
A^8f$<

delta 224
zcmbQhdV@tfpO=@5fq{XcNWUe`jfsKbF^GeVIT;uj92giFiaRE18^$m)q%x+krm`*I
zT*#2Zmcri4oXVKOk;)F{b3*xCsT^QFH<Ztl$_(c7Liv2DEMPuAh@Z|H#g!ru%%CYa
z@r(^4^JGp&&B=C*jm&<U+><Xe2Fc%IPb`iv%S<i5#a&vIQ;=9vl3J8kypo|v0OV?c
b$(~I5VnPfI3_Oeij3SHz%mR!M$nhHhP$n??

diff --git a/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc b/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc
index b5f125413f28b700e37fb77778c585ec39c6ddc8..0f3a5bf6b0e6ff587ef4c85fae26f30fe63f9f50 100644
GIT binary patch
delta 912
zcmeAaoFKxN&&$ijz`($8vbinoEayZ%8OB8uwc|b98B#b?xLO!exKf$31e%$n1l<`@
zxKnsq7*cprIhvWHgxncYcvJXV7*hDad|`Kn6#f)}7KRi7Fkd7^FqlD8XyUn+$&O5t
zlS>$T*+du^7>YzDvok4)@-Q$ka5FG46tgifFmNz%F>){#@octZl3`?%0jZLmoWLBz
zC^>mA^HC{9kN`w4h$YIvz;KJDAiuaoVDcOmJ8NE$ARma}2NA{~0-+kr0%<7{XJB9e
zsrbwSwvvajNPMy#lMGXl*kohY^-2;93=APfQXt(B7l2rD3=9mKEJa`!C{1QzQ(#j7
z3CmB`V#|(_1TmFC1QUo*0THSof*nMtg9ug-p#dT^K?KO=A}tV08${@U2(Sae1lW}z
z=PONq&*rFMf+~wpV#dJ00CtfnNQD{56ZJ)6V3*$F^2txnFD(hnOf63axe64sAPjN{
zD4Lx?VIIZ6z)&Sp!<fa8!YIj5%T&Tx!_>@J%Ur{}fN3EEBSQ&u2}>4h3R5p*I72W4
z3quK8r4A!QEej(<6%SaB9U{lZ5Xq3jP|H%sQXdRulrxqyXfpfV;w?%|D@rZSh%d>{
zPR%O<g_<Tu5jey^p>vBfDJL;GJ14WaqzL4+B2dWO67vWN35Yi`FpUp#4R#HQclP%S
zarFzi#pzm+oLW$lnV(k#3O7yGB3+Qr^+1FrC^fLwgRQy6m|6r5CNKdHPEbG=3o$S-
zFfoGC83!XDBOjv#BS!<%|0;e&c<F(XlwT1@HIgX^qbwL07(lU81P*o(&rehImT*c|
zVqW^>RQ4k#w|Je4QWHy39RoZ;;a>zw<KR?R1d6dDkPZxkC&zNAa0oF8um}i=2mt^H
C?5LUm

delta 532
zcmbOr(kaN7&&$ijz`($err(m*!ZDFghH>6R?Rb|Ih7`^mhFpOt0Y(OQh7_(8?iPj=
z?o{S1!Di+tA$Nupo)q2|h7{gZj%Ma4VRwcUz7+lzh7^7<UnE5!m_bu;;-wZ&O~zZS
zIr-_Cd6R9JBq!%H_OgjGFfbH}PG(|K66IxJVBle3U?^r|U|`^2;9}%pEaIIkz$&vi
zl!=i|7Nk&aax`-cqtxUb%txh^Kmri$AeI;d1H&zrg8bqV!O7EE?5z1fg8U#t07RI8
z2!v`d3#6q;f`Nenq~bFR*j66KB8kZYtg?*all55FD@igiFoYCIgET`N0Ak5AFfeGc
z6oDO}Jo!JX0-GX8SYfg%TXvKbh^Yc1m_URoh)@F&>>xq|M6iMgO%S04B0v@wX@gig
zAVL>JfZYcsz>WmDUU~8>Hb)IpR9S=)a|Q+mu#3z<D$FO}=eTH61Pabuyv{|bi6yCy
m0iI!*spUnWU<UiT2&AP5YyyH1oBV`Rg@cDth)IA&L<j&CDq02r

diff --git a/MisplaceAI/authentication/forms.py b/MisplaceAI/authentication/forms.py
deleted file mode 100644
index ee9fab5..0000000
--- a/MisplaceAI/authentication/forms.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# MisplaceAI/authentication/forms.py
-
-from django import forms
-from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
-from django.contrib.auth.models import User
-
-class RegisterForm(UserCreationForm):
-    email = forms.EmailField()
-
-    class Meta:
-        model = User
-        fields = ['username', 'email', 'password1', 'password2']
-
-class LoginForm(AuthenticationForm):
-    username = forms.CharField(label='Username')
-    password = forms.CharField(widget=forms.PasswordInput)
-
-
-class CustomUserCreationForm(UserCreationForm):
-    email = forms.EmailField(required=True, widget=forms.EmailInput(attrs={'class': 'form-control'}))
-
-    class Meta:
-        model = User
-        fields = ("username", "email", "password1", "password2")
-
-    def save(self, commit=True):
-        user = super(CustomUserCreationForm, self).save(commit=False)
-        user.email = self.cleaned_data["email"]
-        if commit:
-            user.save()
-        return user
\ No newline at end of file
diff --git a/MisplaceAI/authentication/serializers.py b/MisplaceAI/authentication/serializers.py
index 70a5de5..d73b408 100644
--- a/MisplaceAI/authentication/serializers.py
+++ b/MisplaceAI/authentication/serializers.py
@@ -3,7 +3,6 @@
 
 from django.contrib.auth import get_user_model
 from rest_framework import serializers
-from django.contrib.auth.models import User
 from django.contrib.auth import authenticate
 
 UserModel = get_user_model()
diff --git a/MisplaceAI/authentication/urls.py b/MisplaceAI/authentication/urls.py
index fdf2fd9..9f611ab 100644
--- a/MisplaceAI/authentication/urls.py
+++ b/MisplaceAI/authentication/urls.py
@@ -1,7 +1,7 @@
 # MisplaceAI/authentication/urls.py
 from django.urls import path
 from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
-from .views import RegisterView, LoginView, AdminLoginView
+from .views import RegisterView, LoginView, AdminLoginView, LogoutView
 
 urlpatterns = [
     path('register/', RegisterView.as_view(), name='register'),
@@ -9,4 +9,5 @@ urlpatterns = [
     path('admin/login/', AdminLoginView.as_view(), name='admin_login'),
     path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
     path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
+    path('logout/', LogoutView.as_view(), name='logout'),
 ]
diff --git a/MisplaceAI/authentication/views.py b/MisplaceAI/authentication/views.py
index e1e96bd..1413323 100644
--- a/MisplaceAI/authentication/views.py
+++ b/MisplaceAI/authentication/views.py
@@ -7,7 +7,6 @@ from rest_framework.views import APIView
 from rest_framework_simplejwt.tokens import RefreshToken
 from .serializers import RegisterSerializer, LoginSerializer
 from django.contrib.auth.models import User
-from django.contrib.auth import login
 
 class RegisterView(generics.CreateAPIView):
     queryset = User.objects.all()
@@ -48,3 +47,13 @@ class AdminLoginView(APIView):
                 'is_authenticated': True
             }, status=status.HTTP_200_OK)
         return Response({'error': 'Invalid username or password or you do not have the necessary permissions to access this page.'}, status=status.HTTP_400_BAD_REQUEST)
+
+class LogoutView(APIView):
+    def post(self, request):
+        try:
+            refresh_token = request.data["refresh_token"]
+            token = RefreshToken(refresh_token)
+            token.blacklist()
+            return Response(status=status.HTTP_205_RESET_CONTENT)
+        except Exception as e:
+            return Response(status=status.HTTP_400_BAD_REQUEST)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index f61fa33..d244df8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -13,6 +13,7 @@
         "axios": "^1.7.2",
         "bootstrap": "^5.3.3",
         "font-awesome": "^4.7.0",
+        "jwt-decode": "^4.0.0",
         "react": "^18.0.0",
         "react-bootstrap": "^2.10.2",
         "react-dom": "^18.0.0",
@@ -13134,6 +13135,15 @@
         "node": ">=4.0"
       }
     },
+    "node_modules/jwt-decode": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+      "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 38d2da0..16a7394 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -8,6 +8,7 @@
     "axios": "^1.7.2",
     "bootstrap": "^5.3.3",
     "font-awesome": "^4.7.0",
+    "jwt-decode": "^4.0.0",
     "react": "^18.0.0",
     "react-bootstrap": "^2.10.2",
     "react-dom": "^18.0.0",
diff --git a/frontend/src/App.js b/frontend/src/App.js
index 231db48..d3e12a1 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -1,5 +1,6 @@
 // src/App.js
-import React from 'react';
+
+import React, { useEffect } from 'react';
 import { BrowserRouter as Router, Route, Routes, useLocation } from 'react-router-dom';
 import Navbar from './layouts/Navbar/Navbar';
 import HomePage from './pages/Home/Home';
@@ -21,10 +22,15 @@ import ManageDailyLimit from './pages/Admin/ManageDailyLimit';
 import Error500 from './pages/Error/Error500/Error500';
 import ProtectedRoute from './firewall/ProtectedRoute';
 import RouteProtection from './firewall/RouteProtection';
+import { initializeTokenRefresh } from './services/api'; // Import the initializeTokenRefresh function
 
 function App() {
   const location = useLocation();
 
+  useEffect(() => {
+    initializeTokenRefresh(); // Initialize token refresh timer on app load
+  }, []);
+
   return (
     <div className="App">
       {location.pathname !== '/error-500' && <Navbar />}
diff --git a/frontend/src/constants.js b/frontend/src/constants.js
new file mode 100644
index 0000000..2ebf897
--- /dev/null
+++ b/frontend/src/constants.js
@@ -0,0 +1,2 @@
+export const ACCESS_TOKEN = "access";
+export const REFRESH_TOKEN = "refresh";
diff --git a/frontend/src/firewall/ProtectedRoute.js b/frontend/src/firewall/ProtectedRoute.js
index 15da355..c1563a9 100644
--- a/frontend/src/firewall/ProtectedRoute.js
+++ b/frontend/src/firewall/ProtectedRoute.js
@@ -1,17 +1,57 @@
 // src/firewall/ProtectedRoute.js
-import React from 'react';
+
+import React, { useState, useEffect } from 'react';
 import { Navigate } from 'react-router-dom';
+import { jwtDecode } from 'jwt-decode';
+import { ACCESS_TOKEN } from '../constants';
 
 const ProtectedRoute = ({ children, isAdminRoute }) => {
-    const isAuthenticated = !!localStorage.getItem('isAuthenticated');
-    const isAdmin = !!localStorage.getItem('isAdmin');
+    const [isAuthorized, setIsAuthorized] = useState(null);
 
-    if (!isAuthenticated) {
-        return <Navigate to="/login" replace />;
+    useEffect(() => {
+        const auth = async () => {
+            const token = localStorage.getItem(ACCESS_TOKEN);
+
+            if (!token) {
+                console.log('No access token found, redirecting to login');
+                setIsAuthorized(false);
+                return;
+            }
+
+            try {
+                const decoded = jwtDecode(token);
+                const tokenExpiration = decoded.exp;
+                const now = Math.floor(Date.now() / 1000);
+
+                if (tokenExpiration < now) {
+                    console.log('Access token expired, redirecting to login');
+                    setIsAuthorized(false);
+                } else {
+                    if (isAdminRoute) {
+                        const isAdmin = localStorage.getItem('isAdmin');
+                        if (!isAdmin) {
+                            console.log('Not an admin, redirecting to home');
+                            setIsAuthorized(false);
+                            return;
+                        }
+                    }
+                    setIsAuthorized(true);
+                }
+            } catch (error) {
+                console.log('Error decoding token:', error);
+                setIsAuthorized(false);
+            }
+        };
+
+        auth().catch(() => setIsAuthorized(false));
+    }, [isAdminRoute]);
+
+    if (isAuthorized === null) {
+        return <div>Loading...</div>;
     }
 
-    if (isAdminRoute && !isAdmin) {
-        return <Navigate to="/" replace />;
+    if (!isAuthorized) {
+        return <Navigate to="/login" replace />;
     }
 
     return children;
diff --git a/frontend/src/firewall/RouteProtection.js b/frontend/src/firewall/RouteProtection.js
index 68f399c..6636f20 100644
--- a/frontend/src/firewall/RouteProtection.js
+++ b/frontend/src/firewall/RouteProtection.js
@@ -1,9 +1,24 @@
 // src/firewall/RouteProtection.js
+
 import React from 'react';
 import { Outlet, Navigate } from 'react-router-dom';
+import { jwtDecode } from 'jwt-decode';
+import { ACCESS_TOKEN } from '../constants';
 
 const RouteProtection = ({ redirectTo }) => {
-    const isAuthenticated = !!localStorage.getItem('isAuthenticated');
+    const token = localStorage.getItem(ACCESS_TOKEN);
+    let isAuthenticated = false;
+
+    if (token) {
+        try {
+            const decoded = jwtDecode(token);
+            const tokenExpiration = decoded.exp;
+            const now = Date.now() / 1000;
+            isAuthenticated = tokenExpiration > now;
+        } catch (error) {
+            console.error("Invalid token");
+        }
+    }
 
     return isAuthenticated ? <Navigate to={redirectTo} replace /> : <Outlet />;
 };
diff --git a/frontend/src/layouts/Navbar/Navbar.js b/frontend/src/layouts/Navbar/Navbar.js
index c68b43a..c1d837b 100644
--- a/frontend/src/layouts/Navbar/Navbar.js
+++ b/frontend/src/layouts/Navbar/Navbar.js
@@ -2,17 +2,15 @@
 import React from 'react';
 import { Link, useNavigate } from 'react-router-dom';
 import '../../styles/main.css';
+import { logout } from '../../services/auth';
 
 const Navbar = () => {
     const navigate = useNavigate();
     const isAuthenticated = !!localStorage.getItem('isAuthenticated');
     const isAdmin = !!localStorage.getItem('isAdmin');
 
-    const handleLogout = () => {
-        localStorage.removeItem('token');
-        localStorage.removeItem('adminToken');
-        localStorage.removeItem('isAuthenticated');
-        localStorage.removeItem('isAdmin');
+    const handleLogout = async () => {
+        await logout();
         navigate('/');
         window.location.reload(); // Refresh to update Navbar
     };
diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js
index 26c4cd0..fe9d2f9 100644
--- a/frontend/src/services/api.js
+++ b/frontend/src/services/api.js
@@ -1,9 +1,11 @@
 // src/services/api.js
 
 import axios from 'axios';
-import { refreshToken, logout } from './auth';
+import { jwtDecode } from 'jwt-decode';
+import { ACCESS_TOKEN, REFRESH_TOKEN } from '../constants';
+import { logout } from './auth'; // Importing the consolidated logout
 
-export const getCsrfToken = () => {
+const getCsrfToken = () => {
     const cookies = document.cookie.split(';');
     for (let cookie of cookies) {
         const [name, value] = cookie.split('=');
@@ -19,41 +21,107 @@ const api = axios.create({
     headers: {
         'Content-Type': 'application/json',
     },
-    withCredentials: true,  // Ensure credentials are sent with requests
+    withCredentials: true,
 });
 
+let isRefreshing = false;
+
+const setRefreshTimer = (expiryTime) => {
+    const now = Math.floor(Date.now() / 1000);
+    const timeToRefresh = (expiryTime - now - 60) * 1000; // Set timer to 1 minute before expiry
+    setTimeout(() => {
+        if (!isRefreshing) {
+            refreshToken();
+        }
+    }, timeToRefresh);
+};
+
+const refreshToken = async () => {
+    if (isRefreshing) {
+        return; // If a refresh is already in progress, don't start another one
+    }
+    isRefreshing = true;
+    const refresh = localStorage.getItem(REFRESH_TOKEN);
+    if (refresh) {
+        try {
+            console.log('Attempting to refresh token with refresh token:', refresh);
+            const response = await api.post('/api/auth/token/refresh/', { refresh });
+            const { access, refresh: newRefreshToken } = response.data;
+
+            console.log('Access Token:', access);
+            localStorage.setItem(ACCESS_TOKEN, access);
+
+            // If a new refresh token is provided, save it
+            if (newRefreshToken) {
+                console.log('New Refresh Token:', newRefreshToken);
+                localStorage.setItem(REFRESH_TOKEN, newRefreshToken);
+            }
+
+            setRefreshTimer(jwtDecode(access).exp); // Set the timer after refreshing the token
+        } catch (error) {
+            console.log('Token refresh error:', error);
+            logout();
+        } finally {
+            isRefreshing = false;
+        }
+    } else {
+        logout();
+        isRefreshing = false;
+    }
+};
+
+const initializeTokenRefresh = () => {
+    const token = localStorage.getItem(ACCESS_TOKEN);
+    const refresh = localStorage.getItem(REFRESH_TOKEN);
+
+    if (token) {
+        const decoded = jwtDecode(token);
+        const tokenExpiration = decoded.exp;
+        const now = Math.floor(Date.now() / 1000);
+
+        if (tokenExpiration > now) {
+            setRefreshTimer(tokenExpiration);
+        } else if (refresh) {
+            refreshToken(); // Token expired, but refresh token exists
+        }
+    } else if (refresh) {
+        refreshToken(); // No access token, but refresh token exists
+    }
+};
+
+const obtainToken = async (url, credentials) => {
+    try {
+        const response = await api.post(url, credentials);
+        const { access, refresh } = response.data;
+
+        console.log('Access Token:', access);
+        localStorage.setItem(ACCESS_TOKEN, access);
+        localStorage.setItem(REFRESH_TOKEN, refresh);
+
+        setRefreshTimer(jwtDecode(access).exp); // Set the timer after obtaining the token
+
+        return response.data;
+    } catch (error) {
+        console.log('Token obtain error:', error);
+        throw error;
+    }
+};
+
 api.interceptors.request.use(
     async (config) => {
-        let token = localStorage.getItem('token') || localStorage.getItem('adminToken');
+        const token = localStorage.getItem(ACCESS_TOKEN);
+
         if (token) {
-            const tokenExpiry = localStorage.getItem('tokenExpiry');
-            const now = Math.floor(Date.now() / 1000);
-            if (tokenExpiry && now >= tokenExpiry) {
-                try {
-                    const newTokens = await refreshToken();
-                    if (newTokens) {
-                        token = newTokens.access;
-                        localStorage.setItem('token', newTokens.access);
-                        localStorage.setItem('tokenExpiry', newTokens.accessExpiry);
-                    } else {
-                        console.log('Token refresh failed. Logging out...');
-                        logout();
-                        window.location.href = '/login';
-                        return Promise.reject('Session expired. Please log in again.');
-                    }
-                } catch (err) {
-                    console.log('Error during token refresh:', err);
-                    logout();
-                    window.location.href = '/login';
-                    return Promise.reject('Session expired. Please log in again.');
-                }
-            }
             config.headers.Authorization = `Bearer ${token}`;
+        } else {
+            console.log('No access token found for the request');
         }
+
         const csrfToken = getCsrfToken();
         if (csrfToken) {
             config.headers['X-CSRFToken'] = csrfToken;
         }
+
         return config;
     },
     (error) => {
@@ -76,4 +144,5 @@ api.interceptors.response.use(
     }
 );
 
+export { getCsrfToken, setRefreshTimer, initializeTokenRefresh, refreshToken, obtainToken };
 export default api;
diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js
index f0d90d0..b9ea689 100644
--- a/frontend/src/services/auth.js
+++ b/frontend/src/services/auth.js
@@ -1,70 +1,55 @@
 // src/services/auth.js
-import api from './api';
 
+import api, { obtainToken, setRefreshTimer } from './api';
+import { ACCESS_TOKEN, REFRESH_TOKEN } from '../constants';
+import { jwtDecode } from 'jwt-decode'; // Corrected import statement
+
+// Login function for regular users
 export const login = async (credentials) => {
-    try {
-        const response = await api.post('/api/auth/login/', credentials);
-        if (response.data.access) {
-            localStorage.setItem('token', response.data.access);
-            localStorage.setItem('refresh', response.data.refresh);
-            const tokenPayload = JSON.parse(atob(response.data.access.split('.')[1]));
-            localStorage.setItem('tokenExpiry', tokenPayload.exp);
-            localStorage.setItem('isAuthenticated', true);
-        }
-        return response.data;
-    } catch (error) {
-        console.log('Login error:', error);
-        throw error;
-    }
+    const data = await obtainToken('/api/auth/login/', credentials);
+    localStorage.setItem('isAuthenticated', true);
+    return data;
 };
 
+// Register function for new users
 export const register = async (userData) => {
-    try {
-        const response = await api.post('/api/auth/register/', userData);
-        if (response.data.access) {
-            localStorage.setItem('token', response.data.access);
-            localStorage.setItem('refresh', response.data.refresh);
-            const tokenPayload = JSON.parse(atob(response.data.access.split('.')[1]));
-            localStorage.setItem('tokenExpiry', tokenPayload.exp);
-            localStorage.setItem('isAuthenticated', true);
-        }
-        return response.data;
-    } catch (error) {
-        console.log('Registration error:', error);
-        throw error;
-    }
+    const data = await obtainToken('/api/auth/register/', userData);
+    localStorage.setItem('isAuthenticated', true);
+    return data;
 };
 
-export const logout = () => {
-    localStorage.removeItem('token');
-    localStorage.removeItem('refresh');
-    localStorage.removeItem('tokenExpiry');
-    localStorage.removeItem('isAuthenticated');
-    localStorage.removeItem('adminToken');
-    localStorage.removeItem('isAdmin');
-    localStorage.removeItem('username');
+// Admin login function
+export const adminLogin = async (credentials) => {
+    const data = await obtainToken('/api/auth/admin/login/', credentials);
+    localStorage.setItem('isAdmin', true);
+    localStorage.setItem('username', credentials.username);
+    localStorage.setItem('isAuthenticated', true);
+    return data;
 };
 
-export const refreshToken = async () => {
-    const refresh = localStorage.getItem('refresh');
-    if (refresh) {
-        try {
-            const response = await api.post('/api/auth/token/refresh/', { refresh });
-            localStorage.setItem('token', response.data.access);
-            const tokenPayload = JSON.parse(atob(response.data.access.split('.')[1]));
-            localStorage.setItem('tokenExpiry', tokenPayload.exp);
-            return response.data;
-        } catch (error) {
-            console.log('Token refresh error:', error);
-            logout();
-            return null;
-        }
+
+// Logout function
+export const logout = async () => {
+    const refreshToken = localStorage.getItem(REFRESH_TOKEN);
+    try {
+        console.log('Attempting to logout with refresh token:', refreshToken);
+        await api.post('/api/auth/logout/', { refresh_token: refreshToken });
+    } catch (error) {
+        console.error('Logout failed:', error);
+    } finally {
+        console.log('Clearing local storage');
+        localStorage.removeItem(ACCESS_TOKEN);
+        localStorage.removeItem(REFRESH_TOKEN);
+        localStorage.removeItem('isAuthenticated');
+        localStorage.removeItem('isAdmin');
+        localStorage.removeItem('username');
     }
-    return null;
 };
 
+// Function to get the current user
 export const getCurrentUser = async () => {
     try {
+        console.log('Fetching current user');
         const response = await api.get('/api/auth/user/');
         return response.data;
     } catch (error) {
@@ -73,18 +58,29 @@ export const getCurrentUser = async () => {
     }
 };
 
-export const adminLogin = async (credentials) => {
+// Function to refresh the token manually if needed
+export const refreshToken = async () => {
     try {
-        const response = await api.post('/api/auth/admin/login/', credentials);
-        if (response.data.access) {
-            localStorage.setItem('adminToken', response.data.access);
-            localStorage.setItem('isAdmin', true);
-            localStorage.setItem('username', credentials.username);
-            localStorage.setItem('isAuthenticated', true);
+        const refreshToken = localStorage.getItem(REFRESH_TOKEN);
+        if (!refreshToken) {
+            throw new Error('No refresh token found');
+        }
+        const response = await api.post('/api/auth/token/refresh/', { refresh: refreshToken });
+        const { access, refresh: newRefreshToken } = response.data;
+
+        console.log('Access Token:', access);
+        localStorage.setItem(ACCESS_TOKEN, access);
+
+        if (newRefreshToken) {
+            console.log('New Refresh Token:', newRefreshToken);
+            localStorage.setItem(REFRESH_TOKEN, newRefreshToken);
         }
+
+        setRefreshTimer(jwtDecode(access).exp); // Set the timer after refreshing the token
+
         return response.data;
     } catch (error) {
-        console.log('Admin login error:', error);
+        console.log('Token refresh error:', error);
         throw error;
     }
 };
-- 
GitLab