This commit is contained in:
2026-05-23 02:17:37 +02:00
parent fed75d4820
commit 4f0cbe9145
9 changed files with 247 additions and 63 deletions
+11
View File
@@ -0,0 +1,11 @@
<script lang="ts" setup>
</script>
<template>
</template>
<style scoped>
</style>
+28
View File
@@ -0,0 +1,28 @@
import {useAuthStore} from '@/stores/auth'
export async function apiFetch(endpoint: string, options: RequestInit = {}) {
const authStore = useAuthStore()
const baseUrl = '/api' // Votre préfixe configuré dans mod.rs
const headers = new Headers(options.headers)
headers.set('Content-Type', 'application/json')
// On injecte le token s'il existe
if (authStore.token) {
headers.set('Authorization', `Bearer ${authStore.token}`)
}
const response = await fetch(`${baseUrl}${endpoint}`, {
...options,
headers,
})
// Gestion automatique de l'expiration du token (401 Unauthorized)
if (response.status === 401) {
authStore.logout()
// Optionnel : rediriger vers /login
window.location.href = '/login'
}
return response
}
+34 -1
View File
@@ -5,17 +5,50 @@
*/
// Composables
import { createRouter, createWebHistory } from 'vue-router'
import {createRouter, createWebHistory} from 'vue-router'
import Index from '@/pages/index.vue'
import {useAuthStore} from '@/stores/auth'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('@/pages/login.vue'),
},
{
path: '/',
component: Index,
},
{
path: '/admin',
name: 'admin-dashboard',
component: () => import('@/pages/admin/Dashboard.vue'),
meta: {requiresAdmin: true}
},
],
})
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
const publicPages = ['login', 'register']
const authRequired = !publicPages.includes(to.name as string)
const adminRequired = to.matched.some(record => record.meta.requiresAdmin)
if (authRequired && !authStore.isAuthenticated) {
// Non connecté -> Login
next('/login')
} else if (to.name === 'login' && authStore.isAuthenticated) {
// Déjà connecté -> Accueil
next('/')
} else if (adminRequired && !authStore.isAdmin) {
// Connecté mais pas admin -> Accueil (ou page 403)
next('/')
} else {
next()
}
})
export default router
+52
View File
@@ -0,0 +1,52 @@
import {defineStore} from 'pinia'
interface UserClaims {
user_id: string
username: string
is_admin: boolean // On s'assure que le backend l'envoie ou on le déduit
exp: number
}
export const useAuthStore = defineStore('auth', {
state: () => ({
token: localStorage.getItem('token') || null as string | null,
user: JSON.parse(localStorage.getItem('user') || 'null') as UserClaims | null,
}),
getters: {
isAuthenticated: (state) => !!state.token,
isAdmin: (state) => state.user?.is_admin || false,
},
actions: {
setToken(token: string) {
this.token = token
localStorage.setItem('token', token)
try {
// Décoder le payload du JWT (2ème partie du string)
const base64Url = token.split('.')[1]
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join(''))
const decoded = JSON.parse(jsonPayload)
this.user = {
user_id: decoded.user_id,
username: decoded.username,
is_admin: decoded.is_superuser || false, // Vérifiez le nom du champ dans votre Claims Rust
exp: decoded.expire_at
}
localStorage.setItem('user', JSON.stringify(this.user))
} catch (e) {
console.error("Failed to decode token", e)
this.logout()
}
},
logout() {
this.token = null
this.user = null
localStorage.removeItem('token')
localStorage.removeItem('user')
}
}
})