chore(frontend): Refactoring
Refactoring zodat de structuur van de authenticatieservice in de client duidelijker is.
This commit is contained in:
		
							parent
							
								
									a28ec22f29
								
							
						
					
					
						commit
						26d5c09bb4
					
				
					 19 changed files with 215 additions and 183 deletions
				
			
		|  | @ -12,5 +12,5 @@ DWENGO_AUTH_TEACHER_URL=http://localhost:7080/realms/teacher | ||||||
| DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo | DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo | ||||||
| DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://localhost:7080/realms/teacher/protocol/openid-connect/certs | DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://localhost:7080/realms/teacher/protocol/openid-connect/certs | ||||||
| 
 | 
 | ||||||
| # Allow frontend from anywhere to access the backend (for testing purposes). Don't forget to remove this in production! | # Allow Vite dev-server to access the backend (for testing purposes). Don't forget to remove this in production! | ||||||
| DWENGO_CORS_ALLOWED_ORIGINS=* | DWENGO_CORS_ALLOWED_ORIGINS=http://localhost:5173 | ||||||
|  |  | ||||||
|  | @ -24,8 +24,8 @@ app.get('/', (_, res: Response) => { | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| app.use(authenticateUser); |  | ||||||
| app.use(cors); | app.use(cors); | ||||||
|  | app.use(authenticateUser); | ||||||
| 
 | 
 | ||||||
| app.use('/student', studentRouter); | app.use('/student', studentRouter); | ||||||
| app.use('/group', groupRouter); | app.use('/group', groupRouter); | ||||||
|  |  | ||||||
|  | @ -2,5 +2,6 @@ import cors from "cors"; | ||||||
| import {EnvVars, getEnvVar} from "../util/envvars"; | import {EnvVars, getEnvVar} from "../util/envvars"; | ||||||
| 
 | 
 | ||||||
| export default cors({ | export default cors({ | ||||||
|     origin: getEnvVar(EnvVars.CorsAllowedOrigins).split(',') |     origin: getEnvVar(EnvVars.CorsAllowedOrigins).split(','), | ||||||
|  |     allowedHeaders: getEnvVar(EnvVars.CorsAllowedHeaders).split(',') | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| import express from 'express' | import express from 'express' | ||||||
| import {getFrontendAuthConfig} from "../controllers/auth"; | import {getFrontendAuthConfig} from "../controllers/auth"; | ||||||
|  | import {authenticatedOnly, studentsOnly, teachersOnly} from "../middleware/auth/auth"; | ||||||
| const router = express.Router(); | const router = express.Router(); | ||||||
| 
 | 
 | ||||||
| // returns auth configuration for frontend
 | // returns auth configuration for frontend
 | ||||||
|  | @ -7,4 +8,16 @@ router.get('/config', (req, res) => { | ||||||
|     res.json(getFrontendAuthConfig()); |     res.json(getFrontendAuthConfig()); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | router.get('/testAuthenticatedOnly', authenticatedOnly, (req, res) => { | ||||||
|  |     res.json({message: "If you see this, you should be authenticated!"}); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | router.get('/testStudentsOnly', studentsOnly, (req, res) => { | ||||||
|  |     res.json({message: "If you see this, you should be a student!"}); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | router.get('/testTeachersOnly', teachersOnly, (req, res) => { | ||||||
|  |     res.json({message: "If you see this, you should be a teacher!"}); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| export default router; | export default router; | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ const DB_PREFIX = PREFIX + 'DB_'; | ||||||
| const IDP_PREFIX = PREFIX + 'AUTH_'; | const IDP_PREFIX = PREFIX + 'AUTH_'; | ||||||
| const STUDENT_IDP_PREFIX = IDP_PREFIX + 'STUDENT_'; | const STUDENT_IDP_PREFIX = IDP_PREFIX + 'STUDENT_'; | ||||||
| const TEACHER_IDP_PREFIX = IDP_PREFIX + 'TEACHER_'; | const TEACHER_IDP_PREFIX = IDP_PREFIX + 'TEACHER_'; | ||||||
|  | const CORS_PREFIX = PREFIX + 'CORS_'; | ||||||
| 
 | 
 | ||||||
| type EnvVar = { key: string; required?: boolean; defaultValue?: any }; | type EnvVar = { key: string; required?: boolean; defaultValue?: any }; | ||||||
| 
 | 
 | ||||||
|  | @ -21,7 +22,8 @@ export const EnvVars: { [key: string]: EnvVar } = { | ||||||
|     IdpTeacherClientId: { key: TEACHER_IDP_PREFIX + 'CLIENT_ID', required: true }, |     IdpTeacherClientId: { key: TEACHER_IDP_PREFIX + 'CLIENT_ID', required: true }, | ||||||
|     IdpTeacherJwksEndpoint: { key: TEACHER_IDP_PREFIX + 'JWKS_ENDPOINT', required: true }, |     IdpTeacherJwksEndpoint: { key: TEACHER_IDP_PREFIX + 'JWKS_ENDPOINT', required: true }, | ||||||
|     IdpAudience: { key: IDP_PREFIX + 'AUDIENCE', defaultValue: 'account' }, |     IdpAudience: { key: IDP_PREFIX + 'AUDIENCE', defaultValue: 'account' }, | ||||||
|     CorsAllowedOrigins: { key: PREFIX + 'CORS_ALLOWED_ORIGINS', defaultValue: ''} |     CorsAllowedOrigins: { key: CORS_PREFIX + 'ALLOWED_ORIGINS', defaultValue: ''}, | ||||||
|  |     CorsAllowedHeaders: { key: CORS_PREFIX + 'ALLOWED_HEADERS', defaultValue: 'Authorization,Content-Type'} | ||||||
| } as const; | } as const; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import {loadUser} from "@/store/auth-store.ts"; |     import auth from "@/services/auth/auth-service.ts"; | ||||||
|     loadUser(); |     auth.loadUser(); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|  |  | ||||||
|  | @ -1,14 +1,5 @@ | ||||||
| export const authConfig = { | export const apiConfig = { | ||||||
|     student: { |     baseUrl: window.location.hostname == "localhost" ? "http://localhost:3000" : window.location.origin | ||||||
|         authority: import.meta.env.VITE_STUDENT_AUTH_AUTHORITY || "https://auth.sel2-1.ugent.be/realms/student", |  | ||||||
|         clientId: import.meta.env.VITE_STUDENT_AUTH_CLIENT_ID || "dwengo", |  | ||||||
|         redirectUri: window.location.origin + "/callback", |  | ||||||
|         scope: import.meta.env.VITE_STUDENT_AUTH_SCOPE || "openid profile email" |  | ||||||
|     }, |  | ||||||
|     teacher: { |  | ||||||
|         authority: import.meta.env.VITE_TEACHER_AUTH_AUTHORITY || "https://auth.sel2-1.ugent.be/realms/teacher", |  | ||||||
|         clientId: import.meta.env.VITE_TEACHER_AUTH_CLIENT_ID || "dwengo", |  | ||||||
|         redirectUri: window.location.origin + "/callback", |  | ||||||
|         scope: import.meta.env.VITE_TEACHER_AUTH_SCOPE || "openid profile email" |  | ||||||
| } | } | ||||||
| }; | 
 | ||||||
|  | export const loginRoute = "/login"; | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ import NotFound from "@/components/errors/NotFound.vue"; | ||||||
| import CreateClass from "@/views/classes/CreateClass.vue"; | import CreateClass from "@/views/classes/CreateClass.vue"; | ||||||
| import CreateAssignment from "@/views/assignments/CreateAssignment.vue"; | import CreateAssignment from "@/views/assignments/CreateAssignment.vue"; | ||||||
| import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue"; | import CreateDiscussion from "@/views/discussions/CreateDiscussion.vue"; | ||||||
| import CallbackPage from "@/views/discussions/CallbackPage.vue"; | import CallbackPage from "@/views/CallbackPage.vue"; | ||||||
| 
 | 
 | ||||||
| const router = createRouter({ | const router = createRouter({ | ||||||
|     history: createWebHistory(import.meta.env.BASE_URL), |     history: createWebHistory(import.meta.env.BASE_URL), | ||||||
|  |  | ||||||
|  | @ -1,7 +1,8 @@ | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
|  | import {apiConfig} from "@/config.ts"; | ||||||
| 
 | 
 | ||||||
| const apiClient = axios.create({ | const apiClient = axios.create({ | ||||||
|     baseURL: window.location.hostname == "localhost" ? "http://localhost:3000" : window.location.origin, |     baseURL: apiConfig.baseUrl, | ||||||
|     headers: { |     headers: { | ||||||
|         "Content-Type": "application/json", |         "Content-Type": "application/json", | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -1,106 +0,0 @@ | ||||||
| import {User, UserManager} from "oidc-client-ts"; |  | ||||||
| import apiClient from "@/services/api-client.ts"; |  | ||||||
| 
 |  | ||||||
| type FrontendAuthConfig = { |  | ||||||
|     student: FrontendIdpConfig, |  | ||||||
|     teacher: FrontendIdpConfig |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type FrontendIdpConfig = { |  | ||||||
|     authority: string, |  | ||||||
|     clientId: string, |  | ||||||
|     scope: string, |  | ||||||
|     responseType: string |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export type Role = "student" | "teacher"; |  | ||||||
| type UserManagersForRoles = {student: UserManager, teacher: UserManager}; |  | ||||||
| 
 |  | ||||||
| class AuthService { |  | ||||||
|     constructor(private userManagers: UserManagersForRoles) {} |  | ||||||
| 
 |  | ||||||
|     public async loginAs(role: Role) { |  | ||||||
|         // Storing it in local storage so that it won't be lost when redirecting outside of the app.
 |  | ||||||
|         this.setActiveRole(role); |  | ||||||
|         await this.userManagers[role].signinRedirect(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async logout() { |  | ||||||
|         const activeRole = this.getActiveRole(); |  | ||||||
|         if (activeRole) { |  | ||||||
|             await this.userManagers[activeRole].signoutRedirect(); |  | ||||||
|             this.deleteActiveRole(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async getUser(): Promise<User | null> { |  | ||||||
|         const activeRole = this.getActiveRole(); |  | ||||||
|         if (!activeRole) { |  | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|         return await this.userManagers[activeRole].getUser(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async getAccessToken(): Promise<string | null> { |  | ||||||
|         const user = await this.getUser(); |  | ||||||
|         return user?.access_token || null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async renewToken() { |  | ||||||
|         const activeRole = this.getActiveRole(); |  | ||||||
|         if (!activeRole) { |  | ||||||
|             throw new Error("Can't renew the token: Not logged in!"); |  | ||||||
|         } |  | ||||||
|         return this.userManagers[activeRole].signinSilent(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public getActiveRole(): Role | undefined { |  | ||||||
|         return localStorage.getItem("activeRole") as Role | undefined; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public async handleRedirectCallback(): Promise<User | undefined> { |  | ||||||
|         const activeRole = this.getActiveRole(); |  | ||||||
|         if (!activeRole) { |  | ||||||
|             throw new Error("Can't renew the token: Not logged in!"); |  | ||||||
|         } |  | ||||||
|         return this.userManagers[activeRole].signinCallback(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private setActiveRole(role: Role) { |  | ||||||
|         localStorage.setItem("activeRole", role); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private deleteActiveRole() { |  | ||||||
|         localStorage.removeItem("activeRole"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async function initAuthService() { |  | ||||||
|     const authConfig = await apiClient.get<FrontendAuthConfig>("auth/config").then(it => it.data); |  | ||||||
| 
 |  | ||||||
|     const oidcConfig = { |  | ||||||
|         student: { |  | ||||||
|             authority: authConfig.student.authority, |  | ||||||
|             client_id: authConfig.student.clientId, |  | ||||||
|             redirect_uri: window.location.origin + "/callback", |  | ||||||
|             response_type: authConfig.student.responseType, |  | ||||||
|             scope: authConfig.student.scope, |  | ||||||
|             post_logout_redirect_uri: window.location.origin, |  | ||||||
|         }, |  | ||||||
|         teacher: { |  | ||||||
|             authority: authConfig.teacher.authority, |  | ||||||
|             client_id: authConfig.teacher.clientId, |  | ||||||
|             redirect_uri: window.location.origin + "/callback", |  | ||||||
|             response_type: authConfig.teacher.responseType, |  | ||||||
|             scope: authConfig.teacher.scope, |  | ||||||
|             post_logout_redirect_uri: window.location.origin, |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     return new AuthService({ |  | ||||||
|         student: new UserManager(oidcConfig.student), |  | ||||||
|         teacher: new UserManager(oidcConfig.teacher) |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export default await initAuthService(); |  | ||||||
							
								
								
									
										24
									
								
								frontend/src/services/auth/auth-api-client-interceptors.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								frontend/src/services/auth/auth-api-client-interceptors.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | import apiClient from "@/services/api-client.ts"; | ||||||
|  | import type {AuthState} from "@/services/auth/auth-types.ts"; | ||||||
|  | 
 | ||||||
|  | export function configureApiClientAuthInterceptors(authState: AuthState, renewToken: () => Promise<any>) { | ||||||
|  |     apiClient.interceptors.request.use(async (reqConfig) => { | ||||||
|  |         const token = authState?.user?.access_token; | ||||||
|  |         if (token) { | ||||||
|  |             reqConfig.headers.Authorization = `Bearer ${token}`; | ||||||
|  |         } | ||||||
|  |         return reqConfig; | ||||||
|  |     }, (error) => Promise.reject(error)); | ||||||
|  | 
 | ||||||
|  |     apiClient.interceptors.response.use( | ||||||
|  |         response => response, | ||||||
|  |         async (error) => { | ||||||
|  |             if (error.response?.status === 401) { | ||||||
|  |                 console.log("Access token expired, trying to refresh..."); | ||||||
|  |                 await renewToken(); | ||||||
|  |                 return apiClient(error.config); // Retry the request
 | ||||||
|  |             } | ||||||
|  |             return Promise.reject(error); | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								frontend/src/services/auth/auth-config-loader.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								frontend/src/services/auth/auth-config-loader.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | import apiClient from "@/services/api-client.ts"; | ||||||
|  | import type {FrontendAuthConfig} from "@/services/auth/auth-types.ts"; | ||||||
|  | 
 | ||||||
|  | export async function loadAuthConfig() { | ||||||
|  |     const authConfig = (await apiClient.get<FrontendAuthConfig>("auth/config")).data; | ||||||
|  |     return { | ||||||
|  |         student: { | ||||||
|  |             authority: authConfig.student.authority, | ||||||
|  |             client_id: authConfig.student.clientId, | ||||||
|  |             redirect_uri: window.location.origin + "/callback", | ||||||
|  |             response_type: authConfig.student.responseType, | ||||||
|  |             scope: authConfig.student.scope, | ||||||
|  |             post_logout_redirect_uri: window.location.origin, | ||||||
|  |         }, | ||||||
|  |         teacher: { | ||||||
|  |             authority: authConfig.teacher.authority, | ||||||
|  |             client_id: authConfig.teacher.clientId, | ||||||
|  |             redirect_uri: window.location.origin + "/callback", | ||||||
|  |             response_type: authConfig.teacher.responseType, | ||||||
|  |             scope: authConfig.teacher.scope, | ||||||
|  |             post_logout_redirect_uri: window.location.origin, | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
							
								
								
									
										77
									
								
								frontend/src/services/auth/auth-service.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								frontend/src/services/auth/auth-service.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,77 @@ | ||||||
|  | /** | ||||||
|  |  * Service for all authentication- and authorization-related tasks. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | import {computed, reactive} from "vue"; | ||||||
|  | import type {AuthState, Role, UserManagersForRoles} from "@/services/auth/auth-types.ts"; | ||||||
|  | import {User, UserManager} from "oidc-client-ts"; | ||||||
|  | import {loadAuthConfig} from "@/services/auth/auth-config-loader.ts"; | ||||||
|  | import {configureApiClientAuthInterceptors} from "@/services/auth/auth-api-client-interceptors.ts"; | ||||||
|  | import authStorage from "./auth-storage.ts" | ||||||
|  | 
 | ||||||
|  | const authConfig = await loadAuthConfig(); | ||||||
|  | 
 | ||||||
|  | const userManagers: UserManagersForRoles = { | ||||||
|  |     student: new UserManager(authConfig.student), | ||||||
|  |     teacher: new UserManager(authConfig.teacher), | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | async function loadUser(): Promise<User | null> { | ||||||
|  |     const activeRole = authStorage.getActiveRole(); | ||||||
|  |     if (!activeRole) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |     let user = await userManagers[activeRole].getUser(); | ||||||
|  |     authState.user = user; | ||||||
|  |     authState.accessToken = user?.access_token || null; | ||||||
|  |     authState.activeRole = activeRole || null; | ||||||
|  |     return user; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const authState = reactive<AuthState>({ | ||||||
|  |     user: null, | ||||||
|  |     accessToken: null, | ||||||
|  |     activeRole: authStorage.getActiveRole() || null | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const isLoggedIn = computed(() => authState.user !== null); | ||||||
|  | 
 | ||||||
|  | async function loginAs(role: Role): Promise<void> { | ||||||
|  |     // Storing it in local storage so that it won't be lost when redirecting outside of the app.
 | ||||||
|  |     authStorage.setActiveRole(role); | ||||||
|  |     await userManagers[role].signinRedirect(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function handleLoginCallback(): Promise<void> { | ||||||
|  |     const activeRole = authStorage.getActiveRole(); | ||||||
|  |     if (!activeRole) { | ||||||
|  |         throw new Error("Can't renew the token: Not logged in!"); | ||||||
|  |     } | ||||||
|  |     authState.user = await userManagers[activeRole].signinCallback() || null; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function renewToken() { | ||||||
|  |     const activeRole = authStorage.getActiveRole(); | ||||||
|  |     if (!activeRole) { | ||||||
|  |         throw new Error("Can't renew the token: Not logged in!"); | ||||||
|  |     } | ||||||
|  |     try { | ||||||
|  |         return await userManagers[activeRole].signinSilent(); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.log("Can't renew the token:"); | ||||||
|  |         console.log(error); | ||||||
|  |         await loginAs(activeRole); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function logout(): Promise<void> { | ||||||
|  |     const activeRole = authStorage.getActiveRole(); | ||||||
|  |     if (activeRole) { | ||||||
|  |         await userManagers[activeRole].signoutRedirect(); | ||||||
|  |         authStorage.deleteActiveRole(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | configureApiClientAuthInterceptors(authState, renewToken); | ||||||
|  | 
 | ||||||
|  | export default {authState, isLoggedIn, loadUser, handleLoginCallback, loginAs, logout}; | ||||||
							
								
								
									
										13
									
								
								frontend/src/services/auth/auth-storage.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/src/services/auth/auth-storage.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | import type {Role} from "@/services/auth/auth-types.ts"; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |     getActiveRole(): Role | undefined { | ||||||
|  |         return localStorage.getItem("activeRole") as Role | undefined; | ||||||
|  |     }, | ||||||
|  |     setActiveRole(role: Role) { | ||||||
|  |         localStorage.setItem("activeRole", role); | ||||||
|  |     }, | ||||||
|  |     deleteActiveRole() { | ||||||
|  |         localStorage.removeItem("activeRole"); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								frontend/src/services/auth/auth-types.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								frontend/src/services/auth/auth-types.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | ||||||
|  | import {type User, UserManager} from "oidc-client-ts"; | ||||||
|  | 
 | ||||||
|  | export type AuthState = { | ||||||
|  |     user: User | null, | ||||||
|  |     accessToken: string | null, | ||||||
|  |     activeRole: Role | null | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type FrontendAuthConfig = { | ||||||
|  |     student: FrontendIdpConfig, | ||||||
|  |     teacher: FrontendIdpConfig | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type FrontendIdpConfig = { | ||||||
|  |     authority: string, | ||||||
|  |     clientId: string, | ||||||
|  |     scope: string, | ||||||
|  |     responseType: string | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export type Role = "student" | "teacher"; | ||||||
|  | export type UserManagersForRoles = {student: UserManager, teacher: UserManager}; | ||||||
|  | @ -1,40 +0,0 @@ | ||||||
| import {computed, reactive} from "vue"; |  | ||||||
| import authService, {type Role} from "@/services/auth-service.ts"; |  | ||||||
| import type {User} from "oidc-client-ts"; |  | ||||||
| 
 |  | ||||||
| type AuthState = { |  | ||||||
|     user: User | null, |  | ||||||
|     accessToken: string | null, |  | ||||||
|     activeRole: Role | null |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const authState = reactive<AuthState>({ |  | ||||||
|     user: null, |  | ||||||
|     accessToken: null, |  | ||||||
|     activeRole: authService.getActiveRole() || null |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| export const isLoggedIn = computed(() => authState.user !== null); |  | ||||||
| 
 |  | ||||||
| export async function loadUser(): Promise<void> { |  | ||||||
|     const user = await authService.getUser(); |  | ||||||
|     authState.user = user; |  | ||||||
|     authState.accessToken = user?.access_token || null; |  | ||||||
|     authState.activeRole = authService.getActiveRole() || null; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function handleLoginCallback(): Promise<void> { |  | ||||||
|     console.log("Hallooo"); |  | ||||||
|     authState.user = await authService.handleRedirectCallback() || null; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function loginAs(role: Role): Promise<void> { |  | ||||||
|     await authService.loginAs(role); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function logout(): Promise<void> { |  | ||||||
|     await authService.logout(); |  | ||||||
|     authState.user = null; |  | ||||||
|     authState.accessToken = null; |  | ||||||
|     authState.activeRole = null; |  | ||||||
| } |  | ||||||
|  | @ -1,13 +1,13 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import {useRouter} from "vue-router"; |     import {useRouter} from "vue-router"; | ||||||
|     import {onMounted} from "vue"; |     import {onMounted} from "vue"; | ||||||
|     import {handleLoginCallback} from "@/store/auth-store.ts"; |     import auth from "../services/auth/auth-service.ts" | ||||||
| 
 | 
 | ||||||
|     const router = useRouter(); |     const router = useRouter(); | ||||||
| 
 | 
 | ||||||
|     onMounted(async () => { |     onMounted(async () => { | ||||||
|         try { |         try { | ||||||
|             await handleLoginCallback(); |             await auth.handleLoginCallback(); | ||||||
|             await router.replace("/"); // Redirect to home (or dashboard) |             await router.replace("/"); // Redirect to home (or dashboard) | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             console.error("OIDC callback error:", error); |             console.error("OIDC callback error:", error); | ||||||
|  | @ -1,15 +1,26 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
|     import {isLoggedIn, authState} from "@/store/auth-store.ts"; |     import auth from "@/services/auth/auth-service.ts"; | ||||||
|  |     import apiClient from "@/services/api-client.ts"; | ||||||
|  |     import {ref} from "vue"; | ||||||
|  | 
 | ||||||
|  |     const testResponse = ref(null); | ||||||
|  | 
 | ||||||
|  |     async function testAuthenticated() { | ||||||
|  |         testResponse.value = await apiClient.get("/auth/testAuthenticatedOnly") | ||||||
|  |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <main> |     <main> | ||||||
|         <!-- TODO Placeholder implementation to test the login - replace by a more beautiful page later --> |         <!-- TODO Placeholder implementation to test the login - replace by a more beautiful page later --> | ||||||
|         <b>Welcome to the dwengo homepage</b> |         <b>Welcome to the dwengo homepage</b> | ||||||
|         <div v-if="isLoggedIn"> |         <div v-if="auth.isLoggedIn.value"> | ||||||
|             <p>Hello {{authState.user?.profile.name}}!</p> |             <p>Hello {{auth.authState.user?.profile.name}}!</p> | ||||||
|             <p>Your access token for the backend is: <code>{{authState.user?.access_token}}</code></p> |             <p>Your access token for the backend is: <code>{{auth.authState.user?.access_token}}</code></p> | ||||||
|         </div> |         </div> | ||||||
|  | 
 | ||||||
|  |         <v-btn @click="testAuthenticated">Send test request</v-btn> | ||||||
|  |         <p v-if="testResponse">Response from the test request: {{ testResponse }}</p> | ||||||
|     </main> |     </main> | ||||||
| </template> | </template> | ||||||
| <style scoped> | <style scoped> | ||||||
|  |  | ||||||
|  | @ -1,30 +1,29 @@ | ||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| 
 |     import auth from "@/services/auth/auth-service.ts"; | ||||||
| import {isLoggedIn, loginAs, logout, authState} from "@/store/auth-store.ts"; |  | ||||||
| 
 | 
 | ||||||
|     function loginAsStudent() { |     function loginAsStudent() { | ||||||
|         loginAs("student"); |         auth.loginAs("student"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function loginAsTeacher() { |     function loginAsTeacher() { | ||||||
|         loginAs("teacher"); |         auth.loginAs("teacher"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function performLogout() { |     function performLogout() { | ||||||
|         logout(); |         auth.logout(); | ||||||
|     } |     } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <main> |     <main> | ||||||
|         <!-- TODO Placeholder implementation to test the login - replace by a more beautiful page later --> |         <!-- TODO Placeholder implementation to test the login - replace by a more beautiful page later --> | ||||||
|         <div v-if="!isLoggedIn"> |         <div v-if="!auth.isLoggedIn.value"> | ||||||
|             <p>You are currently not logged in.</p> |             <p>You are currently not logged in.</p> | ||||||
|             <v-btn @click="loginAsStudent">Login as student</v-btn> |             <v-btn @click="loginAsStudent">Login as student</v-btn> | ||||||
|             <v-btn @click="loginAsTeacher">Login as teacher</v-btn> |             <v-btn @click="loginAsTeacher">Login as teacher</v-btn> | ||||||
|         </div> |         </div> | ||||||
|         <div v-if="isLoggedIn"> |         <div v-if="auth.isLoggedIn.value"> | ||||||
|             <p>You are currently logged in as {{ authState.user!.profile.name }} ({{ authState.activeRole }})</p> |             <p>You are currently logged in as {{ auth.authState.user!.profile.name }} ({{ auth.authState.activeRole }})</p> | ||||||
|             <v-btn @click="performLogout">Logout</v-btn> |             <v-btn @click="performLogout">Logout</v-btn> | ||||||
|         </div> |         </div> | ||||||
|     </main> |     </main> | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger