mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
Merge e329667ab073c9e393b6a0cd25e0eb465687b714 into 5f2b7ee1050a177785864ab50db4c1c8c118ea20
This commit is contained in:
commit
6268db88a9
@ -26,6 +26,11 @@ function storeAuthenticationInfo(authInfo) {
|
||||
localStorage.setItem('is-authenticated', 'true')
|
||||
}
|
||||
|
||||
const isNetworkError = (error) => {
|
||||
const msg = error?.message || ''
|
||||
return msg === 'Failed to fetch' || msg.includes('NetworkError')
|
||||
}
|
||||
|
||||
const authProvider = {
|
||||
login: ({ username, password }) => {
|
||||
let url = baseUrl('/auth/login')
|
||||
@ -53,10 +58,7 @@ const authProvider = {
|
||||
return response
|
||||
})
|
||||
.catch((error) => {
|
||||
if (
|
||||
error.message === 'Failed to fetch' ||
|
||||
error.stack === 'TypeError: Failed to fetch'
|
||||
) {
|
||||
if (isNetworkError(error)) {
|
||||
throw new Error('errors.network_error')
|
||||
}
|
||||
|
||||
@ -78,11 +80,22 @@ const authProvider = {
|
||||
? Promise.resolve()
|
||||
: Promise.reject(),
|
||||
|
||||
checkError: ({ status }) => {
|
||||
if (status === 401) {
|
||||
checkError: (error) => {
|
||||
if (error?.status === 401) {
|
||||
removeItems()
|
||||
return Promise.reject()
|
||||
}
|
||||
if (config.extAuthLogoutURL && isNetworkError(error)) {
|
||||
const now = Date.now()
|
||||
const lastReload =
|
||||
Number(sessionStorage.getItem('ext-auth-reload-ts')) || 0
|
||||
if (now - lastReload > 30000) {
|
||||
sessionStorage.setItem('ext-auth-reload-ts', String(now))
|
||||
removeItems()
|
||||
window.location.reload()
|
||||
return new Promise(() => {})
|
||||
}
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
|
||||
|
||||
100
ui/src/authProvider.test.js
Normal file
100
ui/src/authProvider.test.js
Normal file
@ -0,0 +1,100 @@
|
||||
import { describe, it, beforeEach, afterEach, expect } from 'vitest'
|
||||
import config from './config'
|
||||
import authProvider from './authProvider'
|
||||
|
||||
describe('authProvider', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
localStorage.setItem('is-authenticated', 'true')
|
||||
localStorage.setItem('token', 'test-token')
|
||||
localStorage.setItem('userId', 'test-user')
|
||||
localStorage.setItem('role', 'admin')
|
||||
config.extAuthLogoutURL = ''
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
config.extAuthLogoutURL = ''
|
||||
})
|
||||
|
||||
describe('checkError', () => {
|
||||
it('rejects and clears storage on 401', async () => {
|
||||
await expect(authProvider.checkError({ status: 401 })).rejects.toBe(
|
||||
undefined,
|
||||
)
|
||||
expect(localStorage.getItem('is-authenticated')).toBeNull()
|
||||
})
|
||||
|
||||
it('resolves on non-401 HTTP errors', async () => {
|
||||
await expect(
|
||||
authProvider.checkError({ status: 500 }),
|
||||
).resolves.toBeUndefined()
|
||||
expect(localStorage.getItem('is-authenticated')).toBe('true')
|
||||
})
|
||||
|
||||
it('resolves on network error without extAuth configured', async () => {
|
||||
config.extAuthLogoutURL = ''
|
||||
await expect(
|
||||
authProvider.checkError(new TypeError('Failed to fetch')),
|
||||
).resolves.toBeUndefined()
|
||||
expect(localStorage.getItem('is-authenticated')).toBe('true')
|
||||
})
|
||||
|
||||
it('clears auth and sets reload guard on TypeError with extAuth', () => {
|
||||
config.extAuthLogoutURL = 'https://auth.example.com/logout'
|
||||
// window.location.reload throws in jsdom, so we catch it
|
||||
try {
|
||||
authProvider.checkError(new TypeError('Failed to fetch'))
|
||||
} catch {
|
||||
// jsdom "Not implemented: navigation" is expected
|
||||
}
|
||||
expect(localStorage.getItem('is-authenticated')).toBeNull()
|
||||
expect(sessionStorage.getItem('ext-auth-reload-ts')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('clears auth on Firefox NetworkError with extAuth', () => {
|
||||
config.extAuthLogoutURL = 'https://auth.example.com/logout'
|
||||
const error = new Error('NetworkError when attempting to fetch resource')
|
||||
try {
|
||||
authProvider.checkError(error)
|
||||
} catch {
|
||||
// jsdom "Not implemented: navigation" is expected
|
||||
}
|
||||
expect(localStorage.getItem('is-authenticated')).toBeNull()
|
||||
expect(sessionStorage.getItem('ext-auth-reload-ts')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('does not reload-loop within 30 seconds', async () => {
|
||||
config.extAuthLogoutURL = 'https://auth.example.com/logout'
|
||||
sessionStorage.setItem('ext-auth-reload-ts', String(Date.now() - 10000))
|
||||
await expect(
|
||||
authProvider.checkError(new TypeError('Failed to fetch')),
|
||||
).resolves.toBeUndefined()
|
||||
expect(localStorage.getItem('is-authenticated')).toBe('true')
|
||||
})
|
||||
|
||||
it('allows reload again after 30 seconds', () => {
|
||||
config.extAuthLogoutURL = 'https://auth.example.com/logout'
|
||||
sessionStorage.setItem('ext-auth-reload-ts', String(Date.now() - 31000))
|
||||
try {
|
||||
authProvider.checkError(new TypeError('Failed to fetch'))
|
||||
} catch {
|
||||
// jsdom "Not implemented: navigation" is expected
|
||||
}
|
||||
expect(localStorage.getItem('is-authenticated')).toBeNull()
|
||||
const ts = parseInt(sessionStorage.getItem('ext-auth-reload-ts'), 10)
|
||||
expect(Date.now() - ts).toBeLessThan(5000)
|
||||
})
|
||||
})
|
||||
|
||||
describe('checkAuth', () => {
|
||||
it('resolves when authenticated', async () => {
|
||||
await expect(authProvider.checkAuth()).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('rejects when not authenticated', async () => {
|
||||
localStorage.removeItem('is-authenticated')
|
||||
await expect(authProvider.checkAuth()).rejects.toBe(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -14,6 +14,9 @@ const localStorageMock = (function () {
|
||||
setItem: function (key, value) {
|
||||
store[key] = value.toString()
|
||||
},
|
||||
removeItem: function (key) {
|
||||
delete store[key]
|
||||
},
|
||||
clear: function () {
|
||||
store = {}
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user