Merge branch 'dev' into feat/assignment-page
This commit is contained in:
		
						commit
						9dcc1091cc
					
				
					 20 changed files with 148 additions and 53 deletions
				
			
		|  | @ -10,10 +10,6 @@ | |||
|     } | ||||
| 
 | ||||
|     const showMenuBar = computed(() => (route.meta as RouteMeta).requiresAuth && auth.authState.user); | ||||
| 
 | ||||
|     auth.loadUser().catch((_error) => { | ||||
|         // TODO Could not load user! | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| <script setup lang="ts"> | ||||
|     import { ref } from "vue"; | ||||
|     import { useI18n } from "vue-i18n"; | ||||
|     import { useRouter } from "vue-router"; | ||||
| 
 | ||||
|     import auth from "@/services/auth/auth-service.ts"; | ||||
| 
 | ||||
|  | @ -10,6 +11,7 @@ | |||
|     const { t, locale } = useI18n(); | ||||
| 
 | ||||
|     const role = auth.authState.activeRole; | ||||
|     const router = useRouter(); | ||||
| 
 | ||||
|     const name = ref(auth.authState.user!.profile.name!); | ||||
|     const initials: string = name.value | ||||
|  |  | |||
|  | @ -88,6 +88,9 @@ | |||
|     "sent": "sent", | ||||
|     "failed": "fehlgeschlagen", | ||||
|     "wrong": "etwas ist schief gelaufen", | ||||
|     "created": "erstellt", | ||||
|     "callbackLoading": "Sie werden angemeldet...", | ||||
|     "loginUnexpectedError": "Anmeldung fehlgeschlagen", | ||||
|     "submitSolution": "Lösung einreichen", | ||||
|     "submitNewSolution": "Neue Lösung einreichen", | ||||
|     "markAsDone": "Als fertig markieren", | ||||
|  | @ -107,8 +110,8 @@ | |||
|     "created": "erstellt", | ||||
|     "remove": "entfernen", | ||||
|     "students": "Studenten", | ||||
|     "classJoinRequests": "Anfragen verbinden", | ||||
|     "reject": "zurückweisen", | ||||
|     "classJoinRequests": "Beitrittsanfragen", | ||||
|     "reject": "ablehnen", | ||||
|     "areusure": "Sind Sie sicher?", | ||||
|     "yes": "ja", | ||||
|     "teachers": "Lehrer", | ||||
|  | @ -117,4 +120,3 @@ | |||
|     "enterUsername": "Geben Sie den Benutzernamen der Lehrkraft ein, die Sie einladen möchten", | ||||
|     "username": "Nutzername", | ||||
|     "invite": "einladen" | ||||
| } | ||||
|  |  | |||
|  | @ -88,6 +88,9 @@ | |||
|     "sent": "sent", | ||||
|     "failed": "failed", | ||||
|     "wrong": "something went wrong", | ||||
|     "created": "created", | ||||
|     "callbackLoading": "You are being logged in...", | ||||
|     "loginUnexpectedError": "Login failed", | ||||
|     "submitSolution": "Submit solution", | ||||
|     "submitNewSolution": "Submit new solution", | ||||
|     "markAsDone": "Mark as completed", | ||||
|  |  | |||
|  | @ -89,6 +89,8 @@ | |||
|     "failed": "échoué", | ||||
|     "wrong": "quelque chose n'a pas fonctionné", | ||||
|     "created": "créé", | ||||
|     "callbackLoading": "Vous serez connecté...", | ||||
|     "loginUnexpectedError": "La connexion a échoué", | ||||
|     "submitSolution": "Soumettre la solution", | ||||
|     "submitNewSolution": "Soumettre une nouvelle solution", | ||||
|     "markAsDone": "Marquer comme terminé", | ||||
|  |  | |||
|  | @ -89,6 +89,8 @@ | |||
|     "failed": "mislukt", | ||||
|     "wrong": "er ging iets verkeerd", | ||||
|     "created": "gecreëerd", | ||||
|     "callbackLoading": "Je wordt ingelogd...", | ||||
|     "loginUnexpectedError": "Inloggen mislukt", | ||||
|     "submitSolution": "Oplossing indienen", | ||||
|     "submitNewSolution": "Nieuwe oplossing indienen", | ||||
|     "markAsDone": "Markeren als afgewerkt", | ||||
|  |  | |||
|  | @ -12,9 +12,11 @@ import App from "./App.vue"; | |||
| import router from "./router"; | ||||
| import { aliases, mdi } from "vuetify/iconsets/mdi"; | ||||
| import { VueQueryPlugin, QueryClient } from "@tanstack/vue-query"; | ||||
| import authService from "./services/auth/auth-service.ts"; | ||||
| 
 | ||||
| const app = createApp(App); | ||||
| 
 | ||||
| await authService.loadUser(); | ||||
| app.use(router); | ||||
| 
 | ||||
| const link = document.createElement("link"); | ||||
|  |  | |||
|  | @ -142,9 +142,8 @@ const router = createRouter({ | |||
| router.beforeEach(async (to, _from, next) => { | ||||
|     // Verify if user is logged in before accessing certain routes
 | ||||
|     if (to.meta.requiresAuth) { | ||||
|         if (!authState.isLoggedIn.value) { | ||||
|             //Next("/login");
 | ||||
|             next(); | ||||
|         if (!authService.isLoggedIn.value) { | ||||
|             next("/login"); | ||||
|         } else { | ||||
|             next(); | ||||
|         } | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ async function getUserManagers(): Promise<UserManagersForRoles> { | |||
| const authState = reactive<AuthState>({ | ||||
|     user: null, | ||||
|     accessToken: null, | ||||
|     activeRole: authStorage.getActiveRole() || null, | ||||
|     activeRole: authStorage.getActiveRole() ?? null, | ||||
| }); | ||||
| 
 | ||||
| /** | ||||
|  | @ -38,18 +38,38 @@ async function loadUser(): Promise<User | null> { | |||
|         return null; | ||||
|     } | ||||
|     const user = await (await getUserManagers())[activeRole].getUser(); | ||||
|     authState.user = user; | ||||
|     authState.accessToken = user?.access_token || null; | ||||
|     authState.activeRole = activeRole || null; | ||||
|     setUserAuthInfo(user); | ||||
|     authState.activeRole = activeRole ?? null; | ||||
|     return user; | ||||
| } | ||||
| 
 | ||||
| const isLoggedIn = computed(() => authState.user !== null); | ||||
| 
 | ||||
| /** | ||||
|  * Clears all the cached information about the current authentication. | ||||
|  */ | ||||
| function clearAuthState(): void { | ||||
|     authStorage.deleteActiveRole(); | ||||
|     authState.accessToken = null; | ||||
|     authState.user = null; | ||||
|     authState.activeRole = null; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Sets the information about the currently logged-in user in the cache. | ||||
|  */ | ||||
| function setUserAuthInfo(newUser: User | null): void { | ||||
|     authState.user = newUser; | ||||
|     authState.accessToken = newUser?.access_token ?? 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(): Promise<void> { | ||||
|     if (isLoggedIn.value) { | ||||
|         clearAuthState(); | ||||
|     } | ||||
|     await router.push(loginRoute); | ||||
| } | ||||
| 
 | ||||
|  | @ -72,6 +92,7 @@ async function handleLoginCallback(): Promise<void> { | |||
|         throw new Error("Login callback received, but the user is not logging in!"); | ||||
|     } | ||||
|     authState.user = (await (await getUserManagers())[activeRole].signinCallback()) || null; | ||||
|     await apiClient.post("/auth/hello"); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | @ -80,14 +101,14 @@ async function handleLoginCallback(): Promise<void> { | |||
| async function renewToken(): Promise<User | null> { | ||||
|     const activeRole = authStorage.getActiveRole(); | ||||
|     if (!activeRole) { | ||||
|         // FIXME console.log("Can't renew the token: Not logged in!");
 | ||||
|         await initiateLogin(); | ||||
|         return null; | ||||
|     } | ||||
|     try { | ||||
|         return await (await getUserManagers())[activeRole].signinSilent(); | ||||
|     } catch (_error) { | ||||
|         // FIXME console.log("Can't renew the token: " + error);
 | ||||
|         const userManagerForRole = (await getUserManagers())[activeRole]; | ||||
|         const user = await userManagerForRole.signinSilent(); | ||||
|         setUserAuthInfo(user); | ||||
|     } catch (_error: unknown) { | ||||
|         await initiateLogin(); | ||||
|     } | ||||
|     return null; | ||||
|  | @ -101,6 +122,7 @@ async function logout(): Promise<void> { | |||
|     if (activeRole) { | ||||
|         await (await getUserManagers())[activeRole].signoutRedirect(); | ||||
|         authStorage.deleteActiveRole(); | ||||
|         clearAuthState(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -119,13 +141,15 @@ apiClient.interceptors.request.use( | |||
| // Registering interceptor to refresh the token when a request failed because it was expired.
 | ||||
| apiClient.interceptors.response.use( | ||||
|     (response) => response, | ||||
|     async (error: AxiosError<{ message?: string }>) => { | ||||
|     async (error: AxiosError<{ message?: string; inner?: { message?: string } }>) => { | ||||
|         if (error.response?.status === 401) { | ||||
|             if (error.response.data.message === "token_expired") { | ||||
|                 // FIXME console.log("Access token expired, trying to refresh...");
 | ||||
|             // If the user should already be logged in, his token is probably just expired.
 | ||||
|             if (isLoggedIn.value) { | ||||
|                 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.
 | ||||
|             } | ||||
| 
 | ||||
|             // Apparently, the user got a 401 because he was not logged in yet at all. Redirect him to login.
 | ||||
|             await initiateLogin(); | ||||
|         } | ||||
|         return Promise.reject(error); | ||||
|  |  | |||
|  | @ -1,8 +1,11 @@ | |||
| <script setup lang="ts"> | ||||
|     import { useRouter } from "vue-router"; | ||||
|     import { useI18n } from "vue-i18n"; | ||||
|     import { onMounted, ref, type Ref } from "vue"; | ||||
|     import auth from "../services/auth/auth-service.ts"; | ||||
| 
 | ||||
|     const { t } = useI18n(); | ||||
| 
 | ||||
|     const router = useRouter(); | ||||
| 
 | ||||
|     const errorMessage: Ref<string | null> = ref(null); | ||||
|  | @ -12,14 +15,34 @@ | |||
|             await auth.handleLoginCallback(); | ||||
|             await router.replace("/user"); // Redirect to theme page | ||||
|         } catch (error) { | ||||
|             errorMessage.value = `OIDC callback error: ${error}`; | ||||
|             errorMessage.value = `${t("loginUnexpectedError")}: ${error}`; | ||||
|         } | ||||
|     }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|     <p v-if="!errorMessage">Logging you in...</p> | ||||
|     <p v-else>{{ errorMessage }}</p> | ||||
|     <div class="callback"> | ||||
|         <div | ||||
|             class="callback-loading" | ||||
|             v-if="!errorMessage" | ||||
|         > | ||||
|             <v-progress-circular indeterminate></v-progress-circular> | ||||
|             <p>{{ t("callbackLoading") }}</p> | ||||
|         </div> | ||||
|         <v-alert | ||||
|             icon="mdi-alert-circle" | ||||
|             type="error" | ||||
|             variant="elevated" | ||||
|             v-if="errorMessage" | ||||
|         > | ||||
|             {{ errorMessage }} | ||||
|         </v-alert> | ||||
|     </div> | ||||
| </template> | ||||
| 
 | ||||
| <style scoped></style> | ||||
| <style scoped> | ||||
|     .callback { | ||||
|         text-align: center; | ||||
|         margin: 20px; | ||||
|     } | ||||
| </style> | ||||
|  |  | |||
		Reference in a new issue
	
	 Laure Jablonski
						Laure Jablonski