Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | import { defineStore } from 'pinia' import { AuthService } from '@/services/auth.service' import { useKeycloak } from '@/composables/useKeycloak' import { useOpenStackCredentialsStore } from '@/stores/openstack-credentials.store' import { invalidateAll as invalidateOpenStackCache } from '@/composables/useOpenStackResourceCache' import type { User, UserRole } from '@/types' const keycloak = useKeycloak() // In-flight promises to dedupe concurrent calls. The router guard, // App mount, and view mounts can all trigger initialize/fetchMe at the // same time on a cold load — without this, each call hits the backend // (token validation, /users/me, credential fetch) once per trigger. let initializePromise: Promise<void> | null = null let fetchMePromise: Promise<void> | null = null export const useAuthStore = defineStore('auth', { state: () => ({ user: null as User | null, isLoading: false, error: null as string | null, }), getters: { isAuthenticated: () => keycloak.isAuthenticated.value, userRole: (state): UserRole | null => state.user?.role || null, isStudent: (state) => state.user?.role === 'student', isTeacher: (state) => state.user?.role === 'teacher', isAdmin: (state) => state.user?.role === 'admin', isTeacherOrAdmin: (state) => state.user?.role === 'teacher' || state.user?.role === 'admin', userId: (state) => state.user?.userId || null, }, actions: { async initialize() { if (initializePromise) return initializePromise initializePromise = (async () => { this.isLoading = true try { await keycloak.initialize() if (keycloak.isAuthenticated.value) { const storedUser = AuthService.getStoredUser() if (storedUser) { this.user = storedUser } this.fetchMe().catch(() => {}) } } catch (error) { console.error('Auth initialization failed:', error) } finally { this.isLoading = false } })() return initializePromise }, async login(returnUrl?: string) { this.error = null try { await keycloak.login(returnUrl) } catch (err: any) { this.error = err.message || 'Login failed' throw err } }, async handleCallback() { /** * Finalize the Authorization Code + PKCE flow. * Resolves return URL from Keycloak, then loads the current user from backend. */ this.isLoading = true this.error = null try { const returnUrl = await keycloak.handleCallback() await this.fetchMe() return returnUrl } catch (err: any) { this.error = err.message || 'Callback handling failed' throw err } finally { this.isLoading = false } }, async fetchMe() { if (fetchMePromise) return fetchMePromise fetchMePromise = (async () => { try { this.user = await AuthService.fetchMe() useOpenStackCredentialsStore().fetch().catch(() => {}) } catch (error) { console.error('Failed to fetch user:', error) this.user = null throw error } finally { fetchMePromise = null } })() return fetchMePromise }, async logout() { AuthService.clearStoredUser() this.user = null this.error = null initializePromise = null fetchMePromise = null useOpenStackCredentialsStore().reset() // OpenStack-Resource-Display-Cache leeren — der nächste User // hat eigene Credentials und sieht ein anderes Project; alte // Resource-Listen dürfen nicht stehenbleiben. invalidateOpenStackCache() try { await keycloak.logout() } catch (error) { console.error('Logout failed:', error) } }, hasRole(role: UserRole): boolean { return this.user?.role === role }, hasAnyRole(...roles: UserRole[]): boolean { if (!this.user?.role) return false if (!Array.isArray(roles)) return false return roles.includes(this.user.role as UserRole) }, }, }) |