mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-30 14:49:47 +02:00
Update to use x-refresh-token header, update logout to clear refresh token, add AbsLogger logs for android refresh
This commit is contained in:
parent
d8cdb7073e
commit
467fedbfe7
10 changed files with 123 additions and 90 deletions
|
@ -216,6 +216,16 @@ class AbsDatabase : Plugin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun clearRefreshToken(call:PluginCall) {
|
||||||
|
val serverConnectionConfigId = call.getString("serverConnectionConfigId", "").toString()
|
||||||
|
|
||||||
|
val refreshToken = secureStorage.removeRefreshToken(serverConnectionConfigId)
|
||||||
|
val result = JSObject()
|
||||||
|
result.put("success", refreshToken)
|
||||||
|
call.resolve(result)
|
||||||
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun getAccessToken(call:PluginCall) {
|
fun getAccessToken(call:PluginCall) {
|
||||||
val serverConnectionConfigId = call.getString("serverConnectionConfigId", "").toString()
|
val serverConnectionConfigId = call.getString("serverConnectionConfigId", "").toString()
|
||||||
|
|
|
@ -123,7 +123,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
response.use {
|
response.use {
|
||||||
if (it.code == 401) {
|
if (it.code == 401) {
|
||||||
// Handle 401 Unauthorized by attempting token refresh
|
// Handle 401 Unauthorized by attempting token refresh
|
||||||
Log.d(tag, "Received 401, attempting token refresh")
|
AbsLogger.info(tag, "makeRequest: 401 Unauthorized for request to \"${request.url}\" - attempt token refresh")
|
||||||
handleTokenRefresh(request, httpClient, cb)
|
handleTokenRefresh(request, httpClient, cb)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -175,12 +175,12 @@ class ApiHandler(var ctx:Context) {
|
||||||
*/
|
*/
|
||||||
private fun handleTokenRefresh(originalRequest: Request, httpClient: OkHttpClient?, callback: (JSObject) -> Unit) {
|
private fun handleTokenRefresh(originalRequest: Request, httpClient: OkHttpClient?, callback: (JSObject) -> Unit) {
|
||||||
try {
|
try {
|
||||||
Log.d(tag, "handleTokenRefresh: Starting token refresh process")
|
AbsLogger.info(tag, "handleTokenRefresh: Attempting to refresh auth tokens for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
|
|
||||||
// Get current server connection config ID
|
// Get current server connection config ID
|
||||||
val serverConnectionConfigId = DeviceManager.serverConnectionConfigId
|
val serverConnectionConfigId = DeviceManager.serverConnectionConfigId
|
||||||
if (serverConnectionConfigId.isEmpty()) {
|
if (serverConnectionConfigId.isEmpty()) {
|
||||||
Log.e(tag, "handleTokenRefresh: No server connection config ID available")
|
AbsLogger.error(tag, "handleTokenRefresh: Unable to refresh auth tokens. No server connection config ID")
|
||||||
val errorObj = JSObject()
|
val errorObj = JSObject()
|
||||||
errorObj.put("error", "No server connection available")
|
errorObj.put("error", "No server connection available")
|
||||||
callback(errorObj)
|
callback(errorObj)
|
||||||
|
@ -190,7 +190,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
// Get refresh token from secure storage
|
// Get refresh token from secure storage
|
||||||
val refreshToken = secureStorage.getRefreshToken(serverConnectionConfigId)
|
val refreshToken = secureStorage.getRefreshToken(serverConnectionConfigId)
|
||||||
if (refreshToken.isNullOrEmpty()) {
|
if (refreshToken.isNullOrEmpty()) {
|
||||||
Log.e(tag, "handleTokenRefresh: No refresh token available for server $serverConnectionConfigId")
|
AbsLogger.error(tag, "handleTokenRefresh: Unable to refresh auth tokens. No refresh token available for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
val errorObj = JSObject()
|
val errorObj = JSObject()
|
||||||
errorObj.put("error", "No refresh token available")
|
errorObj.put("error", "No refresh token available")
|
||||||
callback(errorObj)
|
callback(errorObj)
|
||||||
|
@ -203,7 +203,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
val refreshEndpoint = "${DeviceManager.serverAddress}/auth/refresh"
|
val refreshEndpoint = "${DeviceManager.serverAddress}/auth/refresh"
|
||||||
val refreshRequest = Request.Builder()
|
val refreshRequest = Request.Builder()
|
||||||
.url(refreshEndpoint)
|
.url(refreshEndpoint)
|
||||||
.addHeader("Authorization", "Bearer $refreshToken")
|
.addHeader("x-refresh-token", refreshToken)
|
||||||
.addHeader("Content-Type", "application/json")
|
.addHeader("Content-Type", "application/json")
|
||||||
.post(EMPTY_REQUEST)
|
.post(EMPTY_REQUEST)
|
||||||
.build()
|
.build()
|
||||||
|
@ -213,13 +213,14 @@ class ApiHandler(var ctx:Context) {
|
||||||
client.newCall(refreshRequest).enqueue(object : Callback {
|
client.newCall(refreshRequest).enqueue(object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
Log.e(tag, "handleTokenRefresh: Failed to connect to refresh endpoint", e)
|
Log.e(tag, "handleTokenRefresh: Failed to connect to refresh endpoint", e)
|
||||||
|
AbsLogger.error(tag, "handleTokenRefresh: Failed to connect to refresh endpoint for server ${DeviceManager.serverConnectionConfigString} (error: ${e.message})")
|
||||||
handleRefreshFailure(callback)
|
handleRefreshFailure(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
response.use {
|
response.use {
|
||||||
if (!it.isSuccessful) {
|
if (!it.isSuccessful) {
|
||||||
Log.e(tag, "handleTokenRefresh: Refresh request failed with status ${it.code}")
|
AbsLogger.error(tag, "handleTokenRefresh: Refresh request failed with status ${it.code} for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
handleRefreshFailure(callback)
|
handleRefreshFailure(callback)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -230,7 +231,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
val userObj = responseJson.optJSONObject("user")
|
val userObj = responseJson.optJSONObject("user")
|
||||||
|
|
||||||
if (userObj == null) {
|
if (userObj == null) {
|
||||||
Log.e(tag, "handleTokenRefresh: No user object in refresh response")
|
AbsLogger.error(tag, "handleTokenRefresh: No user object in refresh response for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
handleRefreshFailure(callback)
|
handleRefreshFailure(callback)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -239,7 +240,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
val newRefreshToken = userObj.optString("refreshToken")
|
val newRefreshToken = userObj.optString("refreshToken")
|
||||||
|
|
||||||
if (newAccessToken.isEmpty()) {
|
if (newAccessToken.isEmpty()) {
|
||||||
Log.e(tag, "handleTokenRefresh: No access token in refresh response")
|
AbsLogger.error(tag, "handleTokenRefresh: No access token in refresh response for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
handleRefreshFailure(callback)
|
handleRefreshFailure(callback)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -255,6 +256,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "handleTokenRefresh: Failed to parse refresh response", e)
|
Log.e(tag, "handleTokenRefresh: Failed to parse refresh response", e)
|
||||||
|
AbsLogger.error(tag, "handleTokenRefresh: Failed to parse refresh response for server ${DeviceManager.serverConnectionConfigString} (error: ${e.message})")
|
||||||
handleRefreshFailure(callback)
|
handleRefreshFailure(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,8 +299,10 @@ class ApiHandler(var ctx:Context) {
|
||||||
// Can happen if Webview is never run
|
// Can happen if Webview is never run
|
||||||
Log.i(tag, "AbsDatabaseNotifyListeners is not initialized so cannot send new access token")
|
Log.i(tag, "AbsDatabaseNotifyListeners is not initialized so cannot send new access token")
|
||||||
}
|
}
|
||||||
|
AbsLogger.info(tag, "updateTokens: Successfully refreshed auth tokens for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "updateTokens: Failed to update tokens", e)
|
Log.e(tag, "updateTokens: Failed to update tokens", e)
|
||||||
|
AbsLogger.error(tag, "updateTokens: Failed to refresh auth tokens for server ${DeviceManager.serverConnectionConfigString} (error: ${e.message})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,6 +329,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
client.newCall(newRequest).enqueue(object : Callback {
|
client.newCall(newRequest).enqueue(object : Callback {
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
Log.e(tag, "retryOriginalRequest: Failed to retry request", e)
|
Log.e(tag, "retryOriginalRequest: Failed to retry request", e)
|
||||||
|
AbsLogger.error(tag, "retryOriginalRequest: Failed to retry request after token refresh for server ${DeviceManager.serverConnectionConfigString} (error: ${e.message})")
|
||||||
val errorObj = JSObject()
|
val errorObj = JSObject()
|
||||||
errorObj.put("error", "Failed to retry request after token refresh")
|
errorObj.put("error", "Failed to retry request after token refresh")
|
||||||
callback(errorObj)
|
callback(errorObj)
|
||||||
|
@ -334,6 +339,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
response.use {
|
response.use {
|
||||||
if (!it.isSuccessful) {
|
if (!it.isSuccessful) {
|
||||||
Log.e(tag, "retryOriginalRequest: Retry request failed with status ${it.code}")
|
Log.e(tag, "retryOriginalRequest: Retry request failed with status ${it.code}")
|
||||||
|
AbsLogger.error(tag, "retryOriginalRequest: Retry request failed with status ${it.code} for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
val errorObj = JSObject()
|
val errorObj = JSObject()
|
||||||
errorObj.put("error", "Retry request failed with status ${it.code}")
|
errorObj.put("error", "Retry request failed with status ${it.code}")
|
||||||
callback(errorObj)
|
callback(errorObj)
|
||||||
|
@ -366,6 +372,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "retryOriginalRequest: Unexpected error during retry", e)
|
Log.e(tag, "retryOriginalRequest: Unexpected error during retry", e)
|
||||||
|
AbsLogger.error(tag, "retryOriginalRequest: Unexpected error during retry for server ${DeviceManager.serverConnectionConfigString}")
|
||||||
val errorObj = JSObject()
|
val errorObj = JSObject()
|
||||||
errorObj.put("error", "Failed to retry request")
|
errorObj.put("error", "Failed to retry request")
|
||||||
callback(errorObj)
|
callback(errorObj)
|
||||||
|
@ -389,7 +396,7 @@ class ApiHandler(var ctx:Context) {
|
||||||
|
|
||||||
// Remove refresh token from secure storage
|
// Remove refresh token from secure storage
|
||||||
val serverConnectionConfigId = DeviceManager.serverConnectionConfigId
|
val serverConnectionConfigId = DeviceManager.serverConnectionConfigId
|
||||||
if (!serverConnectionConfigId.isNullOrEmpty()) {
|
if (serverConnectionConfigId.isNotEmpty()) {
|
||||||
secureStorage.removeRefreshToken(serverConnectionConfigId)
|
secureStorage.removeRefreshToken(serverConnectionConfigId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -179,21 +179,7 @@ export default {
|
||||||
this.show = false
|
this.show = false
|
||||||
},
|
},
|
||||||
async logout() {
|
async logout() {
|
||||||
if (this.user) {
|
await this.$store.dispatch('user/logout', {})
|
||||||
if (this.$store.getters['getIsPlayerOpen']) {
|
|
||||||
this.$eventBus.$emit('close-stream')
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.$nativeHttp.post('/logout').catch((error) => {
|
|
||||||
console.error('Failed to logout', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$socket.logout()
|
|
||||||
await this.$db.logout()
|
|
||||||
this.$localStore.removeLastLibraryId()
|
|
||||||
this.$store.commit('user/logout')
|
|
||||||
this.$store.commit('libraries/setCurrentLibrary', null)
|
|
||||||
},
|
},
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
|
|
|
@ -260,6 +260,7 @@ export default {
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
ebookFile() {
|
ebookFile() {
|
||||||
|
if (!this.media) return null
|
||||||
// ebook file id is passed when reading a supplementary ebook
|
// ebook file id is passed when reading a supplementary ebook
|
||||||
if (this.ebookFileId) {
|
if (this.ebookFileId) {
|
||||||
return this.selectedLibraryItem.libraryFiles.find((lf) => lf.ino === this.ebookFileId)
|
return this.selectedLibraryItem.libraryFiles.find((lf) => lf.ino === this.ebookFileId)
|
||||||
|
|
|
@ -48,17 +48,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
async logout() {
|
async logout() {
|
||||||
await this.$hapticsImpact()
|
await this.$hapticsImpact()
|
||||||
if (this.user) {
|
await this.$store.dispatch('user/logout', {})
|
||||||
await this.$nativeHttp.post('/logout').catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$socket.logout()
|
|
||||||
await this.$db.logout()
|
|
||||||
this.$localStore.removeLastLibraryId()
|
|
||||||
this.$store.commit('user/logout')
|
|
||||||
this.$store.commit('libraries/setCurrentLibrary', null)
|
|
||||||
this.$router.push('/connect')
|
this.$router.push('/connect')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,7 +70,12 @@ class AbsAudioPlayerWeb extends WebPlugin {
|
||||||
const deviceInfo = {
|
const deviceInfo = {
|
||||||
deviceId: this.getDeviceId()
|
deviceId: this.getDeviceId()
|
||||||
}
|
}
|
||||||
const playbackSession = await $axios.$post(route, { deviceInfo, mediaPlayer: 'html5-mobile', forceDirectPlay: true })
|
const reqBody = {
|
||||||
|
deviceInfo,
|
||||||
|
mediaPlayer: 'html5-mobile',
|
||||||
|
forceDirectPlay: true
|
||||||
|
}
|
||||||
|
const playbackSession = await $axios.$post(route, reqBody)
|
||||||
if (playbackSession) {
|
if (playbackSession) {
|
||||||
if (startTime !== undefined && startTime !== null) playbackSession.currentTime = startTime
|
if (startTime !== undefined && startTime !== null) playbackSession.currentTime = startTime
|
||||||
this.setAudioPlayer(playbackSession, playWhenReady)
|
this.setAudioPlayer(playbackSession, playWhenReady)
|
||||||
|
@ -245,7 +250,15 @@ class AbsAudioPlayerWeb extends WebPlugin {
|
||||||
this.trackStartTime = Math.max(0, this.startTime - (this.currentTrack.startOffset || 0))
|
this.trackStartTime = Math.max(0, this.startTime - (this.currentTrack.startOffset || 0))
|
||||||
const serverAddressUrl = new URL(vuexStore.getters['user/getServerAddress'])
|
const serverAddressUrl = new URL(vuexStore.getters['user/getServerAddress'])
|
||||||
const serverHost = `${serverAddressUrl.protocol}//${serverAddressUrl.host}`
|
const serverHost = `${serverAddressUrl.protocol}//${serverAddressUrl.host}`
|
||||||
this.player.src = `${serverHost}${this.currentTrack.contentUrl}`
|
|
||||||
|
let sessionTrackUrl = null
|
||||||
|
if (this.currentTrack.contentUrl?.startsWith('/hls')) {
|
||||||
|
sessionTrackUrl = this.currentTrack.contentUrl
|
||||||
|
} else {
|
||||||
|
sessionTrackUrl = `/public/session/${this.playbackSession.id}/track/${this.currentTrack.index}`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.src = `${serverHost}${sessionTrackUrl}`
|
||||||
console.log(`[AbsAudioPlayer] Loading track src ${this.player.src}`)
|
console.log(`[AbsAudioPlayer] Loading track src ${this.player.src}`)
|
||||||
this.player.load()
|
this.player.load()
|
||||||
this.player.playbackRate = this.playbackRate
|
this.player.playbackRate = this.playbackRate
|
||||||
|
|
|
@ -69,6 +69,11 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||||
return refreshToken ? { refreshToken } : null
|
return refreshToken ? { refreshToken } : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clearRefreshToken({ serverConnectionConfigId }) {
|
||||||
|
console.log('[AbsDatabase] Clearing refresh token...', serverConnectionConfigId)
|
||||||
|
localStorage.removeItem(`refresh_token_${serverConnectionConfigId}`)
|
||||||
|
}
|
||||||
|
|
||||||
async removeServerConnectionConfig(serverConnectionConfigCallObject) {
|
async removeServerConnectionConfig(serverConnectionConfigCallObject) {
|
||||||
var serverConnectionConfigId = serverConnectionConfigCallObject.serverConnectionConfigId
|
var serverConnectionConfigId = serverConnectionConfigCallObject.serverConnectionConfigId
|
||||||
var deviceData = await this.getDeviceData()
|
var deviceData = await this.getDeviceData()
|
||||||
|
@ -77,6 +82,7 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
async logout() {
|
||||||
|
console.log('[AbsDatabase] Logging out...')
|
||||||
var deviceData = await this.getDeviceData()
|
var deviceData = await this.getDeviceData()
|
||||||
deviceData.lastServerConnectionConfigId = null
|
deviceData.lastServerConnectionConfigId = null
|
||||||
localStorage.setItem('device', JSON.stringify(deviceData))
|
localStorage.setItem('device', JSON.stringify(deviceData))
|
||||||
|
|
|
@ -10,6 +10,26 @@ class DbService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves refresh token from secure storage
|
||||||
|
* @param {string} serverConnectionConfigId
|
||||||
|
* @return {Promise<string|null>}
|
||||||
|
*/
|
||||||
|
async getRefreshToken(serverConnectionConfigId) {
|
||||||
|
const refreshTokenData = await AbsDatabase.getRefreshToken({ serverConnectionConfigId })
|
||||||
|
return refreshTokenData?.refreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears refresh token from secure storage
|
||||||
|
* @param {string} serverConnectionConfigId
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async clearRefreshToken(serverConnectionConfigId) {
|
||||||
|
const result = await AbsDatabase.clearRefreshToken({ serverConnectionConfigId })
|
||||||
|
return !!result?.success
|
||||||
|
}
|
||||||
|
|
||||||
setServerConnectionConfig(serverConnectionConfig) {
|
setServerConnectionConfig(serverConnectionConfig) {
|
||||||
return AbsDatabase.setCurrentServerConnectionConfig(serverConnectionConfig).then((data) => {
|
return AbsDatabase.setCurrentServerConnectionConfig(serverConnectionConfig).then((data) => {
|
||||||
console.log('Set server connection config', JSON.stringify(data))
|
console.log('Set server connection config', JSON.stringify(data))
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { CapacitorHttp } from '@capacitor/core'
|
import { CapacitorHttp } from '@capacitor/core'
|
||||||
import { AbsDatabase } from '@/plugins/capacitor'
|
|
||||||
|
|
||||||
export default function ({ store }, inject) {
|
export default function ({ store, $db }, inject) {
|
||||||
const nativeHttp = {
|
const nativeHttp = {
|
||||||
async request(method, _url, data, options = {}) {
|
async request(method, _url, data, options = {}) {
|
||||||
// When authorizing before a config is set, server config gets passed in as an option
|
// When authorizing before a config is set, server config gets passed in as an option
|
||||||
|
@ -9,7 +8,7 @@ export default function ({ store }, inject) {
|
||||||
delete options.serverConnectionConfig
|
delete options.serverConnectionConfig
|
||||||
|
|
||||||
let url = _url
|
let url = _url
|
||||||
const headers = {}
|
let headers = {}
|
||||||
if (!url.startsWith('http') && !url.startsWith('capacitor')) {
|
if (!url.startsWith('http') && !url.startsWith('capacitor')) {
|
||||||
const bearerToken = store.getters['user/getToken']
|
const bearerToken = store.getters['user/getToken']
|
||||||
if (bearerToken) {
|
if (bearerToken) {
|
||||||
|
@ -24,6 +23,10 @@ export default function ({ store }, inject) {
|
||||||
if (data) {
|
if (data) {
|
||||||
headers['Content-Type'] = 'application/json'
|
headers['Content-Type'] = 'application/json'
|
||||||
}
|
}
|
||||||
|
if (options.headers) {
|
||||||
|
headers = { ...headers, ...options.headers }
|
||||||
|
delete options.headers
|
||||||
|
}
|
||||||
console.log(`[nativeHttp] Making ${method} request to ${url}`)
|
console.log(`[nativeHttp] Making ${method} request to ${url}`)
|
||||||
return CapacitorHttp.request({
|
return CapacitorHttp.request({
|
||||||
method,
|
method,
|
||||||
|
@ -65,15 +68,15 @@ export default function ({ store }, inject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get refresh token from secure storage
|
// Get refresh token from secure storage
|
||||||
const refreshTokenData = await this.getRefreshToken(serverConnectionConfig.id)
|
const refreshToken = await $db.getRefreshToken(serverConnectionConfig.id)
|
||||||
if (!refreshTokenData || !refreshTokenData.refreshToken) {
|
if (!refreshToken) {
|
||||||
console.error('[nativeHttp] No refresh token available')
|
console.error('[nativeHttp] No refresh token available')
|
||||||
throw new Error('No refresh token available')
|
throw new Error('No refresh token available')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to refresh the token
|
// Attempt to refresh the token
|
||||||
const newTokens = await this.refreshAccessToken(refreshTokenData.refreshToken, serverConnectionConfig.address)
|
const newTokens = await this.refreshAccessToken(refreshToken, serverConnectionConfig.address)
|
||||||
if (!newTokens || !newTokens.accessToken) {
|
if (!newTokens?.accessToken) {
|
||||||
console.error('[nativeHttp] Failed to refresh access token')
|
console.error('[nativeHttp] Failed to refresh access token')
|
||||||
throw new Error('Failed to refresh access token')
|
throw new Error('Failed to refresh access token')
|
||||||
}
|
}
|
||||||
|
@ -83,15 +86,15 @@ export default function ({ store }, inject) {
|
||||||
|
|
||||||
// Retry the original request with the new token
|
// Retry the original request with the new token
|
||||||
console.log('[nativeHttp] Retrying original request with new token...')
|
console.log('[nativeHttp] Retrying original request with new token...')
|
||||||
const newHeaders = options?.headers ? { ...options.headers } : { ...headers }
|
|
||||||
newHeaders['Authorization'] = `Bearer ${newTokens.accessToken}`
|
|
||||||
|
|
||||||
const retryResponse = await CapacitorHttp.request({
|
const retryResponse = await CapacitorHttp.request({
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
data,
|
data,
|
||||||
...options,
|
headers: {
|
||||||
headers: newHeaders
|
...headers,
|
||||||
|
Authorization: `Bearer ${newTokens.accessToken}`
|
||||||
|
},
|
||||||
|
...options
|
||||||
})
|
})
|
||||||
|
|
||||||
if (retryResponse.status >= 400) {
|
if (retryResponse.status >= 400) {
|
||||||
|
@ -104,26 +107,11 @@ export default function ({ store }, inject) {
|
||||||
console.error('[nativeHttp] Token refresh failed:', error)
|
console.error('[nativeHttp] Token refresh failed:', error)
|
||||||
|
|
||||||
// If refresh fails, redirect to login
|
// If refresh fails, redirect to login
|
||||||
await this.handleRefreshFailure()
|
await this.handleRefreshFailure(serverConnectionConfig?.id)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves refresh token from secure storage
|
|
||||||
* @param {string} serverConnectionConfigId - Server connection config ID
|
|
||||||
* @returns {Promise<Object|null>} - Promise that resolves with refresh token data or null
|
|
||||||
*/
|
|
||||||
async getRefreshToken(serverConnectionConfigId) {
|
|
||||||
try {
|
|
||||||
console.log('[nativeHttp] Getting refresh token...')
|
|
||||||
return await AbsDatabase.getRefreshToken({ serverConnectionConfigId })
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[nativeHttp] Failed to get refresh token:', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the access token using the refresh token
|
* Refreshes the access token using the refresh token
|
||||||
* @param {string} refreshToken - The refresh token
|
* @param {string} refreshToken - The refresh token
|
||||||
|
@ -142,8 +130,7 @@ export default function ({ store }, inject) {
|
||||||
url: `${serverAddress}/auth/refresh`,
|
url: `${serverAddress}/auth/refresh`,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${refreshToken}`,
|
'x-refresh-token': refreshToken
|
||||||
'X-Return-Tokens': 'true'
|
|
||||||
},
|
},
|
||||||
data: {}
|
data: {}
|
||||||
})
|
})
|
||||||
|
@ -162,7 +149,8 @@ export default function ({ store }, inject) {
|
||||||
console.log('[nativeHttp] Successfully refreshed access token')
|
console.log('[nativeHttp] Successfully refreshed access token')
|
||||||
return {
|
return {
|
||||||
accessToken: userResponseData.user.accessToken,
|
accessToken: userResponseData.user.accessToken,
|
||||||
refreshToken: userResponseData.user.refreshToken || refreshToken // Use new refresh token if provided, otherwise keep the old one
|
// Refresh token gets returned when refresh token is sent in x-refresh-token header
|
||||||
|
refreshToken: userResponseData.user.refreshToken
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[nativeHttp] Failed to refresh access token:', error)
|
console.error('[nativeHttp] Failed to refresh access token:', error)
|
||||||
|
@ -190,7 +178,7 @@ export default function ({ store }, inject) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save updated config to secure storage
|
// Save updated config to secure storage
|
||||||
const savedConfig = await AbsDatabase.setCurrentServerConnectionConfig(updatedConfig)
|
const savedConfig = await $db.setServerConnectionConfig(updatedConfig)
|
||||||
|
|
||||||
// Update the store
|
// Update the store
|
||||||
store.commit('user/setAccessToken', tokens.accessToken)
|
store.commit('user/setAccessToken', tokens.accessToken)
|
||||||
|
@ -208,19 +196,15 @@ export default function ({ store }, inject) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the case when token refresh fails
|
* Handles the case when token refresh fails
|
||||||
|
* @param {string} [serverConnectionConfigId]
|
||||||
* @returns {Promise} - Promise that resolves when logout is complete
|
* @returns {Promise} - Promise that resolves when logout is complete
|
||||||
*/
|
*/
|
||||||
async handleRefreshFailure() {
|
async handleRefreshFailure(serverConnectionConfigId) {
|
||||||
try {
|
try {
|
||||||
console.log('[nativeHttp] Handling refresh failure - logging out user')
|
console.log('[nativeHttp] Handling refresh failure - logging out user')
|
||||||
|
|
||||||
// Clear the store
|
// Logout from server and clear store
|
||||||
store.commit('user/setUser', null)
|
await store.dispatch('user/logout', { serverConnectionConfigId })
|
||||||
store.commit('user/setAccessToken', null)
|
|
||||||
store.commit('user/setServerConnectionConfig', null)
|
|
||||||
|
|
||||||
// Logout from database
|
|
||||||
await AbsDatabase.logout()
|
|
||||||
|
|
||||||
// Redirect to login page
|
// Redirect to login page
|
||||||
if (window.location.pathname !== '/connect') {
|
if (window.location.pathname !== '/connect') {
|
||||||
|
@ -231,19 +215,6 @@ export default function ({ store }, inject) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets device data from the database
|
|
||||||
* @returns {Promise<Object>} - Promise that resolves with device data
|
|
||||||
*/
|
|
||||||
async getDeviceData() {
|
|
||||||
try {
|
|
||||||
return await AbsDatabase.getDeviceData()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[nativeHttp] Failed to get device data:', error)
|
|
||||||
return { serverConnectionConfigs: [] }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get(url, options = {}) {
|
get(url, options = {}) {
|
||||||
return this.request('GET', url, undefined, options)
|
return this.request('GET', url, undefined, options)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Browser } from '@capacitor/browser'
|
import { Browser } from '@capacitor/browser'
|
||||||
|
import { AbsLogger } from '@/plugins/capacitor'
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
user: null,
|
user: null,
|
||||||
|
@ -135,12 +136,40 @@ export const actions = {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error opening browser', error)
|
console.error('Error opening browser', error)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async logout({ state, commit }, { serverConnectionConfigId }) {
|
||||||
|
if (state.serverConnectionConfig) {
|
||||||
|
const refreshToken = await this.$db.getRefreshToken(state.serverConnectionConfig.id)
|
||||||
|
const options = {}
|
||||||
|
if (refreshToken) {
|
||||||
|
// Refresh token is used to delete the session on the server
|
||||||
|
options.headers = {
|
||||||
|
'x-refresh-token': refreshToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Logout from server
|
||||||
|
await this.$nativeHttp.post('/logout', null, options).catch((error) => {
|
||||||
|
console.error('Failed to logout', error)
|
||||||
|
})
|
||||||
|
await this.$db.clearRefreshToken(state.serverConnectionConfig.id)
|
||||||
|
} else if (serverConnectionConfigId) {
|
||||||
|
// When refresh fails before a server connection config is set, clear refresh token for server connection config
|
||||||
|
await this.$db.clearRefreshToken(serverConnectionConfigId)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$db.logout()
|
||||||
|
this.$socket.logout()
|
||||||
|
this.$localStore.removeLastLibraryId()
|
||||||
|
commit('logout')
|
||||||
|
commit('libraries/setCurrentLibrary', null, { root: true })
|
||||||
|
await AbsLogger.info({ tag: 'user', message: `Logged out from server ${state.serverConnectionConfig?.name || 'Not connected'}` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
logout(state) {
|
logout(state) {
|
||||||
state.user = null
|
state.user = null
|
||||||
|
state.accessToken = null
|
||||||
state.serverConnectionConfig = null
|
state.serverConnectionConfig = null
|
||||||
},
|
},
|
||||||
setUser(state, user) {
|
setUser(state, user) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue