diff --git a/frontend/src/css/login.css b/frontend/src/css/login.css index 14bfd1a5..62150ee6 100644 --- a/frontend/src/css/login.css +++ b/frontend/src/css/login.css @@ -45,6 +45,15 @@ animation: 0.2s opac forwards; } +#login .logout-message { + background: var(--icon-orange); + color: #fff; + padding: 0.5em; + text-align: center; + animation: 0.2s opac forwards; + text-transform: none; +} + @keyframes opac { 0% { opacity: 0; diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 4070619d..9411d91e 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -101,7 +101,10 @@ "submit": "Login", "username": "Username", "usernameTaken": "Username already taken", - "wrongCredentials": "Wrong credentials" + "wrongCredentials": "Wrong credentials", + "logout_reasons": { + "inactivity": "You have been logged out due to inactivity." + } }, "permanent": "Permanent", "prompts": { diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index 459141ad..c33db87a 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -7,9 +7,11 @@ export const useAuthStore = defineStore("auth", { state: (): { user: IUser | null; jwt: string; + logoutTimer: number | null; } => ({ user: null, jwt: "", + logoutTimer: null, }), getters: { // user and jwt getter removed, no longer needed @@ -37,5 +39,8 @@ export const useAuthStore = defineStore("auth", { clearUser() { this.$reset(); }, + setLogoutTimer(logoutTimer: number | null) { + this.logoutTimer = logoutTimer; + }, }, }); diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index b868d90f..2b940cd4 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -16,6 +16,17 @@ export function parseToken(token: string) { const authStore = useAuthStore(); authStore.jwt = token; authStore.setUser(data.user); + + if (authStore.logoutTimer) { + clearTimeout(authStore.logoutTimer); + } + + const expiresAt = new Date(data.exp! * 1000); + authStore.setLogoutTimer( + window.setTimeout(() => { + logout("inactivity"); + }, expiresAt.getTime() - Date.now()) + ); } export async function validateLogin() { @@ -92,7 +103,7 @@ export async function signup(username: string, password: string) { } } -export function logout() { +export function logout(reason?: string) { document.cookie = "auth=; Max-Age=0; Path=/; SameSite=Strict;"; const authStore = useAuthStore(); @@ -102,6 +113,15 @@ export function logout() { if (noAuth) { window.location.reload(); } else { - router.push({ path: "/login" }); + if (typeof reason === "string" && reason.trim() !== "") { + router.push({ + path: "/login", + query: { "logout-reason": reason }, + }); + } else { + router.push({ + path: "/login", + }); + } } } diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 5804789a..c0e78225 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -3,6 +3,9 @@
File Browser

{{ name }}

+

+ {{ t(`login.logout_reasons.${reason}`) }} +

{{ error }}
(createMode.value = !createMode.value); const $showError = inject("$showError")!; +const reason = route.query["logout-reason"] ?? null; + const submit = async (event: Event) => { event.preventDefault(); event.stopPropagation();