diff --git a/internal/authentication/file_user_provider.go b/internal/authentication/file_user_provider.go index e8511fe32..e8969e900 100644 --- a/internal/authentication/file_user_provider.go +++ b/internal/authentication/file_user_provider.go @@ -35,8 +35,15 @@ func NewFileUserProvider(filepath string) *FileUserProvider { database, err := readDatabase(filepath) if err != nil { // Panic since the file does not exist when Authelia is starting. - panic(err) + panic(err.Error()) } + + // Early check whether hashed passwords are correct for all users + err = checkPasswordHashes(database) + if err != nil { + panic(err.Error()) + } + return &FileUserProvider{ path: &filepath, database: database, @@ -44,24 +51,34 @@ func NewFileUserProvider(filepath string) *FileUserProvider { } } +func checkPasswordHashes(database *DatabaseModel) error { + for u, v := range database.Users { + _, err := ParseHash(v.HashedPassword) + if err != nil { + return fmt.Errorf("Unable to parse hash of user %s: %s", u, err) + } + } + return nil +} + func readDatabase(path string) (*DatabaseModel, error) { content, err := ioutil.ReadFile(path) if err != nil { - return nil, err + return nil, fmt.Errorf("Unable to read database from file %s: %s", path, err) } db := DatabaseModel{} err = yaml.Unmarshal(content, &db) if err != nil { - return nil, err + return nil, fmt.Errorf("Unable to parse database: %s", err) } ok, err := govalidator.ValidateStruct(db) if err != nil { - return nil, err + return nil, fmt.Errorf("Invalid schema of database: %s", err) } if !ok { - return nil, fmt.Errorf("The database format is invalid: %s", err.Error()) + return nil, fmt.Errorf("The database format is invalid: %s", err) } return &db, nil } diff --git a/internal/authentication/file_user_provider_test.go b/internal/authentication/file_user_provider_test.go index bb07fc4bf..2e3d03a25 100644 --- a/internal/authentication/file_user_provider_test.go +++ b/internal/authentication/file_user_provider_test.go @@ -84,7 +84,7 @@ func TestShouldUpdatePassword(t *testing.T) { func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) { WithDatabase(MalformedUserDatabaseContent, func(path string) { - assert.Panics(t, func() { + assert.PanicsWithValue(t, "Unable to parse database: yaml: line 4: mapping values are not allowed in this context", func() { NewFileUserProvider(path) }) }) @@ -92,7 +92,15 @@ func TestShouldRaiseWhenLoadingMalformedDatabaseForFirstTime(t *testing.T) { func TestShouldRaiseWhenLoadingDatabaseWithBadSchemaForFirstTime(t *testing.T) { WithDatabase(BadSchemaUserDatabaseContent, func(path string) { - assert.Panics(t, func() { + assert.PanicsWithValue(t, "Invalid schema of database: Users: non zero value required", func() { + NewFileUserProvider(path) + }) + }) +} + +func TestShouldRaiseWhenLoadingDatabaseWithBadHashesForTheFirstTime(t *testing.T) { + WithDatabase(BadHashContent, func(path string) { + assert.PanicsWithValue(t, "Unable to parse hash of user john: Cannot match pattern 'rounds=' to find the number of rounds", func() { NewFileUserProvider(path) }) }) @@ -165,3 +173,16 @@ users: password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" email: james.dean@authelia.com `) + +var BadHashContent = []byte(` +users: + john: + password: "$6$rounds00000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" + email: john.doe@authelia.com + groups: + - admins + - dev + james: + password: "$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/" + email: james.dean@authelia.com +`)