diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index c593da0..2f23a89 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -42,12 +42,13 @@ const logout = () => {
success: () => {
return 'Logout Sucessfull '
},
- error: (data) => `[API] Error saving game - ${data?.response?.data?.message}`,
+ error: (data) => `[API] Error - ${data?.response?.data?.message}`,
})
}
-onMounted(() => {
+onMounted(async () => {
+ await authStore.restoreSession()
socketStore.handleConnection()
})
diff --git a/frontend/src/components/layout/NavBar.vue b/frontend/src/components/layout/NavBar.vue
index cc66d7a..ac999c3 100644
--- a/frontend/src/components/layout/NavBar.vue
+++ b/frontend/src/components/layout/NavBar.vue
@@ -39,6 +39,7 @@ import {
NavigationMenuList,
NavigationMenuTrigger,
} from '@/components/ui/navigation-menu'
+import router from '@/router';
const emits = defineEmits(['logout'])
@@ -46,5 +47,6 @@ const { userLoggedIn } = defineProps(['userLoggedIn'])
const logoutClickHandler = () => {
emits('logout')
+ router.push('/login')
}
diff --git a/frontend/src/pages/login/LoginPage.vue b/frontend/src/pages/login/LoginPage.vue
index 9dd4ce2..1fd8457 100644
--- a/frontend/src/pages/login/LoginPage.vue
+++ b/frontend/src/pages/login/LoginPage.vue
@@ -27,6 +27,14 @@
+
+
+
+
+
@@ -59,7 +67,8 @@ const router = useRouter()
const formData = ref({
email: 'pa@mail.pt',
- password: '123'
+ password: '123',
+ rememberMe: false
})
diff --git a/frontend/src/stores/api.js b/frontend/src/stores/api.js
index 8774bd2..6e2dff6 100644
--- a/frontend/src/stores/api.js
+++ b/frontend/src/stores/api.js
@@ -1,9 +1,11 @@
import { defineStore } from 'pinia'
import axios from 'axios'
import { inject, ref } from 'vue'
+import { useAuthStore } from './auth'
export const useAPIStore = defineStore('api', () => {
const API_BASE_URL = inject('apiBaseURL')
+ const authStore = useAuthStore()
const token = ref()
const gameQueryParameters = ref({
@@ -16,16 +18,35 @@ export const useAPIStore = defineStore('api', () => {
},
})
+ const resetExpiryOnSuccess = (response) => {
+ if (authStore.isLoggedIn && !authStore.isTokenAlmostExpired()) {
+ authStore.resetTokenExpiry()
+ }
+ return response
+ }
+
+ axios.interceptors.response.use(resetExpiryOnSuccess)
+
+ const initializeAxiosHeaders = () => {
+ const storedToken = localStorage.getItem('token')
+ if (storedToken) {
+ axios.defaults.headers.common['Authorization'] = `Bearer ${storedToken}`
+ }
+ }
+
+ initializeAxiosHeaders()
+
const postLogin = async (credentials) => {
- const response = await axios.post(`${API_BASE_URL}/login`, credentials)
+ const response = await axios.post(`${API_BASE_URL}/login`, credentials)
- if (!response.data.token) throw response
+ if (!response.data.token) throw response
- token.value = response.data.token
- axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
+ token.value = response.data.token
+ axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
+
+ return response
+ }
- return response
-}
const postLogout = async () => {
await axios.post(`${API_BASE_URL}/logout`)
token.value = undefined
diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js
index 43c8434..f00e752 100644
--- a/frontend/src/stores/auth.js
+++ b/frontend/src/stores/auth.js
@@ -3,25 +3,46 @@ import { ref, computed } from 'vue'
import { useAPIStore } from './api'
export const useAuthStore = defineStore('auth', () => {
- const apiStore = useAPIStore()
+
+ // Token timeout constants (in milliseconds)
+ const DEFAULT_TIMEOUT = 5 * 60 * 1000 // 5 minutes
+ const REMEMBER_ME_TIMEOUT = 365 * 24 * 60 * 60 * 1000 // 1 year
const currentUser = ref(undefined)
const token = ref(localStorage.getItem('token') || null)
+ const tokenExpiry = ref(localStorage.getItem('tokenExpiry') ? parseInt(localStorage.getItem('tokenExpiry')) : null)
+ const rememberMe = ref(false)
const isLoggedIn = computed(() => {
- return currentUser.value !== undefined && token.value !== null
+ return currentUser.value !== undefined && token.value !== null && !isTokenAlmostExpired()
})
const currentUserID = computed(() => {
return currentUser.value?.id
})
- const setToken = (jwtToken) => {
+ const isTokenAlmostExpired = () => {
+ if (!tokenExpiry.value) return false;
+ return Date.now() > (tokenExpiry.value - 60 * 1000);
+ };
+
+ const resetTokenExpiry = () => {
+ const timeout = rememberMe.value ? REMEMBER_ME_TIMEOUT : DEFAULT_TIMEOUT
+ tokenExpiry.value = Date.now() + timeout
+ localStorage.setItem('tokenExpiry', tokenExpiry.value.toString())
+ }
+
+ const setToken = (jwtToken, shouldRememberMe = false) => {
token.value = jwtToken
+ rememberMe.value = shouldRememberMe
+
if (jwtToken) {
localStorage.setItem('token', jwtToken)
+ resetTokenExpiry()
} else {
localStorage.removeItem('token')
+ localStorage.removeItem('tokenExpiry')
+ tokenExpiry.value = null
}
}
@@ -30,18 +51,70 @@ export const useAuthStore = defineStore('auth', () => {
}
const login = async (credentials) => {
- const loginResp = await apiStore.postLogin(credentials)
- const userResp = await apiStore.getAuthUser()
- const jwtToken = loginResp.data.token
- setToken(jwtToken)
- currentUser.value = userResp.data
- return userResp.data
+ const apiStore = useAPIStore()
+ const loginResp = await apiStore.postLogin(credentials)
+ const userResp = await apiStore.getAuthUser()
+
+ const jwtToken = loginResp.data.token
+ setToken(jwtToken, credentials.rememberMe)
+ currentUser.value = userResp.data
+
+ localStorage.setItem('currentUser', JSON.stringify(userResp.data))
+ return userResp.data
}
const logout = async () => {
+ const apiStore = useAPIStore()
await apiStore.postLogout()
currentUser.value = undefined
setToken(null)
+ localStorage.removeItem('currentUser')
+ }
+
+ const checkTokenExpiry = () => {
+ if (isTokenAlmostExpired()) {
+ logout()
+ return false
+ }
+ return true
+ }
+
+ const restoreSession = async () => {
+ const storedToken = localStorage.getItem('token')
+ const storedUser = localStorage.getItem('currentUser')
+ const storedTokenExpiry = localStorage.getItem('tokenExpiry')
+
+ if (!storedToken || !storedTokenExpiry) {
+ return false
+ }
+ if (Date.now() > parseInt(storedTokenExpiry)) {
+ localStorage.removeItem('token')
+ localStorage.removeItem('tokenExpiry')
+ localStorage.removeItem('currentUser')
+ return false
+ }
+
+ token.value = storedToken
+ tokenExpiry.value = parseInt(storedTokenExpiry)
+
+ if (storedUser) {
+ try {
+ currentUser.value = JSON.parse(storedUser)
+ } catch (e) {
+ // If stored user data is corrupted, fetch fresh data from API
+ try {
+ const userResp = await apiStore.getAuthUser()
+ currentUser.value = userResp.data
+ localStorage.setItem('currentUser', JSON.stringify(userResp.data))
+ } catch (apiError) {
+ // API call failed, likely token is invalid
+ logout()
+ return false
+ }
+ }
+ }
+
+ return true
}
return {
@@ -49,9 +122,14 @@ export const useAuthStore = defineStore('auth', () => {
isLoggedIn,
currentUserID,
token,
+ rememberMe,
setToken,
getToken,
login,
logout,
+ resetTokenExpiry,
+ isTokenAlmostExpired,
+ checkTokenExpiry,
+ restoreSession,
}
})