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";
|
} 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,
|
||||||
|
|||||||
@@ -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