summaryrefslogtreecommitdiff
path: root/internal/authentication/file_user_provider.go
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2022-10-17 22:31:23 +1100
committerGitHub <noreply@github.com>2022-10-17 22:31:23 +1100
commit84cb457cb0496cb8d971cafd4b34b3bb9c863a22 (patch)
tree08a9379a6756fcf614dc8f1f268c1586bc11d6a2 /internal/authentication/file_user_provider.go
parent3a70f6739b82a523fc8112a29ac156446253fc58 (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.go51
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 {