navigation permission and ability checker
This commit is contained in:
parent
cb80771f7a
commit
35cba95887
6 changed files with 213 additions and 92 deletions
|
@ -4,6 +4,7 @@ import { useAccountStore } from "@/stores/account";
|
|||
import { jwtDecode, type JwtPayload } from "jwt-decode";
|
||||
import { refreshToken } from "../serverCom";
|
||||
import type { PermissionObject } from "../types/permissionTypes";
|
||||
import { useAbilityStore } from "../stores/ability";
|
||||
|
||||
export type Payload = JwtPayload & {
|
||||
userId: number;
|
||||
|
@ -37,6 +38,7 @@ export async function isAuthenticatedPromise(): Promise<Payload> {
|
|||
return new Promise<Payload>(async (resolve, reject) => {
|
||||
const auth = useAuthStore();
|
||||
const account = useAccountStore();
|
||||
const ability = useAbilityStore();
|
||||
let decoded: Payload | string = "";
|
||||
try {
|
||||
decoded = jwtDecode<Payload>(localStorage.getItem("accessToken") ?? "");
|
||||
|
@ -72,7 +74,8 @@ export async function isAuthenticatedPromise(): Promise<Payload> {
|
|||
}
|
||||
|
||||
auth.setSuccess();
|
||||
account.setAccountData(firstname, lastname, mail, username, permissions);
|
||||
account.setAccountData(firstname, lastname, mail, username);
|
||||
ability.setAbility(permissions);
|
||||
resolve(decoded);
|
||||
}
|
||||
});
|
||||
|
|
33
src/stores/ability.ts
Normal file
33
src/stores/ability.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { defineStore } from "pinia";
|
||||
import type { PermissionModule, PermissionObject, PermissionSection, PermissionType } from "../types/permissionTypes";
|
||||
|
||||
export const useAbilityStore = defineStore("ability", {
|
||||
state: () => {
|
||||
return {
|
||||
permissions: {} as PermissionObject,
|
||||
};
|
||||
},
|
||||
getters: {
|
||||
can:
|
||||
(state) =>
|
||||
(type: PermissionType | "admin", section: PermissionSection, module?: PermissionModule): boolean => {
|
||||
const permissions = state.permissions;
|
||||
if (type == "admin") return permissions.admin ?? false;
|
||||
if (permissions.admin) return true;
|
||||
if (
|
||||
(!module && permissions[section] != undefined) ||
|
||||
permissions[section]?.all == "*" ||
|
||||
permissions[section]?.all?.includes(type)
|
||||
)
|
||||
return true;
|
||||
if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type)))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setAbility(permissions: PermissionObject) {
|
||||
this.permissions = permissions;
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
import { defineStore } from "pinia";
|
||||
import type { PermissionObject } from "../types/permissionTypes";
|
||||
import { useAbilityStore } from "./ability";
|
||||
|
||||
export const useAccountStore = defineStore("account", {
|
||||
state: () => {
|
||||
|
@ -8,7 +9,6 @@ export const useAccountStore = defineStore("account", {
|
|||
lastname: "" as string,
|
||||
mail: "" as string,
|
||||
alias: "" as string,
|
||||
permissions: {} as PermissionObject,
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
|
@ -17,12 +17,11 @@ export const useAccountStore = defineStore("account", {
|
|||
localStorage.removeItem("refreshToken");
|
||||
window.open("/login", "_self");
|
||||
},
|
||||
setAccountData(firstname: string, lastname: string, mail: string, alias: string, permissions: PermissionObject) {
|
||||
setAccountData(firstname: string, lastname: string, mail: string, alias: string) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
this.mail = mail;
|
||||
this.alias = alias;
|
||||
this.permissions = permissions;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { shallowRef, defineAsyncComponent } from "vue";
|
||||
import { useAccountStore } from "../account";
|
||||
import { useAbilityStore } from "../ability";
|
||||
|
||||
export interface navigationModel {
|
||||
club: navigationSplitModel;
|
||||
|
@ -31,94 +31,11 @@ export interface navigationLinkModel {
|
|||
|
||||
export const useNavigationStore = defineStore("navigation", {
|
||||
state: () => {
|
||||
const accountStore = useAccountStore();
|
||||
return {
|
||||
activeNavigation: "club" as topLevelNavigationType,
|
||||
activeLink: null as null | navigationLinkModel,
|
||||
topLevel: [
|
||||
{
|
||||
key: "club",
|
||||
title: "Verein",
|
||||
levelDefault: "#members",
|
||||
},
|
||||
{
|
||||
key: "settings",
|
||||
title: "Einstellungen",
|
||||
levelDefault: "#qualification",
|
||||
},
|
||||
{
|
||||
key: "user",
|
||||
title: "Benutzer",
|
||||
levelDefault: "#user",
|
||||
},
|
||||
] as Array<topLevelNavigationModel>,
|
||||
navigation: {
|
||||
club: {
|
||||
mainTitle: "Verein",
|
||||
main: [
|
||||
{
|
||||
key: "#members",
|
||||
title: "Mitglieder",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
{
|
||||
key: "#calendar",
|
||||
title: "Termine",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
{
|
||||
key: "#newsletter",
|
||||
title: "Newsletter",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
{
|
||||
key: "#protocol",
|
||||
title: "Protokolle",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
mainTitle: "Einstellungen",
|
||||
main: [
|
||||
{
|
||||
key: "#qualification",
|
||||
title: "Qualifikationen",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
{
|
||||
key: "#award",
|
||||
title: "Auszeichnungen",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
{
|
||||
key: "#executive_position",
|
||||
title: "Vereinsämter",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
{
|
||||
key: "#communication",
|
||||
title: "Mitgliederdaten",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
],
|
||||
},
|
||||
user: {
|
||||
mainTitle: "Benutzer",
|
||||
main: [
|
||||
{
|
||||
key: "#user",
|
||||
title: "Benutzer",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
{
|
||||
key: "#roles",
|
||||
title: "Rollen",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
],
|
||||
},
|
||||
} as navigationModel,
|
||||
topLevel: [] as Array<topLevelNavigationModel>,
|
||||
navigation: {} as navigationModel,
|
||||
componentOverwrite: null as null | any,
|
||||
};
|
||||
},
|
||||
|
@ -161,5 +78,150 @@ export const useNavigationStore = defineStore("navigation", {
|
|||
resetNavigation() {
|
||||
this.$reset();
|
||||
},
|
||||
updateTopLevel() {
|
||||
const abilityStore = useAbilityStore();
|
||||
this.topLevel = [
|
||||
...(abilityStore.can("read", "club")
|
||||
? [
|
||||
{
|
||||
key: "club",
|
||||
title: "Verein",
|
||||
levelDefault: "#members",
|
||||
} as topLevelNavigationModel,
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings")
|
||||
? [
|
||||
{
|
||||
key: "settings",
|
||||
title: "Einstellungen",
|
||||
levelDefault: "#qualification",
|
||||
} as topLevelNavigationModel,
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "user")
|
||||
? [
|
||||
{
|
||||
key: "user",
|
||||
title: "Benutzer",
|
||||
levelDefault: "#user",
|
||||
} as topLevelNavigationModel,
|
||||
]
|
||||
: []),
|
||||
];
|
||||
if (this.topLevel.findIndex((e) => e.key == this.activeNavigation) == -1)
|
||||
this.activeNavigation = this.topLevel[0]?.key ?? "club";
|
||||
},
|
||||
updateNavigation() {
|
||||
const abilityStore = useAbilityStore();
|
||||
this.navigation = {
|
||||
club: {
|
||||
mainTitle: "Verein",
|
||||
main: [
|
||||
...(abilityStore.can("read", "club", "members")
|
||||
? [
|
||||
{
|
||||
key: "#members",
|
||||
title: "Mitglieder",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "club", "calendar")
|
||||
? [
|
||||
{
|
||||
key: "#calendar",
|
||||
title: "Termine",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "club", "newsletter")
|
||||
? [
|
||||
{
|
||||
key: "#newsletter",
|
||||
title: "Newsletter",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "club", "protocoll")
|
||||
? [
|
||||
{
|
||||
key: "#protocol",
|
||||
title: "Protokolle",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
mainTitle: "Einstellungen",
|
||||
main: [
|
||||
...(abilityStore.can("read", "settings", "qualification")
|
||||
? [
|
||||
{
|
||||
key: "#qualification",
|
||||
title: "Qualifikationen",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "award")
|
||||
? [
|
||||
{
|
||||
key: "#award",
|
||||
title: "Auszeichnungen",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "executive_position")
|
||||
? [
|
||||
{
|
||||
key: "#executive_position",
|
||||
title: "Vereinsämter",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("read", "settings", "communication")
|
||||
? [
|
||||
{
|
||||
key: "#communication",
|
||||
title: "Mitgliederdaten",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
user: {
|
||||
mainTitle: "Benutzer",
|
||||
main: [
|
||||
...(abilityStore.can("read", "user", "user")
|
||||
? [
|
||||
{
|
||||
key: "#user",
|
||||
title: "Benutzer",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(abilityStore.can("admin", "user", "role")
|
||||
? [
|
||||
{
|
||||
key: "#role",
|
||||
title: "Rollen",
|
||||
component: shallowRef(defineAsyncComponent(() => import("@/views/admin/members/Overview.vue"))),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
} as navigationModel;
|
||||
if (this.topLevel.findIndex((e) => e.key == this.activeLink?.key) == -1) this.setLink(null);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
export type PermissionSection = "club" | "settings" | "user";
|
||||
|
||||
export type PermissionModule = "protocoll" | "user";
|
||||
export type PermissionModule =
|
||||
| "members"
|
||||
| "calendar"
|
||||
| "newsletter"
|
||||
| "protocoll"
|
||||
| "qualification"
|
||||
| "award"
|
||||
| "executive_position"
|
||||
| "communication"
|
||||
| "user"
|
||||
| "role";
|
||||
|
||||
export type PermissionType = "create" | "read" | "update" | "delete";
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import { useNavigationStore } from "@/stores/admin/navigation";
|
|||
import SidebarLayout from "@/layouts/Sidebar.vue";
|
||||
import SidebarTemplate from "@/templates/Sidebar.vue";
|
||||
import RoutingLink from "@/components/admin/RoutingLink.vue";
|
||||
import { useAbilityStore } from "../../stores/ability";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -57,13 +58,26 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
created() {
|
||||
useAbilityStore().$subscribe(() => {
|
||||
this.updateTopLevel();
|
||||
this.updateNavigation();
|
||||
});
|
||||
this.updateTopLevel();
|
||||
this.updateNavigation();
|
||||
|
||||
this.setLink(this.activeTopLevelObject.levelDefault);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.resetNavigation();
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useNavigationStore, ["setLink", "resetNavigation", "setTopLevel"]),
|
||||
...mapActions(useNavigationStore, [
|
||||
"setLink",
|
||||
"resetNavigation",
|
||||
"setTopLevel",
|
||||
"updateTopLevel",
|
||||
"updateNavigation",
|
||||
]),
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue