mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-02 23:57:59 +00:00
ref(auth/csvauth): enable token use with Verify(dummy, token)
This commit is contained in:
parent
85d42550bf
commit
85c7b78ca6
@ -70,6 +70,7 @@ type Auth struct {
|
|||||||
tokens map[string]Credential
|
tokens map[string]Credential
|
||||||
serviceAccounts map[Purpose]Credential
|
serviceAccounts map[Purpose]Credential
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
|
BasicAuthTokenNames []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes an Auth with an encryption key
|
// New initializes an Auth with an encryption key
|
||||||
@ -82,6 +83,7 @@ func New(aes128key []byte) *Auth {
|
|||||||
credentials: map[Name]Credential{},
|
credentials: map[Name]Credential{},
|
||||||
tokens: map[string]Credential{},
|
tokens: map[string]Credential{},
|
||||||
serviceAccounts: map[Purpose]Credential{},
|
serviceAccounts: map[Purpose]Credential{},
|
||||||
|
BasicAuthTokenNames: []string{"", "api", "apikey"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +309,31 @@ func (a *Auth) CredentialKeys() iter.Seq[Name] {
|
|||||||
return maps.Keys(a.credentials)
|
return maps.Keys(a.credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Auth) LoadToken(secret string) (Credential, error) {
|
||||||
|
hashID := a.tokenCacheID(secret)
|
||||||
|
|
||||||
|
a.mux.Lock()
|
||||||
|
c, ok := a.tokens[hashID]
|
||||||
|
a.mux.Unlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return Credential{}, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.plain == "" {
|
||||||
|
var err error
|
||||||
|
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
||||||
|
return Credential{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Verify("", secret); err != nil {
|
||||||
|
return Credential{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Auth) LoadCredential(name Name) (Credential, error) {
|
func (a *Auth) LoadCredential(name Name) (Credential, error) {
|
||||||
a.mux.Lock()
|
a.mux.Lock()
|
||||||
c, ok := a.credentials[name]
|
c, ok := a.credentials[name]
|
||||||
@ -402,42 +429,50 @@ func (a *Auth) CacheServiceAccount(c Credential) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify checks Basic Auth credentials
|
// Verify checks Basic Auth credentials, i.e. as decoded from Authorization Basic <base64(user:pass)>.
|
||||||
|
// It also supports tokens. In short:
|
||||||
|
// - if <user>:<pass> and 'user' is found, then "login" credentials
|
||||||
|
// - if <token>:"" or <allowed-token-name>:<token>, then "token" credentials
|
||||||
|
//
|
||||||
|
// With a little more nuance and clarity:
|
||||||
|
// - if 'user' is found in the "login" credential store, token is NEVER tried
|
||||||
|
// - either 'user' or 'pass' may be used as the token
|
||||||
|
// (because 'pass' is swapped with 'user' when 'pass' is empty)
|
||||||
|
// - the resulting 'user' must match BasicAuthTokenNames ("", "api", and "apikey" are the defaults)
|
||||||
|
// - then the token is (timing-safe) hashed to check if it exists, and then verified by its algorithm
|
||||||
func (a *Auth) Verify(name, secret string) error {
|
func (a *Auth) Verify(name, secret string) error {
|
||||||
a.mux.Lock()
|
a.mux.Lock()
|
||||||
defer a.mux.Unlock()
|
defer a.mux.Unlock()
|
||||||
c, ok := a.credentials[name]
|
c, ok := a.credentials[name]
|
||||||
if !ok {
|
if ok {
|
||||||
return ErrNotFound
|
|
||||||
}
|
|
||||||
return c.Verify(name, secret)
|
return c.Verify(name, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) VerifyToken(secret string) error {
|
if secret == "" {
|
||||||
hashID := a.tokenCacheID(secret)
|
secret, name = name, secret
|
||||||
|
}
|
||||||
|
if slices.Contains(a.BasicAuthTokenNames, name) {
|
||||||
|
// this still returns ErrNotFound first
|
||||||
|
return a.VerifyToken(secret)
|
||||||
|
}
|
||||||
|
|
||||||
a.mux.Lock()
|
|
||||||
c, ok := a.tokens[hashID]
|
|
||||||
a.mux.Unlock()
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.plain == "" {
|
// VerifyToken uses a short, but timing-safe hash to find the token,
|
||||||
var err error
|
// and then verifies it with HMAC
|
||||||
if c.plain, err = a.maybeDecryptCredential(c); err != nil {
|
func (a *Auth) VerifyToken(secret string) error {
|
||||||
|
_, err := a.LoadToken(secret)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return c.Verify(hashID, secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) tokenCacheID(secret string) string {
|
func (a *Auth) tokenCacheID(secret string) string {
|
||||||
key := a.aes128key[:]
|
key := a.aes128key[:]
|
||||||
mac := hmac.New(sha256.New, key)
|
mac := hmac.New(sha256.New, key)
|
||||||
message := []byte(secret)
|
message := []byte(secret)
|
||||||
mac.Write(message)
|
mac.Write(message)
|
||||||
|
// attack collisions are possible, but will still fail to pass HMAC
|
||||||
|
// practical collisions are not possible for the CSV use case
|
||||||
nameBytes := mac.Sum(nil)[:6]
|
nameBytes := mac.Sum(nil)[:6]
|
||||||
|
|
||||||
name := base64.RawURLEncoding.EncodeToString(nameBytes)
|
name := base64.RawURLEncoding.EncodeToString(nameBytes)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user