From 0256b39e9097afe736ad5d55f0c868c84e64485d Mon Sep 17 00:00:00 2001 From: Ivaylo Novakov Date: Wed, 9 Mar 2022 12:55:07 +0100 Subject: [PATCH 1/2] Handle API keys passed in their lower case form. --- api/routes.go | 8 +++----- database/apikeys.go | 13 ++++++++++++- database/apikeys_test.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 database/apikeys_test.go diff --git a/api/routes.go b/api/routes.go index 4da86330..7937b457 100644 --- a/api/routes.go +++ b/api/routes.go @@ -20,8 +20,6 @@ var ( // ErrNoAPIKey is an error returned when we expect an API key but we don't // find one. ErrNoAPIKey = errors.New("no api key found") - // ErrInvalidAPIKey is an error returned when the given API key is invalid. - ErrInvalidAPIKey = errors.New("invalid api key") // ErrNoToken is returned when we expected a JWT token to be provided but it // was not. ErrNoToken = errors.New("no authorisation token found") @@ -169,9 +167,9 @@ func apiKeyFromRequest(r *http.Request) (database.APIKey, error) { if akStr == "" { return "", ErrNoAPIKey } - ak := database.APIKey(akStr) - if !ak.IsValid() { - return "", ErrInvalidAPIKey + ak, err := database.NewAPIKeyFromString(akStr) + if err != nil { + return "", err } return ak, nil } diff --git a/database/apikeys.go b/database/apikeys.go index 620b9151..f564ea07 100644 --- a/database/apikeys.go +++ b/database/apikeys.go @@ -30,6 +30,8 @@ var ( // ErrMaxNumAPIKeysExceeded is returned when a user tries to create a new // API key after already having the maximum allowed number. ErrMaxNumAPIKeysExceeded = errors.New("maximum number of api keys exceeded") + // ErrInvalidAPIKey is an error returned when the given API key is invalid. + ErrInvalidAPIKey = errors.New("invalid api key") ) type ( @@ -50,9 +52,18 @@ func NewAPIKey() APIKey { return APIKey(base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(fastrand.Bytes(PubKeySize))) } +// NewAPIKeyFromString creates an APIKey struct from a string and validates it. +func NewAPIKeyFromString(s string) (*APIKey, error) { + ak := APIKey(strings.ToUpper(s)) + if !ak.IsValid() { + return nil, ErrInvalidAPIKey + } + return &ak, nil +} + // Bytes returns the raw representation of an API key. func (ak APIKey) Bytes() ([]byte, error) { - return base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(string(ak))) + return base32.HexEncoding.WithPadding(base32.NoPadding).DecodeString(string(ak)) } // IsValid checks whether the underlying string satisfies the type's requirement diff --git a/database/apikeys_test.go b/database/apikeys_test.go new file mode 100644 index 00000000..e2fc9032 --- /dev/null +++ b/database/apikeys_test.go @@ -0,0 +1,29 @@ +package database + +import "testing" + +// TestNewAPIKeyFromString validates that NewAPIKeyFromString properly handles +// valid API keys, upper case or lower case. +func TestNewAPIKeyFromString(t *testing.T) { + tests := []struct { + name string + in string + valid bool + }{ + {name: "empty", in: "", valid: false}, + {name: "valid upper case", in: "6TAOK0RVVKKK25PIA33FHDBD1G04DLO015DAAD6OM2J33KCD5CL0", valid: true}, + {name: "valid lower case", in: "6taok0rvvkkk25pia33fhdbd1g04dlo015daad6om2j33kcd5cl0", valid: true}, + {name: "too short upper case", in: "6TAOK0RVVKKK25PIA33FHDBD1G04DLO015DAAD6OM2J33KCD5CL", valid: false}, + {name: "too short lower case", in: "6taok0rvvkkk25pia33fhdbd1g04dlo015daad6om2j33kcd5cl", valid: false}, + {name: "too long upper case", in: "6TAOK0RVVKKK25PIA33FHDBD1G04DLO015DAAD6OM2J33KCD5CL01", valid: false}, + {name: "too long lower case", in: "6taok0rvvkkk25pia33fhdbd1g04dlo015daad6om2j33kcd5cl01", valid: false}, + {name: "invalid alphabet", in: "!TAOK0RVVKKK25PIA33FHDBD1G04DLO015DAAD6OM2J33KCD5CL0", valid: false}, + } + + for _, tt := range tests { + _, err := NewAPIKeyFromString(tt.in) + if (tt.valid && err != nil) || (!tt.valid && err == nil) { + t.Errorf("Test '%s' failed.", tt.name) + } + } +} From a71332817a409d64ef47b992c7f58c0b752f2afa Mon Sep 17 00:00:00 2001 From: Ivaylo Novakov Date: Wed, 9 Mar 2022 12:58:00 +0100 Subject: [PATCH 2/2] Fix lint. --- api/routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/routes.go b/api/routes.go index 7937b457..c23c924e 100644 --- a/api/routes.go +++ b/api/routes.go @@ -171,7 +171,7 @@ func apiKeyFromRequest(r *http.Request) (database.APIKey, error) { if err != nil { return "", err } - return ak, nil + return *ak, nil } // tokenFromRequest extracts the JWT token from the request and returns it.