337 lines
11 KiB
TypeScript
337 lines
11 KiB
TypeScript
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<typeof setTimeout> | null,
|
|
sessionLogoutTimer: null as ReturnType<typeof setTimeout> | null,
|
|
sessionCountdownTimer: null as ReturnType<typeof setInterval> | 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)
|
|
}
|
|
}
|
|
})
|