diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2022-10-17 22:31:23 +1100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-10-17 22:31:23 +1100 |
| commit | 84cb457cb0496cb8d971cafd4b34b3bb9c863a22 (patch) | |
| tree | 08a9379a6756fcf614dc8f1f268c1586bc11d6a2 /internal/authentication/file_user_provider.go | |
| parent | 3a70f6739b82a523fc8112a29ac156446253fc58 (diff) | |
feat(authentication): file provider hot reload (#4188)
This adds hot reloading to the file auth provider.
Diffstat (limited to 'internal/authentication/file_user_provider.go')
| -rw-r--r-- | internal/authentication/file_user_provider.go | 51 |
1 files changed, 47 insertions, 4 deletions
diff --git a/internal/authentication/file_user_provider.go b/internal/authentication/file_user_provider.go index 6c81139e1..5730470b3 100644 --- a/internal/authentication/file_user_provider.go +++ b/internal/authentication/file_user_provider.go @@ -2,8 +2,11 @@ package authentication import ( _ "embed" // Embed users_database.template.yml. + "errors" "fmt" "os" + "sync" + "time" "github.com/go-crypt/crypt" @@ -13,18 +16,48 @@ import ( // FileUserProvider is a provider reading details from a file. type FileUserProvider struct { - config *schema.FileAuthenticationBackend - hash crypt.Hash - database *FileUserDatabase + config *schema.FileAuthenticationBackend + hash crypt.Hash + database *FileUserDatabase + mutex *sync.Mutex + timeoutReload time.Time } // NewFileUserProvider creates a new instance of FileUserProvider. func NewFileUserProvider(config *schema.FileAuthenticationBackend) (provider *FileUserProvider) { return &FileUserProvider{ - config: config, + config: config, + mutex: &sync.Mutex{}, + timeoutReload: time.Now().Add(-1 * time.Second), } } +// Reload the database. +func (p *FileUserProvider) Reload() (reloaded bool, err error) { + now := time.Now() + + p.mutex.Lock() + + defer p.mutex.Unlock() + + if now.Before(p.timeoutReload) { + return false, nil + } + + switch err = p.database.Load(); { + case err == nil: + p.setTimeoutReload(now) + case errors.Is(err, ErrNoContent): + return false, nil + default: + return false, fmt.Errorf("failed to reload: %w", err) + } + + p.setTimeoutReload(now) + + return true, nil +} + // CheckUserPassword checks if provided password matches for the given user. func (p *FileUserProvider) CheckUserPassword(username string, password string) (match bool, err error) { var details DatabaseUserDetails @@ -73,6 +106,12 @@ func (p *FileUserProvider) UpdatePassword(username string, newPassword string) ( p.database.SetUserDetails(details.Username, &details) + p.mutex.Lock() + + p.setTimeoutReload(time.Now()) + + p.mutex.Unlock() + if err = p.database.Save(); err != nil { return err } @@ -101,6 +140,10 @@ func (p *FileUserProvider) StartupCheck() (err error) { return nil } +func (p *FileUserProvider) setTimeoutReload(now time.Time) { + p.timeoutReload = now.Add(time.Second / 2) +} + // NewFileCryptoHashFromConfig returns a crypt.Hash given a valid configuration. func NewFileCryptoHashFromConfig(config schema.Password) (hash crypt.Hash, err error) { switch config.Algorithm { |
