Begin auth upgrades

moving to Auth0 universal login
This commit is contained in:
Daniel J. Summers 2019-08-25 22:53:23 -05:00
parent 948f64c295
commit d95e2bf483
7 changed files with 155 additions and 72 deletions

View File

@ -57,6 +57,16 @@ export default {
} }
} }
}, },
async created () {
try {
await this.$auth.renewTokens()
} catch (e) {
if (e !== 'Not logged in') {
// eslint-disable-next-line
console.log(e)
}
}
},
mounted () { mounted () {
this.progress.events.$on('show', this.showProgress) this.progress.events.$on('show', this.showProgress)
this.progress.events.$on('done', this.hideProgress) this.progress.events.$on('done', this.hideProgress)
@ -103,10 +113,10 @@ export default {
</script> </script>
<style lang="sass"> <style lang="sass">
// Custom theme
@import "~vue-material/dist/theme/engine" @import "~vue-material/dist/theme/engine"
@include md-register-theme("default", (primary: md-get-palette-color(green, 800), accent: md-get-palette-color(gray, 700))) @include md-register-theme("default", (primary: md-get-palette-color(green, 800), accent: md-get-palette-color(gray, 700)))
@import "~vue-material/dist/theme/all" @import "~vue-material/dist/theme/all"
html, body html, body
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif; font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
font-size: 1rem font-size: 1rem

View File

@ -1,19 +1,36 @@
'use strict' 'use strict'
/* eslint-disable */
import auth0 from 'auth0-js' import auth0 from 'auth0-js'
import EventEmitter from 'events'
import AUTH_CONFIG from './auth0-variables' import AUTH_CONFIG from './auth0-variables'
import mutations from '@/store/mutation-types' import mutations from '@/store/mutation-types'
/* es-lint-enable*/
var tokenRenewalTimeout const webAuth = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.appDomain + AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token',
scope: 'openid profile email'
})
export default class AuthService { const ACCESS_TOKEN = 'access_token'
constructor () { const ID_TOKEN = 'id_token'
this.login = this.login.bind(this) const EXPIRES_AT = 'expires_at'
this.setSession = this.setSession.bind(this)
this.logout = this.logout.bind(this) class AuthService extends EventEmitter {
this.isAuthenticated = this.isAuthenticated.bind(this)
id = {
token: null,
expiry: null
} }
access = {
token: null,
expiry: null
}
profile = null
auth0 = new auth0.WebAuth({ auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.domain, domain: AUTH_CONFIG.domain,
@ -24,8 +41,13 @@ export default class AuthService {
scope: 'openid profile email' scope: 'openid profile email'
}) })
login () { /**
this.auth0.authorize() * Starts the user log in flow
*/
login (customState) {
webAuth.authorize({
appState: customState
})
} }
/** /**
@ -60,7 +82,7 @@ export default class AuthService {
}) })
} }
handleAuthentication (store, router) { handleAuthentication (store) {
this.parseHash() this.parseHash()
.then(authResult => { .then(authResult => {
if (authResult && authResult.accessToken && authResult.idToken) { if (authResult && authResult.accessToken && authResult.idToken) {
@ -68,71 +90,89 @@ export default class AuthService {
this.userInfo(authResult.accessToken) this.userInfo(authResult.accessToken)
.then(user => { .then(user => {
store.commit(mutations.USER_LOGGED_ON, user) store.commit(mutations.USER_LOGGED_ON, user)
router.replace('/journal')
}) })
} }
}) })
.catch(err => { .catch(err => {
router.replace('/')
console.log(err) console.log(err)
alert(`Error: ${err.error}. Check the console for further details.`) alert(`Error: ${err.error}. Check the console for further details.`)
}) })
} }
scheduleRenewal () {
let expiresAt = JSON.parse(localStorage.getItem('expires_at'))
let delay = expiresAt - Date.now()
if (delay > 0) {
tokenRenewalTimeout = setTimeout(() => {
this.renewToken()
}, delay)
}
}
setSession (authResult) { setSession (authResult) {
// Set the time that the access token will expire at this.id.token = authResult.idToken
let expiresAt = JSON.stringify( this.id.expiry = new Date(this.profile.exp * 1000);
authResult.expiresIn * 1000 + new Date().getTime() this.profile = authResult.idTokenPayload
) this.access.token = authResult.accessToken
localStorage.setItem('access_token', authResult.accessToken) this.access.expiry = new Date(Date.now() + authResult.expiresIn * 1000)
localStorage.setItem('id_token', authResult.idToken)
localStorage.setItem('expires_at', expiresAt) localStorage.setItem(ACCESS_TOKEN, authResult.accessToken)
this.scheduleRenewal() localStorage.setItem(ID_TOKEN, authResult.idToken)
localStorage.setItem(EXPIRES_AT, this.id.expiry)
this.emit('loginEvent', {
loggedIn: true,
profile: authResult.idTokenPayload,
state: authResult.appState || {}
})
} }
renewToken () { renewTokens () {
console.log('attempting renewal...') return new Promise((resolve, reject) => {
this.auth0.renewAuth( if (localStorage.getItem(ID_TOKEN)) {
{ webAuth.checkSession({}, (err, authResult) => {
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
redirectUri: `${AUTH_CONFIG.appDomain}/static/silent.html`,
usePostMessage: true
},
(err, result) => {
if (err) { if (err) {
console.log(err) reject(err)
} else { } else {
this.setSession(result) this.setSession(authResult)
resolve(authResult)
} }
})
} else {
reject('Not logged in')
} }
) })
} }
logout (store, router) { logout (store, router) {
// Clear access token and ID token from local storage // Clear access token and ID token from local storage
clearTimeout(tokenRenewalTimeout) localStorage.removeItem(ACCESS_TOKEN)
localStorage.removeItem('access_token') localStorage.removeItem(ID_TOKEN)
localStorage.removeItem('id_token') localStorage.removeItem(EXPIRES_AT)
localStorage.removeItem('expires_at')
localStorage.setItem('user_profile', JSON.stringify({})) this.idToken = null
// navigate to the home route this.idTokenExpiry = null
this.profile = null
store.commit(mutations.USER_LOGGED_OFF) store.commit(mutations.USER_LOGGED_OFF)
router.replace('/')
webAuth.logout({
// navigate to the home route
returnTo: '/'
})
this.emit('loginEvent', { loggedIn: false })
} }
isAuthenticated () { isAuthenticated () {
// Check whether the current time is past the access token's expiry time return Date().now() < this.id.Expiry && localStorage.getItem(ID_TOKEN)
let expiresAt = JSON.parse(localStorage.getItem('expires_at')) }
return new Date().getTime() < expiresAt
isAccessTokenValid () {
return this.access.token && this.access.expiry && Date.now() < this.access.expiry
}
getAccessToken () {
return new Promise((resolve, reject) => {
if (this.isAccessTokenValid()) {
resolve(this.access.token)
} else {
this.renewTokens()
.then(authResult => {
resolve(authResult.accessToken)
}, reject)
}
})
} }
} }
export default new AuthService()

View File

@ -32,29 +32,28 @@
import { mapState } from 'vuex' import { mapState } from 'vuex'
import AuthService from '@/auth/AuthService'
export default { export default {
name: 'navigation', name: 'navigation',
data () { data () {
return { return {}
auth0: new AuthService()
}
}, },
computed: { computed: {
isAuthenticated () {
return this.$auth.isAuthenticated()
},
hasSnoozed () { hasSnoozed () {
return this.isAuthenticated && return this.isAuthenticated &&
Array.isArray(this.journal) && Array.isArray(this.journal) &&
this.journal.filter(req => req.snoozedUntil > Date.now()).length > 0 this.journal.filter(req => req.snoozedUntil > Date.now()).length > 0
}, },
...mapState([ 'journal', 'isAuthenticated' ]) ...mapState([ 'journal' ])
}, },
methods: { methods: {
logOn () { logOn () {
this.auth0.login() this.$auth.login()
}, },
logOff () { logOff () {
this.auth0.logout(this.$store, this.$router) this.$auth.logout(this.$store, this.$router)
} }
} }
} }

View File

@ -7,15 +7,17 @@ article.mpj-main-content(role='main')
<script> <script>
'use strict' 'use strict'
import AuthService from '@/auth/AuthService'
export default { export default {
name: 'log-on', name: 'log-on',
inject: ['progress'], inject: ['progress'],
created () { created () {
this.progress.$emit('show', 'indeterminate') this.progress.$emit('show', 'indeterminate')
new AuthService().handleAuthentication(this.$store, this.$router) this.$auth.handleAuthentication(this.$store)
// Auth service redirects to dashboard, which restarts the progress bar },
methods: {
handleLoginEvent (data) {
this.$router.push(data.state.target || '/journal')
}
} }
} }
</script> </script>

View File

@ -26,6 +26,7 @@ import router from './router'
import store from './store' import store from './store'
import DateFromNow from './components/common/DateFromNow' import DateFromNow from './components/common/DateFromNow'
import PageTitle from './components/common/PageTitle' import PageTitle from './components/common/PageTitle'
import AuthPlugin from './plugins/auth'
/* eslint-enable */ /* eslint-enable */
@ -52,6 +53,7 @@ Vue.use(MdTable)
Vue.use(MdTabs) Vue.use(MdTabs)
Vue.use(MdToolbar) Vue.use(MdToolbar)
Vue.use(MdTooltip) Vue.use(MdTooltip)
Vue.use(AuthPlugin)
Vue.component('date-from-now', DateFromNow) Vue.component('date-from-now', DateFromNow)
Vue.component('page-title', PageTitle) Vue.component('page-title', PageTitle)

View File

@ -0,0 +1,22 @@
'use strict'
import authService from '../auth/AuthService'
export default {
install (Vue) {
Vue.prototype.$auth = authService
Vue.mixin({
created () {
if (this.handleLoginEvent) {
authService.addListener('loginEvent', this.handleLoginEvent)
}
},
destroyed () {
if (this.handleLoginEvent) {
authService.removeListener('loginEvent', this.handleLoginEvent)
}
}
})
}
}

View File

@ -3,6 +3,8 @@
/* eslint-disable */ /* eslint-disable */
import Vue from 'vue' import Vue from 'vue'
import Router from 'vue-router' import Router from 'vue-router'
import auth from './auth/AuthService'
import Home from '@/components/Home' import Home from '@/components/Home'
/* eslint-enable */ /* eslint-enable */
@ -18,6 +20,12 @@ export default new Router({
return { x: 0, y: 0 } return { x: 0, y: 0 }
} }
}, },
beforeEach (to, from, next) {
if (to.path === '/' || to.path === '/user/log-on' || auth.isAuthenticated()) {
return next()
}
auth.login({ target: to.path })
},
routes: [ routes: [
{ {
path: '/', path: '/',