permission system and no access redirect

This commit is contained in:
Julian Krauser 2024-08-26 13:46:54 +02:00
parent 214f0ddf21
commit cb80771f7a
8 changed files with 107 additions and 18 deletions

View file

@ -3,25 +3,33 @@ import { useAuthStore } from "@/stores/auth";
import { useAccountStore } from "@/stores/account";
import { jwtDecode, type JwtPayload } from "jwt-decode";
import { refreshToken } from "../serverCom";
import type { PermissionObject } from "../types/permissionTypes";
type Payload = JwtPayload & { userId: number; username: string; firstname: string; lastname: string; mail: string };
export type Payload = JwtPayload & {
userId: number;
username: string;
firstname: string;
lastname: string;
mail: string;
permissions: PermissionObject;
};
export async function isAuthenticated(to: any, from: any, next: any) {
const auth = useAuthStore();
NProgress.start();
if (auth.authCheck && localStorage.getItem("access_token")) {
if (auth.authCheck && localStorage.getItem("access_token") && localStorage.getItem("refresh_token")) {
NProgress.done();
next();
return;
}
await isAuthenticatedPromise()
.then(async (result: any) => {
.then(async (result: Payload) => {
NProgress.done();
next();
})
.catch((err: Error) => {
.catch((err: string) => {
NProgress.done();
next({ name: "login" });
next({ name: err ?? "login" });
});
}
@ -33,29 +41,38 @@ export async function isAuthenticatedPromise(): Promise<Payload> {
try {
decoded = jwtDecode<Payload>(localStorage.getItem("accessToken") ?? "");
} catch (error) {
reject("failed");
auth.setFailed();
reject("login");
}
auth.setSuccess();
if (typeof decoded == "string" || !decoded) {
reject("failed");
auth.setFailed();
reject("login");
} else {
// check jwt expiry
const exp = decoded.exp ?? 0;
const localTimezoneOffset = new Date().getTimezoneOffset();
const correctedLocalTime = new Date().getTime() + localTimezoneOffset * 60000;
const correctedLocalTime = new Date().getTime();
if (exp < Math.floor(correctedLocalTime / 1000)) {
await refreshToken()
.then(() => {
console.log("fetched new token");
})
.catch(() => {
reject("expired");
.catch((err: string) => {
console.log("expired");
auth.setFailed();
reject(err);
});
}
var { firstname, lastname, mail, username } = decoded;
account.setAccountData(firstname, lastname, mail, username);
var { firstname, lastname, mail, username, permissions } = decoded;
if (Object.keys(permissions).length === 0) {
auth.setFailed();
reject("nopermissions");
}
auth.setSuccess();
account.setAccountData(firstname, lastname, mail, username, permissions);
resolve(decoded);
}
});

View file

@ -42,6 +42,11 @@ const router = createRouter({
component: () => import("../views/admin/View.vue"),
beforeEnter: [isAuthenticated],
},
{
path: "/nopermissions",
name: "nopermissions",
component: () => import("../views/NoPermission.vue"),
},
{
path: "/:pathMatch(.*)*",
name: "404",

View file

@ -1,9 +1,11 @@
import axios from "axios";
import { isAuthenticatedPromise, type Payload } from "./router/authGuards";
import router from "./router";
let devMode = process.env.NODE_ENV === "development";
const http = axios.create({
baseURL: devMode ? "http://localhost:5000" : server_adress,
baseURL: devMode ? "http://localhost:5000" : process.env.SERVER_ADDRESS,
headers: {
"Cache-Control": "no-cache",
Pragma: "no-cache",
@ -59,17 +61,22 @@ export async function refreshToken(): Promise<void> {
accessToken: localStorage.getItem("accessToken"),
refreshToken: localStorage.getItem("refreshToken"),
})
.then((response) => {
.then(async (response) => {
const { accessToken, refreshToken } = response.data;
localStorage.setItem("accessToken", accessToken);
localStorage.setItem("refreshToken", refreshToken);
await isAuthenticatedPromise().catch((err: string) => {
router.push({ name: err ?? "login" });
reject(err);
});
resolve();
})
.catch((error) => {
console.error("Error refreshing token:", error);
reject();
reject("login");
});
});
}

View file

@ -1,4 +1,5 @@
import { defineStore } from "pinia";
import type { PermissionObject } from "../types/permissionTypes";
export const useAccountStore = defineStore("account", {
state: () => {
@ -7,6 +8,7 @@ export const useAccountStore = defineStore("account", {
lastname: "" as string,
mail: "" as string,
alias: "" as string,
permissions: {} as PermissionObject,
};
},
actions: {
@ -15,11 +17,12 @@ export const useAccountStore = defineStore("account", {
localStorage.removeItem("refreshToken");
window.open("/login", "_self");
},
setAccountData(firstname: string, lastname: string, mail: string, alias: string) {
setAccountData(firstname: string, lastname: string, mail: string, alias: string, permissions: PermissionObject) {
this.firstname = firstname;
this.lastname = lastname;
this.mail = mail;
this.alias = alias;
this.permissions = permissions;
},
},
});

View file

@ -1,5 +1,6 @@
import { defineStore } from "pinia";
import { shallowRef, defineAsyncComponent } from "vue";
import { useAccountStore } from "../account";
export interface navigationModel {
club: navigationSplitModel;
@ -30,6 +31,7 @@ export interface navigationLinkModel {
export const useNavigationStore = defineStore("navigation", {
state: () => {
const accountStore = useAccountStore();
return {
activeNavigation: "club" as topLevelNavigationType,
activeLink: null as null | navigationLinkModel,

View file

@ -10,5 +10,8 @@ export const useAuthStore = defineStore("auth", {
setSuccess() {
this.authCheck = true;
},
setFailed() {
this.authCheck = false;
},
},
});

View file

@ -0,0 +1,20 @@
export type PermissionSection = "club" | "settings" | "user";
export type PermissionModule = "protocoll" | "user";
export type PermissionType = "create" | "read" | "update" | "delete";
export type PermissionString =
| `${PermissionSection}.${PermissionModule}.${PermissionType}` // für spezifische Berechtigungen
| `${PermissionSection}.${PermissionModule}.*` // für alle Berechtigungen in einem Modul
| `${PermissionSection}.${PermissionType}` // für spezifische Berechtigungen in einem Abschnitt
| `${PermissionSection}.*` // für alle Berechtigungen in einem Abschnitt
| "*"; // für Admin
export type PermissionObject = {
[section in PermissionSection]?: {
[module in PermissionModule]?: Array<PermissionType> | "*";
} & { all?: PermissionType | "*" };
} & {
admin?: boolean;
};

View file

@ -0,0 +1,32 @@
<template>
<div class="flex flex-col items-center">
<br />
<h1 class="w-full p-4 text-center font-bold text-3xl">Kein Zugriff</h1>
<br />
<p class="w-full text-center">
Sie haben keine Berechtigungen. <br />
Um Zugriff auf das Admin-Portal zu erhalten, wenden Sie sich an einen Administrator.
</p>
<br />
<button primary class="!w-fit" @click="refetch">Zum Admin-Portal</button>
</div>
</template>
<script lang="ts" setup>
import { refreshToken } from "@/serverCom";
import { defineComponent } from "vue";
</script>
<script lang="ts">
export default defineComponent({
methods: {
async refetch() {
await refreshToken()
.then(() => {
this.$router.push({ name: "admin" });
})
.catch(() => {});
},
},
});
</script>