ref!(envauth): change Verify return from bool to error

This commit is contained in:
AJ ONeal 2025-10-04 21:18:41 -06:00
parent 0893b3cb2d
commit e8fbe603af
No known key found for this signature in database
3 changed files with 60 additions and 40 deletions

View File

@ -12,7 +12,7 @@ creds := envauth.BasicCredentials{
Password: os.Getenv("BASIC_AUTH_PASSWORD"),
}
verified := creds.Verify("username", "password")
err := creds.Verify("username", "password")
```
## Basic Credentials: Username + Password
@ -44,12 +44,17 @@ func main() {
Password: password,
}
verified := creds.Verify("api", "secret")
if verified {
println("Authentication successful")
} else {
if err := creds.Verify("api", "secret"); err != nil {
switch err {
case envauth.ErrUnauthorized:
println("Authentication failed")
default:
panic(err)
}
os.Exit(1)
}
println("Authentication successful")
}
```
@ -104,11 +109,16 @@ func main() {
Iterations: iterations,
}
verified := creds.Verify("api", "secret")
if verified {
println("Authentication successful")
} else {
if err := creds.Verify("api", "secret"); err != nil {
switch err {
case envauth.ErrUnauthorized:
println("Authentication failed")
default:
panic(err)
}
os.Exit(1)
}
println("Authentication successful")
}
```

View File

@ -4,10 +4,13 @@ import (
"crypto/pbkdf2"
"crypto/sha256"
"crypto/subtle"
"errors"
)
var ErrUnauthorized = errors.New("unauthorized")
type BasicAuthVerifier interface {
Verify(string, string) bool
Verify(string, string) error
}
// BasicCredentials holds user credentials
@ -18,9 +21,9 @@ type BasicCredentials struct {
// Returns true if username and password match.
// Uses SHA-256 and constant-time techniques to avoid revealing whether the username or password matches through timing attacks.
func (c BasicCredentials) Verify(username string, password string) bool {
func (c BasicCredentials) Verify(username string, password string) error {
if len(password) == 0 {
return false
return ErrUnauthorized
}
equal := 1
@ -38,7 +41,10 @@ func (c BasicCredentials) Verify(username string, password string) bool {
v = subtle.ConstantTimeCompare(knownPasswordHash[:], passwordHash[:]) // 1 if same
equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y
return equal == 1
if equal == 1 {
return nil
}
return ErrUnauthorized
}
// PBKDF2Credentials holds user credentials
@ -51,24 +57,24 @@ type PBKDF2Credentials struct {
// Returns true if username and password match.
// Uses PBKDF2 and constant-time techniques to avoid revealing whether the username or password matches through timing attacks.
func (c PBKDF2Credentials) Verify(username string, password string) bool {
func (c PBKDF2Credentials) Verify(username string, password string) error {
keyLen := len(c.DerivedKey)
dkKnownUser, err := pbkdf2.Key(sha256.New, c.Username, c.Salt, c.Iterations, keyLen)
if err != nil {
return false
return err
}
if len(password) == 0 {
return false
return ErrUnauthorized
}
dkUser, err := pbkdf2.Key(sha256.New, username, c.Salt, c.Iterations, keyLen)
if err != nil {
return false
return err
}
dkPass, err := pbkdf2.Key(sha256.New, password, c.Salt, c.Iterations, keyLen)
if err != nil {
return false
return err
}
equal := 1
@ -79,7 +85,11 @@ func (c PBKDF2Credentials) Verify(username string, password string) bool {
v = subtle.ConstantTimeCompare(dkPass, c.DerivedKey) // 1 if same
equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y
return equal == 1
if equal == 1 {
return nil
}
return ErrUnauthorized
}
func (c PBKDF2Credentials) DeriveKey(username string, password string, keyLen int) ([]byte, error) {

View File

@ -14,50 +14,50 @@ func TestBasicCredentials_Verify(t *testing.T) {
creds BasicCredentials
username string
password string
want bool
want error
}{
{
name: "empty username, correct password",
creds: BasicCredentials{Username: "", Password: "secret"},
username: "",
password: "secret",
want: true,
want: nil,
},
{
name: "correct username, correct password",
creds: BasicCredentials{Username: "user", Password: "secret"},
username: "user",
password: "secret",
want: true,
want: nil,
},
{
name: "incorrect username, correct password",
creds: BasicCredentials{Username: "user", Password: "secret"},
username: "wrong",
password: "secret",
want: false,
want: ErrUnauthorized,
},
{
name: "correct username, incorrect password",
creds: BasicCredentials{Username: "user", Password: "secret"},
username: "user",
password: "wrong",
want: false,
want: ErrUnauthorized,
},
{
name: "correct username, empty password",
creds: BasicCredentials{Username: "user", Password: "secret"},
username: "user",
password: "",
want: false,
want: ErrUnauthorized,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.creds.Verify(tt.username, tt.password)
if got != tt.want {
t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, got, tt.want)
err := tt.creds.Verify(tt.username, tt.password)
if err != tt.want {
t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, err, tt.want)
}
})
}
@ -78,57 +78,57 @@ func TestPBKDF2Credentials_Verify(t *testing.T) {
creds PBKDF2Credentials
username string
password string
want bool
want error
}{
{
name: "empty username, correct password",
creds: PBKDF2Credentials{Username: "", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
username: "",
password: "secret",
want: true,
want: nil,
},
{
name: "correct username, correct password",
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
username: "user",
password: "secret",
want: true,
want: nil,
},
{
name: "incorrect username, correct password",
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
username: "wrong",
password: "secret",
want: false,
want: ErrUnauthorized,
},
{
name: "correct username, incorrect password",
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
username: "user",
password: "wrong",
want: false,
want: ErrUnauthorized,
},
{
name: "correct username, empty password",
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
username: "user",
password: "",
want: false,
want: ErrUnauthorized,
},
{
name: "empty username, empty pre-computed digest",
creds: PBKDF2Credentials{Username: "", DerivedKey: emptyDigest, Salt: salt, Iterations: 1000},
username: "",
password: "",
want: false,
want: ErrUnauthorized,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.creds.Verify(tt.username, tt.password)
if got != tt.want {
t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, got, tt.want)
err := tt.creds.Verify(tt.username, tt.password)
if err != tt.want {
t.Errorf("Verify(%q, %q) = %v; want %v", tt.username, tt.password, err, tt.want)
}
})
}