Merged in feature/hide-cockpit (pull request #26)

Prevent normal users from navigating to the cockpit

Approved-by: Elia Bieri
This commit is contained in:
Christian Cueni 2023-02-27 09:55:18 +00:00
commit 2d580cad18
4 changed files with 150 additions and 4 deletions

View File

@ -0,0 +1,39 @@
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, describe, expect, vi } from "vitest";
import * as courseSessions from "../../stores/courseSessions";
import { expertRequired } from "../guards";
describe("Guards", () => {
afterEach(() => {
vi.restoreAllMocks();
});
beforeEach(() => {
// creates a fresh pinia and make it active so it's automatically picked
// up by any useStore() call without having to pass it to it:
// `useStore(pinia)`
setActivePinia(createPinia());
});
it("cannot route to cockpit", () => {
vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
hasCockpit: false,
});
const slug = "test";
expect(expertRequired({ params: { courseSlug: "test" } })).toEqual(
`/course/${slug}/learn`
);
});
it("can route to cockpit", () => {
vi.spyOn(courseSessions, "useCourseSessionsStore").mockReturnValue({
hasCockpit: true,
});
const to = {
params: {
courseSlug: "test",
},
};
expect(expertRequired(to)).toEqual(to);
});
});

View File

@ -1,7 +1,8 @@
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { useUserStore } from "@/stores/user";
import type { NavigationGuardWithThis, RouteLocationNormalized } from "vue-router";
import type { NavigationGuard, RouteLocationNormalized } from "vue-router";
export const updateLoggedIn: NavigationGuardWithThis<undefined> = async () => {
export const updateLoggedIn: NavigationGuard = async () => {
const loggedIn = getCookieValue("loginStatus") === "true";
const userStore = useUserStore();
@ -11,7 +12,7 @@ export const updateLoggedIn: NavigationGuardWithThis<undefined> = async () => {
}
};
export const redirectToLoginIfRequired: NavigationGuardWithThis<undefined> = (to) => {
export const redirectToLoginIfRequired: NavigationGuard = (to) => {
const userStore = useUserStore();
if (loginRequired(to) && !userStore.loggedIn) {
return `/login?next=${to.fullPath}`;
@ -32,3 +33,13 @@ export const getCookieValue = (cookieName: string): string => {
const loginRequired = (to: RouteLocationNormalized) => {
return !to.meta?.public;
};
export const expertRequired: NavigationGuard = (to: RouteLocationNormalized) => {
const courseSessionsStore = useCourseSessionsStore();
if (courseSessionsStore.hasCockpit) {
return to;
} else {
const courseSlug = to.params.courseSlug as string;
return `/course/${courseSlug}/learn`;
}
};

View File

@ -1,6 +1,10 @@
import DashboardPage from "@/pages/DashboardPage.vue";
import LoginPage from "@/pages/LoginPage.vue";
import { redirectToLoginIfRequired, updateLoggedIn } from "@/router/guards";
import {
expertRequired,
redirectToLoginIfRequired,
updateLoggedIn,
} from "@/router/guards";
import { useAppStore } from "@/stores/app";
import { createRouter, createWebHistory } from "vue-router";
@ -103,6 +107,7 @@ const router = createRouter({
path: "/course/:courseSlug/cockpit",
props: true,
component: () => import("@/pages/cockpit/CockpitParentPage.vue"),
beforeEnter: [expertRequired],
children: [
{
path: "",

View File

@ -0,0 +1,91 @@
import { itGetCached, itPost } from "@/fetchHelpers";
import type { CourseSession } from "@/types";
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, describe, expect, vi } from "vitest";
import { useRoute } from "vue-router";
import { useCourseSessionsStore } from "../courseSessions";
import { useUserStore } from "../user";
let user = {};
let courseSessions: CourseSession[] = [];
describe("CourseSession Store", () => {
vi.mock("vue-router", () => ({
useRoute: () => ({
path: "/course/test-course/learn/",
}),
}));
vi.mock("@/fetchHelpers", () => {
const itGetCached = () => Promise.resolve([]);
const itPost = () => Promise.resolve([]);
return {
itGetCached,
itPost,
};
});
beforeEach(() => {
// creates a fresh pinia and make it active so it's automatically picked
// up by any useStore() call without having to pass it to it:
// `useStore(pinia)`
setActivePinia(createPinia());
user = {
is_superuser: false,
course_session_expert: [],
};
courseSessions = [
{
id: 1,
created_at: "2021-05-11T10:00:00.000000Z",
updated_at: "2023-05-11T10:00:00.000000Z",
course: {
id: 1,
title: "Test Course",
category_name: "Test Category",
slug: "test-course",
},
title: "Test Course Session",
start_date: "2022-05-11T10:00:00.000000Z",
end_date: "2023-05-11T10:00:00.000000Z",
learning_path_url: "/course/test-course/learn/",
competence_url: "/course/test-course/competence/",
course_url: "/course/test-course/",
media_library_url: "/course/test-course/media/",
additional_json_data: {},
documents: [],
users: [],
},
];
});
it("normal user has no cockpit", () => {
const userStore = useUserStore();
const courseSessionsStore = useCourseSessionsStore();
userStore.$patch(user);
courseSessionsStore.$patch({ courseSessions });
expect(courseSessionsStore.hasCockpit).toBeFalsy();
});
it("superuser has cockpit", () => {
const userStore = useUserStore();
const courseSessionsStore = useCourseSessionsStore();
userStore.$patch(Object.assign(user, { is_superuser: true }));
courseSessionsStore.$patch({ courseSessions });
expect(courseSessionsStore.hasCockpit).toBeTruthy();
});
it("expert has cockpit", () => {
const userStore = useUserStore();
const courseSessionsStore = useCourseSessionsStore();
userStore.$patch(
Object.assign(user, { course_session_experts: [courseSessions[0].id] })
);
courseSessionsStore.$patch({ courseSessions });
expect(courseSessionsStore.hasCockpit).toBeTruthy();
});
});