From 22867b5765fc5e2dd385447b7a691ef2f0dbd1e7 Mon Sep 17 00:00:00 2001 From: Jonathan Bourdon Date: Tue, 22 Apr 2025 15:39:53 -0400 Subject: [PATCH] feat: Update authentication and profile components --- frontend/src/stores/authStore.js | 118 ++++++++++---- frontend/src/views/profile/ProfilePage.vue | 174 +++++++++++---------- 2 files changed, 176 insertions(+), 116 deletions(-) diff --git a/frontend/src/stores/authStore.js b/frontend/src/stores/authStore.js index 6e8ca6f..4b260df 100644 --- a/frontend/src/stores/authStore.js +++ b/frontend/src/stores/authStore.js @@ -6,6 +6,7 @@ import {useSessionStorage} from "@vueuse/core"; import {jwtDecode} from "jwt-decode"; function getClaimsFromToken(token) { + if (!token) return null; try { return jwtDecode(token); } catch (error) { @@ -17,12 +18,15 @@ function getClaimsFromToken(token) { function isTokenExpiringSoon(token) { if (!token) return true; const claims = getClaimsFromToken(token); - if (!claims) return true; + if (!claims || !claims.exp) return true; // Check if token will expire in the next 5 minutes const expirationTime = claims.exp * 1000; // Convert to milliseconds const currentTime = Date.now(); - return currentTime >= expirationTime - 5 * 60 * 1000; // 5 minutes before expiration + const fiveMinutesInMs = 5 * 60 * 1000; + + // Return true if token is expired or will expire in the next 5 minutes + return currentTime >= expirationTime - fiveMinutesInMs; } export const useAuthStore = defineStore( @@ -38,77 +42,121 @@ export const useAuthStore = defineStore( const accessToken = useSessionStorage('auth-accessToken', undefined) const refreshToken = useSessionStorage('auth-refreshToken', undefined) + // Cache for decoded claims using session storage with proper serialization + const tokenClaims = useSessionStorage('auth-tokenClaims', null, { + serializer: { + read: (v) => v ? JSON.parse(v) : null, + write: (v) => v ? JSON.stringify(v) : null + } + }) const isAuthenticated = computed(() => !!accessToken.value) - const userId = computed(() => { - const claims = getClaimsFromToken(accessToken.value) - return claims?.sub; - }) + const userId = computed(() => tokenClaims.value?.sub) function updateTokens(data) { - accessToken.value = data.accessToken - refreshToken.value = data.refreshToken + if (!data?.accessToken || !data?.refreshToken) { + throw new Error('Invalid token data'); + } + accessToken.value = data.accessToken; + refreshToken.value = data.refreshToken; + // Update claims cache when we get new tokens + const claims = getClaimsFromToken(data.accessToken); + tokenClaims.value = claims; } function cleanTokens() { - updateTokens({ - accessToken: undefined, - refreshToken: undefined, - }) + accessToken.value = undefined; + refreshToken.value = undefined; + tokenClaims.value = null; + // Clear any other auth-related data if needed } async function logout() { - cleanTokens() - await router.push('/') + try { + // Optionally call logout endpoint if you have one + // await clientApi.post('api/users/logout'); + } catch (error) { + console.error('Logout failed:', error); + } finally { + cleanTokens(); + await router.push('/'); + } } async function login(email, password) { + if (!email || !password) { + throw new Error('Email and password are required'); + } + try { const response = await clientApi.post( 'api/users/login', { - email: email, + email: email.trim(), password: password - }) - updateTokens(response.data) - return true + }); + + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid login response'); + } + + updateTokens(response.data); + return true; } catch (error) { - console.error(error) - cleanTokens() - return false + console.error('Login failed:', error); + cleanTokens(); + throw error; } } async function loginWithGoogle(accessToken) { + if (!accessToken) { + throw new Error('Google access token is required'); + } + try { const response = await clientApi.post( 'api/users/login-with-google', { token: accessToken - }) - updateTokens(response.data) - return true + }); + + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid Google login response'); + } + + updateTokens(response.data); + return true; } catch (error) { - console.error(error) - cleanTokens() - return false + console.error('Google login failed:', error); + cleanTokens(); + throw error; } } async function loginWithFacebook(authResponse) { + if (!authResponse?.accessToken) { + throw new Error('Facebook access token is required'); + } + try { const response = await clientApi.post( 'api/users/login-with-facebook', { token: authResponse.accessToken - }) - updateTokens(response.data) - return true + }); + + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid Facebook login response'); + } + + updateTokens(response.data); + return true; } catch (error) { - console.error(error) - cleanTokens() - return false + console.error('Facebook login failed:', error); + cleanTokens(); + throw error; } } @@ -133,6 +181,10 @@ export const useAuthStore = defineStore( refreshToken: refreshToken.value }); + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid refresh response'); + } + updateTokens({ accessToken: response.data.accessToken, refreshToken: response.data.refreshToken diff --git a/frontend/src/views/profile/ProfilePage.vue b/frontend/src/views/profile/ProfilePage.vue index 9662fa3..df696e2 100644 --- a/frontend/src/views/profile/ProfilePage.vue +++ b/frontend/src/views/profile/ProfilePage.vue @@ -18,9 +18,9 @@ import Linkedin from "@/views/svg/Linkedin.vue"; import Tiktok from "@/views/svg/Tiktok.vue"; import Instagram from "@/views/svg/Instagram.vue"; import Facebook from "@/views/svg/Facebook.vue"; -import { useI18n } from 'vue-i18n'; +import {useI18n} from 'vue-i18n'; -const { t } = useI18n(); +const {t} = useI18n(); const userProfileStore = useUserProfileStore() // ### Fullname @@ -116,63 +116,6 @@ function handleDelete() { + + + + + + + + + + + +
+
{{ t('restoreCreatorPage') }}
+
+

{{ t('restoreWarning') }}

+
+ + +
+
+
+
+ +
+
{{ t('deleteCreatorPage') }}
+
+

{{ t('deleteWarning') }}

+
+ + +
+
+
+
+