Skip to content

Commit

Permalink
Added session timeout logic
Browse files Browse the repository at this point in the history
  • Loading branch information
palashmon committed Jun 1, 2020
1 parent f23a05a commit f423778
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 9 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This is the base URL used for axios API calls in nuxt.config.js
BASE_API_URL=http://127.0.0.1:3000/api

# This is the time minimum time of inactivity in minutes,
# after which user will logged out from the app
IDLE_TIMEOUT=30
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ $ npm run dev
```

For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).

## Logic Implemented

- Home page `/`, register page `/register` and login page `/login` are public, anyone can view it.
- Only My profile page `/profile` is private, only logged in user can view it.
- If not logged in and user tries to go to `/profile`, they will be redirected to `/login` page automatically.
- If already logged in and user tries to go to `/register` or `/login` pages, they will be redirected to home `/` page automatically.
- Implemented the logic to check for the inactivity of the user in our app. If the user is inactive for a period of time, then we automatically log out the user or show a timer first. We can update the timeout value for `IDLE_TIMEOUT` in `.env` file. Right now it set to 30 minutes. So, after 30 minutes a modal will open letting the user know that "Your session is about to expire in 60 seconds"
104 changes: 104 additions & 0 deletions components/IdleModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<div class="text-center">
<header class="modal-card-head">
<h1 class="modal-card-title h2 mb-0">
{{ title }}
</h1>
</header>
<section class="modal-card-body">
<p v-if="!isLoggedOut">
Your session is about to expire in {{ seconds }} seconds
</p>
<p v-else>
Your session has expired.
</p>
</section>
<footer class="modal-card-foot">
<div class="buttons is-right width-100">
<template v-if="!isLoggedOut">
<button
class="button mr-2"
type="button"
@click="logOut"
>
Log out
</button>
<button
class="button is-info"
type="button"
@click="continueSession"
>
Continue session
</button>
</template>
<button
v-else
class="button is-info"
type="button"
@click="close"
>
OK
</button>
</div>
</footer>
</div>
</template>

<script>
const TIME = 60
export default {
name: 'IdleModal',
props: {
dialog: {
type: Boolean,
default: false
}
},
data () {
return {
timer: '',
seconds: TIME,
isLoggedOut: false
}
},
computed: {
isIdle () {
return this.$store.state.idleVue.isIdle
},
title () {
return this.isLoggedOut ? 'Session expired' : 'Session about to expire'
}
},
created () {
this.timer = setInterval(this.countDown, 1000)
},
beforeDestroy () {
clearInterval(this.timer)
this.$store.commit('idleVue/SET_IS_IDLE', false)
},
methods: {
countDown () {
this.seconds -= 1
if (!this.isIdle) {
clearInterval(this.timer)
}
if (this.seconds < 1) {
this.logOut()
}
},
close () {
this.$emit('close')
},
continueSession () {
this.seconds = TIME
this.$store.commit('idleVue/SET_IS_IDLE', false)
clearInterval(this.timer)
},
logOut (event) {
clearInterval(this.timer)
this.isLoggedOut = true
this.$auth.logout()
}
}
}
</script>
19 changes: 18 additions & 1 deletion layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<Navbar />
<v-container>
<nuxt />
<modal v-if="loggedIn && isIdle">
<idle-modal />
</modal>
</v-container>
</v-content>
<v-footer :fixed="fixed" app>
Expand All @@ -14,9 +17,11 @@

<script>
import Navbar from '~/components/Navbar'
import IdleModal from '~/components/IdleModal'
export default {
components: {
Navbar
Navbar, IdleModal
},
data () {
return {
Expand All @@ -40,6 +45,18 @@ export default {
rightDrawer: false,
title: 'Vuetify.js'
}
},
onIdle () {
this.$store.commit('idleVue/SET_IS_IDLE', true)
},
computed: {
isIdle () {
return this.$store.state.idleVue.isIdle
},
loggedIn () {
return this.$store.state.auth.loggedIn
}
}
}
</script>
5 changes: 4 additions & 1 deletion nuxt.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const colors = require('vuetify/es5/util/colors').default
require('dotenv').config()

module.exports = {
mode: 'universal',
Expand Down Expand Up @@ -34,6 +35,8 @@ module.exports = {
** Plugins to load before mounting the App
*/
plugins: [
'~plugins/modal/index.js',
{ src: '~plugins/vue-idle.js', ssr: false }
],
/*
** Nuxt.js dev-modules
Expand All @@ -58,7 +61,7 @@ module.exports = {
** See https://axios.nuxtjs.org/options
*/
axios: {
baseURL: 'http://127.0.0.1:3000/api'
baseURL: process.env.BASE_API_URL
},

auth: {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
"@nuxtjs/axios": "^5.3.6",
"@nuxtjs/dotenv": "^1.4.0",
"cross-env": "^5.2.0",
"dotenv": "^8.2.0",
"express": "^4.16.4",
"nuxt": "^2.0.0"
"idle-vue": "^2.0.5",
"nuxt": "^2.12.2"
},
"devDependencies": {
"nodemon": "^1.18.9",
Expand Down
8 changes: 6 additions & 2 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
</article>
</div>
</article>
<article class="media">
<article v-if="loggedIn" class="media">
<figure class="media-left">
<p class="image is-64x64">
<img src="https://bulma.io/images/placeholders/128x128.png">
Expand All @@ -92,6 +92,10 @@

<script>
export default {
computed: {
loggedIn () {
return this.$store.state.auth.loggedIn
}
}
}
</script>
4 changes: 2 additions & 2 deletions pages/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export default {
data () {
return {
username: '',
password: '',
username: 'testuser1',
password: '123456',
error: null
}
},
Expand Down
2 changes: 0 additions & 2 deletions pages/profile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<h2 class="title has-text-centered">
My Profile
</h2>

<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">Full Name</label>
Expand Down Expand Up @@ -102,6 +101,5 @@ export default {
this.username = this.loggedInUser.username
this.email = this.loggedInUser.email
}
}
</script>
72 changes: 72 additions & 0 deletions plugins/modal/Modal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<div
:class="{ 'is-active': visible }"
class="modal"
>
<div class="modal-background" />
<!-- eslint-disable-next-line vue/require-component-is -->
<div class="modal-card">
<component
:is="component"
v-if="component"
v-bind="props"
@close="close"
/>
<slot v-else />
<!-- eslint-enable-next-line vue/require-component-is -->
</div>
<button class="modal-close is-large" aria-label="close" />
</div>
</template>

<script>
export default {
name: 'Modal',
props: {
component: {
type: Object,
default: () => {}
},
props: {
type: Object,
default: () => {}
},
programmatic: {
type: Boolean,
default: false
}
},
data () {
return {
visible: true
}
},
beforeMount () {
// Insert the Modal component in body tag
this.programmatic && document.body.appendChild(this.$el)
},
methods: {
close () {
if (this.programmatic) {
this.visible = false
setTimeout(() => {
this.removeElement()
this.$destroy()
}, 150)
}
},
removeElement () {
// Removes Modal component in body tag, to stop duplications
this.$el.parentNode.removeChild(this.$el)
}
}
}
</script>
3 changes: 3 additions & 0 deletions plugins/modal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Vue from 'vue'
import Modal from './modal.js'
Vue.use(Modal)
32 changes: 32 additions & 0 deletions plugins/modal/modal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Vue from 'vue'
import Modal from './Modal.vue'

const ModalProgrammatic = {
open (params) {
let parent
const ModalComponent = Vue.extend(Modal)

if (params.parent) {
parent = params.parent
delete params.parent
}

return new ModalComponent({
parent,
el: document.createElement('div'),
propsData: {
programmatic: true,
...params
}
})
}
}

const Plugin = {
install () {
Vue.prototype.$modal = ModalProgrammatic
Vue.component('modal', Modal)
}
}

export default Plugin
11 changes: 11 additions & 0 deletions plugins/vue-idle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import IdleVue from 'idle-vue'
import Vue from 'vue'

const eventsHub = new Vue()

Vue.use(IdleVue, {
eventEmitter: eventsHub,
idleTime: (process.env.IDLE_TIMEOUT || 20) * 60 * 1000, // default: 20 mins
// idleTime: 3 * 1000, // 3 seconds,
startAtIdle: false
})
14 changes: 14 additions & 0 deletions store/idleVue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const state = () => ({
isIdle: false
})

const mutations = {
SET_IS_IDLE (state, data) {
state.isIdle = data
}
}

export default {
state,
mutations
}

0 comments on commit f423778

Please sign in to comment.