Begin auth upgrades
moving to Auth0 universal login
This commit is contained in:
parent
948f64c295
commit
d95e2bf483
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
22
src/app/src/plugins/auth.js
Normal file
22
src/app/src/plugins/auth.js
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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: '/',
|
||||||
|
Loading…
Reference in New Issue
Block a user