summaryrefslogtreecommitdiff
path: root/internal/authentication
diff options
context:
space:
mode:
Diffstat (limited to 'internal/authentication')
-rw-r--r--internal/authentication/cached.go106
-rw-r--r--internal/authentication/cached_test.go35
2 files changed, 141 insertions, 0 deletions
diff --git a/internal/authentication/cached.go b/internal/authentication/cached.go
new file mode 100644
index 000000000..b84d988bf
--- /dev/null
+++ b/internal/authentication/cached.go
@@ -0,0 +1,106 @@
+package authentication
+
+import (
+ "crypto/hmac"
+ "crypto/rand"
+ "fmt"
+ "hash"
+ "sync"
+ "time"
+)
+
+// NewCredentialCacheHMAC creates a new CredentialCacheHMAC with a given hash.Hash func and lifespan.
+func NewCredentialCacheHMAC(h func() hash.Hash, lifespan time.Duration) *CredentialCacheHMAC {
+ secret := make([]byte, h().BlockSize())
+
+ _, _ = rand.Read(secret)
+
+ return &CredentialCacheHMAC{
+ mu: sync.Mutex{},
+ hash: hmac.New(h, secret),
+ lifespan: lifespan,
+
+ values: map[string]CachedCredential{},
+ }
+}
+
+// CredentialCacheHMAC implements in-memory credential caching using a HMAC function and effective lifespan.
+type CredentialCacheHMAC struct {
+ mu sync.Mutex
+ hash hash.Hash
+
+ lifespan time.Duration
+
+ values map[string]CachedCredential
+}
+
+// Valid checks the cache for results for a given username and password in the cache and returns two booleans. The valid
+// return value is indicative if the credential cache had an exact match, and the ok return value returns true if a
+// current cached value exists within the cache.
+func (c *CredentialCacheHMAC) Valid(username, password string) (valid, ok bool) {
+ c.mu.Lock()
+
+ defer c.mu.Unlock()
+
+ var (
+ entry CachedCredential
+ err error
+ )
+
+ if entry, ok = c.values[username]; ok {
+ if entry.expires.Before(time.Now()) {
+ delete(c.values, username)
+
+ return false, false
+ }
+ }
+
+ var value []byte
+
+ if value, err = c.sum(username, password); err != nil {
+ return false, false
+ }
+
+ valid = hmac.Equal(value, entry.value)
+
+ c.hash.Reset()
+
+ return valid, true
+}
+
+func (c *CredentialCacheHMAC) sum(username, password string) (sum []byte, err error) {
+ defer c.hash.Reset()
+
+ if _, err = c.hash.Write([]byte(password)); err != nil {
+ return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err)
+ }
+
+ if _, err = c.hash.Write([]byte(username)); err != nil {
+ return nil, fmt.Errorf("error occurred calculating cache hmac: %w", err)
+ }
+
+ return c.hash.Sum(nil), nil
+}
+
+// Put a new credential combination into the cache.
+func (c *CredentialCacheHMAC) Put(username, password string) (err error) {
+ c.mu.Lock()
+
+ defer c.mu.Unlock()
+
+ var value []byte
+
+ if value, err = c.sum(username, password); err != nil {
+ return err
+ }
+
+ c.values[username] = CachedCredential{expires: time.Now().Add(c.lifespan), value: value}
+
+ return nil
+}
+
+// CachedCredential is a cached credential which has an expiration and checksum value.
+type CachedCredential struct {
+ expires time.Time
+ value []byte
+}
diff --git a/internal/authentication/cached_test.go b/internal/authentication/cached_test.go
new file mode 100644
index 000000000..a60fc3b99
--- /dev/null
+++ b/internal/authentication/cached_test.go
@@ -0,0 +1,35 @@
+package authentication
+
+import (
+ "crypto/sha256"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewCredentialCacheHMAC(t *testing.T) {
+ cache := NewCredentialCacheHMAC(sha256.New, time.Second*2)
+
+ require.NoError(t, cache.Put("abc", "123"))
+
+ var valid, found bool
+
+ valid, found = cache.Valid("abc", "123")
+
+ assert.True(t, found)
+ assert.True(t, valid)
+
+ valid, found = cache.Valid("abc", "123")
+
+ assert.True(t, found)
+ assert.True(t, valid)
+
+ time.Sleep(time.Second * 2)
+
+ valid, found = cache.Valid("abc", "123")
+
+ assert.False(t, found)
+ assert.False(t, valid)
+}