From 8d7b420a7456b719e510553fb7fe858f4d14d5db Mon Sep 17 00:00:00 2001 From: Sally Grindstaff Date: Mon, 3 Jun 2024 09:37:27 -0700 Subject: [PATCH 1/6] Add Crossref publication identifier type --- src/lib/item-types.js | 11 +++++++++++ src/router/index.js | 5 +++++ 2 files changed, 16 insertions(+) diff --git a/src/lib/item-types.js b/src/lib/item-types.js index 4720aaac..8b7d8bc3 100644 --- a/src/lib/item-types.js +++ b/src/lib/item-types.js @@ -67,6 +67,17 @@ const itemTypes = { } } }, + 'crossrefPublicationIdentifier': { + name: 'crossrefPublicationIdentifier', // TODO Redundant, change this structure + restCollectionName: 'publication-identifiers', + primaryKey: 'identifier', + httpOptions: { + list: { + method: 'get', + url: `${config.apiBaseUrl}/publication-identifiers/crossref` + } + } + }, 'publication-identifier-search': { name: 'publication-identifier', // TODO Redundant, change this structure restCollectionName: 'publication-identifiers', diff --git a/src/router/index.js b/src/router/index.js index a3c4def1..2175562f 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -108,6 +108,11 @@ const routes = [{ path: '/publication-identifiers/medrxiv/:identifier', component: PublicationIdentifierView, props: (route) => ({itemId: route.params.identifier, name: route.name, dbId: 'medRxiv'}) +}, { + name: 'crossrefPublicationIdentifier', + path: '/publication-identifiers/crossref/:identifier', + component: PublicationIdentifierView, + props: (route) => ({itemId: route.params.identifier, name: route.name, dbId: 'Crossref'}) }, { path: '/oidc-callback', name: 'oidcCallback', From 69ff1742a96e63bacdcf9ee99caa8c470d9d902b Mon Sep 17 00:00:00 2001 From: Sally Grindstaff Date: Mon, 3 Jun 2024 09:41:37 -0700 Subject: [PATCH 2/6] Encode publication id URI components in internal links --- src/components/common/HighlightsView.vue | 4 ++-- src/components/screens/ExperimentView.vue | 4 ++-- src/components/screens/ScoreSetView.vue | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/common/HighlightsView.vue b/src/components/common/HighlightsView.vue index 19beaf87..9662d284 100644 --- a/src/components/common/HighlightsView.vue +++ b/src/components/common/HighlightsView.vue @@ -91,7 +91,7 @@ @@ -135,7 +135,7 @@ diff --git a/src/components/screens/ExperimentView.vue b/src/components/screens/ExperimentView.vue index 5a2b0b99..d24db7ad 100644 --- a/src/components/screens/ExperimentView.vue +++ b/src/components/screens/ExperimentView.vue @@ -57,7 +57,7 @@
  • Publication: {{ + :href="`${config.appBaseUrl}/publication-identifiers/${publication.dbName}/${encodeURIComponent(publication.identifier)}`">{{ publication.identifier }}
    @@ -74,7 +74,7 @@
  • Publication: {{ + :href="`${config.appBaseUrl}/publication-identifiers/${publication.dbName}/${encodeURIComponent(publication.identifier)}`">{{ publication.identifier }}
    diff --git a/src/components/screens/ScoreSetView.vue b/src/components/screens/ScoreSetView.vue index b3a086e8..ee14e708 100644 --- a/src/components/screens/ScoreSetView.vue +++ b/src/components/screens/ScoreSetView.vue @@ -142,7 +142,7 @@
  • Publication: {{ + :href="`${config.appBaseUrl}/publication-identifiers/${publication.dbName}/${encodeURIComponent(publication.identifier)}`">{{ publication.identifier }}
    @@ -159,7 +159,7 @@
  • From f145018f5336553fe1e6ef568eaff54c1efd6668 Mon Sep 17 00:00:00 2001 From: Sally Grindstaff Date: Tue, 4 Jun 2024 12:28:38 -0700 Subject: [PATCH 3/6] Fix publication identifier types to use correct db name capitalization in api requests --- src/lib/item-types.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/item-types.js b/src/lib/item-types.js index 8b7d8bc3..d26109a8 100644 --- a/src/lib/item-types.js +++ b/src/lib/item-types.js @@ -41,7 +41,7 @@ const itemTypes = { httpOptions: { list: { method: 'get', - url: `${config.apiBaseUrl}/publication-identifiers/pubmed` + url: `${config.apiBaseUrl}/publication-identifiers/PubMed` } } }, @@ -52,7 +52,7 @@ const itemTypes = { httpOptions: { list: { method: 'get', - url: `${config.apiBaseUrl}/publication-identifiers/biorxiv` + url: `${config.apiBaseUrl}/publication-identifiers/bioRxiv` } } }, @@ -63,7 +63,7 @@ const itemTypes = { httpOptions: { list: { method: 'get', - url: `${config.apiBaseUrl}/publication-identifiers/medrxiv` + url: `${config.apiBaseUrl}/publication-identifiers/medRxiv` } } }, @@ -74,7 +74,7 @@ const itemTypes = { httpOptions: { list: { method: 'get', - url: `${config.apiBaseUrl}/publication-identifiers/crossref` + url: `${config.apiBaseUrl}/publication-identifiers/Crossref` } } }, From 5affb66300efbdd37289c26963c6cc8348756727 Mon Sep 17 00:00:00 2001 From: PlushZ Date: Fri, 14 Jun 2024 13:24:53 +0200 Subject: [PATCH 4/6] fix broken Galaxy plugin --- src/components/screens/ScoreSetView.vue | 3 +++ src/store/index.ts | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/screens/ScoreSetView.vue b/src/components/screens/ScoreSetView.vue index b3a086e8..b3b1b39c 100644 --- a/src/components/screens/ScoreSetView.vue +++ b/src/components/screens/ScoreSetView.vue @@ -505,6 +505,9 @@ export default { .urn}&outputType=${params .outputType}&URL=${encodeURIComponent(params.URL)}`; window.location.href = submitGalaxyUrl; + localStorage.removeItem('galaxyUrl'); + localStorage.removeItem('toolId'); + localStorage.removeItem('requestFromGalaxy'); } } catch (error) { console.error('Error sending data:', error); diff --git a/src/store/index.ts b/src/store/index.ts index f7c5eadb..a01bf585 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -9,11 +9,18 @@ import layoutModule from '@/store/modules/layout' const store = createStore({ state: { - routeProps: {}, + routeProps: { + galaxyUrl: localStorage.getItem('galaxyUrl'), + toolId: localStorage.getItem('toolId'), + requestFromGalaxy: localStorage.getItem('requestFromGalaxy'), + }, }, mutations: { setRouteProps(state: any, props: any) { state.routeProps = props; + localStorage.setItem('galaxyUrl', props.galaxyUrl); + localStorage.setItem('toolId', props.toolId); + localStorage.setItem('requestFromGalaxy', props.requestFromGalaxy); }, }, actions: { From 238c8a21b6691427e14c464182c72ff1ff739ed3 Mon Sep 17 00:00:00 2001 From: Jeremy Stone <74574922+jstone-uw@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:10:57 -0700 Subject: [PATCH 5/6] Recognize ORCID session expiration. (#210) ORCID tokens have a lifetime of 24 hours and are not renewable. After 24 hours, a logged-in MaveDB user will begin to get error responses to any API requests that check authentication status, even if they do not require authentication. When this occurs, the UI should detect the change and clear the client-side login status. This will cause the UI to revert to logged-out status. To capture this occurrence, we install an Axios response interceptor that looks for unauthorized (HTTP 401) responses. When one occurs, it makes a request to /users/me; if the response is again 401, it logs the user out. To notify the user, it publishes a toast message to a new Vuex store module. --- src/App.vue | 14 +++++++++ src/lib/auth.ts | 63 ++++++++++++++++++++++++++++++++------ src/main.js | 3 +- src/store/index.ts | 4 ++- src/store/modules/toast.js | 30 ++++++++++++++++++ 5 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 src/store/modules/toast.js diff --git a/src/App.vue b/src/App.vue index 9669cf67..c4d13427 100644 --- a/src/App.vue +++ b/src/App.vue @@ -9,9 +9,23 @@ import ConfirmDialog from 'primevue/confirmdialog' import Toast from 'primevue/toast' import 'primeflex/primeflex.css' +import {mapActions, mapState} from 'vuex' export default { components: {ConfirmDialog, Toast}, + computed: mapState('toast', ['toasts']), + watch: { + toasts: { + deep: true, + handler: function(newValue) { + if (newValue.length > 0) { + this.$toast.add(newValue[0]) + this.removeDequeuedToasts(1) + } + } + } + }, + methods: mapActions('toast', ['removeDequeuedToasts']) } diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 39d83453..090948f1 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -2,7 +2,13 @@ import axios from 'axios' -import {idToken as orcidIdToken} from '@/lib/orcid' +import config from '@/config' +import { + idToken as orcidIdToken, + isAuthenticated as orcidIsAuthenticated, + signOut as orcidSignOut +} from '@/lib/orcid' +import store from '@/store/index' import authStore from '@/store/modules/auth' export interface AuthorizationHeader { @@ -35,20 +41,59 @@ export function authHeader(): AuthorizationHeader { } /** - * Add a bearer authorization token to all requests made using Axios. + * Determine whether a URL refers to a MaveDB API endpoint. + */ +function urlBelongsToApi(url: string) { + return (url.startsWith(config.apiBaseUrl)) +} + +/** + * Add a bearer authorization token to all requests to the MaveDB API made using Axios. * - * If you wish to supply MaveDB credentials with all Axios requests, call this function at application startup time (or - * page load time, in a single-page application). + * Call this function at application startup time (or page load time, in a single-page application) to supply MaveDB + * credentials with all Axios requests to MaveDB API endpoints. */ export function installAxiosAuthHeaderInterceptor() { axios.interceptors.request.use((config) => { - const token = orcidIdToken.value - const activeRoles = authStore.state.activeRoles + // If the request URL belongs to the MaveDB API, add authorization headers. + if (config.url && urlBelongsToApi(config.url)) { + const token = orcidIdToken.value + const activeRoles = authStore.state.activeRoles - if (token) { - config.headers.Authorization = `Bearer ${token}` - config.headers['X-Active-Roles'] = activeRoles + if (token) { + config.headers.Authorization = `Bearer ${token}` + config.headers['X-Active-Roles'] = activeRoles + } } + return config }) } + +export function installAxiosUnauthorizedResponseInterceptor() { + axios.interceptors.response.use( + (response) => response, + async (error) => { + if (error && !error.config?.isSessionCheck && error.response?.status && error.response?.status == 401) { + try { + // @ts-ignore: We need to pass a custom property in the request configuration. + await axios.get(`${config.apiBaseUrl}/users/me`, {isSessionCheck: true}) + } catch (error: any) { + if (error.response?.status == 401) { + // The user's session has expired. This may happen after several requests issued around the same time, so + // only take action if the user has not yet been signed out. + if (orcidIsAuthenticated.value) { + orcidSignOut() + store.dispatch('toast/enqueueToast', { + severity: 'info', + summary: 'Your ORCID session ended. Please log in again.', + life: 5000 + }) + } + } + } + } + return Promise.reject(error) + } + ) +} diff --git a/src/main.js b/src/main.js index 72d28a81..145c9576 100644 --- a/src/main.js +++ b/src/main.js @@ -5,7 +5,7 @@ import Tooltip from 'primevue/tooltip'; import {createApp} from 'vue' import App from '@/App.vue' -import {installAxiosAuthHeaderInterceptor} from '@/lib/auth' +import {installAxiosAuthHeaderInterceptor, installAxiosUnauthorizedResponseInterceptor} from '@/lib/auth' import {initializeAuthentication as initializeOrcidAuthentication} from '@/lib/orcid' import router from '@/router' import store from '@/store' @@ -46,3 +46,4 @@ createApp(App) // Monkey-patch Axios so that all requests will have the user's credentials. installAxiosAuthHeaderInterceptor() +installAxiosUnauthorizedResponseInterceptor() diff --git a/src/store/index.ts b/src/store/index.ts index f7c5eadb..48fa1edd 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -3,6 +3,7 @@ import {createStore} from 'vuex' import authModule from '@/store/modules/auth' import layoutModule from '@/store/modules/layout' +import toastModule from '@/store/modules/toast' // Unfortunately, typed Vuex stores are painful to use, especially with multiple modules. We'll continue using any for // now. Now that we do not use a Vuex module for authentication, we can migrate to Pinia. @@ -20,7 +21,8 @@ const store = createStore({ }, modules: { auth: authModule, - layout: layoutModule + layout: layoutModule, + toast: toastModule } }) diff --git a/src/store/modules/toast.js b/src/store/modules/toast.js new file mode 100644 index 00000000..a264b41e --- /dev/null +++ b/src/store/modules/toast.js @@ -0,0 +1,30 @@ +const module = { + namespaced: true, + + state: { + toasts: [] + }, + + mutations: { + enqueueToast(state, toast) { + state.toasts.push(toast) + console.log(state.toasts) + }, + removeDequeuedToasts(state, numToasts) { + state.toasts.splice(0, Math.min(numToasts, state.toasts.length)) + } + }, + + actions: { + enqueueToast({commit}, toast) { + console.log('here!!!') + commit('enqueueToast', toast) + }, + removeDequeuedToasts({commit}, numToasts) { + console.log('dequeue') + commit('removeDequeuedToasts', numToasts) + } + } +} + +export default module From 0f984da20634a1a7d5d9c4dae418bd8ee95948e1 Mon Sep 17 00:00:00 2001 From: ashsny <1389678+ashsny@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:17:38 -0700 Subject: [PATCH 6/6] Remove console logs and edit toast message. --- src/lib/auth.ts | 2 +- src/store/modules/toast.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 090948f1..bb981403 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -86,7 +86,7 @@ export function installAxiosUnauthorizedResponseInterceptor() { orcidSignOut() store.dispatch('toast/enqueueToast', { severity: 'info', - summary: 'Your ORCID session ended. Please log in again.', + summary: 'Your ORCID session has ended. Please log in again.', life: 5000 }) } diff --git a/src/store/modules/toast.js b/src/store/modules/toast.js index a264b41e..2cf07bf3 100644 --- a/src/store/modules/toast.js +++ b/src/store/modules/toast.js @@ -17,11 +17,9 @@ const module = { actions: { enqueueToast({commit}, toast) { - console.log('here!!!') commit('enqueueToast', toast) }, removeDequeuedToasts({commit}, numToasts) { - console.log('dequeue') commit('removeDequeuedToasts', numToasts) } }