myPrayerJournal v2 #27

Merged
danieljsummers merged 27 commits from version-2 into master 2019-09-03 00:01:26 +00:00
8 changed files with 72 additions and 84 deletions
Showing only changes of commit a392c8f22a - Show all commits

View File

@ -72,7 +72,8 @@ module Configure =
fun opts -> fun opts ->
let jwtCfg = cfg.GetSection "Auth0" let jwtCfg = cfg.GetSection "Auth0"
opts.Authority <- sprintf "https://%s/" jwtCfg.["Domain"] opts.Authority <- sprintf "https://%s/" jwtCfg.["Domain"]
opts.Audience <- jwtCfg.["Id"]) opts.Audience <- jwtCfg.["Id"]
)
|> ignore |> ignore
sc.AddSingleton<IJsonSerializer> (NewtonsoftJsonSerializer jsonSettings) sc.AddSingleton<IJsonSerializer> (NewtonsoftJsonSerializer jsonSettings)
|> ignore |> ignore

View File

@ -35,6 +35,7 @@ import Vue from 'vue'
import Navigation from '@/components/common/Navigation' import Navigation from '@/components/common/Navigation'
import actions from '@/store/action-types'
import { version } from '../package.json' import { version } from '../package.json'
export default { export default {
@ -57,21 +58,12 @@ export default {
} }
} }
}, },
async created () { async mounted () {
try {
await this.$auth.renewTokens()
} catch (e) {
if (e !== 'Not logged in') {
// eslint-disable-next-line
console.log(e)
}
}
},
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)
this.snackbar.events.$on('info', this.showInfo) this.snackbar.events.$on('info', this.showInfo)
this.snackbar.events.$on('error', this.showError) this.snackbar.events.$on('error', this.showError)
await this.$store.dispatch(actions.CHECK_AUTHENTICATION)
}, },
computed: { computed: {
version () { version () {

View File

@ -15,12 +15,12 @@ export default {
* Set the bearer token for all future requests * Set the bearer token for all future requests
* @param {string} token The token to use to identify the user to the server * @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 * 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 * Add a note for a prayer request

View File

@ -11,15 +11,10 @@ const webAuth = new auth0.WebAuth({
domain: AUTH_CONFIG.domain, domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId, clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.appDomain + AUTH_CONFIG.callbackUrl, redirectUri: AUTH_CONFIG.appDomain + AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token', responseType: 'token id_token',
scope: 'openid profile email' scope: 'openid profile email'
}) })
const ACCESS_TOKEN = 'access_token'
const ID_TOKEN = 'id_token'
const EXPIRES_AT = 'expires_at'
class AuthService extends EventEmitter { class AuthService extends EventEmitter {
id = { id = {
@ -32,15 +27,11 @@ class AuthService extends EventEmitter {
} }
profile = null profile = null
auth0 = new auth0.WebAuth({ ACCESS_TOKEN = 'access_token'
domain: AUTH_CONFIG.domain, ID_TOKEN = 'id_token'
clientID: AUTH_CONFIG.clientId, EXPIRES_AT = 'expires_at'
redirectUri: AUTH_CONFIG.appDomain + AUTH_CONFIG.callbackUrl, USER_PROFILE = 'user_profile'
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token',
scope: 'openid profile email'
})
/** /**
* Starts the user log in flow * Starts the user log in flow
*/ */
@ -55,7 +46,7 @@ class AuthService extends EventEmitter {
*/ */
parseHash () { parseHash () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.auth0.parseHash((err, authResult) => { webAuth.parseHash((err, authResult) => {
if (err) { if (err) {
reject(err) reject(err)
} else { } 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) { handleAuthentication (store) {
this.parseHash() this.parseHash()
.then(authResult => { .then(authResult => {
if (authResult && authResult.accessToken && authResult.idToken) { if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult) this.setSession(authResult)
this.userInfo(authResult.accessToken) store.commit(mutations.USER_LOGGED_ON, this.profile)
.then(user => {
store.commit(mutations.USER_LOGGED_ON, user)
})
} }
}) })
.catch(err => { .catch(err => {
@ -100,15 +71,16 @@ class AuthService extends EventEmitter {
} }
setSession (authResult) { setSession (authResult) {
this.profile = authResult.idTokenPayload
this.id.token = authResult.idToken this.id.token = authResult.idToken
this.id.expiry = new Date(this.profile.exp * 1000); this.id.expiry = new Date(this.profile.exp * 1000);
this.profile = authResult.idTokenPayload
this.access.token = authResult.accessToken this.access.token = authResult.accessToken
this.access.expiry = new Date(Date.now() + authResult.expiresIn * 1000) this.access.expiry = new Date(Date.now() + authResult.expiresIn * 1000)
localStorage.setItem(ACCESS_TOKEN, authResult.accessToken) localStorage.setItem(this.ACCESS_TOKEN, authResult.accessToken)
localStorage.setItem(ID_TOKEN, authResult.idToken) localStorage.setItem(this.ID_TOKEN, authResult.idToken)
localStorage.setItem(EXPIRES_AT, this.id.expiry) localStorage.setItem(this.EXPIRES_AT, this.id.expiry)
localStorage.setItem(this.USER_PROFILE, JSON.stringify(this.profile))
this.emit('loginEvent', { this.emit('loginEvent', {
loggedIn: true, loggedIn: true,
@ -119,7 +91,7 @@ class AuthService extends EventEmitter {
renewTokens () { renewTokens () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (localStorage.getItem(ID_TOKEN)) { if (localStorage.getItem(this.ID_TOKEN) !== null) {
webAuth.checkSession({}, (err, authResult) => { webAuth.checkSession({}, (err, authResult) => {
if (err) { if (err) {
reject(err) reject(err)
@ -136,9 +108,10 @@ class AuthService extends EventEmitter {
logout (store, router) { logout (store, router) {
// Clear access token and ID token from local storage // Clear access token and ID token from local storage
localStorage.removeItem(ACCESS_TOKEN) localStorage.removeItem(this.ACCESS_TOKEN)
localStorage.removeItem(ID_TOKEN) localStorage.removeItem(this.ID_TOKEN)
localStorage.removeItem(EXPIRES_AT) localStorage.removeItem(this.EXPIRES_AT)
localStorage.removeItem(this.USER_PROFILE)
this.idToken = null this.idToken = null
this.idTokenExpiry = null this.idTokenExpiry = null
@ -154,7 +127,7 @@ class AuthService extends EventEmitter {
} }
isAuthenticated () { isAuthenticated () {
return Date().now() < this.id.Expiry && localStorage.getItem(ID_TOKEN) return Date.now() < this.id.Expiry && localStorage.getItem(this.ID_TOKEN)
} }
isAccessTokenValid () { isAccessTokenValid () {

View File

@ -38,15 +38,12 @@ export default {
return {} return {}
}, },
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' ]) ...mapState([ 'isAuthenticated', 'journal' ])
}, },
methods: { methods: {
logOn () { logOn () {

View File

@ -3,6 +3,8 @@
export default { export default {
/** Action to add a prayer request (pass request text) */ /** Action to add a prayer request (pass request text) */
ADD_REQUEST: 'add-request', 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 */ /** Action to load the user's prayer journal */
LOAD_JOURNAL: 'load-journal', LOAD_JOURNAL: 'load-journal',
/** Action to update a request */ /** Action to update a request */

View File

@ -4,8 +4,8 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import api from '@/api' import api from '@/api'
import AuthService from '@/auth/AuthService' import auth from '@/auth/AuthService'
import mutations from './mutation-types' import mutations from './mutation-types'
import actions from './action-types' import actions from './action-types'
@ -13,37 +13,45 @@ import actions from './action-types'
Vue.use(Vuex) Vue.use(Vuex)
const auth0 = new AuthService()
const logError = function (error) { const logError = function (error) {
if (error.response) { if (error.response) {
// The request was made and the server responded with a status code // The request was made and the server responded with a status code
// that falls out of the range of 2xx // that falls out of the range of 2xx
console.log(error.response.data) console.error(error.response.data)
console.log(error.response.status) console.error(error.response.status)
console.log(error.response.headers) console.error(error.response.headers)
} else if (error.request) { } else if (error.request) {
// The request was made but no response was received // The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js // http.ClientRequest in node.js
console.log(error.request) console.error(error.request)
} else { } else {
// Something happened in setting up the request that triggered an Error // 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({ export default new Vuex.Store({
state: { state: {
user: JSON.parse(localStorage.getItem('user_profile') || '{}'), user: JSON.parse(localStorage.getItem(auth.USER_PROFILE) || '{}'),
isAuthenticated: (() => { isAuthenticated: auth.isAuthenticated(),
auth0.scheduleRenewal()
if (auth0.isAuthenticated()) {
api.setBearer(localStorage.getItem('id_token'))
}
return auth0.isAuthenticated()
})(),
journal: {}, journal: {},
isLoadingJournal: false isLoadingJournal: false
}, },
@ -62,15 +70,16 @@ export default new Vuex.Store({
if (request.lastStatus !== 'Answered') jrnl.push(request) if (request.lastStatus !== 'Answered') jrnl.push(request)
state.journal = jrnl state.journal = jrnl
}, },
[mutations.SET_AUTHENTICATION] (state, value) {
state.isAuthenticated = value
},
[mutations.USER_LOGGED_OFF] (state) { [mutations.USER_LOGGED_OFF] (state) {
state.user = {} state.user = {}
api.removeBearer() api.removeBearer()
state.isAuthenticated = false state.isAuthenticated = false
}, },
[mutations.USER_LOGGED_ON] (state, user) { [mutations.USER_LOGGED_ON] (state, user) {
localStorage.setItem('user_profile', JSON.stringify(user))
state.user = user state.user = user
api.setBearer(localStorage.getItem('id_token'))
state.isAuthenticated = true state.isAuthenticated = true
} }
}, },
@ -78,6 +87,7 @@ export default new Vuex.Store({
async [actions.ADD_REQUEST] ({ commit }, { progress, requestText, recurType, recurCount }) { async [actions.ADD_REQUEST] ({ commit }, { progress, requestText, recurType, recurCount }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer()
const newRequest = await api.addRequest(requestText, recurType, recurCount) const newRequest = await api.addRequest(requestText, recurType, recurCount)
commit(mutations.REQUEST_ADDED, newRequest.data) commit(mutations.REQUEST_ADDED, newRequest.data)
progress.$emit('done') progress.$emit('done')
@ -86,11 +96,19 @@ export default new Vuex.Store({
progress.$emit('done') 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) { async [actions.LOAD_JOURNAL] ({ commit }, progress) {
commit(mutations.LOADED_JOURNAL, {}) commit(mutations.LOADED_JOURNAL, {})
progress.$emit('show', 'query') progress.$emit('show', 'query')
commit(mutations.LOADING_JOURNAL, true) commit(mutations.LOADING_JOURNAL, true)
api.setBearer(localStorage.getItem('id_token')) await setBearer()
try { try {
const jrnl = await api.journal() const jrnl = await api.journal()
commit(mutations.LOADED_JOURNAL, jrnl.data) 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 }) { async [actions.UPDATE_REQUEST] ({ commit, state }, { progress, requestId, status, updateText, recurType, recurCount }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer()
let oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {} let oldReq = (state.journal.filter(req => req.requestId === requestId) || [])[0] || {}
if (!(status === 'Prayed' && updateText === '')) { if (!(status === 'Prayed' && updateText === '')) {
if (status !== 'Answered' && (oldReq.recurType !== recurType || oldReq.recurCount !== recurCount)) { 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 }) { async [actions.SHOW_REQUEST_NOW] ({ commit }, { progress, requestId, showAfter }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer()
await api.showRequest(requestId, showAfter) await api.showRequest(requestId, showAfter)
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(mutations.REQUEST_UPDATED, request.data) commit(mutations.REQUEST_UPDATED, request.data)
@ -137,6 +157,7 @@ export default new Vuex.Store({
async [actions.SNOOZE_REQUEST] ({ commit }, { progress, requestId, until }) { async [actions.SNOOZE_REQUEST] ({ commit }, { progress, requestId, until }) {
progress.$emit('show', 'indeterminate') progress.$emit('show', 'indeterminate')
try { try {
await setBearer()
await api.snoozeRequest(requestId, until) await api.snoozeRequest(requestId, until)
const request = await api.getRequest(requestId) const request = await api.getRequest(requestId)
commit(mutations.REQUEST_UPDATED, request.data) commit(mutations.REQUEST_UPDATED, request.data)

View File

@ -9,6 +9,8 @@ export default {
REQUEST_ADDED: 'request-added', REQUEST_ADDED: 'request-added',
/** Mutation to replace a prayer request at the top of the current journal */ /** Mutation to replace a prayer request at the top of the current journal */
REQUEST_UPDATED: 'request-updated', REQUEST_UPDATED: 'request-updated',
/** Mutation for setting the authentication state */
SET_AUTHENTICATION: 'set-authentication',
/** Mutation for logging a user off */ /** Mutation for logging a user off */
USER_LOGGED_OFF: 'user-logged-off', USER_LOGGED_OFF: 'user-logged-off',
/** Mutation for logging a user on (pass user) */ /** Mutation for logging a user on (pass user) */