Added warning message when logged out due to timeout.

This commit is contained in:
andrewlalis 2026-01-07 21:10:03 -05:00
parent 6e810997a3
commit 24b531652d
5 changed files with 48 additions and 10 deletions

View File

@ -61,3 +61,15 @@ a:hover {
border: 2px solid var(--theme-secondary); border: 2px solid var(--theme-secondary);
padding: 0.1rem 0.25rem; padding: 0.1rem 0.25rem;
} }
/* An alert banner, usually using <p>, to present some highlighted info. */
.alert-banner {
background-color: var(--bg-lighter);
padding: 0.5rem;
border-radius: 0.5rem;
}
.alert-banner-warning {
background-color: var(--warning);
color: var(--bg);
}

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAuthStore } from '@/stores/auth-store' import { LogoutReason, useAuthStore } from '@/stores/auth-store'
import { useTemplateRef, ref, computed } from 'vue' import { useTemplateRef, ref, computed } from 'vue'
import ModalWrapper from './common/ModalWrapper.vue' import ModalWrapper from './common/ModalWrapper.vue'
import AppButton from './common/AppButton.vue' import AppButton from './common/AppButton.vue'
@ -23,7 +23,7 @@ function start() {
timeoutTimerId.value = window.setInterval(() => { timeoutTimerId.value = window.setInterval(() => {
secondsUntilLogout.value = secondsUntilLogout.value - 1 secondsUntilLogout.value = secondsUntilLogout.value - 1
if (secondsUntilLogout.value <= 0) { if (secondsUntilLogout.value <= 0) {
authStore.onUserLoggedOut() authStore.onUserLoggedOut(LogoutReason.INACTIVITY_TIMEOUT)
window.clearInterval(timeoutTimerId.value) window.clearInterval(timeoutTimerId.value)
} }
}, 1000) }, 1000)

View File

@ -6,7 +6,7 @@ import AppButton from '@/components/common/AppButton.vue'
import AppForm from '@/components/common/form/AppForm.vue' import AppForm from '@/components/common/form/AppForm.vue'
import FormControl from '@/components/common/form/FormControl.vue' import FormControl from '@/components/common/form/FormControl.vue'
import FormGroup from '@/components/common/form/FormGroup.vue' import FormGroup from '@/components/common/form/FormGroup.vue'
import { useAuthStore } from '@/stores/auth-store' import { LogoutReason, useAuthStore } from '@/stores/auth-store'
import { showAlert } from '@/util/alert' import { showAlert } from '@/util/alert'
import { hideLoader, showLoader } from '@/util/loader' import { hideLoader, showLoader } from '@/util/loader'
import { ref } from 'vue' import { ref } from 'vue'
@ -89,6 +89,22 @@ function generateSampleData() {
<p style="text-align: center; font-weight: 600; margin-top: 0"> <p style="text-align: center; font-weight: 600; margin-top: 0">
<em>Personal finance for the modern era.</em> <em>Personal finance for the modern era.</em>
</p> </p>
<p
v-if="authStore.logoutReason === LogoutReason.INACTIVITY_TIMEOUT"
class="alert-banner alert-banner-warning"
>
You were logged out automatically after being inactive for a while. Please log in again to
resume your session.
</p>
<p
v-if="authStore.logoutReason === LogoutReason.TOKEN_EXPIRED"
class="alert-banner alert-banner-warning"
>
You were logged out because your session expired. Please log in again to resume your session.
</p>
<AppForm @submit="doLogin()"> <AppForm @submit="doLogin()">
<FormGroup> <FormGroup>
<FormControl <FormControl

View File

@ -8,7 +8,7 @@ import { AuthApiClient } from '@/api/auth'
import { getSelectedProfile } from '@/api/profile' import { getSelectedProfile } from '@/api/profile'
import { secondsUntilExpired } from '@/api/token-util' import { secondsUntilExpired } from '@/api/token-util'
import IdleTimeoutModal from '@/components/IdleTimeoutModal.vue' import IdleTimeoutModal from '@/components/IdleTimeoutModal.vue'
import { useAuthStore } from '@/stores/auth-store' import { LogoutReason, useAuthStore } from '@/stores/auth-store'
import { useIdleObserver } from '@idle-observer/vue3' import { useIdleObserver } from '@idle-observer/vue3'
import { onMounted, ref, useTemplateRef, type Ref } from 'vue' import { onMounted, ref, useTemplateRef, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
@ -17,7 +17,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
const IDLE_TIMEOUT_SECONDS = 300 const IDLE_TIMEOUT_SECONDS = 10
const idleTimeoutModal = useTemplateRef('idleTimeoutModal') const idleTimeoutModal = useTemplateRef('idleTimeoutModal')
useIdleObserver({ useIdleObserver({
timeout: IDLE_TIMEOUT_SECONDS * 1000, timeout: IDLE_TIMEOUT_SECONDS * 1000,
@ -61,7 +61,7 @@ async function checkAuth() {
console.warn('Failed to refresh token.', err) console.warn('Failed to refresh token.', err)
} }
} else if (secondsUntilExpiration <= 0) { } else if (secondsUntilExpiration <= 0) {
authStore.onUserLoggedOut() authStore.onUserLoggedOut(LogoutReason.TOKEN_EXPIRED)
} }
} }
</script> </script>
@ -87,7 +87,7 @@ async function checkAuth() {
<span <span
class="app-logout-button" class="app-logout-button"
@click="authStore.onUserLoggedOut()" @click="authStore.onUserLoggedOut(LogoutReason.USER_LOGGED_OUT)"
> >
<font-awesome-icon icon="fa-solid fa-arrow-right-from-bracket"></font-awesome-icon> <font-awesome-icon icon="fa-solid fa-arrow-right-from-bracket"></font-awesome-icon>
</span> </span>

View File

@ -9,20 +9,31 @@ export interface AuthenticatedData {
const LOCAL_STORAGE_KEY = 'token' const LOCAL_STORAGE_KEY = 'token'
export enum LogoutReason {
USER_LOGGED_OUT,
INACTIVITY_TIMEOUT,
TOKEN_EXPIRED,
}
export const useAuthStore = defineStore('auth', () => { export const useAuthStore = defineStore('auth', () => {
console.log('Initializing authStore')
const state: Ref<AuthenticatedData | null> = ref(getStateFromLocalStorage()) const state: Ref<AuthenticatedData | null> = ref(getStateFromLocalStorage())
const logoutReason: Ref<LogoutReason | null> = ref(null)
function onUserLoggedIn(username: string, token: string) { function onUserLoggedIn(username: string, token: string) {
state.value = { username, token } state.value = { username, token }
logoutReason.value = null
localStorage.setItem(LOCAL_STORAGE_KEY, token) localStorage.setItem(LOCAL_STORAGE_KEY, token)
} }
function onUserLoggedOut() { function onUserLoggedOut(reason: LogoutReason) {
state.value = null state.value = null
logoutReason.value = reason
localStorage.removeItem(LOCAL_STORAGE_KEY) localStorage.removeItem(LOCAL_STORAGE_KEY)
} }
return { state, onUserLoggedIn, onUserLoggedOut } return { state, logoutReason, onUserLoggedIn, onUserLoggedOut }
}) })
function getStateFromLocalStorage(): AuthenticatedData | null { function getStateFromLocalStorage(): AuthenticatedData | null {
@ -32,6 +43,5 @@ function getStateFromLocalStorage(): AuthenticatedData | null {
return null return null
} }
const username = parseSubject(token) const username = parseSubject(token)
console.info('Loaded authentication information for user', username)
return { username, token } return { username, token }
} }