Update ereaders to handle refreshing, epubjs to use custom request method, separate accessToken in store
Some checks are pending
Run Component Tests / Run Component Tests (push) Waiting to run
Integration Test / build and test (push) Waiting to run
Run Unit Tests / Run Unit Tests (push) Waiting to run

This commit is contained in:
advplyr 2025-07-10 16:54:28 -05:00
parent 25fe4dee3a
commit d3402e30c2
9 changed files with 67 additions and 46 deletions

View file

@ -311,7 +311,7 @@ export default {
if (data.user.id === this.user.id && data.user.accessToken !== this.user.accessToken) {
console.log('Current user access token was updated')
this.$store.commit('user/setUserToken', data.user.accessToken)
this.$store.commit('user/setAccessToken', data.user.accessToken)
}
this.$toast.success(this.$strings.ToastAccountUpdateSuccess)

View file

@ -104,9 +104,6 @@ export default {
}
},
computed: {
userToken() {
return this.$store.getters['user/getToken']
},
libraryItemId() {
return this.libraryItem?.id
},
@ -234,10 +231,7 @@ export default {
async extract() {
this.loading = true
var buff = await this.$axios.$get(this.ebookUrl, {
responseType: 'blob',
headers: {
Authorization: `Bearer ${this.userToken}`
}
responseType: 'blob'
})
const archive = await Archive.open(buff)
const originalFilesObject = await archive.getFilesObject()

View file

@ -57,9 +57,6 @@ export default {
}
},
computed: {
userToken() {
return this.$store.getters['user/getToken']
},
/** @returns {string} */
libraryItemId() {
return this.libraryItem?.id

View file

@ -26,9 +26,6 @@ export default {
return {}
},
computed: {
userToken() {
return this.$store.getters['user/getToken']
},
libraryItemId() {
return this.libraryItem?.id
},
@ -96,11 +93,8 @@ export default {
},
async initMobi() {
// Fetch mobi file as blob
var buff = await this.$axios.$get(this.ebookUrl, {
responseType: 'blob',
headers: {
Authorization: `Bearer ${this.userToken}`
}
const buff = await this.$axios.$get(this.ebookUrl, {
responseType: 'blob'
})
var reader = new FileReader()
reader.onload = async (event) => {

View file

@ -55,7 +55,8 @@ export default {
loadedRatio: 0,
page: 1,
numPages: 0,
pdfDocInitParams: null
pdfDocInitParams: null,
isRefreshing: false
}
},
computed: {
@ -152,7 +153,34 @@ export default {
this.page++
this.updateProgress()
},
error(err) {
async refreshToken() {
if (this.isRefreshing) return
this.isRefreshing = true
const newAccessToken = await this.$store.dispatch('user/refreshToken').catch((error) => {
console.error('Failed to refresh token', error)
return null
})
if (!newAccessToken) {
// Redirect to login on failed refresh
this.$router.push('/login')
return
}
// Force Vue to re-render the PDF component by creating a new object
this.pdfDocInitParams = {
url: this.ebookUrl,
httpHeaders: {
Authorization: `Bearer ${newAccessToken}`
}
}
this.isRefreshing = false
},
async error(err) {
if (err && err.status === 401) {
console.log('Received 401 error, refreshing token')
await this.refreshToken()
return
}
console.error(err)
},
resize() {

View file

@ -189,7 +189,7 @@ export default {
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
this.$store.commit('user/setUser', user)
this.$store.commit('user/setUserToken', user.accessToken)
this.$store.commit('user/setAccessToken', user.accessToken)
this.$store.dispatch('user/loadUserSettings')
},

View file

@ -45,7 +45,7 @@ export default function ({ $axios, store, $root, app }) {
if (originalRequest.url === '/auth/refresh' || originalRequest.url === '/login') {
// Refresh failed or login failed, redirect to login
store.commit('user/setUser', null)
store.commit('user/setUserToken', null)
store.commit('user/setAccessToken', null)
app.router.push('/login')
return Promise.reject(error)
}
@ -72,23 +72,13 @@ export default function ({ $axios, store, $root, app }) {
try {
// Attempt to refresh the token
const response = await $axios.$post('/auth/refresh')
const newAccessToken = response.user.accessToken
// Updates store if successful, otherwise clears store and throw error
const newAccessToken = await store.dispatch('user/refreshToken')
if (!newAccessToken) {
console.error('No new access token received')
return Promise.reject(error)
}
// Update the token in store and localStorage
store.commit('user/setUser', response.user)
store.commit('user/setUserToken', newAccessToken)
// Emit event used to re-authenticate socket in default.vue since $root is not available here
if (app.$eventBus) {
app.$eventBus.$emit('token_refreshed', newAccessToken)
}
// Update the original request with new token
if (!originalRequest.headers) {
originalRequest.headers = {}
@ -106,9 +96,7 @@ export default function ({ $axios, store, $root, app }) {
// Process queued requests with error
processQueue(refreshError, null)
// Clear user data and redirect to login
store.commit('user/setUser', null)
store.commit('user/setUserToken', null)
// Redirect to login
app.router.push('/login')
return Promise.reject(refreshError)

View file

@ -1,5 +1,6 @@
export const state = () => ({
user: null,
accessToken: null,
settings: {
orderBy: 'media.metadata.title',
orderDesc: false,
@ -25,7 +26,7 @@ export const getters = {
getIsRoot: (state) => state.user && state.user.type === 'root',
getIsAdminOrUp: (state) => state.user && (state.user.type === 'admin' || state.user.type === 'root'),
getToken: (state) => {
return state.user?.accessToken || null
return state.accessToken || null
},
getUserMediaProgress:
(state) =>
@ -145,6 +146,27 @@ export const actions = {
} catch (error) {
console.error('Failed to load userSettings from local storage', error)
}
},
refreshToken({ state, commit }) {
return this.$axios
.$post('/auth/refresh')
.then(async (response) => {
const newAccessToken = response.user.accessToken
commit('setUser', response.user)
commit('setAccessToken', newAccessToken)
// Emit event used to re-authenticate socket in default.vue since $root is not available here
if (this.$eventBus) {
this.$eventBus.$emit('token_refreshed', newAccessToken)
}
return newAccessToken
})
.catch((error) => {
console.error('Failed to refresh token', error)
commit('setUser', null)
commit('setAccessToken', null)
// Calling function handles redirect to login
throw error
})
}
}
@ -152,14 +174,12 @@ export const mutations = {
setUser(state, user) {
state.user = user
},
setUserToken(state, token) {
setAccessToken(state, token) {
if (!token) {
localStorage.removeItem('token')
if (state.user) {
state.user.accessToken = null
}
} else if (state.user) {
state.user.accessToken = token
state.accessToken = null
} else {
state.accessToken = token
localStorage.setItem('token', token)
}
},

View file

@ -23,7 +23,7 @@ class RateLimiterFactory {
windowMs = parseInt(process.env.RATE_LIMIT_AUTH_WINDOW)
}
let max = 20 // 20 attempts default
let max = 40 // 40 attempts default
if (parseInt(process.env.RATE_LIMIT_AUTH_MAX) > 0) {
max = parseInt(process.env.RATE_LIMIT_AUTH_MAX)
}