Helpers

Permissions

RBAC client-side per UI. checkPermissions, checkRouteAccess, direttiva v-can via createCanDirective.

API

function checkPermissions(
  requiredPermissions: string[] | undefined,
  userPermissions: string[] | undefined,
): boolean

function checkRouteAccess(
  route: MFERoute,
  userRoles: string[] | undefined,
): boolean

function createCanDirective(
  getPermissions: () => string[] | undefined,
): ObjectDirective<HTMLElement, string | string[]>
Sicurezza UI, NON sicurezza reale. Questi helper nascondono/disabilitano elementi della UI ma non sono un gate di sicurezza: gli elementi nascosti restano nel DOM, possono essere riattivati via DevTools, e i fetch di ctx.http partono comunque. La sicurezza reale è server-side (API authorization).

checkPermissions(required, user)

Verifica se l'utente ha almeno uno dei permessi richiesti (logica OR).

import { checkPermissions } from '@pzeta/mfe-contracts/helpers'

checkPermissions(['crm.write'], ['crm.read', 'crm.write']) // true
checkPermissions(['crm.write'], ['crm.read'])              // false
checkPermissions(undefined, ['crm.read'])                  // true  (no required → allow)
checkPermissions(['crm.write'], undefined)                 // false (no user perms → deny)
checkPermissions(['anything'], ['*'])                      // true  (wildcard admin)

Regole

requireduserRisultatoMotivo
undefined / []qualsiasitrueNessun permesso richiesto
qualsiasiundefined / []falseUtente senza permessi
qualsiasicontiene '*'trueWildcard admin UI
[a, b]contiene a o btrueOR logic
[a, b]non contiene né abfalse

Wildcard *

const admin: MFEUser = { id: '1', username: 'admin', permissions: ['*'] }

L'admin UI ha accesso a tutti gli elementi senza enumerare ogni permesso. Server-side deve comunque validare ogni operazione.

checkRouteAccess(route, userRoles)

Verifica se l'utente può accedere a una rotta in base a route.meta.roles.

import { checkRouteAccess } from '@pzeta/mfe-contracts/helpers'

const route: MFERoute = {
  path: '/admin',
  label: 'Admin Panel',
  meta: { roles: ['admin', 'superuser'] },
}

checkRouteAccess(route, ['user', 'admin'])    // true
checkRouteAccess(route, ['user'])             // false
checkRouteAccess(route, undefined)            // false
checkRouteAccess({ path: '/', label: 'Home' }, undefined) // true (no roles richiesti)

Regole

  • Se route.meta.roles è vuoto o assente → true (nessun vincolo di ruolo)
  • Altrimenti → l'utente deve avere almeno uno dei ruoli richiesti
checkRouteAccess verifica solo i ruoli, non i permessi. Per check permission usa checkPermissions(route.permission ? [route.permission] : [], user.permissions).

Direttiva v-can

Factory che crea una direttiva Vue per mostrare/nascondere/disabilitare elementi in base ai permessi utente.

function createCanDirective(
  getPermissions: () => string[] | undefined,
): ObjectDirective<HTMLElement, string | string[]>

Registrazione nella shell

// shell/main.ts
import { createApp, computed } from 'vue'
import { createCanDirective } from '@pzeta/mfe-contracts'
import App from './App.vue'

const app = createApp(App)

const userPermissions = computed(() => currentUser.value?.permissions ?? [])

app.directive('can', createCanDirective(() => userPermissions.value))

app.mount('#app')
La factory accetta un getter (() => string[] | undefined): viene chiamato ogni volta che la direttiva valuta i permessi. Così quando i permessi dell'utente cambiano (login/logout/refresh), la valutazione si aggiorna.

Uso nei template

<template>
  <!-- Nasconde l'elemento se manca il permesso -->
  <button v-can="'crm.clienti.delete'" @click="onDelete">
    Elimina cliente
  </button>

  <!-- Disabilita invece di nascondere -->
  <button v-can.disable="'crm.clienti.export'" @click="onExport">
    Esporta CSV
  </button>

  <!-- OR logic: basta uno dei permessi -->
  <a v-can="['crm.clienti.read', 'crm.admin']" :href="`/clienti/${id}`">
    Apri dettaglio
  </a>

  <!-- Niente o stringa vuota → elemento sempre visibile -->
  <button v-can="">Sempre visibile</button>
</template>

Modalità

SintassiComportamento se manca permesso
v-can="'perm'"style.display = 'none' (nasconde)
v-can.disable="'perm'"setAttribute('disabled', 'disabled') + opacity: 0.5 + pointer-events: none

Quando il permesso è concesso, l'elemento viene ripristinato allo stato originale (display salvato in mount).

Comportamento lifecycle

HookCosa fa
mounted(el, binding)Salva el.style.display originale. Se value non vuoto, applica check
updated(el, binding)Se value diventa vuoto → ripristina elemento. Altrimenti riapplica check

Reattività

La direttiva si aggiorna automaticamente quando binding.value cambia (Vue chiama updated()). Per cambi nei permessi utente (senza cambio binding), il getter viene rivalutato ad ogni updated() triggering successivo.

Per forzare la rivalutazione su cambio permessi anche senza cambio v-can, ri-renderizza l'albero con :key:

<div :key="`perms-${user.permissions?.join(',')}`">
  <button v-can="'admin'">Admin only</button>
</div>

Pattern: composizione con manifest permission

import { checkPermissions } from '@pzeta/mfe-contracts/helpers'

// In shell: verifica se l'utente può aprire il MFE
const canOpenMfe = (manifest: MFEManifest, user: MFEUser) =>
  checkPermissions(manifest.permissions, user.permissions)

// Per filtrare il menu prima del rendering
const accessibleMfes = manifests.filter((m) => canOpenMfe(m, currentUser))

Tipi correlati