Skip to content

Commit b0bb6af

Browse files
committed
fix: cached basic strategy store pass as plain text unless user provide hashing algo
1 parent 7cf9d7a commit b0bb6af

File tree

3 files changed

+54
-19
lines changed

3 files changed

+54
-19
lines changed

auth/strategies/basic/basic.go

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package basic
44

55
import (
66
"context"
7-
"crypto/sha256"
7+
"crypto"
88
"encoding/hex"
99
"errors"
1010
"fmt"
@@ -27,9 +27,9 @@ var ErrInvalidCredentials = errors.New("basic: Invalid user credentials")
2727
// commonly used when enable/add strategy to go-guardian authenticator.
2828
const StrategyKey = auth.StrategyKey("Basic.Strategy")
2929

30-
// ExtensionKey represents a key for the hashed password in info extensions.
30+
// ExtensionKey represents a key for the password in info extensions.
3131
// Typically used when basic strategy cache the authentication decisions.
32-
const ExtensionKey = "x-go-guardian-basic-hash"
32+
const ExtensionKey = "x-go-guardian-basic-password"
3333

3434
// AuthenticateFunc declare custom function to authenticate request using user credentials.
3535
// the authenticate function invoked by Authenticate Strategy method after extracting user credentials
@@ -65,8 +65,9 @@ func (auth AuthenticateFunc) credentials(r *http.Request) (string, string, error
6565
}
6666

6767
type cachedBasic struct {
68-
cache store.Cache
69-
authFunc AuthenticateFunc
68+
AuthenticateFunc
69+
hash crypto.Hash
70+
cache store.Cache
7071
}
7172

7273
func (c *cachedBasic) authenticate(ctx context.Context, r *http.Request, userName, pass string) (auth.Info, error) { // nolint:lll
@@ -87,18 +88,18 @@ func (c *cachedBasic) authenticate(ctx context.Context, r *http.Request, userNam
8788

8889
info := v.(auth.Info)
8990
ext := info.Extensions()
90-
hash, ok := ext[ExtensionKey]
91+
hashedPass, ok := ext[ExtensionKey]
9192

9293
if !ok {
9394
return c.authenticatAndHash(ctx, r, userName, pass)
9495
}
9596

96-
err = password(pass).compare(hash[0])
97+
err = password(pass).compare(c.hash, hashedPass[0])
9798
return info, err
9899
}
99100

100101
func (c *cachedBasic) authenticatAndHash(ctx context.Context, r *http.Request, userName, pass string) (auth.Info, error) { //nolint:lll
101-
info, err := c.authFunc(ctx, r, userName, pass)
102+
info, err := c.AuthenticateFunc(ctx, r, userName, pass)
102103
if err != nil {
103104
return nil, err
104105
}
@@ -108,8 +109,8 @@ func (c *cachedBasic) authenticatAndHash(ctx context.Context, r *http.Request, u
108109
ext = make(map[string][]string)
109110
}
110111

111-
hash := password(pass).hash()
112-
ext[ExtensionKey] = []string{hash}
112+
hashedPass := password(pass).hash(c.hash)
113+
ext[ExtensionKey] = []string{hashedPass}
113114
info.SetExtensions(ext)
114115

115116
// cache result
@@ -123,25 +124,49 @@ func (c *cachedBasic) authenticatAndHash(ctx context.Context, r *http.Request, u
123124
// New return new auth.Strategy.
124125
// The returned strategy, caches the invocation result of authenticate function.
125126
func New(f AuthenticateFunc, cache store.Cache) auth.Strategy {
127+
return NewWithOptions(f, cache)
128+
}
129+
130+
// NewWithOptions return new auth.Strategy.
131+
// The returned strategy, caches the invocation result of authenticate function.
132+
func NewWithOptions(f AuthenticateFunc, cache store.Cache, opts ...auth.Option) auth.Strategy {
126133
cb := &cachedBasic{
127-
authFunc: f,
128-
cache: cache,
134+
AuthenticateFunc: f,
135+
cache: cache,
136+
}
137+
138+
for _, opt := range opts {
139+
opt.Apply(cb)
129140
}
130141

131142
return AuthenticateFunc(cb.authenticate)
132143
}
133144

145+
// SetHash set the hashing algorithm to hash the user password.
146+
func SetHash(h crypto.Hash) auth.Option {
147+
return auth.OptionFunc(func(s auth.Strategy) {
148+
if v, ok := s.(*cachedBasic); ok {
149+
v.hash = h
150+
}
151+
})
152+
}
153+
134154
type password string
135155

136-
func (p password) hash() string {
137-
sha := sha256.New()
138-
_, _ = sha.Write([]byte(p))
139-
sum := sha.Sum(nil)
156+
func (p password) hash(h crypto.Hash) string {
157+
// check if allow to hash, otherwise return plain password.
158+
if h < crypto.MD4 {
159+
return string(p)
160+
}
161+
162+
hasher := h.New()
163+
_, _ = hasher.Write([]byte(p))
164+
sum := hasher.Sum(nil)
140165
return hex.EncodeToString(sum)
141166
}
142167

143-
func (p password) compare(hash string) error {
144-
if p.hash() == hash {
168+
func (p password) compare(h crypto.Hash, hashedPass string) error {
169+
if p.hash(h) == hashedPass {
145170
return nil
146171
}
147172
return ErrInvalidCredentials

auth/strategies/basic/basic_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package basic
22

33
import (
44
"context"
5+
"crypto"
6+
_ "crypto/sha256"
57
"fmt"
68
"net/http"
79
"sync"
@@ -148,7 +150,8 @@ func TestNewCached(t *testing.T) {
148150
)
149151
c.cache["predefined3"] = auth.NewDefaultUser("predefined3", "10", nil, nil)
150152

151-
info, err := New(authFunc, c).Authenticate(r.Context(), r)
153+
opt := SetHash(crypto.SHA256)
154+
info, err := NewWithOptions(authFunc, c, opt).Authenticate(r.Context(), r)
152155

153156
assert.Equal(t, tt.expectedErr, err != nil, "%s: Got Unexpected error %v", tt.name, err)
154157
assert.Equal(t, !tt.expectedErr, info != nil, "%s: Expected info object, got nil", tt.name)

auth/strategies/basic/example_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package basic
22

33
import (
44
"context"
5+
"crypto"
56
"fmt"
67
"net/http"
78

@@ -54,6 +55,12 @@ func Example_second() {
5455
// basic: Invalid user credentials
5556
}
5657

58+
func ExampleSetHash() {
59+
opt := SetHash(crypto.SHA256) // import _ crypto/sha256
60+
cache := store.New(2)
61+
NewWithOptions(exampleAuthFunc, cache, opt)
62+
}
63+
5764
func exampleAuthFunc(ctx context.Context, r *http.Request, userName, password string) (auth.Info, error) {
5865
// here connect to db or any other service to fetch user and validate it.
5966
if userName == "test" && password == "test" {

0 commit comments

Comments
 (0)