diff --git a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt index c4caccb4..d25e7446 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt @@ -407,6 +407,9 @@ class ApiHandler(var ctx:Context) { if (checkAbsDatabaseNotifyListenersInitted()) { val tokenJsObject = JSObject() tokenJsObject.put("error", "Token refresh failed") + if (serverConnectionConfigId.isNotEmpty()) { + tokenJsObject.put("serverConnectionConfigId", serverConnectionConfigId) + } absDatabaseNotifyListeners("onTokenRefreshFailure", tokenJsObject) } else { // Can happen if Webview is never run diff --git a/components/connection/ServerConnectForm.vue b/components/connection/ServerConnectForm.vue index 404f993d..ebecabdf 100644 --- a/components/connection/ServerConnectForm.vue +++ b/components/connection/ServerConnectForm.vue @@ -30,7 +30,7 @@
-
+
arrow_back
@@ -412,7 +412,7 @@ export default { throw new Error('Authentication failed with the provided token.') } - const duplicateConfig = this.serverConnectionConfigs.find((scc) => scc.address === this.serverConfig.address && scc.username === payload.user.username) + const duplicateConfig = this.serverConnectionConfigs.find((scc) => scc.address === this.serverConfig.address && scc.username === payload.user.username && scc.id !== this.serverConfig.id) if (duplicateConfig) { throw new Error('Config already exists for this address and username.') } @@ -471,7 +471,11 @@ export default { // Will NOT include access token and refresh token this.setUserAndConnection(payload) } else { - this.showAuth = true + let error = this.error + if (await this.submit(true)) { + this.showForm = true + this.error = error + } } }, async removeServerConfigClick() { @@ -500,12 +504,14 @@ export default { this.showForm = !this.serverConnectionConfigs.length } }, - editServerConfig(serverConfig) { + async editServerConfig(serverConfig) { this.serverConfig = { ...serverConfig } - this.showForm = true - this.showAuth = true + + if (await this.submit(true)) { + this.showForm = true + } }, async newServerConfigClick() { await this.$hapticsImpact() @@ -651,9 +657,8 @@ export default { return false }) }, - async submit() { - if (!this.networkConnected) return - if (!this.serverConfig.address) return + async submit(preventAutoLogin = false) { + if (!this.networkConnected || !this.serverConfig.address) return false const initialAddress = this.serverConfig.address // Did the user specify a protocol? @@ -666,6 +671,7 @@ export default { this.authMethods = [] try { + console.log('[ServerConnectForm] submit tryServerUrl: ' + this.serverConfig.address) // Try the server URL. If it fails and the protocol was not provided, try with http instead of https const statusData = await this.tryServerUrl(this.serverConfig.address, !protocolProvided) if (this.validateLoginFormResponse(statusData, this.serverConfig.address, protocolProvided)) { @@ -674,11 +680,12 @@ export default { this.oauth.buttonText = statusData.data.authFormData?.authOpenIDButtonText || 'Login with OpenID' this.serverConfig.version = statusData.data.serverVersion - if (statusData.data.authFormData?.authOpenIDAutoLaunch) { + if (statusData.data.authFormData?.authOpenIDAutoLaunch && !preventAutoLogin) { this.clickLoginWithOpenId() } return true } else { + console.log('[ServerConnectForm] submit validateLoginFormResponse failed: ' + this.serverConfig.address) return false } } catch (error) { @@ -852,7 +859,7 @@ export default { } else { // Detect if the connection config is using the old token. If so, force re-login if (this.serverConfig.token === user.token || user.isOldToken) { - this.setForceReloginForNewAuth() + this.setForceRelogin('oldAuthToken') return } @@ -926,20 +933,29 @@ export default { this.processing = false return authRes }, - async setForceReloginForNewAuth() { + async setForceRelogin(error) { + console.log('[ServerConnectForm] setForceRelogin', error, this.serverConfig.address) // This calls /status on the server and sets the auth methods - const result = await this.submit() + const result = await this.submit(true) if (result) { - this.error = this.$strings.MessageOldServerAuthReLoginRequired + this.showForm = true + + if (error === 'oldAuthToken') { + this.error = this.$strings.MessageOldServerAuthReLoginRequired + } else if (error === 'refreshTokenFailed') { + this.error = this.$strings.MessageFailedToRefreshToken + } } }, init() { - // Handle force re-login for servers using new JWT auth but still using an old token in the server config - if (this.$route.query.error === 'oldAuthToken' && this.$route.query.serverConnectionConfigId) { + if (this.$route.query.serverConnectionConfigId) { + // Handle force re-login for servers using new JWT auth but still using an old token OR refresh token failed this.serverConfig = this.serverConnectionConfigs.find((scc) => scc.id === this.$route.query.serverConnectionConfigId) if (this.serverConfig) { - this.setForceReloginForNewAuth() + this.setForceRelogin(this.$route.query.error) return + } else { + console.error('[ServerConnectForm] init with serverConnectionConfigId but no serverConfig found', this.$route.query.serverConnectionConfigId) } } diff --git a/components/readers/PdfReader.vue b/components/readers/PdfReader.vue index 1446e396..5cefaa7d 100644 --- a/components/readers/PdfReader.vue +++ b/components/readers/PdfReader.vue @@ -173,16 +173,18 @@ export default { try { console.log('[PdfReader] Handling refresh failure - logging out user') + const serverConnectionConfigId = this.$store.getters['user/getServerConnectionConfigId'] + // Clear store await this.$store.dispatch('user/logout') - if (this.$store.getters['user/getServerConnectionConfigId']) { + if (serverConnectionConfigId) { // Clear refresh token for server connection config - await this.$db.clearRefreshToken(this.$store.getters['user/getServerConnectionConfigId']) + await this.$db.clearRefreshToken(serverConnectionConfigId) } if (window.location.pathname !== '/connect') { - window.location.href = '/connect' + window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + serverConnectionConfigId } } catch (error) { console.error('[PdfReader] Failed to handle refresh failure:', error) diff --git a/pages/connect.vue b/pages/connect.vue index b20c5f69..439feae0 100644 --- a/pages/connect.vue +++ b/pages/connect.vue @@ -37,9 +37,9 @@ export default { computed: {}, methods: { async init() { + await this.$store.dispatch('setupNetworkListener') this.deviceData = await this.$db.getDeviceData() this.$store.commit('setDeviceData', this.deviceData) - await this.$store.dispatch('setupNetworkListener') } }, mounted() { diff --git a/plugins/axios.js b/plugins/axios.js index 6424446d..de50a8d9 100644 --- a/plugins/axios.js +++ b/plugins/axios.js @@ -32,7 +32,7 @@ export default function ({ $axios, store, $db }) { } if (window.location.pathname !== '/connect') { - window.location.href = '/connect' + window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + serverConnectionConfigId } } catch (error) { console.error('[axios] Failed to handle refresh failure:', error) diff --git a/plugins/db.js b/plugins/db.js index f41be58f..faa5949e 100644 --- a/plugins/db.js +++ b/plugins/db.js @@ -138,7 +138,7 @@ export default ({ app, store }, inject) => { // Clear store and redirect to login page await store.dispatch('user/logout') if (window.location.pathname !== '/connect') { - window.location.href = '/connect' + window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + data.serverConnectionConfigId || '' } }) } diff --git a/plugins/nativeHttp.js b/plugins/nativeHttp.js index f4089ebb..76aa0e05 100644 --- a/plugins/nativeHttp.js +++ b/plugins/nativeHttp.js @@ -221,7 +221,7 @@ export default function ({ store, $db, $socket }, inject) { // Redirect to login page if (window.location.pathname !== '/connect') { - window.location.href = '/connect' + window.location.href = '/connect?error=refreshTokenFailed&serverConnectionConfigId=' + serverConnectionConfigId } } catch (error) { console.error('[nativeHttp] Failed to handle refresh failure:', error) diff --git a/strings/en-us.json b/strings/en-us.json index ef8fb515..fdfd87f8 100644 --- a/strings/en-us.json +++ b/strings/en-us.json @@ -298,6 +298,7 @@ "MessageDownloading": "Downloading...", "MessageDownloadingEpisode": "Downloading episode", "MessageEpisodesQueuedForDownload": "{0} Episode(s) queued for download", + "MessageFailedToRefreshToken": "Failed to refresh token, re-login required", "MessageFeedURLWillBe": "Feed URL will be {0}", "MessageFetching": "Fetching...", "MessageFollowTheProjectOnGithub": "Follow the project on Github",