feat: implement auth middleware, user sync, and token state persistence

- Define useAuth composable logic with syncUser, handleLoginSuccess, and logout methods
- Add auth.ts and guest.ts route middlewares to guard routes based on authentication and user roles
- Add auth.client.ts plugin to boot-sync logged in user details
- Declare custom PageMeta type definitions to support role-based authorization rules
This commit is contained in:
2026-06-12 19:04:19 +02:00 Unverified
parent 834fdade2b
commit 7499ca60f6
5 changed files with 138 additions and 0 deletions
+58
View File
@@ -8,6 +8,55 @@ import type {
} from "~/types/api"; } from "~/types/api";
export const useAuth = () => { export const useAuth = () => {
const token = useCookie('auth_token', {
maxAge: 60 * 60 * 12,
sameSite: 'lax',
watch: true
})
const tokenState = useState<string | null>('auth_token_state', () => token.value ?? null)
type UserContext = CurrentUserResponse['user']
const user = useState<UserContext | null>('auth_user', () => null)
const isSyncingUser = useState<boolean>('auth_syncing', () => false)
const isAuthenticated = computed(() => !!(tokenState.value || token.value))
watch(() => token.value, (newVal) => {
tokenState.value = newVal || null
})
const syncUser = async () => {
const tokenVal = tokenState.value || token.value
if (!tokenVal || isSyncingUser.value) return
isSyncingUser.value = true
try {
const res = await fetchCurrentUser()
if (res.ok) {
console.log('[Auth] User synced successfully')
user.value = res.user
}
} catch (error) {
console.error('[Auth] Sync failed, logging out:', error)
logout()
} finally {
isSyncingUser.value = false
}
}
const handleLoginSuccess = async (jwtToken: string) => {
token.value = jwtToken
tokenState.value = jwtToken
await syncUser()
}
const logout = () => {
token.value = null
tokenState.value = null
user.value = null
navigateTo('/auth/login')
}
const register = (body: RegisterCitizenRequest) => const register = (body: RegisterCitizenRequest) =>
useApiFetch<RegisterCitizenResponse>('/api/v1/auth/register', { method: 'POST', body }) useApiFetch<RegisterCitizenResponse>('/api/v1/auth/register', { method: 'POST', body })
@@ -36,6 +85,15 @@ export const useAuth = () => {
useApiFetch<OkResponse>(`/api/v1/notifications/${id}/read`, { method: 'PATCH' }) useApiFetch<OkResponse>(`/api/v1/notifications/${id}/read`, { method: 'PATCH' })
return { return {
user,
token,
tokenState,
isSyncingUser,
isAuthenticated,
handleLoginSuccess,
syncUser,
logout,
register, register,
requestOtp, requestOtp,
verifyOtp, verifyOtp,
+39
View File
@@ -0,0 +1,39 @@
export default defineNuxtRouteMiddleware(async (to) => {
const auth = useAuth()
// Se c'è un token ma l'utente non è ancora caricato e non stiamo già sincronizzando, sincronizza
if ((auth.token.value || auth.tokenState.value) && !auth.user.value && !auth.isSyncingUser.value) {
try {
await auth.syncUser()
} catch (error) {
console.error('[Auth Middleware] Sync failed:', error)
return navigateTo('/auth/login')
}
}
// Se non autenticato, redirige al login
if (!auth.isAuthenticated.value) {
return navigateTo('/auth/login')
}
// Verifica ruoli se richiesto
const requiredRole = to.meta.auth?.role
if (requiredRole && auth.user.value) {
const userRole = auth.user.value.role
const roleLevels: Record<string, number> = {
'CITIZEN': 1,
'OPERATOR': 2,
'ADMIN': 3
}
const userLevel = roleLevels[userRole] || 0
const requiredLevel = roleLevels[requiredRole] || 0
if (userLevel < requiredLevel) {
console.warn(`[Auth] User role ${userRole} (level ${userLevel}) insufficient for required ${requiredRole} (level ${requiredLevel})`)
return navigateTo('/')
}
}
})
+17
View File
@@ -0,0 +1,17 @@
export default defineNuxtRouteMiddleware(async () => {
const auth = useAuth()
// Se c'è un token ma l'utente non è ancora caricato e non stiamo già sincronizzando, sincronizza
if ((auth.token.value || auth.tokenState.value) && !auth.user.value && !auth.isSyncingUser.value) {
try {
await auth.syncUser()
} catch (error) {
console.error('[Guest Middleware] Sync failed:', error)
}
}
// Se autenticato, redirige al dashboard
if (auth.isAuthenticated.value && auth.user.value) {
return navigateTo('/dashboard')
}
})
+16
View File
@@ -0,0 +1,16 @@
export default defineNuxtPlugin(async () => {
const auth = useAuth()
if ((auth.token.value || auth.tokenState.value) && !auth.user.value) {
try {
await auth.syncUser()
} catch (error) {
console.error('[Auth Plugin] Sync failed:', error)
auth.logout()
}
}
console.log('[Auth Plugin] Initialized - User:', auth.user.value?.id || 'None', 'Authenticated:', auth.isAuthenticated.value)
})
+8
View File
@@ -0,0 +1,8 @@
declare module '#app' {
interface PageMeta {
auth?: {
role?: 'CITIZEN' | 'OPERATOR' | 'ADMIN'
}
}
}
export {}