auto-logout after session expired

This commit is contained in:
2026-02-14 12:41:32 +01:00
parent 1d5b730e11
commit 2b237d3d71
7 changed files with 78 additions and 6 deletions

View File

@ -81,7 +81,8 @@
"created": "Položka byla vytvořena.", "created": "Položka byla vytvořena.",
"saved": "Změny byly uloženy.", "saved": "Změny byly uloženy.",
"updated": "Změny byly aplikovány.", "updated": "Změny byly aplikovány.",
"deleted": "Položka byla smazána." "deleted": "Položka byla smazána.",
"sessionExpired": "Relace vypršela. Přihlas se znovu."
}, },
"errors": { "errors": {
"loadDay": "Nepodařilo se načíst den.", "loadDay": "Nepodařilo se načíst den.",

View File

@ -81,7 +81,8 @@
"created": "Element wurde erstellt.", "created": "Element wurde erstellt.",
"saved": "Änderungen wurden gespeichert.", "saved": "Änderungen wurden gespeichert.",
"updated": "Änderungen wurden übernommen.", "updated": "Änderungen wurden übernommen.",
"deleted": "Element wurde gelöscht." "deleted": "Element wurde gelöscht.",
"sessionExpired": "Sitzung ist abgelaufen. Bitte melde dich erneut an."
}, },
"errors": { "errors": {
"loadDay": "Tag konnte nicht geladen werden.", "loadDay": "Tag konnte nicht geladen werden.",

View File

@ -81,7 +81,8 @@
"created": "Item was created.", "created": "Item was created.",
"saved": "Changes were saved.", "saved": "Changes were saved.",
"updated": "Changes were applied.", "updated": "Changes were applied.",
"deleted": "Item was deleted." "deleted": "Item was deleted.",
"sessionExpired": "Session expired. Please sign in again."
}, },
"errors": { "errors": {
"loadDay": "Failed to load the day.", "loadDay": "Failed to load the day.",

View File

@ -81,7 +81,8 @@
"created": "Elemento creado.", "created": "Elemento creado.",
"saved": "Cambios guardados.", "saved": "Cambios guardados.",
"updated": "Cambios aplicados.", "updated": "Cambios aplicados.",
"deleted": "Elemento eliminado." "deleted": "Elemento eliminado.",
"sessionExpired": "La sesión ha caducado. Inicia sesión de nuevo."
}, },
"errors": { "errors": {
"loadDay": "No se pudo cargar el día.", "loadDay": "No se pudo cargar el día.",

View File

@ -81,7 +81,8 @@
"created": "Položka bola vytvorená.", "created": "Položka bola vytvorená.",
"saved": "Zmeny boli uložené.", "saved": "Zmeny boli uložené.",
"updated": "Zmeny boli aplikované.", "updated": "Zmeny boli aplikované.",
"deleted": "Položka bola zmazaná." "deleted": "Položka bola zmazaná.",
"sessionExpired": "Relácia vypršala. Prihlás sa znova."
}, },
"errors": { "errors": {
"loadDay": "Nepodarilo sa načítať deň.", "loadDay": "Nepodarilo sa načítať deň.",

View File

@ -5,12 +5,37 @@ import App from './App.vue'
import i18n from './i18n' import i18n from './i18n'
import router from './router' import router from './router'
import { pinia } from './stores' import { pinia } from './stores'
import { useAuthStore } from './stores/auth'
import { useThemeStore } from './stores/theme' import { useThemeStore } from './stores/theme'
import { useUIStore } from './stores/ui'
import { SESSION_EXPIRED_EVENT } from './utils/error'
const app = createApp(App) const app = createApp(App)
const themeStore = useThemeStore(pinia) const themeStore = useThemeStore(pinia)
const authStore = useAuthStore(pinia)
const uiStore = useUIStore(pinia)
themeStore.initialize() themeStore.initialize()
const handleSessionExpired = async () => {
const wasAuthenticated = authStore.isAuthenticated
authStore.clearSession()
if (wasAuthenticated) {
uiStore.info(String(i18n.global.t('ux.toast.sessionExpired')))
}
await router.isReady()
if (router.currentRoute.value.name !== 'auth') {
await router.replace({ name: 'auth' })
}
}
if (typeof window !== 'undefined') {
window.addEventListener(SESSION_EXPIRED_EVENT, () => {
void handleSessionExpired()
})
}
app.component('font-awesome-icon', FontAwesomeIcon) app.component('font-awesome-icon', FontAwesomeIcon)
app.use(pinia) app.use(pinia)
app.use(router) app.use(router)

View File

@ -1,4 +1,46 @@
export const toErrorMessage = (error: unknown, fallback: string): string => { export const SESSION_EXPIRED_EVENT = 'auth:session-expired'
const TOKEN_ERROR_MESSAGES = new Set(['Invalid or expired token'])
const SESSION_EXPIRED_EVENT_THROTTLE_MS = 500
let lastSessionExpiredEventAt = 0
const resolveErrorMessage = (error: unknown): string => {
if (typeof error === 'string') {
return error
}
if (error instanceof Error && typeof error.message === 'string') {
return error.message
}
return ''
}
const emitSessionExpiredEvent = () => {
if (typeof window === 'undefined') {
return
}
const now = Date.now()
if (now - lastSessionExpiredEventAt < SESSION_EXPIRED_EVENT_THROTTLE_MS) {
return
}
lastSessionExpiredEventAt = now
window.dispatchEvent(new Event(SESSION_EXPIRED_EVENT))
}
export const isSessionExpiredError = (error: unknown): boolean => {
return TOKEN_ERROR_MESSAGES.has(resolveErrorMessage(error))
}
export const toErrorMessage = (error: unknown, fallback: string): string => {
if (isSessionExpiredError(error)) {
emitSessionExpiredEvent()
return fallback
}
if (typeof error === 'string' && error.length > 0) { if (typeof error === 'string' && error.length > 0) {
return error return error
} }