Fix DevAutoCreateAdminPassword

This commit is contained in:
Deluan 2021-05-01 18:03:45 -04:00
parent 88105d5c30
commit e3fe8399c8
6 changed files with 93 additions and 30 deletions

View file

@ -7,13 +7,17 @@ type User struct {
UserName string `json:"userName"` UserName string `json:"userName"`
Name string `json:"name"` Name string `json:"name"`
Email string `json:"email"` Email string `json:"email"`
Password string `json:"-"`
IsAdmin bool `json:"isAdmin"` IsAdmin bool `json:"isAdmin"`
LastLoginAt *time.Time `json:"lastLoginAt"` LastLoginAt *time.Time `json:"lastLoginAt"`
LastAccessAt *time.Time `json:"lastAccessAt"` LastAccessAt *time.Time `json:"lastAccessAt"`
CreatedAt time.Time `json:"createdAt"` CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
NewPassword string `json:"password,omitempty"`
// This is only available on the backend, and it is never sent over the wire
Password string `json:"-"`
// This is used to set or change a password when calling Put. If it is empty, the password is not changed.
// It is received from the UI with the name "password"
NewPassword string `json:"password,omitempty"`
} }
type Users []User type Users []User

View file

@ -19,12 +19,12 @@ var _ = Describe("UserRepository", func() {
Describe("Put/Get/FindByUsername", func() { Describe("Put/Get/FindByUsername", func() {
usr := model.User{ usr := model.User{
ID: "123", ID: "123",
UserName: "AdMiN", UserName: "AdMiN",
Name: "Admin", Name: "Admin",
Email: "admin@admin.com", Email: "admin@admin.com",
Password: "wordpass", NewPassword: "wordpass",
IsAdmin: true, IsAdmin: true,
} }
It("saves the user to the DB", func() { It("saves the user to the DB", func() {
Expect(repo.Put(&usr)).To(BeNil()) Expect(repo.Put(&usr)).To(BeNil())
@ -33,6 +33,7 @@ var _ = Describe("UserRepository", func() {
actual, err := repo.Get("123") actual, err := repo.Get("123")
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(actual.Name).To(Equal("Admin")) Expect(actual.Name).To(Equal("Admin"))
Expect(actual.Password).To(Equal("wordpass"))
}) })
It("find the user by case-insensitive username", func() { It("find the user by case-insensitive username", func() {
actual, err := repo.FindByUsername("aDmIn") actual, err := repo.FindByUsername("aDmIn")

View file

@ -47,12 +47,12 @@ func createInitialAdminUser(ds model.DataStore, initialPassword string) error {
log.Warn("Creating initial admin user. This should only be used for development purposes!!", log.Warn("Creating initial admin user. This should only be used for development purposes!!",
"user", consts.DevInitialUserName, "password", initialPassword, "id", id) "user", consts.DevInitialUserName, "password", initialPassword, "id", id)
initialUser := model.User{ initialUser := model.User{
ID: id, ID: id,
UserName: consts.DevInitialUserName, UserName: consts.DevInitialUserName,
Name: consts.DevInitialName, Name: consts.DevInitialName,
Email: "", Email: "",
Password: initialPassword, NewPassword: initialPassword,
IsAdmin: true, IsAdmin: true,
} }
err := users.Put(&initialUser) err := users.Put(&initialUser)
if err != nil { if err != nil {

View file

@ -0,0 +1,36 @@
package server
import (
"context"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("initial_setup", func() {
var ds model.DataStore
BeforeEach(func() {
ds = &tests.MockDataStore{}
})
Describe("createInitialAdminUser", func() {
It("creates a new admin user with specified password if User table is empty", func() {
Expect(createInitialAdminUser(ds, "pass123")).To(BeNil())
ur := ds.User(context.TODO())
admin, err := ur.FindByUsername("admin")
Expect(err).To(BeNil())
Expect(admin.Password).To(Equal("pass123"))
})
It("does not create a new admin user if User table is not empty", func() {
Expect(createInitialAdminUser(ds, "first")).To(BeNil())
ur := ds.User(context.TODO())
Expect(ur.CountAll()).To(Equal(int64(1)))
Expect(createInitialAdminUser(ds, "second")).To(BeNil())
Expect(ur.CountAll()).To(Equal(int64(1)))
})
})
})

View file

@ -36,10 +36,12 @@ func newPostRequest(queryParam string, formFields ...string) *http.Request {
var _ = Describe("Middlewares", func() { var _ = Describe("Middlewares", func() {
var next *mockHandler var next *mockHandler
var w *httptest.ResponseRecorder var w *httptest.ResponseRecorder
var ds model.DataStore
BeforeEach(func() { BeforeEach(func() {
next = &mockHandler{} next = &mockHandler{}
w = httptest.NewRecorder() w = httptest.NewRecorder()
ds = &tests.MockDataStore{}
}) })
Describe("ParsePostForm", func() { Describe("ParsePostForm", func() {
@ -115,11 +117,13 @@ var _ = Describe("Middlewares", func() {
}) })
Describe("Authenticate", func() { Describe("Authenticate", func() {
var ds model.DataStore
BeforeEach(func() { BeforeEach(func() {
ds = &tests.MockDataStore{} ur := ds.User(context.TODO())
_ = ur.Put(&model.User{
UserName: "admin",
NewPassword: "wordpass",
})
}) })
It("passes authentication with correct credentials", func() { It("passes authentication with correct credentials", func() {
r := newGetRequest("u=admin", "p=wordpass") r := newGetRequest("u=admin", "p=wordpass")
cp := authenticate(ds)(next) cp := authenticate(ds)(next)
@ -220,16 +224,18 @@ var _ = Describe("Middlewares", func() {
}) })
Describe("validateUser", func() { Describe("validateUser", func() {
var ds model.DataStore
BeforeEach(func() { BeforeEach(func() {
ds = &tests.MockDataStore{} ur := ds.User(context.TODO())
_ = ur.Put(&model.User{
UserName: "admin",
NewPassword: "wordpass",
})
}) })
Context("Plaintext password", func() { Context("Plaintext password", func() {
It("authenticates with plaintext password ", func() { It("authenticates with plaintext password ", func() {
usr, err := validateUser(context.TODO(), ds, "admin", "wordpass", "", "", "") usr, err := validateUser(context.TODO(), ds, "admin", "wordpass", "", "", "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(usr).To(Equal(&model.User{UserName: "admin", Password: "wordpass"})) Expect(usr.UserName).To(Equal("admin"))
}) })
It("fails authentication with wrong password", func() { It("fails authentication with wrong password", func() {
@ -242,7 +248,7 @@ var _ = Describe("Middlewares", func() {
It("authenticates with simple encoded password ", func() { It("authenticates with simple encoded password ", func() {
usr, err := validateUser(context.TODO(), ds, "admin", "enc:776f726470617373", "", "", "") usr, err := validateUser(context.TODO(), ds, "admin", "enc:776f726470617373", "", "", "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(usr).To(Equal(&model.User{UserName: "admin", Password: "wordpass"})) Expect(usr.UserName).To(Equal("admin"))
}) })
}) })
@ -250,7 +256,7 @@ var _ = Describe("Middlewares", func() {
It("authenticates with token based authentication", func() { It("authenticates with token based authentication", func() {
usr, err := validateUser(context.TODO(), ds, "admin", "", "23b342970e25c7928831c3317edd0b67", "retnlmjetrymazgkt", "") usr, err := validateUser(context.TODO(), ds, "admin", "", "23b342970e25c7928831c3317edd0b67", "retnlmjetrymazgkt", "")
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(usr).To(Equal(&model.User{UserName: "admin", Password: "wordpass"})) Expect(usr.UserName).To(Equal("admin"))
}) })
It("fails if salt is missing", func() { It("fails if salt is missing", func() {
@ -273,7 +279,7 @@ var _ = Describe("Middlewares", func() {
usr, err := validateUser(context.TODO(), ds, "admin", "", "", "", validToken) usr, err := validateUser(context.TODO(), ds, "admin", "", "", "", validToken)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(usr).To(Equal(&model.User{UserName: "admin", Password: "wordpass"})) Expect(usr.UserName).To(Equal("admin"))
}) })
It("fails if JWT token is invalid", func() { It("fails if JWT token is invalid", func() {

View file

@ -2,6 +2,8 @@ package tests
import ( import (
"context" "context"
"encoding/base64"
"strings"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
) )
@ -95,15 +97,29 @@ func (db *MockDataStore) GC(ctx context.Context, rootFolder string) error {
type mockedUserRepo struct { type mockedUserRepo struct {
model.UserRepository model.UserRepository
data map[string]*model.User
}
func (u *mockedUserRepo) CountAll(qo ...model.QueryOptions) (int64, error) {
return int64(len(u.data)), nil
}
func (u *mockedUserRepo) Put(usr *model.User) error {
if u.data == nil {
u.data = make(map[string]*model.User)
}
if usr.ID == "" {
usr.ID = base64.StdEncoding.EncodeToString([]byte(usr.UserName))
}
usr.Password = usr.NewPassword
u.data[strings.ToLower(usr.UserName)] = usr
return nil
} }
func (u *mockedUserRepo) FindByUsername(username string) (*model.User, error) { func (u *mockedUserRepo) FindByUsername(username string) (*model.User, error) {
if username != "admin" { usr, ok := u.data[strings.ToLower(username)]
if !ok {
return nil, model.ErrNotFound return nil, model.ErrNotFound
} }
return &model.User{UserName: "admin", Password: "wordpass"}, nil return usr, nil
}
func (u *mockedUserRepo) UpdateLastAccessAt(id string) error {
return nil
} }