diff --git a/frontend/shared/composables/usePermissions.ts b/frontend/shared/composables/usePermissions.ts new file mode 100644 index 0000000..063b50f --- /dev/null +++ b/frontend/shared/composables/usePermissions.ts @@ -0,0 +1,38 @@ +import { useAuthStore } from '~/shared/stores/auth' + +/** + * Composable d'autorisation cote front. + * + * Source de verite : `useAuthStore().user`, qui porte le payload /api/me + * incluant `isAdmin` et `effectivePermissions` (tableau trie sans doublons). + * + * Regle de bypass dupliquee avec `PermissionVoter` (back) : + * si `user.isAdmin === true`, toutes les permissions sont accordees. + * Cette duplication est volontaire pour offrir un feedback UI immediat + * sans aller-retour serveur. Si la regle de bypass change cote back + * (decision architecturale #343 section 11), ce composable DOIT evoluer + * en meme temps. + * + * Stateless : aucun ref module-level, tout passe par Pinia. Le reset est + * assure automatiquement par `authStore.logout()` qui efface `user`. + */ +export function usePermissions() { + const auth = useAuthStore() + + function can(code: string): boolean { + const user = auth.user + if (!user) return false + if (user.isAdmin) return true + return user.effectivePermissions.includes(code) + } + + function canAny(codes: string[]): boolean { + return codes.some(can) + } + + function canAll(codes: string[]): boolean { + return codes.every(can) + } + + return { can, canAny, canAll } +} diff --git a/frontend/shared/types/user-data.ts b/frontend/shared/types/user-data.ts index 25d5c2b..8fd024e 100644 --- a/frontend/shared/types/user-data.ts +++ b/frontend/shared/types/user-data.ts @@ -2,4 +2,8 @@ export interface UserData { id: number username: string roles: string[] + /** Vrai si l'utilisateur a le bypass admin total (voir ticket #343 section 11). */ + isAdmin: boolean + /** Codes de permission effectifs de l'utilisateur, tries alphabetiquement, sans doublon. */ + effectivePermissions: string[] }