mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-03 04:27:37 +03:00
Create accounts automatically when authenticating from HTTP header (#2087)
* Create accounts automatically when authenticating from HTTP header * Disable password check when header auth is enabled * Formatting * Password change is valid when no password (old or new) is provided * Test suite runs with header auth disabled (mock config) Prevents nil pointer access (panic) while testing password validating logic * Use a constant prefix for autogenerated passwords (header auth case) * Add tests * Add context to log messages Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
9721ef8974
commit
1e24809ed6
4 changed files with 60 additions and 7 deletions
|
@ -25,6 +25,7 @@ const (
|
|||
// Never ever change this! Or it will break all Navidrome installations that don't set the config option
|
||||
DefaultEncryptionKey = "just for obfuscation"
|
||||
PasswordsEncryptedKey = "PasswordsEncryptedKey"
|
||||
PasswordAutogenPrefix = "__NAVIDROME_AUTOGEN__" //nolint:gosec
|
||||
|
||||
DevInitialUserName = "admin"
|
||||
DevInitialName = "Dev Admin"
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -206,12 +207,16 @@ func validatePasswordChange(newUser *model.User, logged *model.User) error {
|
|||
if logged.IsAdmin && newUser.ID != logged.ID {
|
||||
return nil
|
||||
}
|
||||
if newUser.NewPassword != "" && newUser.CurrentPassword == "" {
|
||||
err.Errors["currentPassword"] = "ra.validation.required"
|
||||
if newUser.NewPassword == "" {
|
||||
if newUser.CurrentPassword == "" {
|
||||
return nil
|
||||
}
|
||||
err.Errors["password"] = "ra.validation.required"
|
||||
}
|
||||
if newUser.CurrentPassword != "" {
|
||||
if newUser.NewPassword == "" {
|
||||
err.Errors["password"] = "ra.validation.required"
|
||||
|
||||
if !strings.HasPrefix(logged.Password, consts.PasswordAutogenPrefix) {
|
||||
if newUser.CurrentPassword == "" {
|
||||
err.Errors["currentPassword"] = "ra.validation.required"
|
||||
}
|
||||
if newUser.CurrentPassword != logged.Password {
|
||||
err.Errors["currentPassword"] = "ra.validation.passwordDoesNotMatch"
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
"github.com/deluan/rest"
|
||||
"github.com/google/uuid"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
|
@ -81,6 +83,34 @@ var _ = Describe("UserRepository", func() {
|
|||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
Context("Autogenerated password (used with Reverse Proxy Authentication)", func() {
|
||||
var user model.User
|
||||
BeforeEach(func() {
|
||||
loggedUser.IsAdmin = false
|
||||
loggedUser.Password = consts.PasswordAutogenPrefix + uuid.NewString()
|
||||
})
|
||||
It("does nothing if passwords are not specified", func() {
|
||||
user = *loggedUser
|
||||
err := validatePasswordChange(&user, loggedUser)
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
It("does not requires currentPassword for regular user", func() {
|
||||
user = *loggedUser
|
||||
user.CurrentPassword = ""
|
||||
user.NewPassword = "new"
|
||||
err := validatePasswordChange(&user, loggedUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
It("does not requires currentPassword for admin", func() {
|
||||
loggedUser.IsAdmin = true
|
||||
user = *loggedUser
|
||||
user.CurrentPassword = ""
|
||||
user.NewPassword = "new"
|
||||
err := validatePasswordChange(&user, loggedUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Context("Logged User is admin", func() {
|
||||
BeforeEach(func() {
|
||||
loggedUser.IsAdmin = true
|
||||
|
|
|
@ -285,8 +285,25 @@ func handleLoginFromHeaders(ds model.DataStore, r *http.Request) map[string]inte
|
|||
userRepo := ds.User(r.Context())
|
||||
user, err := userRepo.FindByUsernameWithPassword(username)
|
||||
if user == nil || err != nil {
|
||||
log.Warn(r, "User passed in header not found", "user", username)
|
||||
return nil
|
||||
log.Info(r, "User passed in header not found", "user", username)
|
||||
newUser := model.User{
|
||||
ID: uuid.NewString(),
|
||||
UserName: username,
|
||||
Name: username,
|
||||
Email: "",
|
||||
NewPassword: consts.PasswordAutogenPrefix + uuid.NewString(),
|
||||
IsAdmin: false,
|
||||
}
|
||||
err := userRepo.Put(&newUser)
|
||||
if err != nil {
|
||||
log.Error(r, "Could not create new user", "user", username, err)
|
||||
return nil
|
||||
}
|
||||
user, err = userRepo.FindByUsernameWithPassword(username)
|
||||
if user == nil || err != nil {
|
||||
log.Error(r, "Created user but failed to fetch it", "user", username)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err = userRepo.UpdateLastLoginAt(user.ID)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue