import { defineStore } from "pinia" import router from "#app/plugins/router"; import {Preferences} from "@capacitor/preferences"; export const useAuthStore = defineStore("auth", { state: () => ({ user: null as null | { id?: string; user_id?: string; email: string; tenant_id?: string; role?: string; must_change_password?: boolean; is_admin?: boolean; }, profile: null as null | any, tenants: [] as { tenant_id: string; role: string; tenants: { id: string; name: string }; id?: string; name?: string; hasActiveLicense?: boolean; locked?: string | null }[], permissions: [] as string[], activeTenant: null as any, activeTenantData: null as any, loading: true as boolean, notLoggedIn: true, sessionExpired: false, sessionWarningVisible: false, sessionWarningRemainingSeconds: 0, sessionWarningLeadMs: 5 * 60 * 1000, sessionWarningTimer: null as ReturnType | null, sessionLogoutTimer: null as ReturnType | null, sessionCountdownTimer: null as ReturnType | null, }), actions: { tokenCookie() { return useCookie("token", { path: "/" }) }, getStoredToken() { const rootToken = this.tokenCookie().value if (rootToken || !process.client) return rootToken const tokenCookie = document.cookie .split(";") .map((part) => part.trim()) .find((part) => part.startsWith("token=")) if (tokenCookie) { return decodeURIComponent(tokenCookie.slice("token=".length)) } return localStorage.getItem("token") }, clearScopedTokenCookies() { if (!process.client) return const pathname = window.location.pathname || "/" const pathParts = pathname.split("/").filter(Boolean) const paths = new Set(["/"]) pathParts.reduce((path, part) => { const nextPath = `${path === "/" ? "" : path}/${part}` paths.add(nextPath) return nextPath }, "/") paths.forEach((path) => { document.cookie = `token=; Max-Age=0; path=${path}` }) }, decodeTokenExpiryMs(token: string) { try { const parts = token.split(".") if (parts.length < 2) return null const payload = JSON.parse(atob(parts[1].replace(/-/g, "+").replace(/_/g, "/"))) if (!payload?.exp) return null return Number(payload.exp) * 1000 } catch (err) { console.error("Could not decode token expiry", err) return null } }, clearSessionTimers() { if (!process.client) return if (this.sessionWarningTimer) { clearTimeout(this.sessionWarningTimer) this.sessionWarningTimer = null } if (this.sessionLogoutTimer) { clearTimeout(this.sessionLogoutTimer) this.sessionLogoutTimer = null } if (this.sessionCountdownTimer) { clearInterval(this.sessionCountdownTimer) this.sessionCountdownTimer = null } }, startSessionCountdown(expiresAtMs: number) { if (!process.client) return if (this.sessionCountdownTimer) { clearInterval(this.sessionCountdownTimer) } this.sessionCountdownTimer = setInterval(() => { const remaining = Math.max(0, Math.ceil((expiresAtMs - Date.now()) / 1000)) this.sessionWarningRemainingSeconds = remaining if (remaining <= 0 && this.sessionCountdownTimer) { clearInterval(this.sessionCountdownTimer) this.sessionCountdownTimer = null } }, 1000) }, openSessionWarning(expiresAtMs: number) { this.sessionWarningVisible = true this.sessionWarningRemainingSeconds = Math.max(0, Math.ceil((expiresAtMs - Date.now()) / 1000)) this.startSessionCountdown(expiresAtMs) }, scheduleSessionTimers(token?: string | null) { if (!process.client) return const tokenToUse = token || this.getStoredToken() this.clearSessionTimers() this.sessionWarningVisible = false this.sessionWarningRemainingSeconds = 0 if (!tokenToUse) return const expiresAtMs = this.decodeTokenExpiryMs(tokenToUse) if (!expiresAtMs) return const now = Date.now() const msUntilLogout = expiresAtMs - now if (msUntilLogout <= 0) { this.expireSession() return } this.sessionLogoutTimer = setTimeout(() => { this.expireSession() }, msUntilLogout) const msUntilWarning = msUntilLogout - this.sessionWarningLeadMs if (msUntilWarning <= 0) { this.openSessionWarning(expiresAtMs) return } this.sessionWarningTimer = setTimeout(() => { this.openSessionWarning(expiresAtMs) }, msUntilWarning) }, setToken(token: string | null) { this.clearScopedTokenCookies() this.tokenCookie().value = token if (process.client) { if (token) { localStorage.setItem("token", token) } else { localStorage.removeItem("token") } } if (!token) { this.clearSessionTimers() this.sessionWarningVisible = false this.sessionWarningRemainingSeconds = 0 return } this.scheduleSessionTimers(token) }, async persist(token: string | null) { console.log("On Web") this.setToken(token) }, async initStore() { console.log("Auth initStore") // 1. Check: Haben wir überhaupt ein Token? const token = this.getStoredToken() if (!token) { // Kein Token -> Wir sind fertig, User ist Gast. this.user = null this.loading = false return } // 2. Token existiert -> Versuche User zu laden await this.fetchMe(token) // Wenn fetchMe fertig ist (egal ob Erfolg oder Fehler), ladebalken weg // Optional: Wenn eingeloggt, leite zur Home, falls gewünscht if(this.activeTenant > 0) { this.loading = false // Hier vorsichtig sein: Nicht navigieren, wenn der User auf eine Deep-Link URL will! // navigateTo("/") <-- Das würde ich hier evtl. sogar weglassen und der Middleware überlassen } this.loading = false }, async init(token=null) { console.log("Auth init") await this.fetchMe(token) const tempStore = useTempStore() if(this.profile?.temp_config) tempStore.setStoredTempConfig(this.profile.temp_config) if(this.activeTenant > 0) { this.loading = false navigateTo("/") } }, async login(email: string, password: string) { try { console.log("Auth login") const { token } = await useNuxtApp().$api("/auth/login", { method: "POST", body: { email, password } }) console.log("Token: " + token) // 1. WICHTIG: Token sofort ins Cookie schreiben, damit es persistiert wird this.setToken(token) this.sessionExpired = false // 2. User Daten laden await this.fetchMe(token) console.log(this.user) // 3. WICHTIG: Jetzt explizit weiterleiten! // Prüfen, ob der User geladen wurde if (this.user) { // Falls Passwort-Änderung erzwungen wird (passend zu deiner Middleware) if (this.user.must_change_password) { return navigateTo("/password-change") } // Normaler Login -> Dashboard return navigateTo("/") } } catch (e) { console.log("login error:" + e) // Hier könnte man noch eine Fehlermeldung im UI anzeigen } }, async logout() { console.log("Auth logout") try { await useNuxtApp().$api("/auth/logout", { method: "POST" }) } catch (e) { console.error("Logout API fehlgeschlagen (egal):", e) } // State resetten this.resetState() // Token löschen this.setToken(null) // Nur beim expliziten Logout navigieren wir navigateTo("/login") }, expireSession() { console.log("Auth session expired") this.resetState() this.sessionExpired = true this.setToken(null) this.loading = false if (process.client) { navigateTo("/login") } }, resetState() { this.clearSessionTimers() this.user = null this.permissions = [] this.profile = null this.activeTenant = null this.tenants = [] this.activeTenantData = null this.sessionExpired = false this.sessionWarningVisible = false this.sessionWarningRemainingSeconds = 0 this.loading = false }, async fetchMe(jwt= null) { console.log("Auth fetchMe") const tempStore = useTempStore() // Token aus Argument oder Cookie holen const tokenToUse = jwt || this.getStoredToken() if (!tokenToUse) { const wasSessionExpired = this.sessionExpired this.resetState() this.sessionExpired = wasSessionExpired return } try { const me = await useNuxtApp().$api("/api/me", { headers: { Authorization: `Bearer ${tokenToUse}`, context: { jwt: tokenToUse } } }) // ... (Deine Logik für tenants, sorting etc. bleibt gleich) ... console.log(me) this.user = me.user this.permissions = me.permissions this.tenants = me.tenants this.tenants.sort(function (a, b) { if (a.id < b.id) return -1 if (a.id > b.id) return 1 }) this.profile = me.profile if(this.profile?.temp_config) tempStore.setStoredTempConfig(this.profile.temp_config) if(me.activeTenant > 0) { const normalizedActiveTenant = String(me.activeTenant) this.activeTenant = normalizedActiveTenant this.activeTenantData = me.tenants.find(i => String(i.id) === normalizedActiveTenant) || null try { const tenant = await useNuxtApp().$api("/api/tenant", { headers: { Authorization: `Bearer ${tokenToUse}`, context: { jwt: tokenToUse } } }) if (tenant?.id) { this.activeTenantData = tenant } } catch (tenantError) { console.error("fetch active tenant failed", tenantError) } } this.scheduleSessionTimers(tokenToUse) console.log(this) } catch (err: any) { // WICHTIG: Hier NICHT this.logout() aufrufen, weil das navigiert! console.log("fetchMe failed (Invalid Token or Network)", err) if (err?.response?.status === 401 || err?.status === 401 || err?.statusCode === 401) { this.expireSession() return } this.loading = false // Wir werfen den Fehler nicht weiter, damit initStore normal durchläuft } }, async refreshSession() { try { const { token } = await useNuxtApp().$api("/api/auth/refresh", { method: "POST", }) this.setToken(token) await this.fetchMe(token) this.sessionWarningVisible = false } catch (err) { console.error("JWT refresh failed", err) await this.logout() } }, async switchTenant(tenant_id: string) { console.log("Auth switchTenant") this.loading = true const res = await useNuxtApp().$api("/api/tenant/switch", { method: "POST", body: { tenant_id } }) console.log(res) const {token} = res this.setToken(token) await this.init(token) }, hasPermission(key: string) { return this.permissions.includes(key) } } })