Merge remote-tracking branch 'origin/dev' into feat/user-routes
# Conflicts: # backend/src/controllers/students.ts # backend/src/controllers/teachers.ts # backend/src/data/classes/class-join-request-repository.ts # backend/src/routes/students.ts # backend/src/services/students.ts # backend/src/services/teachers.ts # backend/tests/test_assets/users/students.testdata.ts # frontend/src/controllers/controllers.ts # frontend/src/queries/themes.ts
This commit is contained in:
		
						commit
						7f189188e8
					
				
					 139 changed files with 3594 additions and 3063 deletions
				
			
		|  | @ -1,10 +1,28 @@ | |||
| <script setup lang="ts"> | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
|     auth.loadUser(); | ||||
|     import MenuBar from "@/components/MenuBar.vue"; | ||||
|     import { useRoute } from "vue-router"; | ||||
|     import { computed } from "vue"; | ||||
| 
 | ||||
|     const route = useRoute(); | ||||
|     interface RouteMeta { | ||||
|         requiresAuth?: boolean; | ||||
|     } | ||||
| 
 | ||||
|     const showMenuBar = computed(() => (route.meta as RouteMeta).requiresAuth && auth.authState.user); | ||||
| 
 | ||||
|     auth.loadUser().catch((_error) => { | ||||
|         // TODO Could not load user! | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <router-view /> | ||||
|     <v-app> | ||||
|         <menu-bar v-if="showMenuBar"></menu-bar> | ||||
|         <v-main> | ||||
|             <router-view /> | ||||
|         </v-main> | ||||
|     </v-app> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped></style> | ||||
|  |  | |||
|  | @ -26,84 +26,99 @@ | |||
|     ]); | ||||
| 
 | ||||
|     // Logic to change the language of the website to the selected language | ||||
|     const changeLanguage = (langCode: string) => { | ||||
|     function changeLanguage(langCode: string): void { | ||||
|         locale.value = langCode; | ||||
|         localStorage.setItem("user-lang", langCode); | ||||
|     }; | ||||
|     } | ||||
| 
 | ||||
|     // contains functionality to let the collapsed menu appear and disappear | ||||
|     // when the screen size varies | ||||
|     // Contains functionality to let the collapsed menu appear and disappear when the screen size varies | ||||
|     const drawer = ref(false); | ||||
| 
 | ||||
