redirection page 401 pour les user non autorises
This commit is contained in:
parent
1d18659bd7
commit
34875321ac
@ -65,6 +65,8 @@
|
||||
<script>
|
||||
import SidenavItem from "./SidenavItem.vue";
|
||||
import SidenavCollapse from "./SidenavCollapse.vue";
|
||||
import pinia from "@/plugins/pinia";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
|
||||
import Shop from "../../components/Icon/Shop.vue";
|
||||
import Office from "../../components/Icon/Office.vue";
|
||||
@ -87,10 +89,13 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState(["isRTL"]),
|
||||
authStore() {
|
||||
return useAuthStore(pinia);
|
||||
},
|
||||
|
||||
// Navigation data structure
|
||||
navigationItems() {
|
||||
return [
|
||||
const items = [
|
||||
{
|
||||
id: "dashboard",
|
||||
type: "collapse",
|
||||
@ -410,37 +415,118 @@ export default {
|
||||
icon: "Office",
|
||||
collapseRef: "parametrageMenu",
|
||||
routeKey: "parametrage",
|
||||
requiredAny: [
|
||||
{ role: "administrator" },
|
||||
{ permission: "config.view" },
|
||||
{ permission: "config.view_roles" },
|
||||
{ permission: "crud.user" },
|
||||
],
|
||||
children: [
|
||||
{
|
||||
id: "utilisateurs",
|
||||
route: { name: "Gestion utilisateurs" },
|
||||
miniIcon: "U",
|
||||
text: "Gestion utilisateurs",
|
||||
requiredPermission: "crud.user",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
{
|
||||
id: "utilisateurs-create",
|
||||
route: { name: "Créer utilisateur" },
|
||||
miniIcon: "A",
|
||||
text: "Créer utilisateur",
|
||||
requiredPermission: "crud.user",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
{
|
||||
id: "droits",
|
||||
route: { name: "Gestion droits" },
|
||||
miniIcon: "D",
|
||||
text: "Gestion des droits",
|
||||
requiredPermission: "config.view_roles",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
{
|
||||
id: "emails",
|
||||
route: { name: "Gestion emails" },
|
||||
miniIcon: "E",
|
||||
text: "Gestion des emails",
|
||||
requiredPermission: "config.view",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
{
|
||||
id: "modeles",
|
||||
route: { name: "Gestion modeles" },
|
||||
miniIcon: "M",
|
||||
text: "Gestion des modèles",
|
||||
requiredPermission: "config.view",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
{
|
||||
id: "categories-produits",
|
||||
route: { name: "Product Categories" },
|
||||
miniIcon: "C",
|
||||
text: "Catégories Produits",
|
||||
requiredPermission: "config.view",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return this.filterNavigationItems(items);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
canAccessItem(item) {
|
||||
if (item.allowAdministrator && this.authStore.hasRole("administrator")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.requiredPermission) {
|
||||
return this.authStore.hasPermission(item.requiredPermission);
|
||||
}
|
||||
|
||||
if (Array.isArray(item.requiredAny) && item.requiredAny.length > 0) {
|
||||
return item.requiredAny.some((access) => {
|
||||
if (access.role) {
|
||||
return this.authStore.hasRole(access.role);
|
||||
}
|
||||
|
||||
if (access.permission) {
|
||||
return this.authStore.hasPermission(access.permission);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
filterNavigationItems(items) {
|
||||
return items
|
||||
.map((item) => {
|
||||
if (item.type !== "collapse") {
|
||||
return item;
|
||||
}
|
||||
|
||||
const children = (item.children || []).filter((child) =>
|
||||
this.canAccessItem(child)
|
||||
);
|
||||
|
||||
if (!children.length || !this.canAccessItem(item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
children,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
},
|
||||
|
||||
getRoute() {
|
||||
const routeArr = this.$route.path.split("/");
|
||||
return routeArr[1];
|
||||
|
||||
@ -48,6 +48,7 @@ import VerificationIllustration from "../views/auth/verification/Illustration.vu
|
||||
import SignupBasic from "../views/auth/signup/Basic.vue";
|
||||
import SignupCover from "../views/auth/signup/Cover.vue";
|
||||
import SignupIllustration from "../views/auth/signup/Illustration.vue";
|
||||
import Error401 from "../views/auth/error/Error401.vue";
|
||||
import Error404 from "../views/auth/error/Error404.vue";
|
||||
import Error500 from "../views/auth/error/Error500.vue";
|
||||
import lockBasic from "../views/auth/lock/Basic.vue";
|
||||
@ -353,6 +354,12 @@ const routes = [
|
||||
component: SignupIllustration,
|
||||
meta: { guestLayout: true },
|
||||
},
|
||||
{
|
||||
path: "/authentication/error/error401",
|
||||
name: "Error Error401",
|
||||
component: Error401,
|
||||
meta: { public: true, guestLayout: true },
|
||||
},
|
||||
{
|
||||
path: "/authentication/error/error404",
|
||||
name: "Error Error404",
|
||||
@ -782,25 +789,77 @@ const routes = [
|
||||
component: () => import("@/views/pages/Defunts/DefuntDetails.vue"),
|
||||
},
|
||||
// Paramétrage
|
||||
{
|
||||
path: "/parametrage",
|
||||
name: "Paramétrage",
|
||||
redirect: "/parametrage/droits",
|
||||
meta: {
|
||||
requiredPermission: "config.view",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/parametrage/droits",
|
||||
name: "Gestion droits",
|
||||
component: () => import("@/views/pages/Parametrage/Droits.vue"),
|
||||
meta: {
|
||||
requiredPermission: "config.view_roles",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/parametrage/utilisateurs",
|
||||
name: "Gestion utilisateurs",
|
||||
component: () => import("@/views/pages/Parametrage/Users.vue"),
|
||||
meta: {
|
||||
requiredPermission: "crud.user",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/parametrage/utilisateurs/creation",
|
||||
name: "Créer utilisateur",
|
||||
component: () => import("@/views/pages/Parametrage/UserCreate.vue"),
|
||||
meta: {
|
||||
requiredPermission: "crud.user",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/parametrage/utilisateurs/:id",
|
||||
name: "Détail utilisateur",
|
||||
component: () => import("@/views/pages/Parametrage/UserDetails.vue"),
|
||||
meta: {
|
||||
requiredPermission: "crud.user",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/parametrage/emails",
|
||||
name: "Gestion emails",
|
||||
component: () => import("@/views/pages/Parametrage/Emails.vue"),
|
||||
meta: {
|
||||
requiredPermission: "config.view",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/parametrage/modeles",
|
||||
name: "Gestion modeles",
|
||||
component: () => import("@/views/pages/Parametrage/Modeles.vue"),
|
||||
meta: {
|
||||
requiredPermission: "config.view",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/parametrage/categories-produits",
|
||||
name: "Product Categories",
|
||||
component: () => import("@/views/pages/Parametrage/ProductCategories.vue"),
|
||||
meta: {
|
||||
requiredPermission: "config.view",
|
||||
allowAdministrator: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -816,6 +875,32 @@ import pinia from "@/plugins/pinia";
|
||||
|
||||
const DASHBOARD = "/dashboards/dashboard-default";
|
||||
const LOGIN = "/login"; // canonical login path without redirect query
|
||||
const UNAUTHORIZED = "/authentication/error/error401";
|
||||
|
||||
const canAccessRoute = (auth, to) => {
|
||||
const accessMeta = to.matched.reduce(
|
||||
(carry, record) => ({
|
||||
requiredPermission:
|
||||
record.meta?.requiredPermission ?? carry.requiredPermission,
|
||||
allowAdministrator:
|
||||
record.meta?.allowAdministrator ?? carry.allowAdministrator,
|
||||
}),
|
||||
{
|
||||
requiredPermission: null,
|
||||
allowAdministrator: false,
|
||||
}
|
||||
);
|
||||
|
||||
if (accessMeta.allowAdministrator && auth.hasRole("administrator")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (accessMeta.requiredPermission) {
|
||||
return auth.hasPermission(accessMeta.requiredPermission);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const auth = useAuthStore(pinia);
|
||||
@ -848,6 +933,26 @@ router.beforeEach(async (to, from, next) => {
|
||||
}
|
||||
|
||||
if (auth.isAuthenticated) {
|
||||
if (to.path === "/parametrage") {
|
||||
if (auth.hasRole("administrator") || auth.hasPermission("crud.user")) {
|
||||
return next({ name: "Gestion utilisateurs" });
|
||||
}
|
||||
|
||||
if (auth.hasPermission("config.view_roles")) {
|
||||
return next({ name: "Gestion droits" });
|
||||
}
|
||||
|
||||
if (auth.hasPermission("config.view")) {
|
||||
return next({ name: "Gestion emails" });
|
||||
}
|
||||
|
||||
return next({ path: UNAUTHORIZED, replace: true });
|
||||
}
|
||||
|
||||
if (!canAccessRoute(auth, to)) {
|
||||
return next({ path: UNAUTHORIZED, replace: true });
|
||||
}
|
||||
|
||||
// Prevent authenticated users from visiting guest-only routes (like sign-in)
|
||||
if (isGuestOnly || isLoginRoute) return next(DASHBOARD);
|
||||
return next();
|
||||
|
||||
@ -19,6 +19,16 @@ export interface User {
|
||||
email_verified_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
roles?: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
guard_name?: string;
|
||||
}>;
|
||||
permissions?: Array<{
|
||||
id: number;
|
||||
name: string;
|
||||
guard_name?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
|
||||
@ -39,6 +39,7 @@ export interface UpdateUserPayload {
|
||||
name: string;
|
||||
email: string;
|
||||
password?: string | null;
|
||||
clear_password?: boolean;
|
||||
roles?: string[];
|
||||
permissions?: string[];
|
||||
}
|
||||
|
||||
@ -15,8 +15,21 @@ export const useAuthStore = defineStore("auth", {
|
||||
}),
|
||||
getters: {
|
||||
isAuthenticated: (state) => !!state.user && !!state.token,
|
||||
roleNames: (state) =>
|
||||
(state.user?.roles || []).map((role) => role.name.toLowerCase()),
|
||||
permissionNames: (state) =>
|
||||
(state.user?.permissions || []).map((permission) => permission.name),
|
||||
},
|
||||
actions: {
|
||||
hasRole(role: string) {
|
||||
return this.roleNames.includes(role.toLowerCase());
|
||||
},
|
||||
hasPermission(permission: string) {
|
||||
return this.permissionNames.includes(permission);
|
||||
},
|
||||
hasAnyPermission(permissions: string[] = []) {
|
||||
return permissions.some((permission) => this.hasPermission(permission));
|
||||
},
|
||||
async fetchMe() {
|
||||
try {
|
||||
const userData = await AuthService.me();
|
||||
|
||||
@ -76,7 +76,10 @@ export const useUserStore = defineStore("user", () => {
|
||||
|
||||
try {
|
||||
const user = await UserService.createUser(payload);
|
||||
users.value = [user, ...users.value.filter((item) => item.id !== user.id)];
|
||||
users.value = [
|
||||
user,
|
||||
...users.value.filter((item) => item.id !== user.id),
|
||||
];
|
||||
currentUser.value = user;
|
||||
searchedUser.value = user;
|
||||
return user;
|
||||
@ -95,7 +98,9 @@ export const useUserStore = defineStore("user", () => {
|
||||
|
||||
try {
|
||||
const user = await UserService.updateUser(payload);
|
||||
users.value = users.value.map((item) => (item.id === user.id ? user : item));
|
||||
users.value = users.value.map((item) =>
|
||||
item.id === user.id ? user : item
|
||||
);
|
||||
currentUser.value = user;
|
||||
searchedUser.value = user;
|
||||
return user;
|
||||
|
||||
78
thanasoft-front/src/views/auth/error/Error401.vue
Normal file
78
thanasoft-front/src/views/auth/error/Error401.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<navbar btn-background="bg-gradient-dark" :dark-mode="true" />
|
||||
<main class="main-content mt-0 ps">
|
||||
<section class="my-10">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 my-auto">
|
||||
<h1 class="display-1 text-bolder text-gradient text-warning">
|
||||
401
|
||||
</h1>
|
||||
<h2>Accès non autorisé</h2>
|
||||
<p class="lead">
|
||||
Vous n'avez pas les droits nécessaires pour accéder à cette page.
|
||||
</p>
|
||||
<router-link to="/dashboards/dashboard-default">
|
||||
<soft-button color="dark" variant="gradient" class="mt-4">
|
||||
Retour au dashboard
|
||||
</soft-button>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="col-lg-6 my-auto position-relative text-center">
|
||||
<div class="error-401-visual mx-auto">
|
||||
<span>401</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<app-footer />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Navbar from "@/examples/PageLayout/Navbar.vue";
|
||||
import AppFooter from "@/examples/PageLayout/Footer.vue";
|
||||
import SoftButton from "@/components/SoftButton.vue";
|
||||
import { mapMutations } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "Error401",
|
||||
components: {
|
||||
Navbar,
|
||||
AppFooter,
|
||||
SoftButton,
|
||||
},
|
||||
created() {
|
||||
this.toggleEveryDisplay();
|
||||
this.toggleHideConfig();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.toggleEveryDisplay();
|
||||
this.toggleHideConfig();
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["toggleEveryDisplay", "toggleHideConfig"]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-401-visual {
|
||||
width: 260px;
|
||||
height: 260px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #f59e0b, #f97316);
|
||||
box-shadow: 0 1.5rem 3rem rgba(245, 158, 11, 0.25);
|
||||
}
|
||||
|
||||
.error-401-visual span {
|
||||
color: #fff;
|
||||
font-size: 4.5rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user