feat(authentication): file case-insensitive and email search (#4194)
This allows both case-insensitive and email searching for the file auth provider. Closes #3383pull/4192/head^2
parent
d610874be4
commit
a0b2e78e5d
|
@ -392,6 +392,10 @@ authentication_backend:
|
||||||
##
|
##
|
||||||
# file:
|
# file:
|
||||||
# path: /config/users_database.yml
|
# path: /config/users_database.yml
|
||||||
|
# watch: false
|
||||||
|
# search:
|
||||||
|
# email: false
|
||||||
|
# case_insensitive: false
|
||||||
# password:
|
# password:
|
||||||
# algorithm: argon2
|
# algorithm: argon2
|
||||||
# argon2:
|
# argon2:
|
||||||
|
|
|
@ -21,6 +21,9 @@ authentication_backend:
|
||||||
file:
|
file:
|
||||||
path: /config/users.yml
|
path: /config/users.yml
|
||||||
watch: false
|
watch: false
|
||||||
|
search:
|
||||||
|
email: false
|
||||||
|
case_insensitive: false
|
||||||
password:
|
password:
|
||||||
algorithm: argon2
|
algorithm: argon2
|
||||||
argon2:
|
argon2:
|
||||||
|
@ -65,6 +68,30 @@ The path to the file with the user details list. Supported file types are:
|
||||||
|
|
||||||
Enables reloading the database by watching it for changes.
|
Enables reloading the database by watching it for changes.
|
||||||
|
|
||||||
|
### search
|
||||||
|
|
||||||
|
Username searching functionality options.
|
||||||
|
|
||||||
|
*__Important Note:__ This functionality is experimental.*
|
||||||
|
|
||||||
|
#### email
|
||||||
|
|
||||||
|
{{< confkey type="boolean" default="false" required="no" >}}
|
||||||
|
|
||||||
|
Allows users to login using their email address. If enabled two users must not have the same emails and their usernames
|
||||||
|
must not be an email.
|
||||||
|
|
||||||
|
*__Note:__ Emails are always checked using case-insensitive lookup.*
|
||||||
|
|
||||||
|
#### case_insensitive
|
||||||
|
|
||||||
|
{{< confkey type="boolean" default="false" required="no" >}}
|
||||||
|
|
||||||
|
Enabling this search option allows users to login with their username regardless of case. If enabled users must only
|
||||||
|
have lowercase usernames.
|
||||||
|
|
||||||
|
*__Note:__ Emails are always checked using case-insensitive lookup.*
|
||||||
|
|
||||||
## Password Options
|
## Password Options
|
||||||
|
|
||||||
A [reference guide](../../reference/guides/passwords.md) exists specifically for choosing password hashing values. This
|
A [reference guide](../../reference/guides/passwords.md) exists specifically for choosing password hashing values. This
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -131,7 +131,7 @@ func (p *FileUserProvider) StartupCheck() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p.database = NewFileUserDatabase(p.config.Path)
|
p.database = NewFileUserDatabase(p.config.Path, p.config.Search.Email, p.config.Search.CaseInsensitive)
|
||||||
|
|
||||||
if err = p.database.Load(); err != nil {
|
if err = p.database.Load(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -3,19 +3,24 @@ package authentication
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/asaskevich/govalidator"
|
"github.com/asaskevich/govalidator"
|
||||||
"github.com/go-crypt/crypt"
|
"github.com/go-crypt/crypt"
|
||||||
yaml "gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFileUserDatabase creates a new FileUserDatabase.
|
// NewFileUserDatabase creates a new FileUserDatabase.
|
||||||
func NewFileUserDatabase(filePath string) (database *FileUserDatabase) {
|
func NewFileUserDatabase(filePath string, searchEmail, searchCI bool) (database *FileUserDatabase) {
|
||||||
return &FileUserDatabase{
|
return &FileUserDatabase{
|
||||||
RWMutex: &sync.RWMutex{},
|
RWMutex: &sync.RWMutex{},
|
||||||
Path: filePath,
|
Path: filePath,
|
||||||
Users: map[string]DatabaseUserDetails{},
|
Users: map[string]DatabaseUserDetails{},
|
||||||
|
Emails: map[string]string{},
|
||||||
|
Aliases: map[string]string{},
|
||||||
|
SearchEmail: searchEmail,
|
||||||
|
SearchCI: searchCI,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +28,13 @@ func NewFileUserDatabase(filePath string) (database *FileUserDatabase) {
|
||||||
type FileUserDatabase struct {
|
type FileUserDatabase struct {
|
||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
|
|
||||||
Path string
|
Path string
|
||||||
Users map[string]DatabaseUserDetails
|
Users map[string]DatabaseUserDetails
|
||||||
|
Emails map[string]string
|
||||||
|
Aliases map[string]string
|
||||||
|
|
||||||
|
SearchEmail bool
|
||||||
|
SearchCI bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the database to disk.
|
// Save the database to disk.
|
||||||
|
@ -56,6 +66,79 @@ func (m *FileUserDatabase) Load() (err error) {
|
||||||
return fmt.Errorf("error decoding the authentication database: %w", err)
|
return fmt.Errorf("error decoding the authentication database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return m.LoadAliases()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAliases performs the loading of alias information from the database.
|
||||||
|
func (m *FileUserDatabase) LoadAliases() (err error) {
|
||||||
|
if m.SearchEmail || m.SearchCI {
|
||||||
|
for k, user := range m.Users {
|
||||||
|
if m.SearchEmail && user.Email != "" {
|
||||||
|
if err = m.loadAliasEmail(k, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.SearchCI {
|
||||||
|
if err = m.loadAlias(k); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileUserDatabase) loadAlias(k string) (err error) {
|
||||||
|
u := strings.ToLower(k)
|
||||||
|
|
||||||
|
if u != k {
|
||||||
|
return fmt.Errorf("error loading authentication database: username '%s' is not lowercase but this is required when case-insensitive search is enabled", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for username, details := range m.Users {
|
||||||
|
if k == username {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(u, details.Email) {
|
||||||
|
return fmt.Errorf("error loading authentication database: username '%s' is configured as an email for user with username '%s' which isn't allowed when case-insensitive search is enabled", u, username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Aliases[u] = k
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FileUserDatabase) loadAliasEmail(k string, user DatabaseUserDetails) (err error) {
|
||||||
|
e := strings.ToLower(user.Email)
|
||||||
|
|
||||||
|
var duplicates []string
|
||||||
|
|
||||||
|
for username, details := range m.Users {
|
||||||
|
if k == username {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.EqualFold(e, details.Email) {
|
||||||
|
duplicates = append(duplicates, username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(duplicates) != 0 {
|
||||||
|
duplicates = append(duplicates, k)
|
||||||
|
|
||||||
|
return fmt.Errorf("error loading authentication database: email '%s' is configured for for more than one user (users are '%s') which isn't allowed when email search is enabled", e, strings.Join(duplicates, "', '"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := m.Users[e]; ok && k != e {
|
||||||
|
return fmt.Errorf("error loading authentication database: email '%s' is also a username which isn't allowed when email search is enabled", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Emails[e] = k
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +149,20 @@ func (m *FileUserDatabase) GetUserDetails(username string) (user DatabaseUserDet
|
||||||
|
|
||||||
defer m.RUnlock()
|
defer m.RUnlock()
|
||||||
|
|
||||||
|
u := strings.ToLower(username)
|
||||||
|
|
||||||
|
if m.SearchEmail {
|
||||||
|
if key, ok := m.Emails[u]; ok {
|
||||||
|
return m.Users[key], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.SearchCI {
|
||||||
|
if key, ok := m.Aliases[u]; ok {
|
||||||
|
return m.Users[key], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if details, ok := m.Users[username]; ok {
|
if details, ok := m.Users[username]; ok {
|
||||||
return details, nil
|
return details, nil
|
||||||
}
|
}
|
||||||
|
@ -145,10 +242,6 @@ func (m *DatabaseModel) ReadToFileUserDatabase(db *FileUserDatabase) (err error)
|
||||||
var udm *DatabaseUserDetails
|
var udm *DatabaseUserDetails
|
||||||
|
|
||||||
for user, details := range m.Users {
|
for user, details := range m.Users {
|
||||||
if details.Disabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if udm, err = details.ToDatabaseUserDetailsModel(user); err != nil {
|
if udm, err = details.ToDatabaseUserDetailsModel(user); err != nil {
|
||||||
return fmt.Errorf("failed to parse hash for user '%s': %w", user, err)
|
return fmt.Errorf("failed to parse hash for user '%s': %w", user, err)
|
||||||
}
|
}
|
||||||
|
@ -224,6 +317,7 @@ func (m UserDetailsModel) ToDatabaseUserDetailsModel(username string) (model *Da
|
||||||
return &DatabaseUserDetails{
|
return &DatabaseUserDetails{
|
||||||
Username: username,
|
Username: username,
|
||||||
Digest: d,
|
Digest: d,
|
||||||
|
Disabled: m.Disabled,
|
||||||
DisplayName: m.DisplayName,
|
DisplayName: m.DisplayName,
|
||||||
Email: m.Email,
|
Email: m.Email,
|
||||||
Groups: m.Groups,
|
Groups: m.Groups,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package authentication
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -304,6 +305,137 @@ func TestShouldSupportHashPasswordWithoutCRYPT(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldNotAllowLoginOfDisabledUsers(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContent, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
|
ok, err := provider.CheckUserPassword("dis", "password")
|
||||||
|
|
||||||
|
assert.False(t, ok)
|
||||||
|
assert.EqualError(t, err, "user not found")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldErrorOnInvalidCaseSensitiveFile(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContentInvalidSearchCaseInsenstive, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
config.Search.Email = false
|
||||||
|
config.Search.CaseInsensitive = true
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.EqualError(t, provider.StartupCheck(), "error loading authentication database: username 'JOHN' is not lowercase but this is required when case-insensitive search is enabled")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldErrorOnDuplicateEmail(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContentInvalidSearchEmail, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
config.Search.Email = true
|
||||||
|
config.Search.CaseInsensitive = false
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
err := provider.StartupCheck()
|
||||||
|
assert.Regexp(t, regexp.MustCompile(`^error loading authentication database: email 'john.doe@authelia.com' is configured for for more than one user \(users are '(harry|john)', '(harry|john)'\) which isn't allowed when email search is enabled$`), err.Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldNotErrorOnEmailAsUsername(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContentSearchEmailAsUsername, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
config.Search.Email = true
|
||||||
|
config.Search.CaseInsensitive = false
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmail(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsername, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
config.Search.Email = true
|
||||||
|
config.Search.CaseInsensitive = false
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.EqualError(t, provider.StartupCheck(), "error loading authentication database: email 'john.doe@authelia.com' is also a username which isn't allowed when email search is enabled")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldErrorOnEmailAsUsernameWithDuplicateEmailCase(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContentInvalidSearchEmailAsUsernameCase, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
config.Search.Email = false
|
||||||
|
config.Search.CaseInsensitive = true
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.EqualError(t, provider.StartupCheck(), "error loading authentication database: username 'john.doe@authelia.com' is configured as an email for user with username 'john' which isn't allowed when case-insensitive search is enabled")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldAllowLookupByEmail(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContent, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
config.Search.Email = true
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
|
ok, err := provider.CheckUserPassword("john", "password")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
ok, err = provider.CheckUserPassword("john.doe@authelia.com", "password")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
ok, err = provider.CheckUserPassword("JOHN.doe@authelia.com", "password")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldAllowLookupCI(t *testing.T) {
|
||||||
|
WithDatabase(UserDatabaseContent, func(path string) {
|
||||||
|
config := DefaultFileAuthenticationBackendConfiguration
|
||||||
|
config.Path = path
|
||||||
|
config.Search.CaseInsensitive = true
|
||||||
|
|
||||||
|
provider := NewFileUserProvider(&config)
|
||||||
|
|
||||||
|
assert.NoError(t, provider.StartupCheck())
|
||||||
|
|
||||||
|
ok, err := provider.CheckUserPassword("john", "password")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
ok, err = provider.CheckUserPassword("John", "password")
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, ok)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackend{
|
DefaultFileAuthenticationBackendConfiguration = schema.FileAuthenticationBackend{
|
||||||
Path: "",
|
Path: "",
|
||||||
|
@ -343,7 +475,99 @@ users:
|
||||||
enumeration:
|
enumeration:
|
||||||
displayname: "Enumeration"
|
displayname: "Enumeration"
|
||||||
password: "$argon2id$v=19$m=131072,p=8$BpLnfgDsc2WD8F2q$O126GHPeZ5fwj7OLSs7PndXsTbje76R+QW9/EGfhkJg"
|
password: "$argon2id$v=19$m=131072,p=8$BpLnfgDsc2WD8F2q$O126GHPeZ5fwj7OLSs7PndXsTbje76R+QW9/EGfhkJg"
|
||||||
email: james.dean@authelia.com
|
email: enumeration@authelia.com
|
||||||
|
|
||||||
|
|
||||||
|
dis:
|
||||||
|
displayname: "Enumeration"
|
||||||
|
password: "$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
|
||||||
|
disabled: true
|
||||||
|
email: disabled@authelia.com
|
||||||
|
`)
|
||||||
|
|
||||||
|
var UserDatabaseContentInvalidSearchCaseInsenstive = []byte(`
|
||||||
|
users:
|
||||||
|
john:
|
||||||
|
displayname: "John Doe"
|
||||||
|
password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups:
|
||||||
|
- admins
|
||||||
|
- dev
|
||||||
|
|
||||||
|
JOHN:
|
||||||
|
displayname: "Harry Potter"
|
||||||
|
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: harry.potter@authelia.com
|
||||||
|
groups: []
|
||||||
|
`)
|
||||||
|
|
||||||
|
var UserDatabaseContentInvalidSearchEmail = []byte(`
|
||||||
|
users:
|
||||||
|
john:
|
||||||
|
displayname: "John Doe"
|
||||||
|
password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups:
|
||||||
|
- admins
|
||||||
|
- dev
|
||||||
|
|
||||||
|
harry:
|
||||||
|
displayname: "Harry Potter"
|
||||||
|
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups: []
|
||||||
|
`)
|
||||||
|
|
||||||
|
var UserDatabaseContentSearchEmailAsUsername = []byte(`
|
||||||
|
users:
|
||||||
|
john.doe@authelia.com:
|
||||||
|
displayname: "John Doe"
|
||||||
|
password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups:
|
||||||
|
- admins
|
||||||
|
- dev
|
||||||
|
|
||||||
|
harry:
|
||||||
|
displayname: "Harry Potter"
|
||||||
|
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: harry.potter@authelia.com
|
||||||
|
groups: []
|
||||||
|
`)
|
||||||
|
|
||||||
|
var UserDatabaseContentInvalidSearchEmailAsUsername = []byte(`
|
||||||
|
users:
|
||||||
|
john.doe@authelia.com:
|
||||||
|
displayname: "John Doe"
|
||||||
|
password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
|
||||||
|
email: john@authelia.com
|
||||||
|
groups:
|
||||||
|
- admins
|
||||||
|
- dev
|
||||||
|
|
||||||
|
harry:
|
||||||
|
displayname: "Harry Potter"
|
||||||
|
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups: []
|
||||||
|
`)
|
||||||
|
|
||||||
|
var UserDatabaseContentInvalidSearchEmailAsUsernameCase = []byte(`
|
||||||
|
users:
|
||||||
|
john.doe@authelia.com:
|
||||||
|
displayname: "John Doe"
|
||||||
|
password: "{CRYPT}$argon2id$v=19$m=65536,t=3,p=2$BpLnfgDsc2WD8F2q$o/vzA4myCqZZ36bUGsDY//8mKUYNZZaR0t4MFFSs+iM"
|
||||||
|
email: JOHN@authelia.com
|
||||||
|
groups:
|
||||||
|
- admins
|
||||||
|
- dev
|
||||||
|
|
||||||
|
john:
|
||||||
|
displayname: "John Potter"
|
||||||
|
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
|
||||||
|
email: john.doe@authelia.com
|
||||||
|
groups: []
|
||||||
`)
|
`)
|
||||||
|
|
||||||
var MalformedUserDatabaseContent = []byte(`
|
var MalformedUserDatabaseContent = []byte(`
|
||||||
|
|
|
@ -392,6 +392,10 @@ authentication_backend:
|
||||||
##
|
##
|
||||||
# file:
|
# file:
|
||||||
# path: /config/users_database.yml
|
# path: /config/users_database.yml
|
||||||
|
# watch: false
|
||||||
|
# search:
|
||||||
|
# email: false
|
||||||
|
# case_insensitive: false
|
||||||
# password:
|
# password:
|
||||||
# algorithm: argon2
|
# algorithm: argon2
|
||||||
# argon2:
|
# argon2:
|
||||||
|
|
|
@ -26,6 +26,14 @@ type FileAuthenticationBackend struct {
|
||||||
Path string `koanf:"path"`
|
Path string `koanf:"path"`
|
||||||
Watch bool `koanf:"watch"`
|
Watch bool `koanf:"watch"`
|
||||||
Password Password `koanf:"password"`
|
Password Password `koanf:"password"`
|
||||||
|
|
||||||
|
Search FileSearchAuthenticationBackend `koanf:"search"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSearchAuthenticationBackend represents the configuration related to file-based backend searching.
|
||||||
|
type FileSearchAuthenticationBackend struct {
|
||||||
|
Email bool `koanf:"email"`
|
||||||
|
CaseInsensitive bool `koanf:"case_insensitive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password represents the configuration related to password hashing.
|
// Password represents the configuration related to password hashing.
|
||||||
|
|
|
@ -76,6 +76,8 @@ var Keys = []string{
|
||||||
"authentication_backend.file.password.parallelism",
|
"authentication_backend.file.password.parallelism",
|
||||||
"authentication_backend.file.password.key_length",
|
"authentication_backend.file.password.key_length",
|
||||||
"authentication_backend.file.password.salt_length",
|
"authentication_backend.file.password.salt_length",
|
||||||
|
"authentication_backend.file.search.email",
|
||||||
|
"authentication_backend.file.search.case_insensitive",
|
||||||
"authentication_backend.ldap.implementation",
|
"authentication_backend.ldap.implementation",
|
||||||
"authentication_backend.ldap.url",
|
"authentication_backend.ldap.url",
|
||||||
"authentication_backend.ldap.timeout",
|
"authentication_backend.ldap.timeout",
|
||||||
|
|
Loading…
Reference in New Issue