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:
@@ -8,6 +8,55 @@ import type {
|
||||
} from "~/types/api";
|
||||
|
||||
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) =>
|
||||
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' })
|
||||
|
||||
return {
|
||||
user,
|
||||
token,
|
||||
tokenState,
|
||||
isSyncingUser,
|
||||
isAuthenticated,
|
||||
handleLoginSuccess,
|
||||
syncUser,
|
||||
logout,
|
||||
|
||||
register,
|
||||
requestOtp,
|
||||
verifyOtp,
|
||||
|
||||
@@ -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('/')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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')
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
declare module '#app' {
|
||||
interface PageMeta {
|
||||
auth?: {
|
||||
role?: 'CITIZEN' | 'OPERATOR' | 'ADMIN'
|
||||
}
|
||||
}
|
||||
}
|
||||
export {}
|
||||
Reference in New Issue
Block a user