mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-14 21:37:09 +02:00
fix(jellyfin): reject empty login password, wire ServerName, add negative auth tests
This commit is contained in:
parent
535d252d17
commit
92c66178de
4 changed files with 44 additions and 3 deletions
|
|
@ -25,7 +25,7 @@ func (api *Router) authenticateByName(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Navidrome stores recoverable passwords; this mirrors Subsonic's validateCredentials plaintext path.
|
||||
usr, err := api.ds.User(ctx).FindByUsernameWithPassword(body.Username)
|
||||
if err != nil || usr.Password != body.Pw {
|
||||
if body.Pw == "" || err != nil || usr.Password != body.Pw {
|
||||
log.Warn(ctx, "Jellyfin API: invalid login", "username", body.Username, "remoteAddr", r.RemoteAddr)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
|
|
@ -49,6 +49,7 @@ func userToDto(u *model.User, serverName string) *dto.UserDto {
|
|||
return &dto.UserDto{
|
||||
Name: u.UserName,
|
||||
Id: u.ID,
|
||||
ServerName: serverName,
|
||||
HasPassword: true,
|
||||
HasConfiguredPassword: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package jellyfin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
|
@ -20,7 +21,7 @@ var _ = Describe("AuthenticateByName", func() {
|
|||
BeforeEach(func() {
|
||||
ds = &tests.MockDataStore{}
|
||||
auth.Init(ds)
|
||||
ur := ds.User(nil).(*tests.MockedUserRepo)
|
||||
ur := ds.User(context.Background()).(*tests.MockedUserRepo)
|
||||
Expect(ur.Put(&model.User{ID: "u1", UserName: "alice", NewPassword: "secret"})).To(Succeed())
|
||||
api = &Router{ds: ds}
|
||||
})
|
||||
|
|
@ -48,4 +49,15 @@ var _ = Describe("AuthenticateByName", func() {
|
|||
api.authenticateByName(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||||
})
|
||||
|
||||
It("rejects an empty password even for a user with an empty stored password with 401", func() {
|
||||
ur := ds.User(context.Background()).(*tests.MockedUserRepo)
|
||||
Expect(ur.Put(&model.User{ID: "e", UserName: "empty", NewPassword: ""})).To(Succeed())
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/Users/AuthenticateByName",
|
||||
strings.NewReader(`{"Username":"empty","Pw":""}`))
|
||||
api.authenticateByName(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ type QueryResult struct {
|
|||
type UserDto struct {
|
||||
Name string `json:"Name"`
|
||||
ServerId string `json:"ServerId,omitempty"`
|
||||
ServerName string `json:"ServerName,omitempty"`
|
||||
Id string `json:"Id"`
|
||||
HasPassword bool `json:"HasPassword"`
|
||||
HasConfiguredPassword bool `json:"HasConfiguredPassword"`
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package jellyfin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ var _ = Describe("authenticate middleware", func() {
|
|||
BeforeEach(func() {
|
||||
ds = &tests.MockDataStore{}
|
||||
auth.Init(ds)
|
||||
ur := ds.User(nil).(*tests.MockedUserRepo)
|
||||
ur := ds.User(context.Background()).(*tests.MockedUserRepo)
|
||||
Expect(ur.Put(&model.User{ID: "u1", UserName: "alice", NewPassword: "secret"})).To(Succeed())
|
||||
api = &Router{ds: ds}
|
||||
})
|
||||
|
|
@ -50,4 +51,30 @@ var _ = Describe("authenticate middleware", func() {
|
|||
api.authenticate(next).ServeHTTP(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||||
})
|
||||
|
||||
It("rejects a garbage token with 401 and does not call next", func() {
|
||||
nextCalled := false
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
nextCalled = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/Items", nil)
|
||||
r.Header.Set("X-Emby-Token", "not-a-jwt")
|
||||
api.authenticate(next).ServeHTTP(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||||
Expect(nextCalled).To(BeFalse())
|
||||
})
|
||||
|
||||
It("rejects a valid token whose subject user does not exist with 401", func() {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
t, err := auth.CreateToken(&model.User{ID: "x", UserName: "ghost"})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/Items", nil)
|
||||
r.Header.Set("X-Emby-Token", t)
|
||||
api.authenticate(next).ServeHTTP(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue