mirror of
https://github.com/therootcompany/golib.git
synced 2025-10-07 01:28:19 +00:00
feat(envauth): add verifiers for single-user credentials
This commit is contained in:
parent
7ece4eb9e6
commit
23ff6225f5
110
auth/envauth/README.md
Normal file
110
auth/envauth/README.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# envauth
|
||||||
|
|
||||||
|
Auth utils for single-user environments. \
|
||||||
|
(standard library only, constant-time)
|
||||||
|
|
||||||
|
- Password
|
||||||
|
- PBKDF2 Digest (sha-256)
|
||||||
|
|
||||||
|
```go
|
||||||
|
creds := envauth.BasicCredentials{
|
||||||
|
Username: os.Getenv("BASIC_AUTH_USERNAME"),
|
||||||
|
Password: os.Getenv("BASIC_AUTH_PASSWORD"),
|
||||||
|
}
|
||||||
|
|
||||||
|
verified := creds.Verify("username", "password")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Credentials: Username + Password
|
||||||
|
|
||||||
|
Plain-text username + password, typically something like `api:somereallylongapikey`.
|
||||||
|
|
||||||
|
`.env`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export BASIC_AUTH_USERNAME="api"
|
||||||
|
export BASIC_AUTH_PASSWORD="secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/therootcompany/golib/auth/envauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
username := os.Getenv("BASIC_AUTH_USERNAME")
|
||||||
|
password := os.Getenv("BASIC_AUTH_PASSWORD")
|
||||||
|
|
||||||
|
creds := envauth.BasicCredentials{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
verified := creds.Verify("api", "secret")
|
||||||
|
if verified {
|
||||||
|
println("Authentication successful")
|
||||||
|
} else {
|
||||||
|
println("Authentication failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## PBKDF2 Derived Key / Digest
|
||||||
|
|
||||||
|
Salted and hashed password.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go run ./cmd/pbkdf2-sha256/ 'secret' 'i63wDd7K-60'
|
||||||
|
|
||||||
|
derived-key: 553ce8846c2304e93021dab03bacb5ca
|
||||||
|
```
|
||||||
|
|
||||||
|
`.env`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export BASIC_AUTH_USERNAME="api"
|
||||||
|
export BASIC_AUTH_PBKDF256_DERIVED_KEY="553ce8846c2304e93021dab03bacb5ca"
|
||||||
|
export BASIC_AUTH_PBKDF256_SALT="i63wDd7K-60"
|
||||||
|
export BASIC_AUTH_PBKDF256_ITERATIONS=1000
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/therootcompany/golib/auth/envauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
username := os.Getenv("BASIC_AUTH_USERNAME")
|
||||||
|
derivedKeyHex := os.Getenv("BASIC_AUTH_PBKDF256_DERIVED_KEY")
|
||||||
|
saltBase64 := os.Getenv("BASIC_AUTH_PBKDF256_SALT")
|
||||||
|
itersStr := os.Getenv("BASIC_AUTH_PBKDF256_ITERATIONS")
|
||||||
|
|
||||||
|
derivedKey, _ := hex.DecodeString(derivedKeyB64)
|
||||||
|
salt, _ := base64.URLEncoding.DecodeString(saltHex)
|
||||||
|
iterations, _ := strconv.Atoi(itersStr)
|
||||||
|
|
||||||
|
creds := envauth.PBKDF2Credentials{
|
||||||
|
Username: username,
|
||||||
|
DerivedKey: derivedKey,
|
||||||
|
Salt: salt,
|
||||||
|
Iterations: iterations,
|
||||||
|
}
|
||||||
|
|
||||||
|
verified := creds.Verify("api", "secret")
|
||||||
|
if verified {
|
||||||
|
println("Authentication successful")
|
||||||
|
} else {
|
||||||
|
println("Authentication failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
149
auth/envauth/cmd/pbkdf2-sha256/main.go
Normal file
149
auth/envauth/cmd/pbkdf2-sha256/main.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/pbkdf2"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version = "v1.0.0"
|
||||||
|
var help = "pbkdf2-sha256 [password=random] [salt=random] [iterations=1000] [keySize=16]"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "-V", "version", "-version", "--version":
|
||||||
|
fmt.Println(version)
|
||||||
|
return
|
||||||
|
case "help", "-help", "--help":
|
||||||
|
fmt.Println("Usage:", help)
|
||||||
|
os.Exit(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var password string
|
||||||
|
var salt []byte
|
||||||
|
var iterations int
|
||||||
|
var keySize int
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Default values
|
||||||
|
iterations = 1000
|
||||||
|
keySize = 16
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
|
args := os.Args[1:]
|
||||||
|
if len(args) > 4 {
|
||||||
|
fmt.Fprintf(os.Stderr, "USAGE\n\t%s\n", help)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password
|
||||||
|
if len(args) > 0 && args[0] != "" {
|
||||||
|
password = args[0]
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nUSAGE\n\t%s\n\n", help)
|
||||||
|
rnd := make([]byte, 8)
|
||||||
|
_, _ = rand.Read(rnd)
|
||||||
|
hexPass := hex.EncodeToString(rnd)
|
||||||
|
password = fmt.Sprintf("%s-%s-%s-%s", hexPass[:4], hexPass[4:8], hexPass[8:12], hexPass[12:])
|
||||||
|
fmt.Printf("password : %s\n", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salt
|
||||||
|
if len(args) > 1 && args[1] != "" {
|
||||||
|
saltStr := args[1]
|
||||||
|
salt, err = parseHexOrBase64(saltStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error decoding salt: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
salt = make([]byte, 16)
|
||||||
|
_, _ = rand.Read(salt)
|
||||||
|
fmt.Printf("salt : %s\n", base64.RawURLEncoding.EncodeToString(salt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterations
|
||||||
|
if len(args) > 2 && args[2] != "0" && args[2] != "" {
|
||||||
|
iterations, err = parseInt(args[2])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing iterations: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("iterations : %d\n", iterations)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key size
|
||||||
|
if len(args) > 3 && args[3] != "0" && args[3] != "" {
|
||||||
|
keySize, err = parseInt(args[3])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing key size: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("key-size : %d\n", keySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate PBKDF2 key
|
||||||
|
derivedKey, err := pbkdf2.Key(sha256.New, password, salt, iterations, keySize)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error parsing key size: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("derived-key: %s\n\n", hex.EncodeToString(derivedKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHexOrBase64(data string) ([]byte, error) {
|
||||||
|
var b []byte
|
||||||
|
|
||||||
|
// Check if salt is hex (all uppercase or lowercase, valid hex chars)
|
||||||
|
isHex := true
|
||||||
|
for _, c := range data {
|
||||||
|
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
|
||||||
|
isHex = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for mixed case
|
||||||
|
hasUpper := strings.ContainsAny(data, "ABCDEF")
|
||||||
|
hasLower := strings.ContainsAny(data, "abcdef")
|
||||||
|
if isHex && !(hasUpper && hasLower) {
|
||||||
|
var err error
|
||||||
|
b, err = hex.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Assume URL-safe base64, convert to RFC base64
|
||||||
|
rfcData := strings.ReplaceAll(data, "-", "+")
|
||||||
|
rfcData = strings.ReplaceAll(rfcData, "_", "/")
|
||||||
|
rfcData = strings.ReplaceAll(rfcData, "=", "")
|
||||||
|
var err error
|
||||||
|
b, err = base64.RawStdEncoding.DecodeString(rfcData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(s string) (int, error) {
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n < 0 {
|
||||||
|
return 0, fmt.Errorf("value must be positive")
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
52
auth/envauth/cmd/salt/main.go
Normal file
52
auth/envauth/cmd/salt/main.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var version = "v1.0.0"
|
||||||
|
var help = "salt [size=16]"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "-V", "version", "-version", "--version":
|
||||||
|
fmt.Println(version)
|
||||||
|
return
|
||||||
|
case "help", "-help", "--help":
|
||||||
|
fmt.Println("Usage:", help)
|
||||||
|
os.Exit(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var size int
|
||||||
|
|
||||||
|
switch len(os.Args) {
|
||||||
|
case 1:
|
||||||
|
size = 16
|
||||||
|
case 2:
|
||||||
|
size, err = strconv.Atoi(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s\n", help)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s\n", help)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, size)
|
||||||
|
_, _ = rand.Read(salt)
|
||||||
|
fmt.Printf("hex : %s\n", hex.EncodeToString(salt))
|
||||||
|
fmt.Printf("HEX : %s\n", strings.ToUpper(hex.EncodeToString(salt)))
|
||||||
|
fmt.Printf("url-base64: %s\n", base64.RawURLEncoding.EncodeToString(salt))
|
||||||
|
fmt.Printf("rfc-base64: %s\n", base64.StdEncoding.EncodeToString(salt))
|
||||||
|
}
|
93
auth/envauth/envauth.go
Normal file
93
auth/envauth/envauth.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package envauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/pbkdf2"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BasicAuthVerifier interface {
|
||||||
|
Verify(string, string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicCredentials holds user credentials
|
||||||
|
type BasicCredentials struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
equal := 1
|
||||||
|
|
||||||
|
// We hash rather than completely relying on subtle.ConstantTimeCompare([]byte(c.Username), []byte(username))
|
||||||
|
// out of an abundance of caution since optimizations have caused similar methods to fail in other languages.
|
||||||
|
// (we also use it because it gives back 1 rather than true, which we can use in the next step)
|
||||||
|
knownUsernameHash := sha256.Sum256([]byte(c.Username))
|
||||||
|
usernameHash := sha256.Sum256([]byte(username))
|
||||||
|
v := subtle.ConstantTimeCompare(knownUsernameHash[:], usernameHash[:]) // 1 if same
|
||||||
|
equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y
|
||||||
|
|
||||||
|
knownPasswordHash := sha256.Sum256([]byte(c.Password))
|
||||||
|
passwordHash := sha256.Sum256([]byte(password))
|
||||||
|
v = subtle.ConstantTimeCompare(knownPasswordHash[:], passwordHash[:]) // 1 if same
|
||||||
|
equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y
|
||||||
|
|
||||||
|
return equal == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// PBKDF2Credentials holds user credentials
|
||||||
|
type PBKDF2Credentials struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
DerivedKey []byte `json:"derived_key"`
|
||||||
|
Salt []byte `json:"salt"` // should be at least 8 bytes
|
||||||
|
Iterations int `json:"iterations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
keyLen := len(c.DerivedKey)
|
||||||
|
dkKnownUser, err := pbkdf2.Key(sha256.New, c.Username, c.Salt, c.Iterations, keyLen)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(password) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
dkUser, err := pbkdf2.Key(sha256.New, username, c.Salt, c.Iterations, keyLen)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dkPass, err := pbkdf2.Key(sha256.New, password, c.Salt, c.Iterations, keyLen)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
equal := 1
|
||||||
|
|
||||||
|
v := subtle.ConstantTimeCompare(dkUser, dkKnownUser) // 1 if same
|
||||||
|
equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y
|
||||||
|
|
||||||
|
v = subtle.ConstantTimeCompare(dkPass, c.DerivedKey) // 1 if same
|
||||||
|
equal = subtle.ConstantTimeSelect(v, equal, 0) // v ? x : y
|
||||||
|
|
||||||
|
return equal == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c PBKDF2Credentials) DeriveKey(username string, password string, keyLen int) ([]byte, error) {
|
||||||
|
if keyLen == 0 {
|
||||||
|
keyLen = len(c.DerivedKey)
|
||||||
|
}
|
||||||
|
return pbkdf2.Key(sha256.New, password, c.Salt, c.Iterations, keyLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ BasicAuthVerifier = (*BasicCredentials)(nil)
|
||||||
|
var _ BasicAuthVerifier = (*PBKDF2Credentials)(nil)
|
135
auth/envauth/envauth_test.go
Normal file
135
auth/envauth/envauth_test.go
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package envauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/pbkdf2"
|
||||||
|
"crypto/sha256"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var salt = []byte("buzzword")
|
||||||
|
|
||||||
|
func TestBasicCredentials_Verify(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
creds BasicCredentials
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty username, correct password",
|
||||||
|
creds: BasicCredentials{Username: "", Password: "secret"},
|
||||||
|
username: "",
|
||||||
|
password: "secret",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct username, correct password",
|
||||||
|
creds: BasicCredentials{Username: "user", Password: "secret"},
|
||||||
|
username: "user",
|
||||||
|
password: "secret",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect username, correct password",
|
||||||
|
creds: BasicCredentials{Username: "user", Password: "secret"},
|
||||||
|
username: "wrong",
|
||||||
|
password: "secret",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct username, incorrect password",
|
||||||
|
creds: BasicCredentials{Username: "user", Password: "secret"},
|
||||||
|
username: "user",
|
||||||
|
password: "wrong",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct username, empty password",
|
||||||
|
creds: BasicCredentials{Username: "user", Password: "secret"},
|
||||||
|
username: "user",
|
||||||
|
password: "",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPBKDF2Credentials_Verify(t *testing.T) {
|
||||||
|
secretDigest, err := pbkdf2.Key(sha256.New, "secret", salt, 1000, 16)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("pbkdf2.Key(sha256.New, \"secret\", salt, 1000, 16) = %v", err)
|
||||||
|
}
|
||||||
|
emptyDigest, err := pbkdf2.Key(sha256.New, "", salt, 1000, 16)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("pbkdf2.Key(sha256.New, \"\", salt, 1000, 16) = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
creds PBKDF2Credentials
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty username, correct password",
|
||||||
|
creds: PBKDF2Credentials{Username: "", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
|
||||||
|
username: "",
|
||||||
|
password: "secret",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct username, correct password",
|
||||||
|
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
|
||||||
|
username: "user",
|
||||||
|
password: "secret",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect username, correct password",
|
||||||
|
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
|
||||||
|
username: "wrong",
|
||||||
|
password: "secret",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct username, incorrect password",
|
||||||
|
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
|
||||||
|
username: "user",
|
||||||
|
password: "wrong",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "correct username, empty password",
|
||||||
|
creds: PBKDF2Credentials{Username: "user", DerivedKey: secretDigest, Salt: salt, Iterations: 1000},
|
||||||
|
username: "user",
|
||||||
|
password: "",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty username, empty pre-computed digest",
|
||||||
|
creds: PBKDF2Credentials{Username: "", DerivedKey: emptyDigest, Salt: salt, Iterations: 1000},
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
3
auth/envauth/go.mod
Normal file
3
auth/envauth/go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/therootcompany/golib/auth/envauth
|
||||||
|
|
||||||
|
go 1.24.6
|
Loading…
x
Reference in New Issue
Block a user