diff --git a/consts/consts.go b/consts/consts.go index 52736fddc..ea353c3aa 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -14,6 +14,7 @@ const ( DefaultDbPath = "navidrome.db?cache=shared&_busy_timeout=15000&_journal_mode=WAL" InitialSetupFlagKey = "InitialSetup" + UIAuthorizationHeader = "X-ND-Authorization" JWTSecretKey = "JWTSecret" JWTIssuer = "ND" DefaultSessionTimeout = 30 * time.Minute diff --git a/server/app/app.go b/server/app/app.go index c1879fbaf..cd09a7c2f 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -38,8 +38,9 @@ func (app *Router) routes(path string) http.Handler { r.Post("/createAdmin", CreateAdmin(app.ds)) r.Route("/api", func(r chi.Router) { + r.Use(mapAuthHeader()) r.Use(jwtauth.Verifier(auth.TokenAuth)) - r.Use(Authenticator(app.ds)) + r.Use(authenticator(app.ds)) app.R(r, "/user", model.User{}) app.R(r, "/song", model.MediaFile{}) app.R(r, "/album", model.Album{}) diff --git a/server/app/auth.go b/server/app/auth.go index cc5403af5..d2f2b3baa 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -169,7 +169,18 @@ func getToken(ds model.DataStore, ctx context.Context) (*jwt.Token, error) { return nil, errors.New("invalid authentication") } -func Authenticator(ds model.DataStore) func(next http.Handler) http.Handler { +// This method maps the custom authorization header to the default 'Authorization', used by the jwtauth library +func mapAuthHeader() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + bearer := r.Header.Get(consts.UIAuthorizationHeader) + r.Header.Set("Authorization", bearer) + next.ServeHTTP(w, r) + }) + } +} + +func authenticator(ds model.DataStore) func(next http.Handler) http.Handler { auth.InitTokenAuth(ds) return func(next http.Handler) http.Handler { @@ -194,7 +205,7 @@ func Authenticator(ds model.DataStore) func(next http.Handler) http.Handler { return } - w.Header().Set("Authorization", newTokenString) + w.Header().Set(consts.UIAuthorizationHeader, newTokenString) next.ServeHTTP(w, r.WithContext(newCtx)) }) } diff --git a/server/app/auth_test.go b/server/app/auth_test.go new file mode 100644 index 000000000..0edc1a299 --- /dev/null +++ b/server/app/auth_test.go @@ -0,0 +1,27 @@ +package app + +import ( + "net/http" + "net/http/httptest" + + "github.com/deluan/navidrome/consts" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Auth", func() { + Describe("mapAuthHeader", func() { + It("maps the custom header to Authorization header", func() { + r := httptest.NewRequest("GET", "/index.html", nil) + r.Header.Set(consts.UIAuthorizationHeader, "test authorization bearer") + w := httptest.NewRecorder() + + mapAuthHeader()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Expect(r.Header.Get("Authorization")).To(Equal("test authorization bearer")) + w.WriteHeader(200) + })).ServeHTTP(w, r) + + Expect(w.Code).To(Equal(200)) + }) + }) +}) diff --git a/ui/src/dataProvider.js b/ui/src/dataProvider.js index 49745fbfc..8518bab26 100644 --- a/ui/src/dataProvider.js +++ b/ui/src/dataProvider.js @@ -12,7 +12,7 @@ const httpClient = (url, options = {}) => { } const token = localStorage.getItem('token') if (token) { - options.headers.set('Authorization', `Bearer ${token}`) + options.headers.set('X-ND-Authorization', `Bearer ${token}`) } return fetchUtils.fetchJson(url, options).then((response) => { const token = response.headers.get('authorization')