Compare commits
3
Commits
65f2bb9c52
...
edf7b4b329
@@ -1,6 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MemberDot from "~/components/MemberDot.vue";
|
import MemberDot from "~/components/MemberDot.vue";
|
||||||
import BIIcon from "~/components/BIIcon.vue";
|
|
||||||
import { useMembers } from "~/composables/useMembers";
|
import { useMembers } from "~/composables/useMembers";
|
||||||
|
|
||||||
const { sortedMembers } = useMembers();
|
const { sortedMembers } = useMembers();
|
||||||
@@ -15,7 +14,7 @@ const { sortedMembers } = useMembers();
|
|||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="it-brand-wrapper">
|
<div class="it-brand-wrapper">
|
||||||
<a href="#" data-focus-mouse="false" class="d-flex align-items-center">
|
<a href="#" data-focus-mouse="false" class="d-flex align-items-center">
|
||||||
<img src="/icon.svg" alt="Logo Visione" style="width: 42px; height: 42px; margin-right: 12px;" />
|
<img alt="Logo Visione" src="/logo-light.svg" style="width: 42px; height: 42px; margin-right: 12px;"/>
|
||||||
|
|
||||||
<div class="it-brand-text">
|
<div class="it-brand-text">
|
||||||
<h2>Visione</h2>
|
<h2>Visione</h2>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, onUnmounted } from 'vue'
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import BIIcon from "~/components/BIIcon.vue";
|
import BIIcon from "~/components/BIIcon.vue";
|
||||||
|
|
||||||
@@ -26,15 +26,15 @@ onUnmounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { isAuthenticated, user, logout } = useAuth()
|
||||||
|
const panelLabel = computed(() => isAdmin(user.value?.role) ? 'Pannello' : 'Dashboard')
|
||||||
|
|
||||||
const navLinks = [
|
const navLinks = [
|
||||||
{ name: 'Home', path: '/' },
|
{ name: 'Home', path: '/' },
|
||||||
{ name: 'Fai una segnalazione', path: '/reports/new' },
|
...(isCitizen(user.value?.role) ? [{ name: 'Fai una segnalazione', path: '/reports/new' }] : []),
|
||||||
{ name: 'Esplora i dati', path: '/open-data' },
|
{ name: 'Esplora i dati', path: '/open-data' },
|
||||||
{ name: 'Come funziona', path: '/info' }
|
{ name: 'Come funziona', path: '/info' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const { isAuthenticated, user, logout } = useAuth()
|
|
||||||
const panelLabel = computed(() => isAdmin(user.value?.role) ? 'Pannello' : 'Dashboard')
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -107,7 +107,7 @@ const panelLabel = computed(() => isAdmin(user.value?.role) ? 'Pannello' : 'Dash
|
|||||||
<div class="it-header-center-content-wrapper">
|
<div class="it-header-center-content-wrapper">
|
||||||
<div class="it-brand-wrapper">
|
<div class="it-brand-wrapper">
|
||||||
<NuxtLink to="/">
|
<NuxtLink to="/">
|
||||||
<img src="/icon.svg" alt="Logo Visione" class="logo"/>
|
<img alt="Logo Visione" class="logo" src="/logo-light.svg"/>
|
||||||
<div class="it-brand-text">
|
<div class="it-brand-text">
|
||||||
<div class="it-brand-title">Visione</div>
|
<div class="it-brand-title">Visione</div>
|
||||||
<div class="it-brand-tagline d-none d-md-block">Un occhio per la sicurezza del cittadino.</div>
|
<div class="it-brand-tagline d-none d-md-block">Un occhio per la sicurezza del cittadino.</div>
|
||||||
|
|||||||
@@ -660,6 +660,7 @@ watch([() => form.value.latitude, () => form.value.longitude], ([newLat, newLng]
|
|||||||
height: 300px;
|
height: 300px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.address-suggestions {
|
.address-suggestions {
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import type {
|
import type {
|
||||||
CieSessionResponse,
|
CieSessionResponse,
|
||||||
CieStartResponse,
|
CieStartResponse,
|
||||||
CurrentUserResponse, EmailRequest, NotificationListResponse,
|
CurrentUserResponse,
|
||||||
|
EmailRequest,
|
||||||
|
NotificationListResponse,
|
||||||
OkResponse,
|
OkResponse,
|
||||||
OtpRequestResponse, RegisterCitizenRequest,
|
OtpRequestResponse,
|
||||||
RegisterCitizenResponse, RewardsResponse,
|
RegisterCitizenRequest,
|
||||||
TokenResponse, VerifyOtpRequest, UpdateProfileRequest
|
RegisterCitizenResponse,
|
||||||
|
RewardsResponse,
|
||||||
|
TokenResponse,
|
||||||
|
UpdateProfileRequest,
|
||||||
|
VerifyOtpRequest
|
||||||
} from "~/types/api";
|
} from "~/types/api";
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
@@ -66,6 +72,7 @@ export const useAuth = () => {
|
|||||||
token.value = jwtToken
|
token.value = jwtToken
|
||||||
tokenState.value = jwtToken
|
tokenState.value = jwtToken
|
||||||
await syncUser({ redirectOnSuccess: true })
|
await syncUser({ redirectOnSuccess: true })
|
||||||
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { checkRoleAccess, type MetaRoleRequirement } from '~/utils/roles'
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async (to) => {
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
const auth = useAuth()
|
const auth = useAuth()
|
||||||
|
|
||||||
@@ -14,13 +16,13 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|||||||
return navigateTo('/auth/login')
|
return navigateTo('/auth/login')
|
||||||
}
|
}
|
||||||
|
|
||||||
const requiredRole = to.meta.auth?.role
|
const requiredRole = to.meta.auth?.role as MetaRoleRequirement | undefined
|
||||||
|
|
||||||
if (requiredRole && auth.user.value) {
|
if (requiredRole && auth.user.value) {
|
||||||
const userRole = auth.user.value.role
|
const userRole = auth.user.value.role
|
||||||
|
|
||||||
if (!hasMinimumRole(userRole, requiredRole)) {
|
if (!checkRoleAccess(userRole, requiredRole)) {
|
||||||
console.warn(`[Auth] User role ${userRole} insufficient for required ${requiredRole}`)
|
console.warn(`[Auth] User role '${userRole}' rejected for route requirement:`, requiredRole)
|
||||||
return navigateTo('/')
|
return navigateTo('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import type { CreateReportRequest } from '~/types/api'
|
|||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
|
auth: {
|
||||||
|
role: ['!ADMIN', '!OPERATOR']
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useHead({ title: 'Nuova segnalazione - Visione' })
|
useHead({ title: 'Nuova segnalazione - Visione' })
|
||||||
|
|||||||
+33
-2
@@ -5,6 +5,8 @@ export const ROLES = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type Role = keyof typeof ROLES
|
export type Role = keyof typeof ROLES
|
||||||
|
export type NegatedRole = `!${Role}`
|
||||||
|
export type MetaRoleRequirement = Role | NegatedRole | Array<Role | NegatedRole>
|
||||||
|
|
||||||
export const ROLE_LEVELS: Record<string, number> = {
|
export const ROLE_LEVELS: Record<string, number> = {
|
||||||
[ROLES.CITIZEN]: 1,
|
[ROLES.CITIZEN]: 1,
|
||||||
@@ -17,12 +19,12 @@ export function normalizeRole(role?: string | null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isRole(userRole: string | undefined | null, targetRole: string): boolean {
|
export function isRole(userRole: string | undefined | null, targetRole: string): boolean {
|
||||||
return normalizeRole(userRole) === targetRole.toUpperCase()
|
return normalizeRole(userRole) === normalizeRole(targetRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasMinimumRole(userRole: string | undefined | null, requiredRole: string): boolean {
|
export function hasMinimumRole(userRole: string | undefined | null, requiredRole: string): boolean {
|
||||||
const userLvl = ROLE_LEVELS[normalizeRole(userRole)] || 0
|
const userLvl = ROLE_LEVELS[normalizeRole(userRole)] || 0
|
||||||
const reqLvl = ROLE_LEVELS[requiredRole.toUpperCase()] || 0
|
const reqLvl = ROLE_LEVELS[normalizeRole(requiredRole)] || 0
|
||||||
return userLvl >= reqLvl
|
return userLvl >= reqLvl
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,3 +43,32 @@ export function isCitizen(userRole?: string | null): boolean {
|
|||||||
export function canAccess(userRole: string | undefined | null, requiredRole: string): boolean {
|
export function canAccess(userRole: string | undefined | null, requiredRole: string): boolean {
|
||||||
return hasMinimumRole(userRole, requiredRole)
|
return hasMinimumRole(userRole, requiredRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkRoleAccess(userRole: string | undefined | null, requirement: MetaRoleRequirement | undefined): boolean {
|
||||||
|
if (!requirement) return true
|
||||||
|
|
||||||
|
const normalizedUserRole = normalizeRole(userRole)
|
||||||
|
if (!normalizedUserRole) return false
|
||||||
|
|
||||||
|
const requirements = Array.isArray(requirement) ? requirement : [requirement]
|
||||||
|
|
||||||
|
for (const req of requirements) {
|
||||||
|
const isNegated = req.startsWith('!')
|
||||||
|
|
||||||
|
const rawTarget = isNegated ? req.substring(1) : req
|
||||||
|
const targetRole = normalizeRole(rawTarget)
|
||||||
|
|
||||||
|
if (isNegated) {
|
||||||
|
console.log("negated", normalizedUserRole, targetRole)
|
||||||
|
if (normalizedUserRole === targetRole) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!hasMinimumRole(normalizedUserRole, targetRole)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
+4
-1
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1940.28 1402.502">
|
||||||
|
<path d="M1750.752 723.02a183 183 0 0 0-.378-15.172c-12.607-201.546-306.563-352.873-691.458-375.02L1104.93 0c-32.106 110.228-64.212 220.499-96.318 330.727-52.026-1.47-105.564-.546-160.069 2.858C365.313 363.842-14.414 580.516.42 817.489c14.877 236.972 418.642 404.52 901.83 374.263 115.02-7.186 224.197-24.962 323.584-51.059l255.63 261.809-207.975-275.172c271.389-81.148 459.572-225.752 476.087-383.425 63.582-4.329 127.122-8.657 190.704-12.985-63.162-2.648-126.366-5.253-189.528-7.9M1022.27 597.998c256.178 5.84 455.202 49.924 515.884 116.153-176.29-7.312-352.621-14.666-528.91-22.02 4.328-31.392 8.656-62.742 13.027-94.133ZM39.713 849.637C34.628 768.32 215.876 686.753 480.08 639.182c-69.381 32.568-116.742 97.747-116.742 172.801 0 100.605 85.056 183.392 194.234 193.814-296.562-8.867-512.438-69.381-517.86-156.16m771.22 151.664c-73.878 4.623-145.444 6.303-213.27 5.379 16.599-.84 32.652-3.74 47.906-8.531-16.64 4.664-34.207 7.564-52.32 8.447-2.898.168-5.882.252-8.824.294a165 165 0 0 1-13.742-.714c-99.765-8.573-178.055-92.285-178.055-194.277 0-78.71 46.605-146.536 113.8-177.34 22.778-10.464 47.95-16.641 74.467-17.523h.294q3.214-.127 6.43-.127c9.287 0 18.364.63 27.315 1.891.547-.084 1.093-.126 1.64-.21a77 77 0 0 1-2.396-1.554l6.009 1.134c53.244-6.514 108.758-11.64 165.826-15.213 49.504-3.11 97.915-4.874 144.898-5.421-7.229 24.879-14.499 49.715-21.727 74.592L620.187 618.17a106 106 0 0 0-3.614.42c.252.21.504.378.756.547h.043c11.052 1.722 21.81 4.328 32.19 7.858.21.042.42.084.588.168 28.66 9.666 54.379 25.845 75.307 46.773 35.342 35.3 57.194 84.047 57.194 137.963 0 2.27-.042 4.497-.126 6.724-2.774 82.493-56.816 152.042-131.324 177.72-.841.293-1.724.587-2.606.881 10.338-2.941 20.298-6.64 29.837-10.926 90.561-37.023 159.648-102.664 250-150.193 44.923 46.016 89.846 91.99 134.77 138.006-78.669 12.313-163.557 21.642-252.269 27.19m335.686-41.814-125.483-165.994c178.727-12.145 357.412-24.332 536.139-36.519 1.513 77.408-165.195 154.774-410.656 202.513"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1940.28 1402.502">
|
||||||
|
<path d="M1750.752 723.02a183 183 0 0 0-.378-15.172c-12.607-201.546-306.563-352.873-691.458-375.02L1104.93 0c-32.106 110.228-64.212 220.499-96.318 330.727-52.026-1.47-105.564-.546-160.069 2.858C365.313 363.842-14.414 580.516.42 817.489c14.877 236.972 418.642 404.52 901.83 374.263 115.02-7.186 224.197-24.962 323.584-51.059l255.63 261.809-207.975-275.172c271.389-81.148 459.572-225.752 476.087-383.425 63.582-4.329 127.122-8.657 190.704-12.985-63.162-2.648-126.366-5.253-189.528-7.9M1022.27 597.998c256.178 5.84 455.202 49.924 515.884 116.153-176.29-7.312-352.621-14.666-528.91-22.02 4.328-31.392 8.656-62.742 13.027-94.133ZM39.713 849.637C34.628 768.32 215.876 686.753 480.08 639.182c-69.381 32.568-116.742 97.747-116.742 172.801 0 100.605 85.056 183.392 194.234 193.814-296.562-8.867-512.438-69.381-517.86-156.16m771.22 151.664c-73.878 4.623-145.444 6.303-213.27 5.379 16.599-.84 32.652-3.74 47.906-8.531-16.64 4.664-34.207 7.564-52.32 8.447-2.898.168-5.882.252-8.824.294a165 165 0 0 1-13.742-.714c-99.765-8.573-178.055-92.285-178.055-194.277 0-78.71 46.605-146.536 113.8-177.34 22.778-10.464 47.95-16.641 74.467-17.523h.294q3.214-.127 6.43-.127c9.287 0 18.364.63 27.315 1.891.547-.084 1.093-.126 1.64-.21a77 77 0 0 1-2.396-1.554l6.009 1.134c53.244-6.514 108.758-11.64 165.826-15.213 49.504-3.11 97.915-4.874 144.898-5.421-7.229 24.879-14.499 49.715-21.727 74.592L620.187 618.17a106 106 0 0 0-3.614.42c.252.21.504.378.756.547h.043c11.052 1.722 21.81 4.328 32.19 7.858.21.042.42.084.588.168 28.66 9.666 54.379 25.845 75.307 46.773 35.342 35.3 57.194 84.047 57.194 137.963 0 2.27-.042 4.497-.126 6.724-2.774 82.493-56.816 152.042-131.324 177.72-.841.293-1.724.587-2.606.881 10.338-2.941 20.298-6.64 29.837-10.926 90.561-37.023 159.648-102.664 250-150.193 44.923 46.016 89.846 91.99 134.77 138.006-78.669 12.313-163.557 21.642-252.269 27.19m335.686-41.814-125.483-165.994c178.727-12.145 357.412-24.332 536.139-36.519 1.513 77.408-165.195 154.774-410.656 202.513"
|
||||||
|
style="fill:#fff"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
Reference in New Issue
Block a user