redirection page 401 pour les user non autorises
This commit is contained in:
parent
1d18659bd7
commit
34875321ac
@ -65,6 +65,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import SidenavItem from "./SidenavItem.vue";
|
import SidenavItem from "./SidenavItem.vue";
|
||||||
import SidenavCollapse from "./SidenavCollapse.vue";
|
import SidenavCollapse from "./SidenavCollapse.vue";
|
||||||
|
import pinia from "@/plugins/pinia";
|
||||||
|
import { useAuthStore } from "@/stores/auth";
|
||||||
|
|
||||||
import Shop from "../../components/Icon/Shop.vue";
|
import Shop from "../../components/Icon/Shop.vue";
|
||||||
import Office from "../../components/Icon/Office.vue";
|
import Office from "../../components/Icon/Office.vue";
|
||||||
@ -87,10 +89,13 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["isRTL"]),
|
...mapState(["isRTL"]),
|
||||||
|
authStore() {
|
||||||
|
return useAuthStore(pinia);
|
||||||
|
},
|
||||||
|
|
||||||
// Navigation data structure
|
// Navigation data structure
|
||||||
navigationItems() {
|
navigationItems() {
|
||||||
return [
|
const items = [
|
||||||
{
|
{
|
||||||
id: "dashboard",
|
id: "dashboard",
|
||||||
type: "collapse",
|
type: "collapse",
|
||||||
@ -410,37 +415,118 @@ export default {
|
|||||||
icon: "Office",
|
icon: "Office",
|
||||||
collapseRef: "parametrageMenu",
|
collapseRef: "parametrageMenu",
|
||||||
routeKey: "parametrage",
|
routeKey: "parametrage",
|
||||||
|
requiredAny: [
|
||||||
|
{ role: "administrator" },
|
||||||
|
{ permission: "config.view" },
|
||||||
|
{ permission: "config.view_roles" },
|
||||||
|
{ permission: "crud.user" },
|
||||||
|
],
|
||||||
children: [
|
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",
|
id: "droits",
|
||||||
route: { name: "Gestion droits" },
|
route: { name: "Gestion droits" },
|
||||||
miniIcon: "D",
|
miniIcon: "D",
|
||||||
text: "Gestion des droits",
|
text: "Gestion des droits",
|
||||||
|
requiredPermission: "config.view_roles",
|
||||||
|
allowAdministrator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "emails",
|
id: "emails",
|
||||||
route: { name: "Gestion emails" },
|
route: { name: "Gestion emails" },
|
||||||
miniIcon: "E",
|
miniIcon: "E",
|
||||||
text: "Gestion des emails",
|
text: "Gestion des emails",
|
||||||
|
requiredPermission: "config.view",
|
||||||
|
allowAdministrator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "modeles",
|
id: "modeles",
|
||||||
route: { name: "Gestion modeles" },
|
route: { name: "Gestion modeles" },
|
||||||
miniIcon: "M",
|
miniIcon: "M",
|
||||||
text: "Gestion des modèles",
|
text: "Gestion des modèles",
|
||||||
|
requiredPermission: "config.view",
|
||||||
|
allowAdministrator: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "categories-produits",
|
id: "categories-produits",
|
||||||
route: { name: "Product Categories" },
|
route: { name: "Product Categories" },
|
||||||
miniIcon: "C",
|
miniIcon: "C",
|
||||||
text: "Catégories Produits",
|
text: "Catégories Produits",
|
||||||
|
requiredPermission: "config.view",
|
||||||
|
allowAdministrator: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return this.filterNavigationItems(items);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
getRoute() {
|
||||||
const routeArr = this.$route.path.split("/");
|
const routeArr = this.$route.path.split("/");
|
||||||
return routeArr[1];
|
return routeArr[1];
|
||||||
|
|||||||
@ -48,6 +48,7 @@ import VerificationIllustration from "../views/auth/verification/Illustration.vu
|
|||||||
import SignupBasic from "../views/auth/signup/Basic.vue";
|
import SignupBasic from "../views/auth/signup/Basic.vue";
|
||||||
import SignupCover from "../views/auth/signup/Cover.vue";
|
import SignupCover from "../views/auth/signup/Cover.vue";
|
||||||
import SignupIllustration from "../views/auth/signup/Illustration.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 Error404 from "../views/auth/error/Error404.vue";
|
||||||
import Error500 from "../views/auth/error/Error500.vue";
|
import Error500 from "../views/auth/error/Error500.vue";
|
||||||
import lockBasic from "../views/auth/lock/Basic.vue";
|
import lockBasic from "../views/auth/lock/Basic.vue";
|
||||||
@ -353,6 +354,12 @@ const routes = [
|
|||||||
component: SignupIllustration,
|
component: SignupIllustration,
|
||||||
meta: { guestLayout: true },
|
meta: { guestLayout: true },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/authentication/error/error401",
|
||||||
|
name: "Error Error401",
|
||||||
|
component: Error401,
|
||||||
|
meta: { public: true, guestLayout: true },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/authentication/error/error404",
|
path: "/authentication/error/error404",
|
||||||
name: "Error Error404",
|
name: "Error Error404",
|
||||||
@ -782,25 +789,77 @@ const routes = [
|
|||||||
component: () => import("@/views/pages/Defunts/DefuntDetails.vue"),
|
component: () => import("@/views/pages/Defunts/DefuntDetails.vue"),
|
||||||
},
|
},
|
||||||
// Paramétrage
|
// Paramétrage
|
||||||
|
{
|
||||||
|
path: "/parametrage",
|
||||||
|
name: "Paramétrage",
|
||||||
|
redirect: "/parametrage/droits",
|
||||||
|
meta: {
|
||||||
|
requiredPermission: "config.view",
|
||||||
|
allowAdministrator: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/parametrage/droits",
|
path: "/parametrage/droits",
|
||||||
name: "Gestion droits",
|
name: "Gestion droits",
|
||||||
component: () => import("@/views/pages/Parametrage/Droits.vue"),
|
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",
|
path: "/parametrage/emails",
|
||||||
name: "Gestion emails",
|
name: "Gestion emails",
|
||||||
component: () => import("@/views/pages/Parametrage/Emails.vue"),
|
component: () => import("@/views/pages/Parametrage/Emails.vue"),
|
||||||
|
meta: {
|
||||||
|
requiredPermission: "config.view",
|
||||||
|
allowAdministrator: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/parametrage/modeles",
|
path: "/parametrage/modeles",
|
||||||
name: "Gestion modeles",
|
name: "Gestion modeles",
|
||||||
component: () => import("@/views/pages/Parametrage/Modeles.vue"),
|
component: () => import("@/views/pages/Parametrage/Modeles.vue"),
|
||||||
|
meta: {
|
||||||
|
requiredPermission: "config.view",
|
||||||
|
allowAdministrator: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/parametrage/categories-produits",
|
path: "/parametrage/categories-produits",
|
||||||
name: "Product Categories",
|
name: "Product Categories",
|
||||||
component: () => import("@/views/pages/Parametrage/ProductCategories.vue"),
|
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 DASHBOARD = "/dashboards/dashboard-default";
|
||||||
const LOGIN = "/login"; // canonical login path without redirect query
|
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) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
const auth = useAuthStore(pinia);
|
const auth = useAuthStore(pinia);
|
||||||
@ -848,6 +933,26 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (auth.isAuthenticated) {
|
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)
|
// Prevent authenticated users from visiting guest-only routes (like sign-in)
|
||||||
if (isGuestOnly || isLoginRoute) return next(DASHBOARD);
|
if (isGuestOnly || isLoginRoute) return next(DASHBOARD);
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@ -19,6 +19,16 @@ export interface User {
|
|||||||
email_verified_at: string | null;
|
email_verified_at: string | null;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_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 {
|
export interface LoginResponse {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export interface UpdateUserPayload {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
password?: string | null;
|
password?: string | null;
|
||||||
|
clear_password?: boolean;
|
||||||
roles?: string[];
|
roles?: string[];
|
||||||
permissions?: string[];
|
permissions?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,8 +15,21 @@ export const useAuthStore = defineStore("auth", {
|
|||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isAuthenticated: (state) => !!state.user && !!state.token,
|
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: {
|
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() {
|
async fetchMe() {
|
||||||
try {
|
try {
|
||||||
const userData = await AuthService.me();
|
const userData = await AuthService.me();
|
||||||
|
|||||||
@ -76,7 +76,10 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await UserService.createUser(payload);
|
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;
|
currentUser.value = user;
|
||||||
searchedUser.value = user;
|
searchedUser.value = user;
|
||||||
return user;
|
return user;
|
||||||
@ -95,7 +98,9 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await UserService.updateUser(payload);
|
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;
|
currentUser.value = user;
|
||||||
searchedUser.value = user;
|
searchedUser.value = user;
|
||||||
return 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