diff --git a/src/app/src/App.vue b/src/app/src/App.vue index ec693bd..9ebd92a 100644 --- a/src/app/src/App.vue +++ b/src/app/src/App.vue @@ -35,7 +35,7 @@ import Vue from 'vue' import Navigation from '@/components/common/Navigation' -import actions from '@/store/action-types' +import { Actions } from '@/store/types' import { version } from '../package.json' export default { @@ -63,7 +63,7 @@ export default { 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) + await this.$store.dispatch(Actions.CheckAuthentication) }, computed: { version () { diff --git a/src/app/src/api/index.ts b/src/app/src/api/index.ts index 4766239..bcecb32 100644 --- a/src/app/src/api/index.ts +++ b/src/app/src/api/index.ts @@ -13,9 +13,9 @@ export default { /** * Set the bearer token for all future requests - * @param {string} token The token to use to identify the user to the server + * @param token The token to use to identify the user to the server */ - setBearer: token => { http.defaults.headers.common.Authorization = `Bearer ${token}` }, + setBearer: (token: string) => { http.defaults.headers.common.Authorization = `Bearer ${token}` }, /** * Remove the bearer token @@ -24,18 +24,19 @@ export default { /** * Add a note for a prayer request - * @param {string} requestId The Id of the request to which the note applies - * @param {string} notes The notes to be added + * @param requestId The Id of the request to which the note applies + * @param notes The notes to be added */ - addNote: (requestId, notes) => http.post(`request/${requestId}/note`, { notes }), + addNote: (requestId: string, notes: string) => http.post(`request/${requestId}/note`, { notes }), /** * Add a new prayer request - * @param {string} requestText The text of the request to be added - * @param {string} recurType The type of recurrence for this request - * @param {number} recurCount The number of intervals of recurrence + * @param requestText The text of the request to be added + * @param recurType The type of recurrence for this request + * @param recurCount The number of intervals of recurrence */ - addRequest: (requestText, recurType, recurCount) => http.post('request', { requestText, recurType, recurCount }), + addRequest: (requestText: string, recurType: string, recurCount: number) => + http.post('request', { requestText, recurType, recurCount }), /** * Get all answered requests, along with the text they had when it was answered @@ -44,21 +45,21 @@ export default { /** * Get a prayer request (full; includes all history and notes) - * @param {string} requestId The Id of the request to retrieve + * @param requestId The Id of the request to retrieve */ - getFullRequest: requestId => http.get(`request/${requestId}/full`), + getFullRequest: (requestId: string) => http.get(`request/${requestId}/full`), /** * Get past notes for a prayer request - * @param {string} requestId The Id of the request for which notes should be retrieved + * @param requestId The Id of the request for which notes should be retrieved */ - getNotes: requestId => http.get(`request/${requestId}/notes`), + getNotes: (requestId: string) => http.get(`request/${requestId}/notes`), /** * Get a prayer request (journal-style; only latest update) - * @param {string} requestId The Id of the request to retrieve + * @param requestId The Id of the request to retrieve */ - getRequest: requestId => http.get(`request/${requestId}`), + getRequest: (requestId: string) => http.get(`request/${requestId}`), /** * Get all prayer requests and their most recent updates @@ -67,32 +68,33 @@ export default { /** * Show a request after the given date (used for "show now") - * @param {string} requestId The ID of the request which should be shown - * @param {number} showAfter The ticks after which the request should be shown + * @param requestId The ID of the request which should be shown + * @param showAfter The ticks after which the request should be shown */ - showRequest: (requestId, showAfter) => http.patch(`request/${requestId}/show`, { showAfter }), + showRequest: (requestId: string, showAfter: number) => http.patch(`request/${requestId}/show`, { showAfter }), /** * Snooze a request until the given time - * @param {string} requestId The ID of the prayer request to be snoozed - * @param {number} until The ticks until which the request should be snoozed + * @param requestId The ID of the prayer request to be snoozed + * @param until The ticks until which the request should be snoozed */ - snoozeRequest: (requestId, until) => http.patch(`request/${requestId}/snooze`, { until }), + snoozeRequest: (requestId: string, until: number) => http.patch(`request/${requestId}/snooze`, { until }), /** * Update recurrence for a prayer request - * @param {string} requestId The ID of the prayer request for which recurrence is being updated - * @param {string} recurType The type of recurrence to set - * @param {number} recurCount The number of recurrence intervals to set + * @param requestId The ID of the prayer request for which recurrence is being updated + * @param recurType The type of recurrence to set + * @param recurCount The number of recurrence intervals to set */ - updateRecurrence: (requestId, recurType, recurCount) => + updateRecurrence: (requestId: string, recurType: string, recurCount: number) => http.patch(`request/${requestId}/recurrence`, { recurType, recurCount }), /** * Update a prayer request - * @param {string} requestId The ID of the request to be updated - * @param {string} status The status of the update - * @param {string} updateText The text of the update (optional) + * @param requestId The ID of the request to be updated + * @param status The status of the update + * @param updateText The text of the update (optional) */ - updateRequest: (requestId, status, updateText) => http.post(`request/${requestId}/history`, { status, updateText }) + updateRequest: (requestId: string, status: string, updateText: string) => + http.post(`request/${requestId}/history`, { status, updateText }) } diff --git a/src/app/src/auth/AuthService.ts b/src/app/src/auth/index.ts similarity index 59% rename from src/app/src/auth/AuthService.ts rename to src/app/src/auth/index.ts index 5a1c912..f10010d 100644 --- a/src/app/src/auth/AuthService.ts +++ b/src/app/src/auth/index.ts @@ -1,18 +1,18 @@ -'use strict' -/* eslint-disable */ -import auth0 from 'auth0-js' +import { Store } from 'vuex' +import auth0, { Auth0DecodedHash } from 'auth0-js' import { EventEmitter } from 'events' -import AUTH_CONFIG from './auth0-variables' -import mutations from '@/store/mutation-types' -/* es-lint-enable*/ +import { Mutations, AppState } from '@/store/types' + +import Auth0Config from './auth0-variables' +import { Session } from './types' // Auth0 web authentication instance to use for our calls 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`, + domain: Auth0Config.domain, + clientID: Auth0Config.clientId, + redirectUri: Auth0Config.appDomain + Auth0Config.callbackUrl, + audience: `https://${Auth0Config.domain}/userinfo`, responseType: 'token id_token', scope: 'openid profile email' }) @@ -21,14 +21,13 @@ const webAuth = new auth0.WebAuth({ * A class to handle all authentication calls and determinations */ class AuthService extends EventEmitter { - // Local storage key for our session data AUTH_SESSION = 'auth-session' // Received and calculated values for our ssesion (initially loaded from local storage if present) - session: any = {} + session = new Session() - constructor() { + constructor () { super() this.refreshSession() } @@ -36,7 +35,7 @@ class AuthService extends EventEmitter { /** * Starts the user log in flow */ - login (customState) { + login (customState: any) { webAuth.authorize({ appState: customState }) @@ -45,10 +44,10 @@ class AuthService extends EventEmitter { /** * Promisified parseHash function */ - parseHash () { + parseHash (): Promise { return new Promise((resolve, reject) => { webAuth.parseHash((err, authResult) => { - if (err) { + if (err || authResult === null) { reject(err) } else { resolve(authResult) @@ -59,33 +58,31 @@ class AuthService extends EventEmitter { /** * Handle authentication replies from Auth0 - * * @param store The Vuex store */ - async handleAuthentication (store) { + async handleAuthentication (store: Store) { try { - const authResult: any = await this.parseHash() + const authResult = await this.parseHash() if (authResult && authResult.accessToken && authResult.idToken) { this.setSession(authResult) - store.commit(mutations.USER_LOGGED_ON, this.session.profile) + store.commit(Mutations.UserLoggedOn, this.session.profile) } - } catch(err) { - console.error(err) + } catch (err) { + console.error(err) // eslint-disable-line no-console alert(`Error: ${err.error}. Check the console for further details.`) } } /** * Set up the session and commit it to local storage - * * @param authResult The authorization result */ - setSession (authResult) { + setSession (authResult: Auth0DecodedHash) { this.session.profile = authResult.idTokenPayload - this.session.id.token = authResult.idToken + this.session.id.token = authResult.idToken! this.session.id.expiry = this.session.profile.exp * 1000 - this.session.access.token = authResult.accessToken - this.session.access.expiry = authResult.expiresIn * 1000 + Date.now() + this.session.access.token = authResult.accessToken! + this.session.access.expiry = authResult.expiresIn! * 1000 + Date.now() localStorage.setItem(this.AUTH_SESSION, JSON.stringify(this.session)) @@ -100,25 +97,27 @@ class AuthService extends EventEmitter { * Refresh this instance's session from the one in local storage */ refreshSession () { - this.session = + const emptySession = { + profile: {}, + id: { + token: null, + expiry: null + }, + access: { + token: null, + expiry: null + } + } + this.session = localStorage.getItem(this.AUTH_SESSION) - ? JSON.parse(localStorage.getItem(this.AUTH_SESSION) || '{}') - : { profile: {}, - id: { - token: null, - expiry: null - }, - access: { - token: null, - expiry: null - } - } + ? JSON.parse(localStorage.getItem(this.AUTH_SESSION) || '{}') + : emptySession } /** * Renew authorzation tokens with Auth0 */ - renewTokens () { + renewTokens (): Promise { return new Promise((resolve, reject) => { this.refreshSession() if (this.session.id.token !== null) { @@ -126,67 +125,58 @@ class AuthService extends EventEmitter { if (err) { reject(err) } else { - this.setSession(authResult) - resolve(authResult) + const result = authResult as Auth0DecodedHash + this.setSession(result) + resolve(result) } }) } else { - reject('Not logged in') + reject(new Error('Not logged in')) } }) } /** * Log out of myPrayerJournal - * * @param store The Vuex store */ - logout (store) { + logout (store: Store) { // Clear access token and ID token from local storage localStorage.removeItem(this.AUTH_SESSION) this.refreshSession() - store.commit(mutations.USER_LOGGED_OFF) + store.commit(Mutations.UserLoggedOff) webAuth.logout({ - returnTo: `${AUTH_CONFIG.appDomain}/`, - clientID: AUTH_CONFIG.clientId + returnTo: `${Auth0Config.appDomain}/`, + clientID: Auth0Config.clientId }) this.emit('loginEvent', { loggedIn: false }) } - /** - * Check expiration for a token (the way it's stored in the session) - */ - checkExpiry = (it) => it.token && it.expiry && Date.now() < it.expiry - /** * Is there a user authenticated? */ isAuthenticated () { - return this.checkExpiry(this.session.id) + return this.session.id.isValid() } /** * Is the current access token valid? */ isAccessTokenValid () { - return this.checkExpiry(this.session.access) + return this.session.access.isValid() } /** * Get the user's access token, renewing it if required */ - async getAccessToken () { + async getAccessToken (): Promise { if (this.isAccessTokenValid()) { return this.session.access.token } else { - try { - const authResult: any = await this.renewTokens() - return authResult.accessToken - } catch (reject) { - throw reject - } + const authResult = await this.renewTokens() + return authResult.accessToken! } } } diff --git a/src/app/src/auth/types.ts b/src/app/src/auth/types.ts new file mode 100644 index 0000000..e3bc8af --- /dev/null +++ b/src/app/src/auth/types.ts @@ -0,0 +1,25 @@ +/** A token and expiration set */ +export class Token { + /** The actual token */ + token: string = '' + + /** The expiration for the token */ + expiry: number = 0 + + /** Whether this token is currently valid */ + isValid (): boolean { + return this.token !== '' && this.expiry !== 0 && Date.now() < this.expiry + } +} + +/** A user's current session */ +export class Session { + /** The user's profile from Auth0 */ + profile: any = {} + + /** The user's ID token */ + id = new Token() + + /** The user's access token */ + access = new Token() +} diff --git a/src/app/src/components/Journal.vue b/src/app/src/components/Journal.vue index f5d30bf..a44b50e 100644 --- a/src/app/src/components/Journal.vue +++ b/src/app/src/components/Journal.vue @@ -31,7 +31,7 @@ import NotesEdit from './request/NotesEdit' import RequestCard from './request/RequestCard' import SnoozeRequest from './request/SnoozeRequest' -import actions from '@/store/action-types' +import { Actions } from '@/store/types' export default { name: 'journal', @@ -59,7 +59,7 @@ export default { ...mapState(['user', 'journal', 'isLoadingJournal']) }, async created () { - await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) + await this.$store.dispatch(Actions.LoadJournal, this.progress) this.messages.$emit('info', `Loaded ${this.journal.length} prayer requests`) }, provide () { diff --git a/src/app/src/components/request/ActiveRequests.vue b/src/app/src/components/request/ActiveRequests.vue index 5353488..504edc8 100644 --- a/src/app/src/components/request/ActiveRequests.vue +++ b/src/app/src/components/request/ActiveRequests.vue @@ -21,7 +21,7 @@ import { mapState } from 'vuex' import RequestList from '@/components/request/RequestList' -import actions from '@/store/action-types' +import { Actions } from '@/store/types' export default { name: 'active-requests', @@ -46,7 +46,7 @@ export default { async ensureJournal () { if (!Array.isArray(this.journal)) { this.loaded = false - await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) + await this.$store.dispatch(Actions.LoadJournal, this.progress) } this.requests = this.journal .sort((a, b) => a.showAfter - b.showAfter) diff --git a/src/app/src/components/request/EditRequest.vue b/src/app/src/components/request/EditRequest.vue index 8c578ac..2f0110a 100644 --- a/src/app/src/components/request/EditRequest.vue +++ b/src/app/src/components/request/EditRequest.vue @@ -54,7 +54,7 @@ md-content(role='main').mpj-narrow import { mapState } from 'vuex' -import actions from '@/store/action-types' +import { Actions } from '@/store/types' export default { name: 'edit-request', @@ -114,7 +114,7 @@ export default { this.title = 'Edit Prayer Request' this.isNew = false if (this.journal.length === 0) { - await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) + await this.$store.dispatch(Actions.LoadJournal, this.progress) } const req = this.journal.filter(r => r.requestId === this.id)[0] this.form.requestId = this.id @@ -140,12 +140,12 @@ export default { }, async ensureJournal () { if (!Array.isArray(this.journal)) { - await this.$store.dispatch(actions.LOAD_JOURNAL, this.progress) + await this.$store.dispatch(Actions.LoadJournal, this.progress) } }, async saveRequest () { if (this.isNew) { - await this.$store.dispatch(actions.ADD_REQUEST, { + await this.$store.dispatch(Actions.AddRequest, { progress: this.progress, requestText: this.form.requestText, recurType: this.form.recur.typ === 'Immediate' ? 'Immediate' : this.form.recur.other, @@ -153,7 +153,7 @@ export default { }) this.messages.$emit('info', 'New prayer request added') } else { - await this.$store.dispatch(actions.UPDATE_REQUEST, { + await this.$store.dispatch(Actions.UpdateRequest, { progress: this.progress, requestId: this.form.requestId, updateText: this.form.requestText, diff --git a/src/app/src/components/request/RequestCard.vue b/src/app/src/components/request/RequestCard.vue index a3b5fbf..bb6dab0 100644 --- a/src/app/src/components/request/RequestCard.vue +++ b/src/app/src/components/request/RequestCard.vue @@ -27,7 +27,7 @@ md-card(v-if='shouldDisplay'