From 889e4858dbe6234fc96405c18a626e810254bdba Mon Sep 17 00:00:00 2001 From: Bram Reyniers <55666730+reyniersbram@users.noreply.github.com> Date: Wed, 22 May 2024 21:37:33 +0200 Subject: [PATCH] Permission checks for pages (#166) * generic permission middleware * placeholder middleware implementations * implement isAdmin check * permissions on admin page + fix typo * logical functions for conditions * pass MiddlewareContext to conditions * permission to check if user is part of subject * permission to check if user can create a project for a subject * run formatter * restructure navigation guard middlewares * subject details permissions * project details permissions * go to /not-found instead of /forbidden * create project permissions * fix project permission checks * permissions for submit page * permissions for groups overview * permissions for group details * permissions for submission list of project * remove unused imports * Apply suggestions from code review Co-authored-by: Xander Bil <47951455+xerbalind@users.noreply.github.com> * permissions for new pages * allow admins to visit all pages --------- Co-authored-by: Xander Bil <47951455+xerbalind@users.noreply.github.com> --- frontend/src/composables/useIsAdmin.ts | 7 +- frontend/src/composables/useIsTeacher.ts | 7 +- frontend/src/main.ts | 7 +- frontend/src/queries/Group.ts | 58 ++++--- frontend/src/queries/Project.ts | 33 ++-- frontend/src/queries/Subject.ts | 17 +- frontend/src/queries/User.ts | 15 +- frontend/src/router/index.ts | 80 ++++++++- frontend/src/router/middleware/canVisit.ts | 163 ++++++++++++++++++ frontend/src/router/middleware/index.ts | 18 +- .../src/router/middleware/isAuthenticated.ts | 12 +- frontend/src/router/middleware/logger.ts | 11 ++ frontend/src/router/middleware/login.ts | 15 +- 13 files changed, 361 insertions(+), 82 deletions(-) create mode 100644 frontend/src/router/middleware/canVisit.ts create mode 100644 frontend/src/router/middleware/logger.ts diff --git a/frontend/src/composables/useIsAdmin.ts b/frontend/src/composables/useIsAdmin.ts index b92daca7..4d554efb 100644 --- a/frontend/src/composables/useIsAdmin.ts +++ b/frontend/src/composables/useIsAdmin.ts @@ -1,8 +1,9 @@ import { computed } from "vue"; import { useCurrentUserQuery } from "@/queries/User"; +import type { QueryClient } from "@tanstack/vue-query"; -export default function useIsAdmin() { - const { data: user } = useCurrentUserQuery(); +export default function useIsAdmin(queryClient?: QueryClient) { + const { data: user, isLoading } = useCurrentUserQuery(queryClient); const isAdmin = computed(() => user.value?.is_admin || false); - return { isAdmin }; + return { isAdmin, isLoading }; } diff --git a/frontend/src/composables/useIsTeacher.ts b/frontend/src/composables/useIsTeacher.ts index 0725da78..0a097104 100644 --- a/frontend/src/composables/useIsTeacher.ts +++ b/frontend/src/composables/useIsTeacher.ts @@ -1,8 +1,9 @@ import { computed } from "vue"; import { useCurrentUserQuery } from "@/queries/User"; +import type { QueryClient } from "@tanstack/vue-query"; -export default function useIsTeacher() { - const { data: user } = useCurrentUserQuery(); +export default function useIsTeacher(queryClient?: QueryClient) { + const { data: user, isLoading } = useCurrentUserQuery(queryClient); const isTeacher = computed(() => user.value?.is_teacher || false); - return { isTeacher }; + return { isTeacher, isLoading }; } diff --git a/frontend/src/main.ts b/frontend/src/main.ts index f31b4b77..d4758d18 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -3,7 +3,7 @@ import "@mdi/font/css/materialdesignicons.css"; import { createApp } from "vue"; import { createPinia } from "pinia"; -import { VueQueryPlugin } from "@tanstack/vue-query"; +import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query"; import App from "./App.vue"; import router from "./router"; @@ -16,10 +16,13 @@ const app = createApp(App); const pinia = createPinia(); +const queryClient = new QueryClient(); +app.provide("queryClient", queryClient); + app.use(router); app.use(pinia); app.use(vuetify); app.use(i18n); -app.use(VueQueryPlugin); +app.use(VueQueryPlugin, { queryClient }); app.mount("#app"); diff --git a/frontend/src/queries/Group.ts b/frontend/src/queries/Group.ts index 26b13a90..0cdbf151 100644 --- a/frontend/src/queries/Group.ts +++ b/frontend/src/queries/Group.ts @@ -1,7 +1,7 @@ import { computed, toValue } from "vue"; import type { MaybeRefOrGetter } from "vue"; import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; -import type { UseMutationReturnType, UseQueryReturnType } from "@tanstack/vue-query"; +import type { QueryClient, UseMutationReturnType, UseQueryReturnType } from "@tanstack/vue-query"; import type Group from "@/models/Group"; import type { GroupForm } from "@/models/Group"; import { @@ -38,26 +38,34 @@ function PROJECT_USER_GROUP_QUERY_KEY(projectId: number): (string | number)[] { * Query composable for fetching a group by id */ export function useGroupQuery( - groupId: MaybeRefOrGetter + groupId: MaybeRefOrGetter, + queryClient?: QueryClient ): UseQueryReturnType { - return useQuery({ - queryKey: GROUP_QUERY_KEY(toValue(groupId)!), - queryFn: () => getGroup(toValue(groupId)!), - enabled: () => !!toValue(groupId), - }); + return useQuery( + { + queryKey: GROUP_QUERY_KEY(toValue(groupId)!), + queryFn: () => getGroup(toValue(groupId)!), + enabled: () => !!toValue(groupId), + }, + queryClient + ); } /** * Query composable for fetching all groups of a project */ export function useProjectGroupsQuery( - projectId: MaybeRefOrGetter + projectId: MaybeRefOrGetter, + queryClient?: QueryClient ): UseQueryReturnType { - return useQuery({ - queryKey: computed(() => PROJECT_GROUPS_QUERY_KEY(toValue(projectId)!)), - queryFn: () => getProjectGroups(toValue(projectId)!), - enabled: !!toValue(projectId), - }); + return useQuery( + { + queryKey: computed(() => PROJECT_GROUPS_QUERY_KEY(toValue(projectId)!)), + queryFn: () => getProjectGroups(toValue(projectId)!), + enabled: !!toValue(projectId), + }, + queryClient + ); } export function useUserGroupsQuery(): UseQueryReturnType { @@ -73,20 +81,26 @@ export function useUserGroupsQuery(): UseQueryReturnType { * @returns The group the user is in for the project, undefined if the user is not in a group */ export function useProjectGroupQuery( - projectId: MaybeRefOrGetter + projectId: MaybeRefOrGetter, + queryClient?: QueryClient ): UseQueryReturnType { - const { data: projectGroups } = useProjectGroupsQuery(projectId); - const { data: user } = useCurrentUserQuery(); - const userGroup = computed( - () => + const { data: projectGroups } = useProjectGroupsQuery(projectId, queryClient); + const { data: user } = useCurrentUserQuery(queryClient); + const userGroup = computed(() => { + if (projectGroups.value === undefined || user.value === undefined) return undefined; + return ( projectGroups.value?.find((group) => group.members.some((member) => member.uid === user.value?.uid) ) || null - ); - return useQuery({ - queryKey: computed(() => PROJECT_USER_GROUP_QUERY_KEY(toValue(projectId)!)), - queryFn: () => userGroup, + ); }); + return useQuery( + { + queryKey: computed(() => PROJECT_USER_GROUP_QUERY_KEY(toValue(projectId)!)), + queryFn: () => userGroup, + }, + queryClient + ); } /** diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index 9d27d23f..14f30516 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -1,7 +1,7 @@ import { computed, toValue } from "vue"; import type { MaybeRefOrGetter } from "vue"; import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; -import type { UseMutationReturnType, UseQueryReturnType } from "@tanstack/vue-query"; +import type { QueryClient, UseMutationReturnType, UseQueryReturnType } from "@tanstack/vue-query"; import type { ProjectForm, UserProjectList } from "@/models/Project"; import type Project from "@/models/Project"; import { @@ -31,23 +31,32 @@ function TEST_FILES_QUERY_KEY(projectId: number): (string | number)[] { // Hook for fetching project details export function useProjectQuery( - projectId: MaybeRefOrGetter + projectId: MaybeRefOrGetter, + queryClient?: QueryClient ): UseQueryReturnType { - return useQuery({ - queryKey: computed(() => PROJECT_QUERY_KEY(toValue(projectId)!)), - queryFn: () => getProject(toValue(projectId)!), - enabled: () => !!toValue(projectId), - }); + return useQuery( + { + queryKey: computed(() => PROJECT_QUERY_KEY(toValue(projectId)!)), + queryFn: () => getProject(toValue(projectId)!), + enabled: () => !!toValue(projectId), + }, + queryClient + ); } /** * Query composable for fetching all projects of the current user */ -export function useProjectsQuery(): UseQueryReturnType { - return useQuery({ - queryKey: PROJECTS_QUERY_KEY(), - queryFn: getProjects, - }); +export function useProjectsQuery( + queryClient?: QueryClient +): UseQueryReturnType { + return useQuery( + { + queryKey: PROJECTS_QUERY_KEY(), + queryFn: getProjects, + }, + queryClient + ); } /** diff --git a/frontend/src/queries/Subject.ts b/frontend/src/queries/Subject.ts index 70f330d3..dad0cd48 100644 --- a/frontend/src/queries/Subject.ts +++ b/frontend/src/queries/Subject.ts @@ -1,7 +1,7 @@ import { computed, toValue } from "vue"; import type { MaybeRefOrGetter } from "vue"; import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; -import type { UseQueryReturnType, UseMutationReturnType } from "@tanstack/vue-query"; +import type { UseQueryReturnType, UseMutationReturnType, QueryClient } from "@tanstack/vue-query"; import { getSubject, getSubjectInstructors, @@ -74,11 +74,16 @@ export function useSubjectUuidQuery( /** * Query composable for fetching all subjects of the current user */ -export function useSubjectsQuery(): UseQueryReturnType { - return useQuery({ - queryKey: SUBJECTS_QUERY_KEY(), - queryFn: getSubjects, - }); +export function useSubjectsQuery( + queryClient?: QueryClient +): UseQueryReturnType { + return useQuery( + { + queryKey: SUBJECTS_QUERY_KEY(), + queryFn: getSubjects, + }, + queryClient + ); } /** diff --git a/frontend/src/queries/User.ts b/frontend/src/queries/User.ts index d7719a60..bcafd5fe 100644 --- a/frontend/src/queries/User.ts +++ b/frontend/src/queries/User.ts @@ -1,7 +1,7 @@ import { computed, toValue } from "vue"; import type { MaybeRefOrGetter } from "vue"; import { useQuery, useMutation, useQueryClient } from "@tanstack/vue-query"; -import type { UseQueryReturnType, UseMutationReturnType } from "@tanstack/vue-query"; +import type { UseQueryReturnType, UseMutationReturnType, QueryClient } from "@tanstack/vue-query"; import type User from "@/models/User"; import { getCurrentUser, @@ -27,11 +27,14 @@ export function USERS_QUERY_KEY(): string[] { /** * Query composable for fetching the current user */ -export function useCurrentUserQuery(): UseQueryReturnType { - return useQuery({ - queryKey: CURRENT_USER_QUERY_KEY(), - queryFn: () => getCurrentUser(), - }); +export function useCurrentUserQuery(queryClient?: QueryClient): UseQueryReturnType { + return useQuery( + { + queryKey: CURRENT_USER_QUERY_KEY(), + queryFn: () => getCurrentUser(), + }, + queryClient + ); } /** diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 1b483e23..6c6c30dc 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,7 +1,19 @@ import { createRouter, createWebHistory } from "vue-router"; -import { type Middleware, type MiddlewareContext, nextFactory } from "./middleware/index"; +import { type Middleware, type MiddlewareContext } from "./middleware/index"; import isAuthenticated from "./middleware/isAuthenticated"; import loginMiddleware from "./middleware/login"; +import useCanVisit, { + useIsAdminCondition, + useIsStudentOfSubjectCondition, + useIsInstructorOfSubjectCondition, + useIsStudentOfProjectCondition, + useIsInstructorOfProjectCondition, + useIsInGroupCondition, + useIsInstructorOfGroupCondition, + useOrCondition, + useIsInGroupOfProjectCondition, + useIsTeacherCondition, +} from "./middleware/canVisit"; declare module "vue-router" { interface RouteMeta { @@ -51,30 +63,57 @@ const router = createRouter({ name: "project", component: () => import("../views/ProjectView.vue"), props: (route) => ({ projectId: Number(route.params.projectId) }), + meta: { + middleware: useCanVisit( + useOrCondition( + useIsStudentOfProjectCondition, + useIsInstructorOfProjectCondition + ) + ), + }, }, { path: "/project/:projectId(\\d+)/submit", name: "onSubmit", component: () => import("../views/SubmitView.vue"), props: (route) => ({ projectId: Number(route.params.projectId) }), + meta: { + middleware: useCanVisit(useIsInGroupOfProjectCondition), + }, }, { path: "/project/:projectId(\\d+)/groups", name: "groups", component: () => import("../views/GroupsView.vue"), props: (route) => ({ projectId: Number(route.params.projectId) }), + meta: { + middleware: useCanVisit( + useOrCondition( + useIsStudentOfProjectCondition, + useIsInstructorOfProjectCondition + ) + ), + }, }, { path: "/submissions/:groupId(\\d+)", name: "submissions", component: () => import("../views/GroupView.vue"), props: (route) => ({ groupId: Number(route.params.groupId) }), + meta: { + middleware: useCanVisit( + useOrCondition(useIsInGroupCondition, useIsInstructorOfGroupCondition) + ), + }, }, { path: "/project/:projectId(\\d+)/submissions", name: "projectSubmissions", component: () => import("../views/SubmissionsTeacherView.vue"), props: (route) => ({ projectId: Number(route.params.projectId) }), + meta: { + middleware: useCanVisit(useIsInstructorOfProjectCondition), + }, }, { path: "/subjects", @@ -85,24 +124,41 @@ const router = createRouter({ path: "/subjects/create", name: "create-subject", component: () => import("../views/subject/CreateSubjectView.vue"), + meta: { + middleware: useCanVisit(useOrCondition(useIsAdminCondition, useIsTeacherCondition)), + }, }, { path: "/subjects/:subjectId(\\d+)", name: "subject", component: () => import("../views/subject/SubjectView.vue"), props: (route) => ({ subjectId: Number(route.params.subjectId) }), + meta: { + middleware: useCanVisit( + useOrCondition( + useIsStudentOfSubjectCondition, + useIsInstructorOfSubjectCondition + ) + ), + }, }, { path: "/subjects/:subjectId(\\d+)/create-project", name: "create-project", component: () => import("../views/CreateProjectView.vue"), props: (route) => ({ subjectId: Number(route.params.subjectId) }), + meta: { + middleware: useCanVisit(useIsInstructorOfSubjectCondition), + }, }, { path: "/project/:projectId(\\d+)/edit", name: "edit-project", component: () => import("../views/CreateProjectView.vue"), // Ensure this is correct props: (route) => ({ projectId: Number(route.params.projectId), isEditMode: true }), + meta: { + middleware: useCanVisit(useIsInstructorOfProjectCondition), + }, }, { path: "/subjects/register/:uuid", @@ -125,6 +181,9 @@ const router = createRouter({ path: "/admin", name: "admin", component: () => import("../views/AdminView.vue"), + meta: { + middleware: useCanVisit(useIsAdminCondition), + }, }, { path: "/:pathMatch(.*)", @@ -138,22 +197,29 @@ const router = createRouter({ }); router.beforeEach(async (to, from, next) => { - const middleware: Middleware[] = []; + const middlewares: Middleware[] = []; // Always check for authentication - middleware.push(isAuthenticated); + middlewares.push(isAuthenticated); // Add additional middleware if specified if (to.meta.middleware) { const meta_middleware = Array.isArray(to.meta.middleware) ? to.meta.middleware : [to.meta.middleware]; - middleware.push(...meta_middleware.filter((m) => m !== isAuthenticated)); + middlewares.push(...meta_middleware.filter((m) => m !== isAuthenticated)); } - const context: MiddlewareContext = { to, from, next, router }; - const nextMiddleware = nextFactory(context, middleware, 0); - return nextMiddleware(); + let new_next = next; + for (let middleware of middlewares) { + const context: MiddlewareContext = { to, from, next: new_next, router }; + const { next: returned_next, final } = await middleware(context); + if (final) { + return returned_next(); + } + new_next = returned_next; + } + return new_next(); }); export default router; diff --git a/frontend/src/router/middleware/canVisit.ts b/frontend/src/router/middleware/canVisit.ts new file mode 100644 index 00000000..0081e1c8 --- /dev/null +++ b/frontend/src/router/middleware/canVisit.ts @@ -0,0 +1,163 @@ +import { computed, type Ref } from "vue"; +import type { Middleware, MiddlewareContext } from "./index"; +import { QueryClient } from "@tanstack/vue-query"; +import useIsAdmin from "@/composables/useIsAdmin"; +import useIsTeacher from "@/composables/useIsTeacher"; +import { useSubjectsQuery } from "@/queries/Subject"; +import { useProjectsQuery } from "@/queries/Project"; +import { useGroupQuery, useProjectGroupQuery } from "@/queries/Group"; +import { useCurrentUserQuery } from "@/queries/User"; + +export interface CanVisitCondition { + ( + queryClient: QueryClient, + context: MiddlewareContext + ): { condition: Ref; isLoading: Ref }; +} + +function useAwaitLoading(isLoading: Ref): Promise { + return new Promise((resolve) => { + const interval = setInterval(() => { + if (!isLoading.value) { + clearInterval(interval); + resolve(); + } + }, 10); + }); +} + +function useCanVisit(useCondition: CanVisitCondition): Middleware { + return async (context) => { + const { next } = context; + const queryClient = new QueryClient(); + const { condition: isAdmin, isLoading: isAdminLoading } = useIsAdminCondition( + queryClient, + context + ); + const { condition, isLoading } = useCondition(queryClient, context); + await useAwaitLoading(computed(() => isAdminLoading.value || isLoading.value)); + if (isAdmin.value || condition.value) { + return { next, final: false }; + } + return { + next: () => next({ path: "not-found" }), + final: true, + }; + }; +} + +export default useCanVisit; + +export function useOrCondition( + condition1: CanVisitCondition, + condition2: CanVisitCondition +): CanVisitCondition { + return (qc, ctx) => { + const { condition: condition1Value, isLoading: isLoading1 } = condition1(qc, ctx); + const { condition: condition2Value, isLoading: isLoading2 } = condition2(qc, ctx); + return { + condition: computed(() => condition1Value.value || condition2Value.value), + isLoading: computed(() => isLoading1.value || isLoading2.value), + }; + }; +} + +export function useAndCondition( + condition1: CanVisitCondition, + condition2: CanVisitCondition +): CanVisitCondition { + return (qc, ctx) => { + const { condition: condition1Value, isLoading: isLoading1 } = condition1(qc, ctx); + const { condition: condition2Value, isLoading: isLoading2 } = condition2(qc, ctx); + return { + condition: condition1Value && condition2Value, + isLoading: isLoading1 || isLoading2, + }; + }; +} + +export const useIsAdminCondition: CanVisitCondition = (qc) => { + const { isAdmin, isLoading } = useIsAdmin(qc); + return { condition: isAdmin, isLoading }; +}; + +export const useIsTeacherCondition: CanVisitCondition = (qc) => { + const { isTeacher, isLoading } = useIsTeacher(qc); + return { condition: isTeacher, isLoading }; +}; + +export const useIsStudentOfSubjectCondition: CanVisitCondition = (qc, ctx) => { + const subjectId = Number(ctx.to.params.subjectId); + const { data: subjects, isLoading } = useSubjectsQuery(qc); + const condition = computed(() => { + return subjects.value?.as_student.findIndex((subject) => subject.id === subjectId) !== -1; + }); + return { condition, isLoading }; +}; + +export const useIsInstructorOfSubjectCondition: CanVisitCondition = (qc, ctx) => { + const subjectId = Number(ctx.to.params.subjectId); + const { data: subjects, isLoading } = useSubjectsQuery(qc); + const condition = computed(() => { + return ( + subjects.value?.as_instructor.findIndex((subject) => subject.id === subjectId) !== -1 + ); + }); + return { condition, isLoading }; +}; + +export const useIsStudentOfProjectCondition: CanVisitCondition = (qc, ctx) => { + const projectId = Number(ctx.to.params.projectId); + const { data: projects, isLoading } = useProjectsQuery(qc); + const condition = computed(() => { + return projects.value?.as_student.findIndex((project) => project.id === projectId) !== -1; + }); + return { condition, isLoading }; +}; + +export const useIsInstructorOfProjectCondition: CanVisitCondition = (qc, ctx) => { + const projectId = Number(ctx.to.params.projectId); + const { data: projects, isLoading } = useProjectsQuery(qc); + const condition = computed(() => { + return ( + projects.value?.as_instructor.findIndex((project) => project.id === projectId) !== -1 + ); + }); + return { condition, isLoading }; +}; + +export const useIsInGroupOfProjectCondition: CanVisitCondition = (qc, ctx) => { + const projectId = Number(ctx.to.params.projectId); + const { data: group, isLoading } = useProjectGroupQuery(projectId, qc); + const condition = computed(() => { + return group.value !== null; + }); + return { condition, isLoading }; +}; + +export const useIsInGroupCondition: CanVisitCondition = (qc, ctx) => { + const groupId = Number(ctx.to.params.groupId); + const { data: group, isLoading: isGroupLoading } = useGroupQuery(groupId, qc); + const { data: user, isLoading: isUserLoading } = useCurrentUserQuery(qc); + const condition = computed(() => { + return group.value?.members.findIndex((member) => member.uid === user.value?.uid) !== -1; + }); + return { condition, isLoading: computed(() => isGroupLoading.value || isUserLoading.value) }; +}; + +export const useIsInstructorOfGroupCondition: CanVisitCondition = (qc, ctx) => { + const groupId = Number(ctx.to.params.groupId); + const { data: group, isLoading: isGroupLoading } = useGroupQuery(groupId, qc); + const { data: projects, isLoading: isProjectsLoading } = useProjectsQuery(qc); + const condition = computed(() => { + return ( + projects.value?.as_instructor.findIndex( + (project) => project.id === group.value?.project_id + ) !== -1 + ); + }); + return { + condition, + isLoading: computed(() => isGroupLoading.value || isProjectsLoading.value), + }; +}; diff --git a/frontend/src/router/middleware/index.ts b/frontend/src/router/middleware/index.ts index ae4e230c..d6eeffda 100644 --- a/frontend/src/router/middleware/index.ts +++ b/frontend/src/router/middleware/index.ts @@ -7,17 +7,9 @@ export interface MiddlewareContext { router: Router; } -export type Middleware = (_: MiddlewareContext) => void; - -export function nextFactory( - context: MiddlewareContext, - middleware: Array, - index: number -): () => void { - const currentMiddleware = middleware[index]; - if (!currentMiddleware) { - return context.next; - } - const nextMiddleware = nextFactory(context, middleware, index + 1); - return () => currentMiddleware({ ...context, next: nextMiddleware }); +export interface MiddlewareResponse { + next: NavigationGuardNext; + final: boolean; } + +export type Middleware = (_: MiddlewareContext) => Promise; diff --git a/frontend/src/router/middleware/isAuthenticated.ts b/frontend/src/router/middleware/isAuthenticated.ts index 0b5449ba..edacd521 100644 --- a/frontend/src/router/middleware/isAuthenticated.ts +++ b/frontend/src/router/middleware/isAuthenticated.ts @@ -1,13 +1,19 @@ import { type Middleware } from "./index"; import { useAuthStore } from "@/stores/auth-store"; -const isAuthenticated: Middleware = ({ to, next, router }) => { +const isAuthenticated: Middleware = async ({ to, next }) => { const requiresAuth = to.meta.requiresAuth !== undefined ? to.meta.requiresAuth : true; const { isLoggedIn } = useAuthStore(); if (requiresAuth && !isLoggedIn) { - router.replace({ name: "login", query: { redirect: to.fullPath } }); + return { + next: () => next({ name: "login", query: { redirect: to.fullPath } }), + final: true, + }; } - return next(); + return { + next, + final: false, + }; }; export default isAuthenticated; diff --git a/frontend/src/router/middleware/logger.ts b/frontend/src/router/middleware/logger.ts new file mode 100644 index 00000000..3ccf16bc --- /dev/null +++ b/frontend/src/router/middleware/logger.ts @@ -0,0 +1,11 @@ +import type { Middleware, MiddlewareContext } from "@/router/middleware"; + +/** + * This middleware logs a message to the console. + * It's main purpose is debugging. + */ +const logger: Middleware = async ({ next }: MiddlewareContext) => { + console.log("Middleware logger"); + return { next, final: false }; +}; +export default logger; diff --git a/frontend/src/router/middleware/login.ts b/frontend/src/router/middleware/login.ts index 0a153f04..5c8f0913 100644 --- a/frontend/src/router/middleware/login.ts +++ b/frontend/src/router/middleware/login.ts @@ -1,20 +1,25 @@ import { type Middleware } from "./index"; import { useAuthStore } from "@/stores/auth-store"; -const login: Middleware = async ({ to, next, router }) => { +const login: Middleware = async ({ to, next }) => { const { login, setRedirect, isLoggedIn } = useAuthStore(); const nextPage = to.query.redirect?.toString() || "/home"; if (isLoggedIn) { - router.replace(nextPage); - return next(); + return { + next: () => next(nextPage), + final: true, + }; } const ticket = to.query.ticket?.toString(); setRedirect(`${nextPage}`); const redirect = await login(nextPage, ticket); if (redirect) { - router.replace(redirect); + return { + next: () => next(redirect), + final: true, + }; } - return next(); + return { next, final: false }; }; export default login;