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 | { user_id: string; email: string; tenant_id?: string; role?: string }, profile: null as null | any, tenants: [] as { tenant_id: string; role: string; tenants: { id: string; name: string } }[], permissions: [] as string[], activeTenant: null as any, activeTenantData: null as any, loading: true as boolean, notLoggedIn: true, sessionWarningVisible: false, sessionWarningRemainingSeconds: 0, sessionWarningLeadMs: 5 * 60 * 1000, sessionWarningTimer: null as ReturnType | null, sessionLogoutTimer: null as ReturnType | null, sessionCountdownTimer: null as ReturnType | null, }), actions: { 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 || useCookie("token").value 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) { void this.logout() return } this.sessionLogoutTimer = setTimeout(() => { this.sessionWarningVisible = false void this.logout() }, msUntilLogout) const msUntilWarning = msUntilLogout - this.sessionWarningLeadMs if (msUntilWarning <= 0) { this.openSessionWarning(expiresAtMs) return } this.sessionWarningTimer = setTimeout(() => { this.openSessionWarning(expiresAtMs) }, msUntilWarning) }, setToken(token: string | null) { useCookie("token").value = 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 = useCookie("token").value /*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 } }, 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) // 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") }, resetState() { this.clearSessionTimers() this.user = null this.permissions = [] this.profile = null this.activeTenant = null this.tenants = [] this.activeTenantData = null this.sessionWarningVisible = false this.sessionWarningRemainingSeconds = 0 }, async fetchMe(jwt= null) { console.log("Auth fetchMe") const tempStore = useTempStore() // Token aus Argument oder Cookie holen const tokenToUse = jwt || useCookie("token").value 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) { this.activeTenant = me.activeTenant this.activeTenantData = me.tenants.find(i => i.id === me.activeTenant) } 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) // Stattdessen nur den State sauber machen und Token löschen this.resetState() this.setToken(null) // 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) } } })