|     // when the user wants to logout, a popup is shown to verify this | ||||
|     // if verified, the user should be logged out | ||||
|     const performLogout = () => { | ||||
|         auth.logout(); | ||||
|     }; | ||||
|     // When the user wants to logout, a popup is shown to verify this | ||||
|     // If verified, the user should be logged out | ||||
|     async function performLogout(): Promise<void> { | ||||
|         await auth.logout(); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <main> | ||||
|         <v-app class="menu_collapsed"> | ||||
|             <v-app-bar | ||||
|                 app | ||||
|                 style="background-color: #f6faf2" | ||||
|     <v-app-bar | ||||
|         class="app-bar" | ||||
|         app | ||||
|     > | ||||
|         <v-app-bar-nav-icon | ||||
|             class="menu_collapsed" | ||||
|             @click="drawer = !drawer" | ||||
|         /> | ||||
|         <router-link | ||||
|             to="/user" | ||||
|             class="dwengo_home" | ||||
|         > | ||||
|             <div> | ||||
|                 <img | ||||
|                     class="dwengo_logo" | ||||
|                     alt="Dwengo logo" | ||||
|                     :src="dwengoLogo" | ||||
|                 /> | ||||
|                 <p class="caption"> | ||||
|                     {{ t(`${role}`) }} | ||||
|                 </p> | ||||
|             </div> | ||||
|         </router-link> | ||||
|         <v-toolbar-items class="menu"> | ||||
|             <v-btn | ||||
|                 class="menu_item" | ||||
|                 variant="text" | ||||
|                 to="/user/assignment" | ||||
|             > | ||||
|                 <template v-slot:prepend> | ||||
|                     <v-app-bar-nav-icon @click="drawer = !drawer" /> | ||||
|                 </template> | ||||
| 
 | ||||
|                 <v-app-bar-title> | ||||
|                     <router-link | ||||
|                         to="/user" | ||||
|                         class="dwengo_home" | ||||
|                 {{ t("assignments") }} | ||||
|             </v-btn> | ||||
|             <v-btn | ||||
|                 class="menu_item" | ||||
|                 variant="text" | ||||
|                 to="/user/class" | ||||
|             > | ||||
|                 {{ t("classes") }} | ||||
|             </v-btn> | ||||
|             <v-btn | ||||
|                 class="menu_item" | ||||
|                 variant="text" | ||||
|                 to="/user/discussion" | ||||
|             > | ||||
|                 {{ t("discussions") }} | ||||
|             </v-btn> | ||||
|             <v-menu open-on-hover> | ||||
|                 <template v-slot:activator="{ props }"> | ||||
|                     <v-btn | ||||
|                         v-bind="props" | ||||
|                         icon | ||||
|                         variant="text" | ||||
|                     > | ||||
|                         <div> | ||||
|                             <img | ||||
|                                 class="dwengo_logo" | ||||
|                                 :src="dwengoLogo" | ||||
|                                 style="width: 100px" | ||||
|                             /> | ||||
|                             <p | ||||
|                                 class="caption" | ||||
|                                 style="font-size: smaller" | ||||
|                             > | ||||
|                                 {{ t(`${role}`) }} | ||||
|                             </p> | ||||
|                         </div> | ||||
|                     </router-link> | ||||
|                 </v-app-bar-title> | ||||
| 
 | ||||
|                 <v-spacer></v-spacer> | ||||
| 
 | ||||
|                 <v-menu open-on-hover> | ||||
|                     <template v-slot:activator="{ props }"> | ||||
|                         <v-btn | ||||
|                             v-bind="props" | ||||
|                             icon | ||||
|                             variant="text" | ||||
|                         > | ||||
|                             <v-icon | ||||
|                                 icon="mdi-translate" | ||||
|                                 size="small" | ||||
|                                 color="#0e6942" | ||||
|                             ></v-icon> | ||||
|                         </v-btn> | ||||
|                     </template> | ||||
|                     <v-list> | ||||
|                         <v-list-item | ||||
|                             v-for="(language, index) in languages" | ||||
|                             :key="index" | ||||
|                             @click="changeLanguage(language.code)" | ||||
|                         > | ||||
|                             <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                         </v-list-item> | ||||
|                     </v-list> | ||||
|                 </v-menu> | ||||
| 
 | ||||
|                         <v-icon | ||||
|                             icon="mdi-translate" | ||||
|                             size="small" | ||||
|                             color="#0e6942" | ||||
|                         ></v-icon> | ||||
|                     </v-btn> | ||||
|                 </template> | ||||
|                 <v-list> | ||||
|                     <v-list-item | ||||
|                         v-for="(language, index) in languages" | ||||
|                         :key="index" | ||||
|                         @click="changeLanguage(language.code)" | ||||
|                     > | ||||
|                         <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                     </v-list-item> | ||||
|                 </v-list> | ||||
|             </v-menu> | ||||
|         </v-toolbar-items> | ||||
|         <v-spacer></v-spacer> | ||||
|         <v-dialog max-width="500"> | ||||
|             <template v-slot:activator="{ props: activatorProps }"> | ||||
|                 <v-btn | ||||
|                     @click="performLogout" | ||||
|                     text | ||||
|                     v-bind="activatorProps" | ||||
|                     :rounded="true" | ||||
|                     variant="text" | ||||
|                 > | ||||
|                     <v-tooltip | ||||
|                         :text="t('logout')" | ||||
|  | @ -115,201 +130,81 @@ | |||
|                                 icon="mdi-logout" | ||||
|                                 size="x-large" | ||||
|                                 color="#0e6942" | ||||
|                             /> | ||||
|                             > | ||||
|                             </v-icon> | ||||
|                         </template> | ||||
|                     </v-tooltip> | ||||
|                 </v-btn> | ||||
|             </v-app-bar> | ||||
|             </template> | ||||
| 
 | ||||
|             <v-navigation-drawer | ||||
|                 v-model="drawer" | ||||
|                 app | ||||
|             > | ||||
|                 <v-list> | ||||
|                     <v-list-item | ||||
|                         to="/user/assignment" | ||||
|                         link | ||||
|                     > | ||||
|                         <v-list-item-content> | ||||
|                             <v-list-item-title class="menu_item">{{ t("assignments") }}</v-list-item-title> | ||||
|                         </v-list-item-content> | ||||
|                     </v-list-item> | ||||
|             <template v-slot:default="{ isActive }"> | ||||
|                 <v-card :title="t('logoutVerification')"> | ||||
|                     <v-card-actions> | ||||
|                         <v-spacer></v-spacer> | ||||
| 
 | ||||
|                     <v-list-item | ||||
|                         to="/user/class" | ||||
|                         link | ||||
|                     > | ||||
|                         <v-list-item-content> | ||||
|                             <v-list-item-title class="menu_item">{{ t("classes") }}</v-list-item-title> | ||||
|                         </v-list-item-content> | ||||
|                     </v-list-item> | ||||
| 
 | ||||
|                     <v-list-item | ||||
|                         to="/user/discussion" | ||||
|                         link | ||||
|                     > | ||||
|                         <v-list-item-content> | ||||
|                             <v-list-item-title class="menu_item">{{ t("discussions") }}</v-list-item-title> | ||||
|                         </v-list-item-content> | ||||
|                     </v-list-item> | ||||
|                 </v-list> | ||||
|             </v-navigation-drawer> | ||||
|         </v-app> | ||||
| 
 | ||||
|         <nav class="menu"> | ||||
|             <div class="left"> | ||||
|                 <ul> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             to="/user" | ||||
|                             class="dwengo_home" | ||||
|                         > | ||||
|                             <img | ||||
|                                 class="dwengo_logo" | ||||
|                                 :src="dwengoLogo" | ||||
|                             /> | ||||
|                             <p class="caption"> | ||||
|                                 {{ t(`${role}`) }} | ||||
|                             </p> | ||||
|                         </router-link> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             :to="`/user/assignment`" | ||||
|                             class="menu_item" | ||||
|                         > | ||||
|                             {{ t("assignments") }} | ||||
|                         </router-link> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             to="/user/class" | ||||
|                             class="menu_item" | ||||
|                             >{{ t("classes") }}</router-link | ||||
|                         > | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <router-link | ||||
|                             to="/user/discussion" | ||||
|                             class="menu_item" | ||||
|                             >{{ t("discussions") }} | ||||
|                         </router-link> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <v-menu open-on-hover> | ||||
|                             <template v-slot:activator="{ props }"> | ||||
|                                 <v-btn | ||||
|                                     v-bind="props" | ||||
|                                     icon | ||||
|                                     variant="text" | ||||
|                                 > | ||||
|                                     <v-icon | ||||
|                                         icon="mdi-translate" | ||||
|                                         size="small" | ||||
|                                         color="#0e6942" | ||||
|                                     ></v-icon> | ||||
|                                 </v-btn> | ||||
|                             </template> | ||||
|                             <v-list> | ||||
|                                 <v-list-item | ||||
|                                     v-for="(language, index) in languages" | ||||
|                                     :key="index" | ||||
|                                     @click="changeLanguage(language.code)" | ||||
|                                 > | ||||
|                                     <v-list-item-title>{{ language.name }}</v-list-item-title> | ||||
|                                 </v-list-item> | ||||
|                             </v-list> | ||||
|                         </v-menu> | ||||
|                     </li> | ||||
|                 </ul> | ||||
|             </div> | ||||
|             <div class="right"> | ||||
|                 <li> | ||||
|                     <!-- <v-btn | ||||
|                         @click="performLogout" | ||||
|                         to="/login" | ||||
|                         style="background-color: transparent; box-shadow: none !important" | ||||
|                     > | ||||
|                         <v-tooltip | ||||
|                         <v-btn | ||||
|                             :text="t('cancel')" | ||||
|                             @click="isActive.value = false" | ||||
|                         ></v-btn> | ||||
|                         <v-btn | ||||
|                             :text="t('logout')" | ||||
|                             location="bottom" | ||||
|                         > | ||||
|                             <template v-slot:activator="{ props }"> | ||||
|                                 <v-icon | ||||
|                                     v-bind="props" | ||||
|                                     icon="mdi-logout" | ||||
|                                     size="x-large" | ||||
|                                     color="#0e6942" | ||||
|                                 ></v-icon> | ||||
|                             </template> | ||||
|                         </v-tooltip> | ||||
|                     </v-btn> --> | ||||
|                     <v-dialog max-width="500"> | ||||
|                         <template v-slot:activator="{ props: activatorProps }"> | ||||
|                             <v-btn | ||||
|                                 v-bind="activatorProps" | ||||
|                                 style="background-color: transparent; box-shadow: none !important" | ||||
|                             > | ||||
|                                 <v-tooltip | ||||
|                                     :text="t('logout')" | ||||
|                                     location="bottom" | ||||
|                                 > | ||||
|                                     <template v-slot:activator="{ props }"> | ||||
|                                         <v-icon | ||||
|                                             v-bind="props" | ||||
|                                             icon="mdi-logout" | ||||
|                                             size="x-large" | ||||
|                                             color="#0e6942" | ||||
|                                         > | ||||
|                                         </v-icon> | ||||
|                                     </template> | ||||
|                                 </v-tooltip> | ||||
|                             </v-btn> | ||||
|                         </template> | ||||
|                             @click="performLogout" | ||||
|                             to="/login" | ||||
|                         ></v-btn> | ||||
|                     </v-card-actions> | ||||
|                 </v-card> | ||||
|             </template> | ||||
|         </v-dialog> | ||||
|         <v-avatar | ||||
|             size="large" | ||||
|             color="#0e6942" | ||||
|             class="user-button" | ||||
|             >{{ initials }}</v-avatar | ||||
|         > | ||||
|     </v-app-bar> | ||||
|     <v-navigation-drawer | ||||
|         v-model="drawer" | ||||
|         temporary | ||||
|         app | ||||
|     > | ||||
|         <v-list> | ||||
|             <v-list-item | ||||
|                 to="/user/assignment" | ||||
|                 link | ||||
|             > | ||||
|                 <v-list-item-title class="menu_item">{{ t("assignments") }}</v-list-item-title> | ||||
|             </v-list-item> | ||||
| 
 | ||||
|                         <template v-slot:default="{ isActive }"> | ||||
|                             <v-card :title="t('logoutVerification')"> | ||||
|                                 <v-card-actions> | ||||
|                                     <v-spacer></v-spacer> | ||||
|             <v-list-item | ||||
|                 to="/user/class" | ||||
|                 link | ||||
|             > | ||||
|                 <v-list-item-title class="menu_item">{{ t("classes") }}</v-list-item-title> | ||||
|             </v-list-item> | ||||
| 
 | ||||
|                                     <v-btn | ||||
|                                         :text="t('cancel')" | ||||
|                                         @click="isActive.value = false" | ||||
|                                     ></v-btn> | ||||
|                                     <v-btn | ||||
|                                         :text="t('logout')" | ||||
|                                         @click="performLogout" | ||||
|                                         to="/login" | ||||
|                                     ></v-btn> | ||||
|                                 </v-card-actions> | ||||
|                             </v-card> | ||||
|                         </template> | ||||
|                     </v-dialog> | ||||
|                 </li> | ||||
|                 <li> | ||||
|                     <v-avatar | ||||
|                         size="large" | ||||
|                         color="#0e6942" | ||||
|                         style="font-size: large; font-weight: bold" | ||||
|                         >{{ initials }}</v-avatar | ||||
|                     > | ||||
|                 </li> | ||||
|             </div> | ||||
|         </nav> | ||||
|         <router-view /> | ||||
|     </main> | ||||
|             <v-list-item | ||||
|                 to="/user/discussion" | ||||
|                 link | ||||
|             > | ||||
|                 <v-list-item-title class="menu_item">{{ t("discussions") }}</v-list-item-title> | ||||
|             </v-list-item> | ||||
|         </v-list> | ||||
|     </v-navigation-drawer> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped> | ||||
|     .app-bar { | ||||
|         background-color: #f6faf2; | ||||
|     } | ||||
|     .menu { | ||||
|         background-color: #f6faf2; | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|     } | ||||
| 
 | ||||
|     .right { | ||||
|         align-items: center; | ||||
|         padding: 10px; | ||||
|     .user-button { | ||||
|         margin-right: 10px; | ||||
|         font-size: large; | ||||
|         font-weight: bold; | ||||
|     } | ||||
| 
 | ||||
|     .right li { | ||||
|  | @ -347,16 +242,19 @@ | |||
|         color: #0e6942; | ||||
|         text-decoration: none; | ||||
|         font-size: large; | ||||
|     } | ||||
| 
 | ||||
|     nav a.router-link-active { | ||||
|         font-weight: bold; | ||||
|         text-transform: none; | ||||
|     } | ||||
| 
 | ||||
|     @media (max-width: 700px) { | ||||
|         .menu { | ||||
|             display: none; | ||||
|         } | ||||
|         .caption { | ||||
|             font-size: smaller; | ||||
|         } | ||||
|         .dwengo_logo { | ||||
|             width: 100px; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @media (min-width: 701px) { | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ export class BaseController { | |||
|         this.baseUrl = `${apiConfig.baseUrl}/${basePath}`; | ||||
|     } | ||||
| 
 | ||||
|     protected async get<T>(path: string, queryParams?: Record<string, any>): Promise<T> { | ||||
|     protected async get<T>(path: string, queryParams?: Record<string, string | number | boolean>): Promise<T> { | ||||
|         let url = `${this.baseUrl}${path}`; | ||||
|         if (queryParams) { | ||||
|             const query = new URLSearchParams(); | ||||
|  |  | |||
|  | @ -5,12 +5,12 @@ export class ThemeController extends BaseController { | |||
|         super("theme"); | ||||
|     } | ||||
| 
 | ||||
|     getAll(language: string | null = null) { | ||||
|     async getAll(language: string | null = null): Promise<unknown> { | ||||
|         const query = language ? { language } : undefined; | ||||
|         return this.get<any[]>("/", query); | ||||
|         return this.get("/", query); | ||||
|     } | ||||
| 
 | ||||
|     getHruidsByKey(themeKey: string) { | ||||
|     async getHruidsByKey(themeKey: string): Promise<string[]> { | ||||
|         return this.get<string[]>(`/${encodeURIComponent(themeKey)}`); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,8 +4,8 @@ import { ThemeController } from "@/controllers/themes.ts"; | |||
| 
 | ||||
| const themeController = new ThemeController(); | ||||
| 
 | ||||
| export const useThemeQuery = (language: MaybeRefOrGetter<string>) => | ||||
|     useQuery({ | ||||
| export function useThemeQuery(language: MaybeRefOrGetter<string | undefined>){ | ||||
|     return useQuery({ | ||||
|         queryKey: ["themes", language], | ||||
|         queryFn: () => { | ||||
|             const lang = toValue(language); | ||||
|  | @ -13,10 +13,12 @@ export const useThemeQuery = (language: MaybeRefOrGetter<string>) => | |||
|         }, | ||||
|         enabled: () => Boolean(toValue(language)), | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export const useThemeHruidsQuery = (themeKey: string | null) => | ||||
|     useQuery({ | ||||
| export function useThemeHruidsQuery(themeKey: MaybeRefOrGetter<string | undefined>){ | ||||
|     return useQuery({ | ||||
|         queryKey: ["theme-hruids", themeKey], | ||||
|         queryFn: () => themeController.getHruidsByKey(themeKey!), | ||||
|         queryFn: () => themeController.getHruidsByKey(toValue(themeKey)!), | ||||
|         enabled: Boolean(themeKey), | ||||
|     }); | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,4 @@ | |||
| import { createRouter, createWebHistory } from "vue-router"; | ||||
| import MenuBar from "@/components/MenuBar.vue"; | ||||
| import SingleAssignment from "@/views/assignments/SingleAssignment.vue"; | ||||
| import SingleClass from "@/views/classes/SingleClass.vue"; | ||||
| import SingleDiscussion from "@/views/discussions/SingleDiscussion.vue"; | ||||
|  | @ -21,13 +20,13 @@ const router = createRouter({ | |||
|         { | ||||
|             path: "/", | ||||
|             name: "home", | ||||
|             component: () => import("../views/HomePage.vue"), | ||||
|             component: async (): Promise<unknown> => import("../views/HomePage.vue"), | ||||
|             meta: { requiresAuth: false }, | ||||
|         }, | ||||
|         { | ||||
|             path: "/login", | ||||
|             name: "LoginPage", | ||||
|             component: () => import("../views/LoginPage.vue"), | ||||
|             component: async (): Promise<unknown> => import("../views/LoginPage.vue"), | ||||
|             meta: { requiresAuth: false }, | ||||
|         }, | ||||
|         { | ||||
|  | @ -38,7 +37,6 @@ const router = createRouter({ | |||
| 
 | ||||
|         { | ||||
|             path: "/user", | ||||
|             component: MenuBar, | ||||
|             meta: { requiresAuth: true }, | ||||
|             children: [ | ||||
|                 { | ||||
|  | @ -115,7 +113,7 @@ const router = createRouter({ | |||
|     ], | ||||
| }); | ||||
| 
 | ||||
| router.beforeEach(async (to, from, next) => { | ||||
| router.beforeEach(async (to, _from, next) => { | ||||
|     // Verify if user is logged in before accessing certain routes
 | ||||
|     if (to.meta.requiresAuth) { | ||||
|         if (!authState.isLoggedIn.value) { | ||||
|  |  | |||
|  | @ -1,11 +1,15 @@ | |||
| import apiClient from "@/services/api-client.ts"; | ||||
| import type { FrontendAuthConfig } from "@/services/auth/auth.d.ts"; | ||||
| import type { UserManagerSettings } from "oidc-client-ts"; | ||||
| 
 | ||||
| export const AUTH_CONFIG_ENDPOINT = "auth/config"; | ||||
| 
 | ||||
| /** | ||||
|  * Fetch the authentication configuration from the backend. | ||||
|  */ | ||||
| export async function loadAuthConfig() { | ||||
|     const authConfig = (await apiClient.get<FrontendAuthConfig>("auth/config")).data; | ||||
| export async function loadAuthConfig(): Promise<Record<string, UserManagerSettings>> { | ||||
|     const authConfigResponse = await apiClient.get<FrontendAuthConfig>(AUTH_CONFIG_ENDPOINT); | ||||
|     const authConfig = authConfigResponse.data; | ||||
|     return { | ||||
|         student: { | ||||
|             authority: authConfig.student.authority, | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ | |||
| import { computed, reactive } from "vue"; | ||||
| import type { AuthState, Role, UserManagersForRoles } from "@/services/auth/auth.d.ts"; | ||||
| import { User, UserManager } from "oidc-client-ts"; | ||||
| import { loadAuthConfig } from "@/services/auth/auth-config-loader.ts"; | ||||
| import { AUTH_CONFIG_ENDPOINT, loadAuthConfig } from "@/services/auth/auth-config-loader.ts"; | ||||
| import authStorage from "./auth-storage.ts"; | ||||
| import { loginRoute } from "@/config.ts"; | ||||
| import apiClient from "@/services/api-client.ts"; | ||||
|  | @ -49,7 +49,7 @@ const isLoggedIn = computed(() => authState.user !== null); | |||
| /** | ||||
|  * Redirect the user to the login page where he/she can choose whether to log in as a student or teacher. | ||||
|  */ | ||||
| async function initiateLogin() { | ||||
| async function initiateLogin(): Promise<void> { | ||||
|     await router.push(loginRoute); | ||||
| } | ||||
| 
 | ||||
|  | @ -77,20 +77,20 @@ async function handleLoginCallback(): Promise<void> { | |||
| /** | ||||
|  * Refresh an expired authorization token. | ||||
|  */ | ||||
| async function renewToken() { | ||||
| async function renewToken(): Promise<User | null> { | ||||
|     const activeRole = authStorage.getActiveRole(); | ||||
|     if (!activeRole) { | ||||
|         console.log("Can't renew the token: Not logged in!"); | ||||
|         // FIXME console.log("Can't renew the token: Not logged in!");
 | ||||
|         await initiateLogin(); | ||||
|         return; | ||||
|         return null; | ||||
|     } | ||||
|     try { | ||||
|         return await (await getUserManagers())[activeRole].signinSilent(); | ||||
|     } catch (error) { | ||||
|         console.log("Can't renew the token:"); | ||||
|         console.log(error); | ||||
|     } catch (_error) { | ||||
|         // FIXME console.log("Can't renew the token: " + error);
 | ||||
|         await initiateLogin(); | ||||
|     } | ||||
|     return null; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | @ -108,12 +108,12 @@ async function logout(): Promise<void> { | |||
| apiClient.interceptors.request.use( | ||||
|     async (reqConfig) => { | ||||
|         const token = authState?.user?.access_token; | ||||
|         if (token) { | ||||
|         if (token && reqConfig.url !== AUTH_CONFIG_ENDPOINT) { | ||||
|             reqConfig.headers.Authorization = `Bearer ${token}`; | ||||
|         } | ||||
|         return reqConfig; | ||||
|     }, | ||||
|     (error) => Promise.reject(error), | ||||
|     async (error) => Promise.reject(error), | ||||
| ); | ||||
| 
 | ||||
| // Registering interceptor to refresh the token when a request failed because it was expired.
 | ||||
|  | @ -121,8 +121,8 @@ apiClient.interceptors.response.use( | |||
|     (response) => response, | ||||
|     async (error: AxiosError<{ message?: string }>) => { | ||||
|         if (error.response?.status === 401) { | ||||
|             if (error.response!.data.message === "token_expired") { | ||||
|                 console.log("Access token expired, trying to refresh..."); | ||||
|             if (error.response.data.message === "token_expired") { | ||||
|                 // FIXME console.log("Access token expired, trying to refresh...");
 | ||||
|                 await renewToken(); | ||||
|                 return apiClient(error.config!); // Retry the request
 | ||||
|             } // Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login.
 | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ export default { | |||
|      * Set the role the user is currently logged in as from the local persistent storage. | ||||
|      * This should happen when the user logs in with another account. | ||||
|      */ | ||||
|     setActiveRole(role: Role) { | ||||
|     setActiveRole(role: Role): void { | ||||
|         localStorage.setItem("activeRole", role); | ||||
|     }, | ||||
| 
 | ||||
|  | @ -20,7 +20,7 @@ export default { | |||
|      * Remove the saved current role from the local persistent storage. | ||||
|      * This should happen when the user is logged out. | ||||
|      */ | ||||
|     deleteActiveRole() { | ||||
|     deleteActiveRole(): void { | ||||
|         localStorage.removeItem("activeRole"); | ||||
|     }, | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										17
									
								
								frontend/src/services/auth/auth.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								frontend/src/services/auth/auth.d.ts
									
										
									
									
										vendored
									
									
								
							|  | @ -1,22 +1,25 @@ | |||
| import { type User, UserManager } from "oidc-client-ts"; | ||||
| 
 | ||||
| export type AuthState = { | ||||
| export interface AuthState { | ||||
|     user: User | null; | ||||
|     accessToken: string | null; | ||||
|     activeRole: Role | null; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export type FrontendAuthConfig = { | ||||
| export interface FrontendAuthConfig { | ||||
|     student: FrontendIdpConfig; | ||||
|     teacher: FrontendIdpConfig; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export type FrontendIdpConfig = { | ||||
| export interface FrontendIdpConfig { | ||||
|     authority: string; | ||||
|     clientId: string; | ||||
|     scope: string; | ||||
|     responseType: string; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export type Role = "student" | "teacher"; | ||||
| export type UserManagersForRoles = { student: UserManager; teacher: UserManager }; | ||||
| export interface UserManagersForRoles { | ||||
|     student: UserManager; | ||||
|     teacher: UserManager; | ||||
| } | ||||
|  |  | |||
|  | @ -8,9 +8,9 @@ | |||
|     onMounted(async () => { | ||||
|         try { | ||||
|             await auth.handleLoginCallback(); | ||||
|             await router.replace("/"); // Redirect to home (or dashboard) | ||||
|         } catch (error) { | ||||
|             console.error("OIDC callback error:", error); | ||||
|             await router.replace("/user"); // Redirect to theme page | ||||
|         } catch (_error) { | ||||
|             // FIXME console.error("OIDC callback error:", error); | ||||
|         } | ||||
|     }); | ||||
| </script> | ||||
|  |  | |||
|  | @ -13,10 +13,10 @@ | |||
|     ]); | ||||
| 
 | ||||
|     // Logic to change the language of the website to the selected language | ||||
|     const changeLanguage = (langCode: string) => { | ||||
|     function changeLanguage(langCode: string): void { | ||||
|         locale.value = langCode; | ||||
|         localStorage.setItem("user-lang", langCode); | ||||
|     }; | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -25,9 +25,10 @@ | |||
|             <div class="container_left"> | ||||
|                 <img | ||||
|                     :src="dwengoLogo" | ||||
|                     alt="Dwengo logo" | ||||
|                     style="align-self: center" | ||||
|                 /> | ||||
|                 <h> {{ t("homeTitle") }}</h> | ||||
|                 <h1>{{ t("homeTitle") }}</h1> | ||||
|                 <p class="info"> | ||||
|                     {{ t("homeIntroduction1") }} | ||||
|                 </p> | ||||
|  | @ -55,7 +56,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/innovative.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("innovative") }}</h> | ||||
|                     <h2 class="big">{{ t("innovative") }}</h2> | ||||
|                 </div> | ||||
|                 <div class="img_small"> | ||||
|                     <v-img | ||||
|  | @ -63,7 +64,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/research_based.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("researchBased") }}</h> | ||||
|                     <h2 class="big">{{ t("researchBased") }}</h2> | ||||
|                 </div> | ||||
|                 <div class="img_small"> | ||||
|                     <v-img | ||||
|  | @ -71,7 +72,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/inclusive.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("sociallyRelevant") }}</h> | ||||
|                     <h2 class="big">{{ t("sociallyRelevant") }}</h2> | ||||
|                 </div> | ||||
|                 <div class="img_small"> | ||||
|                     <v-img | ||||
|  | @ -79,7 +80,7 @@ | |||
|                         width="125" | ||||
|                         src="/assets/home/socially_relevant.png" | ||||
|                     ></v-img> | ||||
|                     <h class="big">{{ t("inclusive") }}</h> | ||||
|                     <h2 class="big">{{ t("inclusive") }}</h2> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="container_right"> | ||||
|  | @ -158,7 +159,7 @@ | |||
|         margin-bottom: 10px; | ||||
|     } | ||||
| 
 | ||||
|     h { | ||||
|     h2 { | ||||
|         font-size: large; | ||||
|         font-weight: bold; | ||||
|         align-self: center; | ||||
|  |  | |||
|  | @ -2,16 +2,16 @@ | |||
|     import dwengoLogo from "../../../assets/img/dwengo-groen-zwart.svg"; | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
| 
 | ||||
|     function loginAsStudent() { | ||||
|         auth.loginAs("student"); | ||||
|     async function loginAsStudent(): Promise<void> { | ||||
|         await auth.loginAs("student"); | ||||
|     } | ||||
| 
 | ||||
|     function loginAsTeacher() { | ||||
|         auth.loginAs("teacher"); | ||||
|     async function loginAsTeacher(): Promise<void> { | ||||
|         await auth.loginAs("teacher"); | ||||
|     } | ||||
| 
 | ||||
|     function performLogout() { | ||||
|         auth.logout(); | ||||
|     async function performLogout(): Promise<void> { | ||||
|         await auth.logout(); | ||||
|     } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
		Reference in a new issue
	
	 Gabriellvl
						Gabriellvl