1
0
Fork 0

Cache hashed passwords

master
Philip O'Toole 2 years ago
parent 2f9a11c308
commit bfcd21b8fd

@ -5,6 +5,7 @@ package auth
import (
"encoding/json"
"io"
"sync"
"golang.org/x/crypto/bcrypt"
)
@ -41,6 +42,43 @@ type BasicAuther interface {
BasicAuth() (string, string, bool)
}
// HashCache store hash values for users. Safe for use from multiple goroutines.
type HashCache struct {
mu sync.RWMutex
m map[string]map[string]bool
}
// NewHashCache returns a instantiated HashCache
func NewHashCache() *HashCache {
return &HashCache{
m: make(map[string]map[string]bool),
}
}
// Check returns whether hash is valid for username.
func (h *HashCache) Check(username, hash string) bool {
h.mu.RLock()
defer h.mu.RUnlock()
m, ok := h.m[username]
if !ok {
return false
}
_, ok = m[hash]
return ok
}
// Store stores the given hash as a valid hash for username.
func (h *HashCache) Store(username, hash string) {
h.mu.Lock()
defer h.mu.Unlock()
_, ok := h.m[username]
if !ok {
h.m[username] = make(map[string]bool)
}
h.m[username][hash] = true
}
// Credential represents authentication and authorization configuration for a single user.
type Credential struct {
Username string `json:"username,omitempty"`
@ -52,13 +90,18 @@ type Credential struct {
type CredentialsStore struct {
store map[string]string
perms map[string]map[string]bool
UseCache bool
hashCache *HashCache
}
// NewCredentialsStore returns a new instance of a CredentialStore.
func NewCredentialsStore() *CredentialsStore {
return &CredentialsStore{
store: make(map[string]string),
perms: make(map[string]map[string]bool),
store: make(map[string]string),
perms: make(map[string]map[string]bool),
hashCache: NewHashCache(),
UseCache: true,
}
}
@ -99,8 +142,27 @@ func (c *CredentialsStore) Check(username, password string) bool {
if !ok {
return false
}
return password == pw ||
bcrypt.CompareHashAndPassword([]byte(pw), []byte(password)) == nil
// Simple match with plaintext password in creds?
if password == pw {
return true
}
// Maybe the given a password is a hash -- check if the hash is good
// for the given user.
if c.UseCache && c.hashCache.Check(username, password) {
return true
}
// Next, what's in the file may be hashed, so hash the given password
// and compare.
if bcrypt.CompareHashAndPassword([]byte(pw), []byte(password)) != nil {
return false
}
// It's good -- cache that result for this user.
c.hashCache.Store(username, password)
return true
}
// Password returns the password for the given user.

@ -15,6 +15,53 @@ func (t *testBasicAuther) BasicAuth() (string, string, bool) {
return t.username, t.password, t.ok
}
func Test_HashCache(t *testing.T) {
hc := NewHashCache()
if hc.Check("user", "hash1") {
t.Fatalf("hash cache check OK for empty cache")
}
if hc.Check("user", "") {
t.Fatalf("hash cache check OK for empty cache")
}
if hc.Check("", "") {
t.Fatalf("hash cache check OK for empty cache")
}
hc.Store("user1", "hash1")
if !hc.Check("user1", "hash1") {
t.Fatalf("hash cache check not OK for user1")
}
if hc.Check("user", "hash1") {
t.Fatalf("hash cache check OK for bad user")
}
hc.Store("user1", "hash2")
if !hc.Check("user1", "hash1") {
t.Fatalf("hash cache check not OK for user1")
}
if !hc.Check("user1", "hash2") {
t.Fatalf("hash cache check not OK for user1")
}
hc.Store("user3", "hash3")
if !hc.Check("user1", "hash1") {
t.Fatalf("hash cache check not OK for user1")
}
if !hc.Check("user1", "hash2") {
t.Fatalf("hash cache check not OK for user1")
}
if hc.Check("user", "hash1") {
t.Fatalf("hash cache check OK for bad user")
}
if !hc.Check("user3", "hash3") {
t.Fatalf("hash cache check not OK for user3")
}
if hc.Check("user3", "hash1") {
t.Fatalf("hash cache check OK for user3, with bad hash")
}
}
func Test_AuthLoadSingle(t *testing.T) {
const jsonStream = `
[

Loading…
Cancel
Save