Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package apikey | |
import ( | |
"crypto/sha256" | |
"crypto/subtle" | |
"fmt" | |
errors "github.com/go-openapi/errors" | |
"github.com/weaviate/weaviate/entities/models" | |
"github.com/weaviate/weaviate/usecases/config" | |
) | |
type Client struct { | |
config config.APIKey | |
keystorage [][sha256.Size]byte | |
} | |
func New(cfg config.Config) (*Client, error) { | |
c := &Client{ | |
config: cfg.Authentication.APIKey, | |
} | |
if err := c.validateConfig(); err != nil { | |
return nil, fmt.Errorf("invalid apikey config: %w", err) | |
} | |
c.parseKeys() | |
return c, nil | |
} | |
func (c *Client) parseKeys() { | |
c.keystorage = make([][sha256.Size]byte, len(c.config.AllowedKeys)) | |
for i, rawKey := range c.config.AllowedKeys { | |
c.keystorage[i] = sha256.Sum256([]byte(rawKey)) | |
} | |
} | |
func (c *Client) validateConfig() error { | |
if !c.config.Enabled { | |
// don't validate if this scheme isn't used | |
return nil | |
} | |
if len(c.config.AllowedKeys) < 1 { | |
return fmt.Errorf("need at least one valid allowed key") | |
} | |
for _, key := range c.config.AllowedKeys { | |
if len(key) == 0 { | |
return fmt.Errorf("keys cannot have length 0") | |
} | |
} | |
if len(c.config.Users) < 1 { | |
return fmt.Errorf("need at least one user") | |
} | |
for _, key := range c.config.Users { | |
if len(key) == 0 { | |
return fmt.Errorf("users cannot have length 0") | |
} | |
} | |
if len(c.config.Users) > 1 && len(c.config.Users) != len(c.config.AllowedKeys) { | |
return fmt.Errorf("length of users and keys must match, alternatively provide single user for all keys") | |
} | |
return nil | |
} | |
func (c *Client) ValidateAndExtract(token string, scopes []string) (*models.Principal, error) { | |
if !c.config.Enabled { | |
return nil, errors.New(401, "apikey auth is not configured, please try another auth scheme or set up weaviate with apikey configured") | |
} | |
tokenPos, ok := c.isTokenAllowed(token) | |
if !ok { | |
return nil, errors.New(401, "invalid api key, please provide a valid api key") | |
} | |
return &models.Principal{ | |
Username: c.getUser(tokenPos), | |
}, nil | |
} | |
func (c *Client) isTokenAllowed(token string) (int, bool) { | |
tokenHash := sha256.Sum256([]byte(token)) | |
for i, allowed := range c.keystorage { | |
if subtle.ConstantTimeCompare(tokenHash[:], allowed[:]) == 1 { | |
return i, true | |
} | |
} | |
return -1, false | |
} | |
func (c *Client) getUser(pos int) string { | |
// passed validation guarantees that one of those options will work | |
if pos >= len(c.config.Users) { | |
return c.config.Users[0] | |
} | |
return c.config.Users[pos] | |
} | |