Added warning message when logged out due to timeout.
This commit is contained in:
parent
6e810997a3
commit
24b531652d
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue