Skip to content

Commit

Permalink
feat: implement timeout warning for SSO token timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
timisenco2015 committed Oct 11, 2023
1 parent ba404d7 commit 0b077ae
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 4 deletions.
89 changes: 89 additions & 0 deletions app/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@vueuse/core": "^10.5.0",
"axios": "^1.4.0",
"bootstrap-scss": "^5.3.1",
"crypto-js": "^4.1.1",
Expand Down
10 changes: 7 additions & 3 deletions app/frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,34 @@ import BaseNotificationContainer from '~/components/base/BaseNotificationContain
import BCGovHeader from '~/components/bcgov/BCGovHeader.vue';
import BCGovNavBar from './components/bcgov/BCGovNavBar.vue';
import BCGovFooter from '~/components/bcgov/BCGovFooter.vue';
import BaseWarningDialog from '~/components/base/BaseWarningDialog.vue';
export default {
components: {
BaseNotificationContainer,
BCGovHeader,
BCGovNavBar,
BCGovFooter,
BaseWarningDialog,
},
};
</script>

<template>
<v-layout ref="app" class="app">
<BCGovHeader />
<BCGovNavBar />
<v-main class="app">
<BaseNotificationContainer />
<BCGovHeader />
<BCGovNavBar />

<RouterView v-slot="{ Component }">
<transition name="component-fade" mode="out-in">
<component :is="Component" class="main" />
</transition>
</RouterView>
<BCGovFooter />
<BaseWarningDialog />
</v-main>
<BCGovFooter />
</v-layout>
</template>

Expand Down
53 changes: 53 additions & 0 deletions app/frontend/src/components/base/BaseWarningDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script>
import { mapState, mapActions } from 'pinia';
import { useAuthStore } from '~/store/auth';
import BaseDialog from '~/components/base/BaseDialog.vue';
export default {
name: 'BaseWarningDialog',
components: {
BaseDialog,
},
computed: {
...mapState(useAuthStore, ['showTokenExpiredWarningMSg']),
},
methods: {
...mapActions(useAuthStore, ['setTokenExpirationWarningDialog']),
},
};
</script>
<template>
<BaseDialog
type="CONTINUE"
:show-close-button="true"
:width="'50%'"
v-model="showTokenExpiredWarningMSg"
@close-dialog="
() => {
setTokenExpirationWarningDialog({
showTokenExpiredWarningMSg: false,
resetToken: false,
});
}
"
@continue-dialog="
() => {
setTokenExpirationWarningDialog({
showTokenExpiredWarningMSg: false,
resetToken: true,
});
}
"
>
<template #title><span>Session expiring</span></template>
<template #text>
<div class="text-display-4">
Your session will expire soon and you will be signed out automatically.
</div>
<div class="text-display-3 mt-3">Do you want to stay signed in?</div>
</template>
<template #button-text-continue>
<span>Confirm</span>
</template>
</BaseDialog>
</template>
1 change: 1 addition & 0 deletions app/frontend/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ export default function getRouter(basePath = '/') {
if (authStore?.ready && authStore?.authenticated) {
const formStore = useFormStore();
formStore.getFormsForCurrentUser();
authStore.checkTokenExpiration();
}

// Handle proper redirections on first page load
Expand Down
66 changes: 66 additions & 0 deletions app/frontend/src/store/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { defineStore } from 'pinia';
import getRouter from '~/router';
import { useIdle, useTimestamp, watchPausable } from '@vueuse/core';
import { ref } from 'vue';
import moment from 'moment';

/**
* @function hasRoles
Expand All @@ -20,12 +23,19 @@ export const useAuthStore = defineStore('auth', {
redirectUri: undefined,
ready: false,
authenticated: false,
showTokenExpiredWarningMSg: false,
inActiveCheckInterval: null,
updateTokenInterval: null,
watchPausable: null,
}),
getters: {
createLoginUrl: (state) => (options) =>
state.keycloak.createLoginUrl(options),
createLogoutUrl: (state) => (options) =>
state.keycloak.createLogoutUrl(options),
updateToken: (state) => (minValidity) =>
state.keycloak.updateToken(minValidity),
clearToken: (state) => () => state.keycloak.clearToken(),
email: (state) =>
state.keycloak.tokenParsed ? state.keycloak.tokenParsed.email : '',
fullName: (state) => state.keycloak.tokenParsed.name,
Expand Down Expand Up @@ -122,5 +132,61 @@ export const useAuthStore = defineStore('auth', {
);
}
},
async setTokenExpirationWarningDialog({
showTokenExpiredWarningMSg,
resetToken,
}) {
if (!showTokenExpiredWarningMSg && resetToken) {
this.watchPausable.resume();
this.updateToken(-1).catch(() => {
this.clearToken();
this.logout();
});
} else if (!resetToken) {
clearInterval(this.updateTokenInterval);
clearInterval(this.inActiveCheckInterval);
this.logout();
}
this.showTokenExpiredWarningMSg = showTokenExpiredWarningMSg;
if (showTokenExpiredWarningMSg) {
setTimeout(() => {
this.logout();
}, 180000);
}
},
async checkTokenExpiration() {
if (this.authenticated) {
const { idle, lastActive } = useIdle(1000, { initialState: true });
const source = ref(idle);
const now = useTimestamp({ interval: 1000 });
this.watchPausable = watchPausable(source, (value) => {
if (value) {
console.log('I am that');
clearInterval(this.updateTokenInterval);
this.inActiveCheckInterval = setInterval(() => {
let end = moment(now.value);
let active = moment(lastActive.value);
let duration = moment.duration(end.diff(active)).as('minutes');
if (duration > 1) {
this.watchPausable.pause();
this.setTokenExpirationWarningDialog({
showTokenExpiredWarningMSg: true,
resetToken: true,
});
}
}, 120000);
} else {
console.log('I am there');
clearInterval(this.inActiveCheckInterval);
this.updateTokenInterval = setInterval(() => {
this.updateToken(-1).catch(() => {
this.clearToken();
});
}, 240000);
}
});
this.watchPausable.resume();
}
},
},
});
2 changes: 1 addition & 1 deletion app/frontend/src/views/file/Download.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export default {
>
<v-icon class="mb-2" size="90" icon="mdi:mdi-file-download" /><br />
If your file does not automatically download
<a href="#" @click="getFile(id)" :hreflang="lang">{{
<a href="#" :hreflang="lang" @click="getFile(id)">{{
$t('trans.download.downloadInfoB')
}}</a>
</div>
Expand Down

0 comments on commit 0b077ae

Please sign in to comment.