package jsonweb import ( "errors" "github.com/golang-jwt/jwt/v4" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/kit/platform" ) const kind = "jwt" var ( // ErrKeyNotFound should be returned by a KeyStore when // a key cannot be located for the provided key ID ErrKeyNotFound = errors.New("key not found") // EmptyKeyStore is a KeyStore implementation which contains no keys EmptyKeyStore = KeyStoreFunc(func(string) ([]byte, error) { return nil, ErrKeyNotFound }) ) // KeyStore is a type which holds a set of keys accessed // via an id type KeyStore interface { Key(string) ([]byte, error) } // KeyStoreFunc is a function which can be used as a KeyStore type KeyStoreFunc func(string) ([]byte, error) // Key delegates to the receiver KeyStoreFunc func (k KeyStoreFunc) Key(v string) ([]byte, error) { return k(v) } // TokenParser is a type which can parse and validate tokens type TokenParser struct { keyStore KeyStore parser *jwt.Parser } // NewTokenParser returns a configured token parser used to // parse Token types from strings func NewTokenParser(keyStore KeyStore) *TokenParser { return &TokenParser{ keyStore: keyStore, parser: &jwt.Parser{ ValidMethods: []string{jwt.SigningMethodHS256.Alg()}, }, } } // Parse takes a string then parses and validates it as a jwt based on // the key described within the token func (t *TokenParser) Parse(v string) (*Token, error) { jwt, err := t.parser.ParseWithClaims(v, &Token{}, func(jwt *jwt.Token) (interface{}, error) { token, ok := jwt.Claims.(*Token) if !ok { return nil, errors.New("missing kid in token claims") } // fetch key for "kid" from key store return t.keyStore.Key(token.KeyID) }) if err != nil { return nil, err } token, ok := jwt.Claims.(*Token) if !ok { return nil, errors.New("token is unexpected type") } return token, nil } // IsMalformedError returns true if the error returned represents // a jwt malformed token error func IsMalformedError(err error) bool { verr, ok := err.(*jwt.ValidationError) return ok && verr.Errors&jwt.ValidationErrorMalformed > 0 } // Token is a structure which is serialized as a json web token // It contains the necessary claims required to authorize type Token struct { jwt.StandardClaims // KeyID is the identifier of the key used to sign the token KeyID string `json:"kid"` // Permissions is the set of authorized permissions for the token Permissions []influxdb.Permission `json:"permissions"` // UserID for the token UserID string `json:"uid,omitempty"` } // PermissionSet returns the set of permissions associated with the token. func (t *Token) PermissionSet() (influxdb.PermissionSet, error) { return t.Permissions, nil } // Identifier returns the identifier for this Token // as found in the standard claims func (t *Token) Identifier() platform.ID { id, err := platform.IDFromString(t.Id) if err != nil || id == nil { return platform.ID(1) } return *id } // GetUserID returns an invalid id as tokens are generated // with permissions rather than for or by a particular user func (t *Token) GetUserID() platform.ID { id, err := platform.IDFromString(t.UserID) if err != nil { return platform.InvalidID() } return *id } // Kind returns the string "jwt" which is used for auditing func (t *Token) Kind() string { return kind } // EphemeralAuth creates a influxdb Auth form a jwt token func (t *Token) EphemeralAuth(orgID platform.ID) *influxdb.Authorization { return &influxdb.Authorization{ ID: t.Identifier(), OrgID: orgID, Status: influxdb.Active, Permissions: t.Permissions, } }