Spaces:
Sleeping
Sleeping
| // _ _ | |
| // __ _____ __ ___ ___ __ _| |_ ___ | |
| // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
| // \ V V / __/ (_| |\ V /| | (_| | || __/ | |
| // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
| // | |
| // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
| // | |
| // CONTACT: hello@weaviate.io | |
| // | |
| 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] | |
| } | |