github.com/weaviate/weaviate@v1.24.6/usecases/auth/authentication/apikey/client.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package apikey 13 14 import ( 15 "crypto/sha256" 16 "crypto/subtle" 17 "fmt" 18 19 errors "github.com/go-openapi/errors" 20 "github.com/weaviate/weaviate/entities/models" 21 "github.com/weaviate/weaviate/usecases/config" 22 ) 23 24 type Client struct { 25 config config.APIKey 26 keystorage [][sha256.Size]byte 27 } 28 29 func New(cfg config.Config) (*Client, error) { 30 c := &Client{ 31 config: cfg.Authentication.APIKey, 32 } 33 34 if err := c.validateConfig(); err != nil { 35 return nil, fmt.Errorf("invalid apikey config: %w", err) 36 } 37 38 c.parseKeys() 39 40 return c, nil 41 } 42 43 func (c *Client) parseKeys() { 44 c.keystorage = make([][sha256.Size]byte, len(c.config.AllowedKeys)) 45 for i, rawKey := range c.config.AllowedKeys { 46 c.keystorage[i] = sha256.Sum256([]byte(rawKey)) 47 } 48 } 49 50 func (c *Client) validateConfig() error { 51 if !c.config.Enabled { 52 // don't validate if this scheme isn't used 53 return nil 54 } 55 56 if len(c.config.AllowedKeys) < 1 { 57 return fmt.Errorf("need at least one valid allowed key") 58 } 59 60 for _, key := range c.config.AllowedKeys { 61 if len(key) == 0 { 62 return fmt.Errorf("keys cannot have length 0") 63 } 64 } 65 66 if len(c.config.Users) < 1 { 67 return fmt.Errorf("need at least one user") 68 } 69 70 for _, key := range c.config.Users { 71 if len(key) == 0 { 72 return fmt.Errorf("users cannot have length 0") 73 } 74 } 75 76 if len(c.config.Users) > 1 && len(c.config.Users) != len(c.config.AllowedKeys) { 77 return fmt.Errorf("length of users and keys must match, alternatively provide single user for all keys") 78 } 79 80 return nil 81 } 82 83 func (c *Client) ValidateAndExtract(token string, scopes []string) (*models.Principal, error) { 84 if !c.config.Enabled { 85 return nil, errors.New(401, "apikey auth is not configured, please try another auth scheme or set up weaviate with apikey configured") 86 } 87 88 tokenPos, ok := c.isTokenAllowed(token) 89 if !ok { 90 return nil, errors.New(401, "invalid api key, please provide a valid api key") 91 } 92 93 return &models.Principal{ 94 Username: c.getUser(tokenPos), 95 }, nil 96 } 97 98 func (c *Client) isTokenAllowed(token string) (int, bool) { 99 tokenHash := sha256.Sum256([]byte(token)) 100 101 for i, allowed := range c.keystorage { 102 if subtle.ConstantTimeCompare(tokenHash[:], allowed[:]) == 1 { 103 return i, true 104 } 105 } 106 107 return -1, false 108 } 109 110 func (c *Client) getUser(pos int) string { 111 // passed validation guarantees that one of those options will work 112 if pos >= len(c.config.Users) { 113 return c.config.Users[0] 114 } 115 116 return c.config.Users[pos] 117 }