From ad8a3e639b0ec4506a1d9fdaaef220d9e36ada67 Mon Sep 17 00:00:00 2001 From: a2-imeri <Alfret2.imeri@live.uwe.ac.uk> Date: Tue, 2 Jul 2024 18:07:16 +0100 Subject: [PATCH] Update Authorization Token --- .../__pycache__/settings.cpython-310.pyc | Bin 3680 -> 3897 bytes MisplaceAI/MisplaceAI/settings.py | 7 +++ MisplaceAI/admin_app/urls.py | 2 - MisplaceAI/admin_app/views.py | 16 ------- .../__pycache__/urls.cpython-310.pyc | Bin 335 -> 600 bytes .../__pycache__/views.cpython-310.pyc | Bin 1555 -> 2185 bytes MisplaceAI/authentication/urls.py | 7 ++- MisplaceAI/authentication/views.py | 21 ++++++++- frontend/src/forms/Auth/AdminLoginForm.js | 8 +--- frontend/src/forms/Auth/LoginForm.js | 8 +--- frontend/src/forms/Auth/RegisterForm.js | 7 +-- frontend/src/services/api.js | 27 +++++++++--- frontend/src/services/auth.js | 41 ++++++++++++++++++ 13 files changed, 100 insertions(+), 44 deletions(-) diff --git a/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc b/MisplaceAI/MisplaceAI/__pycache__/settings.cpython-310.pyc index 3e6038a00a1f6a1ac6472eb94b2cba19f6f3c152..7fa541b2a5a2e158311f32a20c37fca92143ce0d 100644 GIT binary patch delta 687 zcmaDLvr~>QpO=@5fq{XcU%DkNM|2{e3}e?sZRz?H#uSzumR!~-Rz`+Yrc}0O#s%yP z8L~LCIHEXHSfjX7*rK>onX`Bn@TPL6urFka;!EL(;!ojBXN(d^XGq~%#26)*&XB^r zh%rhig(sacN*GLvr0_<Grtn3HrSPXSq;RBw)Cw+Q%w$Xvn!}RL7$u$}93_#?kWw$Q zh%rSpoiRmh4pWq5Dr<^(ibOACDr<^lic~LS3P%b@iu4?oD5(^g6xkHH6!{c|6vY&! z6y-T=nT%1=DJm(dnT#oFDe5U2nT#o#bC^<8QnWG|Q&dv4Ga1uaqGVEZqGVHaqvTRJ zqU2LJq7+i}q7+j(Q}m@7QaMu$q#5ednWB_Z45O4&`HK#xFs3l)s6?q$DW<9{P)#vf z$hbglAw!C>Btr_LBtwdcBtw*Xs`>(r6w`%_DQ1!k3p7&97czq6qBK(l7HFkvE@Wh6 z2xmxP4rX9sNL9(w&eEwo#K-`GkzmnsFsW&g7{tiHz@^}uxp^*Q3?r{5<1NmT%-qzJ z)SQyU%|Drv+3H`iGB7Y`GTvg(&CDw;NiBZK2;sA&Bvuw{vP6kGIy<`t2gir_d%OC@ z`*^y!hIsnAMu`Qvx&^rgd!UPp2Kk3LhPcL~NC!trIQcj_d;53>hr~O&g}4UAgOz#u z`%RAFEMpU7U|^{7n0$asV-i;hH;7ln4id5l2~A$k^@&k;@+@vy-ju|WRFLzFm>C!t zHgDx-VdRP83ik93@Ntdz3J;mQljj+mEdv8Xk?rKAy!zZz7#J8>7<hO&7<ne&<u&Eu U;Nf6oVq{`u`@_UFS%l9A02#-udH?_b delta 452 zcmdlf_dtd(pO=@5fq{WRwW=zuLu4YK3}ebfZRz?{rc{<@#s#bk8M4^2*rM1|SfV&m zSfe;onX|YSaHq1Tuq|YY;z?nT;!WX5XN=-YXGq~(#2Ce&&XB^jh%rhag*%-wN)Sv6 zrSL=vr|?FJq<~bhr-0N5EMm-LOc9*JlFk?<nj#b>md=nOyofPHB%LuubPiKgy?BaP zig=1die!pZigb!hitHS=OvWgQ6uA`nOvV(26vY&!OvV)DIZP>XDJq$aDRL>QnT+Wy zQIaWYQBo=DQPL^wQ8Fp)QL-r-QF5v5DVov@sq86Q(hTWLQSvF;Q3|QNMH^BWQ<!rU zqZF$YQxz8|rRXeVT%f#=Aw^e`As~fOk|9M;k|9bZRb_!{ivB{z6az_y1*$2A3mHLj zQEI7t3)EB97BVt2gfpZt2Q#oRq$*}<WNB8eXJi1uNU&%*nA9}dyo)iFakCC{GTUT6 zt}-?r1_p*IkI9DI8k0A1m4LZLY@2ns<rx{ZCj0QnZm!^AVw~K@`;5(;fq|jOd~y+= bK4%950|N^K4==~$HGHN_O#GAI^7#M&Zw_fB diff --git a/MisplaceAI/MisplaceAI/settings.py b/MisplaceAI/MisplaceAI/settings.py index a1a77a2..f163600 100644 --- a/MisplaceAI/MisplaceAI/settings.py +++ b/MisplaceAI/MisplaceAI/settings.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/4.0/ref/settings/ from pathlib import Path import os +from datetime import timedelta # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -110,6 +111,12 @@ REST_FRAMEWORK = { ], } +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, +} # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases diff --git a/MisplaceAI/admin_app/urls.py b/MisplaceAI/admin_app/urls.py index ea58610..3648c70 100644 --- a/MisplaceAI/admin_app/urls.py +++ b/MisplaceAI/admin_app/urls.py @@ -1,7 +1,6 @@ # MisplaceAI/admin_app/urls.py from django.urls import path from .views import ( - AdminLoginView, admin_dashboard_view, admin_users_view, admin_deactivate_user_view, @@ -10,7 +9,6 @@ from .views import ( ) urlpatterns = [ - path('login/', AdminLoginView.as_view(), name='admin_login'), path('dashboard/', admin_dashboard_view, name='admin_dashboard'), path('users/', admin_users_view, name='admin_users'), path('users/deactivate/<int:user_id>/', admin_deactivate_user_view, name='admin_deactivate_user'), diff --git a/MisplaceAI/admin_app/views.py b/MisplaceAI/admin_app/views.py index 5e137a3..46dd4a4 100644 --- a/MisplaceAI/admin_app/views.py +++ b/MisplaceAI/admin_app/views.py @@ -12,22 +12,6 @@ from django.contrib.auth.decorators import login_required from .serializers import UserSerializer -class AdminLoginView(APIView): - def post(self, request, *args, **kwargs): - username = request.data.get('username') - password = request.data.get('password') - user = authenticate(request, username=username, password=password) - if user is not None and user.is_superuser: - login(request, user) - refresh = RefreshToken.for_user(user) - return Response({ - 'refresh': str(refresh), - 'access': str(refresh.access_token), - 'is_admin': True, - '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) - @api_view(['GET']) @permission_classes([IsAuthenticated]) def admin_dashboard_view(request): diff --git a/MisplaceAI/authentication/__pycache__/urls.cpython-310.pyc b/MisplaceAI/authentication/__pycache__/urls.cpython-310.pyc index 8bb1713bf934e8764d4b1e4c353a79cd98bf0961..1008e47ecfc3b133b5260926e8c9ad6097420ed4 100644 GIT binary patch literal 600 zcmd1j<>g{vU|=ZHZ%K1wVqka-;vi#A1_lNP1_p-W4h9B>6owSW9EM!RC`LvQn<<AW zmpO_#mnDjYks*aSg(ZhImo17dmpzI-mm`XUks*~ag*BCJ0p~)76t)!hUglKB6pmDO zFrO33=St-O^SPmXo>XQqpBKvKOJxD``9b`2)+nwNfnWwr!IvO6YBJtpDM&2I&}6zL z9Fm`%n&+QXl9-tnkeFE%mYG_9O8_hwl$utQTATqAdCADYz@W)|izg^GJ+rtZ6{3vO zCqF$i4<vt!&oL!8GY`(M;w*wH)W5|6V}fkWOUzBJV#@*P)4#<ErmJ`oL3;JU9Q`P6 zFfSg=t70nwxlTVy5KPDCgPj>)keFFi#RnGBFM>EyKZ+O3kB9R8G`Vkar(`AOrRVFF z7UdKdF)=VOM9CGU7MH}Q6(#1Tmgg5`#}{Yj7UZO6m6zz1Wu}%F7cqk(l!bwT;T9`I zfDI(T4k9=}qU?#q@gRv?+@(c11&JjksYQ9kD;bIeKq^4QFKPY6f&%@-(vpnSypqi1 v#FEVXJbjP@^a?6(aoFVMr<CTT+JQo|Scrjvfrn9mQG`)|S%47&Ier5Gsm-nw delta 228 zcmcb?a-OL^pO=@5fq{X+qs2dsk&%JnF^GeV*%%lY92giFid`5O7*ZHg7;_kM8KW2( z8B&;1m~)tNnWLC<S)y1N8B!TjSW=l6ur6dsVNGG{Wlm*GVNYcN^Ep8Lbfze_6wY7< zO|F+9^EDZ7u@odqmSntSWME*>WV*!@l$xGdT#{N8mYG_9i*vFlqp7l=Ci^Y!l&r+O z^nAV2qMYI)CI$wETdZZ7spZ8*EDQ_`MXZxQGRm;=f|$IMg_-pEK)&H&6krr!7GUJ~ F2LON8FOvWO diff --git a/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc b/MisplaceAI/authentication/__pycache__/views.cpython-310.pyc index 5fe73a8a01b42fc98c48199de2c648f55a70125c..b5f125413f28b700e37fb77778c585ec39c6ddc8 100644 GIT binary patch delta 924 zcmbQt(<#W8&&$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;;-(f(O~zZS zIr-_Cd6Q+Bq$US2_OOXEFfbH}PJYCwB+ARcz`(=6z);M_z`(%4z{SYHSj0P7l1YZ~ z^JFuoGh8bfZ?U8#mL!I(oXo?l#<`NINEW17Zn8DAD&xw@iOgn<Qj=#eCo$fd{F~W; zaphzk7DFi|kP?U`AeI;d1H&zrg8bqV!O7(;)^;mdiugd{{2)RAM3{gGga$ARq^n4R zfq?;};xh}_c|43o5+KJJ6^S!2Fle&e;&V*N&CK%w`7SIowLF;#6pm2L&cMI`@|8cx zdli$*n9S;pYgn^bi;PRy7O>Z_E@Z4_E8!^NtYK?r1d&->&5S8bDNHHsb685avv^WC zdYOV5N;pzDQ@Ao2OSrRmAtIXGenpZD3=APf(jaF;!UM#T2fL{V?0aPf28Jk(%;NY& zkOOZCfLNvVB^jxCC7H>IC8;SziXbft3=9laah`c)i8+}m3Z=!VMR|$2sS5c;3I&P9 z#pU@$DIj)beyKuAzCvDpi9$wVS*k)wMyf(yYI16Eabi)WLP2U#Zf0?DW`16=LP@?t zVlqfVp(G=-SfL;>JylPWuLzX9ili9o85oLGK-PkSsYn&XQUej7kSfvuu~<QbCWz1i z5ui{n(gv|~K!h%c07nj(0LKt0>Oh_?26>x@k%y6qk%N(gk?kKB3l}33BO4<JBNHPN zBg-EaRyL2{LTpvSs8OZ|N*8`brl>|E%r|FXU;r6iWCl_J;`wQcPX5MnI-v-ZZg25A z7o{eaq&fz8f)YRxC_RHyO%X_G5!f6A0dnyz4x8Nkl+v73J4R5#015Fh2{3}3&cg_y Kg_s0bM1%k`&%lBJ delta 362 zcmeAaoXo?Q&&$ijz`(%J+U%bu&N`7#hOud)cD#l=LkedKR|`W5S1NOsKr?fcpgThf zcM4AnLkdqSM>BJjPzrA_gC^g^b1jn{nItEdF!r*EFfcF_iA-i^QWE81U|;|lTg=A5 zz`()4#mK=}#538BNrtg_ax~K!E=|T;EGda4i6NSk4Vl$AHJOSeL7Jr|CororYEEur zHe(c@yq7tNF>0~`ivgqN<UkffDH)Iwh$SGFC<6n-EtZ1(;u8MJb6BihG+ByxLE?NM zf*(ZafCz*JFbkxsNQ{Aj0i>*$1?)T?MzDyVrtsuA_R~R8yv{|bi6yCy0iI!*spUl= u&wv#aflWXVAhT|9*yQG?l;)(`F)}bP6bmshFz_%6F!F#Qh!$cJVF3VM^+6o~ diff --git a/MisplaceAI/authentication/urls.py b/MisplaceAI/authentication/urls.py index aeb6386..fdf2fd9 100644 --- a/MisplaceAI/authentication/urls.py +++ b/MisplaceAI/authentication/urls.py @@ -1,9 +1,12 @@ # MisplaceAI/authentication/urls.py from django.urls import path -from .views import RegisterView, LoginView +from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView +from .views import RegisterView, LoginView, AdminLoginView urlpatterns = [ path('register/', RegisterView.as_view(), name='register'), path('login/', LoginView.as_view(), name='login'), - + 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'), ] diff --git a/MisplaceAI/authentication/views.py b/MisplaceAI/authentication/views.py index f0eb4cd..e1e96bd 100644 --- a/MisplaceAI/authentication/views.py +++ b/MisplaceAI/authentication/views.py @@ -1,4 +1,5 @@ # MisplaceAI/authentication/views.py + from django.contrib.auth import authenticate from rest_framework import generics, status from rest_framework.response import Response @@ -6,6 +7,7 @@ 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() @@ -29,5 +31,20 @@ class LoginView(APIView): }, status=status.HTTP_200_OK) return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST) - - \ No newline at end of file +class AdminLoginView(APIView): + def post(self, request, *args, **kwargs): + serializer = LoginSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = authenticate( + username=serializer.validated_data['username'], + password=serializer.validated_data['password'] + ) + if user and user.is_superuser: + refresh = RefreshToken.for_user(user) + return Response({ + 'refresh': str(refresh), + 'access': str(refresh.access_token), + 'is_admin': True, + '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) diff --git a/frontend/src/forms/Auth/AdminLoginForm.js b/frontend/src/forms/Auth/AdminLoginForm.js index 6899fa3..3e295dc 100644 --- a/frontend/src/forms/Auth/AdminLoginForm.js +++ b/frontend/src/forms/Auth/AdminLoginForm.js @@ -1,7 +1,7 @@ // src/forms/Auth/AdminLoginForm.js import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import api from '../../services/api'; +import { adminLogin } from '../../services/auth'; // Import the adminLogin function import FormContainer from '../../components/Common/Form/FormContainer'; import FormField from '../../components/Common/Form/FormField'; import PasswordInputField from '../../components/Common/Password/PasswordInputField'; @@ -18,11 +18,7 @@ const AdminLoginForm = () => { const handleSubmit = async (e) => { e.preventDefault(); try { - const response = await api.post('/api/admin-app/login/', { username, password }); - localStorage.setItem('adminToken', response.data.access); - localStorage.setItem('isAdmin', true); - localStorage.setItem('username', username); - localStorage.setItem('isAuthenticated', true); + await adminLogin({ username, password }); navigate('/admin/dashboard'); window.location.reload(); // Refresh to update Navbar } catch (error) { diff --git a/frontend/src/forms/Auth/LoginForm.js b/frontend/src/forms/Auth/LoginForm.js index 930e31c..3c010c7 100644 --- a/frontend/src/forms/Auth/LoginForm.js +++ b/frontend/src/forms/Auth/LoginForm.js @@ -1,8 +1,7 @@ // src/forms/Auth/LoginForm.js - import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import api from '../../services/api'; +import { login } from '../../services/auth'; import FormContainer from '../../components/Common/Form/FormContainer'; import FormField from '../../components/Common/Form/FormField'; import PasswordInputField from '../../components/Common/Password/PasswordInputField'; @@ -19,10 +18,7 @@ const LoginForm = () => { const handleSubmit = async (e) => { e.preventDefault(); try { - const response = await api.post('/api/auth/login/', { username, password }); - localStorage.setItem('token', response.data.access); - localStorage.setItem('username', username); - localStorage.setItem('isAuthenticated', true); + await login({ username, password }); navigate('/'); window.location.reload(); // Refresh to update Navbar } catch (error) { diff --git a/frontend/src/forms/Auth/RegisterForm.js b/frontend/src/forms/Auth/RegisterForm.js index 41e5e8a..df18d2e 100644 --- a/frontend/src/forms/Auth/RegisterForm.js +++ b/frontend/src/forms/Auth/RegisterForm.js @@ -1,7 +1,7 @@ // src/forms/Auth/RegisterForm.js import React, { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; -import api from '../../services/api'; +import { register } from '../../services/auth'; import FormContainer from '../../components/Common/Form/FormContainer'; import FormField from '../../components/Common/Form/FormField'; import PasswordInputField from '../../components/Common/Password/PasswordInputField'; @@ -79,10 +79,7 @@ const RegisterForm = () => { return; } try { - const response = await api.post('/api/auth/register/', { username, email, password, password2 }); - localStorage.setItem('token', response.data.access); - localStorage.setItem('username', username); - localStorage.setItem('isAuthenticated', true); + await register({ username, email, password }); navigate('/'); window.location.reload(); // Refresh to update Navbar } catch (error) { diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 5e6e9d1..d74f49b 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -1,5 +1,6 @@ // src/services/api.js import axios from 'axios'; +import { refreshToken } from './auth'; // Import the refresh token function export const getCsrfToken = () => { const cookies = document.cookie.split(';'); @@ -13,25 +14,41 @@ export const getCsrfToken = () => { }; const api = axios.create({ - baseURL: 'http://localhost:8080', // Update this to match your Django backend address + baseURL: 'http://localhost:8080', headers: { 'Content-Type': 'application/json', }, }); api.interceptors.request.use( - config => { - const token = localStorage.getItem('token') || localStorage.getItem('adminToken'); - const csrfToken = getCsrfToken(); + async (config) => { + let token = localStorage.getItem('token') || localStorage.getItem('adminToken'); if (token) { + const tokenExpiry = localStorage.getItem('tokenExpiry'); + const now = Math.floor(Date.now() / 1000); + if (tokenExpiry && now >= tokenExpiry) { + // Token expired, refresh it + const newTokens = await refreshToken(); + if (newTokens) { + token = newTokens.access; + } else { + // Refresh token also expired or failed, logout user + localStorage.removeItem('token'); + localStorage.removeItem('refresh'); + localStorage.removeItem('tokenExpiry'); + window.location.href = '/login'; + return Promise.reject('Session expired. Please log in again.'); + } + } config.headers.Authorization = `Bearer ${token}`; } + const csrfToken = getCsrfToken(); if (csrfToken) { config.headers['X-CSRFToken'] = csrfToken; } return config; }, - error => Promise.reject(error) + (error) => Promise.reject(error) ); export default api; diff --git a/frontend/src/services/auth.js b/frontend/src/services/auth.js index 73b599b..42da518 100644 --- a/frontend/src/services/auth.js +++ b/frontend/src/services/auth.js @@ -5,6 +5,9 @@ export const login = async (credentials) => { 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; @@ -12,15 +15,53 @@ export const login = async (credentials) => { export const register = async (userData) => { 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; }; export const logout = () => { localStorage.removeItem('token'); + localStorage.removeItem('refresh'); + localStorage.removeItem('tokenExpiry'); localStorage.removeItem('isAuthenticated'); }; +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) { + logout(); + } + } + return null; +}; + export const getCurrentUser = async () => { const response = await api.get('/api/auth/user/'); return response.data; }; + + +export const adminLogin = async (credentials) => { + 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); + } + return response.data; +} + -- GitLab