From a392c8f22a0d174d8c488763e66348839404bed7 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Fri, 30 Aug 2019 22:46:11 -0500 Subject: [PATCH] Continue auth upgrades Something's still not quite right... --- src/MyPrayerJournal.Api/Program.fs | 3 +- src/app/src/App.vue | 14 +---- src/app/src/api/index.js | 4 +- src/app/src/auth/AuthService.js | 63 ++++++-------------- src/app/src/components/common/Navigation.vue | 5 +- src/app/src/store/action-types.js | 2 + src/app/src/store/index.js | 63 +++++++++++++------- src/app/src/store/mutation-types.js | 2 + 8 files changed, 72 insertions(+), 84 deletions(-) diff --git a/src/MyPrayerJournal.Api/Program.fs b/src/MyPrayerJournal.Api/Program.fs index e272c4b..563fbb7 100644 --- a/src/MyPrayerJournal.Api/Program.fs +++ b/src/MyPrayerJournal.Api/Program.fs @@ -72,7 +72,8 @@ module Configure = fun opts -> let jwtCfg = cfg.GetSection "Auth0" opts.Authority <- sprintf "https://%s/" jwtCfg.["Domain"] - opts.Audience <- jwtCfg.["Id"]) + opts.Audience <- jwtCfg.["Id"] + ) |> ignore sc.AddSingleton (NewtonsoftJsonSerializer jsonSettings) |> ignore diff --git a/src/app/src/App.vue b/src/app/src/App.vue index bbf26de..e6e38ea 100644 --- a/src/app/src/App.vue +++ b/src/app/src/App.vue @@ -35,6 +35,7 @@ import Vue from 'vue' import Navigation from '@/components/common/Navigation' +import actions from '@/store/action-types' import { version } from '../package.json' export default { @@ -57,21 +58,12 @@ export default { } } }, - async created () { - try { - await this.$auth.renewTokens() - } catch (e) { - if (e !== 'Not logged in') { - // eslint-disable-next-line - console.log(e) - } - } - }, - mounted () { + async mounted () { this.progress.events.$on('show', this.showProgress) this.progress.events.$on('done', this.hideProgress) this.snackbar.events.$on('info', this.showInfo) this.snackbar.events.$on('error', this.showError) + await this.$store.dispatch(actions.CHECK_AUTHENTICATION) }, computed: { version () { diff --git a/src/app/src/api/index.js b/src/app/src/api/index.js index ac1046a..bb431fc 100644 --- a/src/app/src/api/index.js +++ b/src/app/src/api/index.js @@ -15,12 +15,12 @@ export default { * Set the bearer token for all future requests * @param {string} token The token to use to identify the user to the server */ - setBearer: token => { http.defaults.headers.common['authorization'] = `Bearer ${token}` }, + setBearer: token => { http.defaults.headers.common['Authorization'] = `Bearer ${token}` }, /** * Remove the bearer token */ - removeBearer: () => delete http.defaults.headers.common['authorization'], + removeBearer: () => delete http.defaults.headers.common['Authorization'], /** * Add a note for a prayer request diff --git a/src/app/src/auth/AuthService.js b/src/app/src/auth/AuthService.js index b697663..cc4c7e3 100644 --- a/src/app/src/auth/AuthService.js +++ b/src/app/src/auth/AuthService.js @@ -11,15 +11,10 @@ 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' }) -const ACCESS_TOKEN = 'access_token' -const ID_TOKEN = 'id_token' -const EXPIRES_AT = 'expires_at' - class AuthService extends EventEmitter { id = { @@ -32,15 +27,11 @@ class AuthService extends EventEmitter { } profile = null - auth0 = 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' - }) - + ACCESS_TOKEN = 'access_token' + ID_TOKEN = 'id_token' + EXPIRES_AT = 'expires_at' + USER_PROFILE = 'user_profile' + /** * Starts the user log in flow */ @@ -55,7 +46,7 @@ class AuthService extends EventEmitter { */ parseHash () { return new Promise((resolve, reject) => { - this.auth0.parseHash((err, authResult) => { + webAuth.parseHash((err, authResult) => { if (err) { reject(err) } else { @@ -65,32 +56,12 @@ class AuthService extends EventEmitter { }) } - /** - * Promisified userInfo function - * - * @param token The auth token from the login result - */ - userInfo (token) { - return new Promise((resolve, reject) => { - this.auth0.client.userInfo(token, (err, user) => { - if (err) { - reject(err) - } else { - resolve(user) - } - }) - }) - } - handleAuthentication (store) { this.parseHash() .then(authResult => { if (authResult && authResult.accessToken && authResult.idToken) { this.setSession(authResult) - this.userInfo(authResult.accessToken) - .then(user => { - store.commit(mutations.USER_LOGGED_ON, user) - }) + store.commit(mutations.USER_LOGGED_ON, this.profile) } }) .catch(err => { @@ -100,15 +71,16 @@ class AuthService extends EventEmitter { } setSession (authResult) { + this.profile = authResult.idTokenPayload this.id.token = authResult.idToken this.id.expiry = new Date(this.profile.exp * 1000); - this.profile = authResult.idTokenPayload this.access.token = authResult.accessToken this.access.expiry = new Date(Date.now() + authResult.expiresIn * 1000) - localStorage.setItem(ACCESS_TOKEN, authResult.accessToken) - localStorage.setItem(ID_TOKEN, authResult.idToken) - localStorage.setItem(EXPIRES_AT, this.id.expiry) + localStorage.setItem(this.ACCESS_TOKEN, authResult.accessToken) + localStorage.setItem(this.ID_TOKEN, authResult.idToken) + localStorage.setItem(this.EXPIRES_AT, this.id.expiry) + localStorage.setItem(this.USER_PROFILE, JSON.stringify(this.profile)) this.emit('loginEvent', { loggedIn: true, @@ -119,7 +91,7 @@ class AuthService extends EventEmitter { renewTokens () { return new Promise((resolve, reject) => { - if (localStorage.getItem(ID_TOKEN)) { + if (localStorage.getItem(this.ID_TOKEN) !== null) { webAuth.checkSession({}, (err, authResult) => { if (err) { reject(err) @@ -136,9 +108,10 @@ class AuthService extends EventEmitter { logout (store, router) { // Clear access token and ID token from local storage - localStorage.removeItem(ACCESS_TOKEN) - localStorage.removeItem(ID_TOKEN) - localStorage.removeItem(EXPIRES_AT) + localStorage.removeItem(this.ACCESS_TOKEN) + localStorage.removeItem(this.ID_TOKEN) + localStorage.removeItem(this.EXPIRES_AT) + localStorage.removeItem(this.USER_PROFILE) this.idToken = null this.idTokenExpiry = null @@ -154,7 +127,7 @@ class AuthService extends EventEmitter { } isAuthenticated () { - return Date().now() < this.id.Expiry && localStorage.getItem(ID_TOKEN) + return Date.now() < this.id.Expiry && localStorage.getItem(this.ID_TOKEN) } isAccessTokenValid () { diff --git a/src/app/src/components/common/Navigation.vue b/src/app/src/components/common/Navigation.vue index b19cd71..f9ae4e3 100644 --- a/src/app/src/components/common/Navigation.vue +++ b/src/app/src/components/common/Navigation.vue @@ -38,15 +38,12 @@ export default { return {} }, computed: { - isAuthenticated () { - return this.$auth.isAuthenticated() - }, hasSnoozed () { return this.isAuthenticated && Array.isArray(this.journal) && this.journal.filter(req => req.snoozedUntil > Date.now()).length > 0 }, - ...mapState([ 'journal' ]) + ...mapState([ 'isAuthenticated', 'journal' ]) }, methods: { logOn () { diff --git a/src/app/src/store/action-types.js b/src/app/src/store/action-types.js index 958bba8..9b20443 100644 --- a/src/app/src/store/action-types.js +++ b/src/app/src/store/action-types.js @@ -3,6 +3,8 @@ export default { /** Action to add a prayer request (pass request text) */ ADD_REQUEST: 'add-request', + /** Action to check if a user is authenticated, refreshing the session first if it exists */ + CHECK_AUTHENTICATION: 'check-authentication', /** Action to load the user's prayer journal */ LOAD_JOURNAL: 'load-journal', /** Action to update a request */ diff --git a/src/app/src/store/index.js b/src/app/src/store/index.js index a805d41..c64b804 100644 --- a/src/app/src/store/index.js +++ b/src/app/src/store/index.js @@ -4,8 +4,8 @@ import Vue from 'vue' import Vuex from 'vuex' -import api from '@/api' -import AuthService from '@/auth/AuthService' +import api from '@/api' +import auth from '@/auth/AuthService' import mutations from './mutation-types' import actions from './action-types' @@ -13,37 +13,45 @@ import actions from './action-types' Vue.use(Vuex) -const auth0 = new AuthService() - const logError = function (error) { if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx - console.log(error.response.data) - console.log(error.response.status) - console.log(error.response.headers) + console.error(error.response.data) + console.error(error.response.status) + console.error(error.response.headers) } else if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.log(error.request) + console.error(error.request) } else { // Something happened in setting up the request that triggered an Error - console.log('Error', error.message) + console.error('Error', error.message) + } + console.error(`config: ${error.config}`) +} + +/** + * Set the "Bearer" authorization header with the current access token + */ +const setBearer = async function () { + try { + await auth.getAccessToken() + api.setBearer(localStorage.getItem(auth.ID_TOKEN)) + } catch(err) { + if (err === 'Not logged in') { + console.warn('API request attempted when user was not logged in') + } else { + console.error(err) + } } - console.log(error.config) } export default new Vuex.Store({ state: { - user: JSON.parse(localStorage.getItem('user_profile') || '{}'), - isAuthenticated: (() => { - auth0.scheduleRenewal() - if (auth0.isAuthenticated()) { - api.setBearer(localStorage.getItem('id_token')) - } - return auth0.isAuthenticated() - })(), + user: JSON.parse(localStorage.getItem(auth.USER_PROFILE) || '{}'), + isAuthenticated: auth.isAuthenticated(), journal: {}, isLoadingJournal: false }, @@ -62,15 +70,16 @@ export default new Vuex.Store({ if (request.lastStatus !== 'Answered') jrnl.push(request) state.journal = jrnl }, + [mutations.SET_AUTHENTICATION] (state, value) { + state.isAuthenticated = value + }, [mutations.USER_LOGGED_OFF] (state) { state.user = {} api.removeBearer() state.isAuthenticated = false }, [mutations.USER_LOGGED_ON] (state, user) { - localStorage.setItem('user_profile', JSON.stringify(user)) state.user = user - api.setBearer(localStorage.getItem('id_token')) state.isAuthenticated = true } }, @@ -78,6 +87,7 @@ export default new Vuex.Store({ async [actions.ADD_REQUEST] ({ commit }, { progress, requestText, recurType, recurCount }) { progress.$emit('show', 'indeterminate') try { + await setBearer() const newRequest = await api.addRequest(requestText, recurType, recurCount) commit(mutations.REQUEST_ADDED, newRequest.data) progress.$emit('done') @@ -86,11 +96,19 @@ export default new Vuex.Store({ progress.$emit('done') } }, + async [actions.CHECK_AUTHENTICATION] ({ commit }) { + try { + await auth.renewTokens() + commit(mutations.SET_AUTHENTICATION, auth.isAuthenticated()) + } catch(_) { + commit(mutations.SET_AUTHENTICATION, false) + } + }, async [actions.LOAD_JOURNAL] ({ commit }, progress) { commit(mutations.LOADED_JOURNAL, {}) progress.$emit('show', 'query') commit(mutations.LOADING_JOURNAL, true) - api.setBearer(localStorage.getItem('id_token')) + await setBearer() try { const jrnl = await api.journal() commit(mutations.LOADED_JOURNAL, jrnl.data) @@ -105,6 +123,7 @@ export default new Vuex.Store({ async [actions.UPDATE_REQUEST] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) { progress.$emit('show', 'indeterminate') try { + await setBearer() let oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {} if (!(status === 'Prayed' && updateText === '')) { if (status !== 'Answered' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) { @@ -125,6 +144,7 @@ export default new Vuex.Store({ async [actions.SHOW_REQUEST_NOW] ({ commit }, { progress, requestId, showAfter }) { progress.$emit('show', 'indeterminate') try { + await setBearer() await api.showRequest(requestId, showAfter) const request = await api.getRequest(requestId) commit(mutations.REQUEST_UPDATED, request.data) @@ -137,6 +157,7 @@ export default new Vuex.Store({ async [actions.SNOOZE_REQUEST] ({ commit }, { progress, requestId, until }) { progress.$emit('show', 'indeterminate') try { + await setBearer() await api.snoozeRequest(requestId, until) const request = await api.getRequest(requestId) commit(mutations.REQUEST_UPDATED, request.data) diff --git a/src/app/src/store/mutation-types.js b/src/app/src/store/mutation-types.js index a356d20..b59c3c5 100644 --- a/src/app/src/store/mutation-types.js +++ b/src/app/src/store/mutation-types.js @@ -9,6 +9,8 @@ export default { REQUEST_ADDED: 'request-added', /** Mutation to replace a prayer request at the top of the current journal */ REQUEST_UPDATED: 'request-updated', + /** Mutation for setting the authentication state */ + SET_AUTHENTICATION: 'set-authentication', /** Mutation for logging a user off */ USER_LOGGED_OFF: 'user-logged-off', /** Mutation for logging a user on (pass user) */