From 0a985e2dcfc07b2dce45ac167e2a83e3ec1dedc1 Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 21 Nov 2022 14:53:13 +0100 Subject: [PATCH 01/15] make Validator and ContextKey required --- main.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index 6cb3e22..d473370 100644 --- a/main.go +++ b/main.go @@ -46,11 +46,9 @@ type Config struct { AuthScheme string // Validator is a function to validate key. - // Optional. Default: nil Validator func(*fiber.Ctx, string) (bool, error) // Context key to store the bearertoken from the token into context. - // Optional. Default: "token". ContextKey string } @@ -70,7 +68,7 @@ func New(config ...Config) fiber.Handler { if cfg.ErrorHandler == nil { cfg.ErrorHandler = func(c *fiber.Ctx, err error) error { if err == ErrMissingOrMalformedAPIKey { - return c.Status(fiber.StatusBadRequest).SendString(err.Error()) + return c.Status(fiber.StatusUnauthorized).SendString(err.Error()) } return c.Status(fiber.StatusUnauthorized).SendString("Invalid or expired API Key") } @@ -83,12 +81,10 @@ func New(config ...Config) fiber.Handler { } } if cfg.Validator == nil { - cfg.Validator = func(c *fiber.Ctx, t string) (bool, error) { - return true, nil - } + panic("fiber: keyauth middleware requires a validator function") } if cfg.ContextKey == "" { - cfg.ContextKey = "token" + panic("fiber: keyauth middleware requires a ContextKey") } // Initialize @@ -118,10 +114,11 @@ func New(config ...Config) fiber.Handler { return cfg.ErrorHandler(c, err) } + // first set the ContextKey as Locals so the Validator can be checked correctly + c.Locals("ContextKey", cfg.ContextKey) valid, err := cfg.Validator(c, key) if err == nil && valid { - c.Locals(cfg.ContextKey, key) return cfg.SuccessHandler(c) } return cfg.ErrorHandler(c, err) From d856b21d0dc025d041a675c933c8a391c302140e Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 21 Nov 2022 14:53:54 +0100 Subject: [PATCH 02/15] udpate Readme with new Validator/ContextKey --- README.md | 62 ++++++++++++++++++------------------------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 521c013..47f59fc 100644 --- a/README.md +++ b/README.md @@ -22,31 +22,20 @@ import ( "github.com/gofiber/keyauth/v2" ) -const ( - apiKey = "my-super-secret-key" -) - -var ( - errMissing = &fiber.Error{Code: 403, Message: "Missing API key"} - errInvalid = &fiber.Error{Code: 403, Message: "Invalid API key"} -) - -func validateApiKey(ctx *fiber.Ctx, s string) (bool, error) { - if s == "" { - return false, errMissing - } - if s == apiKey { - return true, nil - } - return false, errInvalid +func validateApiKey(c *fiber.Ctx, key string) (bool, error) { + if key == c.Locals("ContextKey") { + return true, nil + } + return false, keyauth.ErrMissingOrMalformedAPIKey } func main() { app := fiber.New() app.Use(keyauth.New(keyauth.Config{ - KeyLookup: "cookie:access_token", - Validator: validateApiKey, + KeyLookup: "cookie:access_token", + Validator: validateApiKey, + ContextKey: "my-super-secret-key-123" })) app.Get("/", func(c *fiber.Ctx) error { @@ -64,11 +53,11 @@ func main() { curl http://localhost:3000 #> missing or malformed API Key -curl --cookie "access_token=my-super-secret-key" http://localhost:3000 +curl --cookie "access_token=my-super-secret-key-123" http://localhost:3000 #> Successfully authenticated! curl --cookie "access_token=Clearly A Wrong Key" http://localhost:3000 -#> Invalid or expired API Key +#> missing or malformed API Key ``` For a more detailed example, see also the [`github.com/gofiber/recipes`](https://github.com/gofiber/recipes) repository and specifically the `fiber-envoy-extauthz` repository and the [`keyauth example`](https://github.com/gofiber/recipes/blob/master/fiber-envoy-extauthz/authz/main.go) code. @@ -86,23 +75,11 @@ import ( "github.com/gofiber/keyauth/v2" ) -const ( - apiKey = "my-super-secret-key" -) - -var ( - errMissing = &fiber.Error{Code: 403, Message: "Missing API key"} - errInvalid = &fiber.Error{Code: 403, Message: "Invalid API key"} -) - -func validateApiKey(ctx *fiber.Ctx, s string) (bool, error) { - if s == "" { - return false, errMissing - } - if s == apiKey { - return true, nil - } - return false, errInvalid +func validateApiKey(c *fiber.Ctx, key string) (bool, error) { + if key == c.Locals("ContextKey") { + return true, nil + } + return false, keyauth.ErrMissingOrMalformedAPIKey } func authFilter(c *fiber.Ctx) bool { @@ -116,8 +93,9 @@ func main() { app.Use(keyauth.New(keyauth.Config{ Filter: authFilter, - KeyLookup: "cookie:access_token", - Validator: validateApiKey, + KeyLookup: "cookie:access_token", + Validator: validateApiKey, + ContextKey: "my-super-secret-key-123" })) app.Get("/", func(c *fiber.Ctx) error { @@ -142,10 +120,10 @@ curl http://localhost:3000 #> Welcome # /authenticated needs to be authenticated -curl --cookie "access_token=my-super-secret-key" http://localhost:3000/authenticated +curl --cookie "access_token=my-super-secret-key-123" http://localhost:3000/authenticated #> Successfully authenticated! # /auth2 needs to be authenticated too -curl --cookie "access_token=my-super-secret-key" http://localhost:3000/auth2 +curl --cookie "access_token=my-super-secret-key-123" http://localhost:3000/auth2 #> Successfully authenticated 2! ``` From eda77b9ac911d34cf4ab64cbf1e8e529d5d0e1f3 Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 21 Nov 2022 14:54:07 +0100 Subject: [PATCH 03/15] add unit tests --- main_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/main_test.go b/main_test.go index 47dd09e..302b5f7 100644 --- a/main_test.go +++ b/main_test.go @@ -3,3 +3,84 @@ // 📝 Github Repository: https://github.com/gofiber/fiber package keyauth + +import ( + "io/ioutil" + "net/http" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/utils" +) + +func validateApiKey(c *fiber.Ctx, key string) (bool, error) { + if key == c.Locals("ContextKey") { + return true, nil + } + return false, ErrMissingOrMalformedAPIKey +} + +func TestKeyAuth(t *testing.T) { + + // setup the fiber endpoint + app := fiber.New() + + // use keyauth.New and keyauth.Config outside of testing + app.Use(New(Config{ + KeyLookup: "header:key", + Validator: validateApiKey, + ContextKey: "MySecretPassword", + })) + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("Successfully authenticated!") + }) + + // define test cases + tests := []struct { + description string + apiKey string + expectedCode int + expectedBody string + }{ + { + description: "Normal Authentication Case", + apiKey: "MySecretPassword", + expectedCode: 200, + expectedBody: "Successfully authenticated!", + }, + { + description: "Wrong API Key", + apiKey: "WRONG KEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + { + description: "Wrong API Key", + apiKey: "", // NO KEY + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + } + + // run the tests + for _, test := range tests { + var req *http.Request + req, _ = http.NewRequest("GET", "/", nil) + if test.apiKey != "" { + req.Header.Set("key", test.apiKey) + } + + res, err := app.Test(req, -1) + + utils.AssertEqual(t, nil, err, test.description) + + // test the body of the request + body, err := ioutil.ReadAll(res.Body) + utils.AssertEqual(t, test.expectedCode, res.StatusCode, test.description) + + // body + utils.AssertEqual(t, nil, err, test.description) + utils.AssertEqual(t, test.expectedBody, string(body), test.description) + } +} From f8c7b8496d93a2a76a4aab01f221401254838a6b Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 21 Nov 2022 15:01:58 +0100 Subject: [PATCH 04/15] standardize function names --- README.md | 8 ++++---- main_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 47f59fc..080cb9a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ import ( "github.com/gofiber/keyauth/v2" ) -func validateApiKey(c *fiber.Ctx, key string) (bool, error) { +func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { if key == c.Locals("ContextKey") { return true, nil } @@ -34,7 +34,7 @@ func main() { app.Use(keyauth.New(keyauth.Config{ KeyLookup: "cookie:access_token", - Validator: validateApiKey, + Validator: validateAPIKey, ContextKey: "my-super-secret-key-123" })) @@ -75,7 +75,7 @@ import ( "github.com/gofiber/keyauth/v2" ) -func validateApiKey(c *fiber.Ctx, key string) (bool, error) { +func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { if key == c.Locals("ContextKey") { return true, nil } @@ -94,7 +94,7 @@ func main() { app.Use(keyauth.New(keyauth.Config{ Filter: authFilter, KeyLookup: "cookie:access_token", - Validator: validateApiKey, + Validator: validateAPIKey, ContextKey: "my-super-secret-key-123" })) diff --git a/main_test.go b/main_test.go index 302b5f7..debebe6 100644 --- a/main_test.go +++ b/main_test.go @@ -13,7 +13,7 @@ import ( "github.com/gofiber/fiber/v2/utils" ) -func validateApiKey(c *fiber.Ctx, key string) (bool, error) { +func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { if key == c.Locals("ContextKey") { return true, nil } @@ -28,7 +28,7 @@ func TestKeyAuth(t *testing.T) { // use keyauth.New and keyauth.Config outside of testing app.Use(New(Config{ KeyLookup: "header:key", - Validator: validateApiKey, + Validator: validateAPIKey, ContextKey: "MySecretPassword", })) From edf3d283ef224e65444d81331a3b27d84a776305 Mon Sep 17 00:00:00 2001 From: DavZim Date: Wed, 14 Dec 2022 09:00:34 +0100 Subject: [PATCH 05/15] add API Key to Config --- main.go | 11 +++++++++-- main_test.go | 16 ++++++++-------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index d473370..cb2fd62 100644 --- a/main.go +++ b/main.go @@ -49,7 +49,11 @@ type Config struct { Validator func(*fiber.Ctx, string) (bool, error) // Context key to store the bearertoken from the token into context. + // Optional. Default: "token". ContextKey string + + // API Key which is used by the Validator function for authentication + ApiKey string } // New ... @@ -84,7 +88,10 @@ func New(config ...Config) fiber.Handler { panic("fiber: keyauth middleware requires a validator function") } if cfg.ContextKey == "" { - panic("fiber: keyauth middleware requires a ContextKey") + cfg.ContextKey = "token" + } + if cfg.ApiKey == "" { + panic("fiber: keyauth middleware requires an ApiKey") } // Initialize @@ -115,7 +122,7 @@ func New(config ...Config) fiber.Handler { } // first set the ContextKey as Locals so the Validator can be checked correctly - c.Locals("ContextKey", cfg.ContextKey) + c.Locals(cfg.ContextKey, cfg.ApiKey) valid, err := cfg.Validator(c, key) if err == nil && valid { diff --git a/main_test.go b/main_test.go index debebe6..bfd3b5a 100644 --- a/main_test.go +++ b/main_test.go @@ -13,12 +13,6 @@ import ( "github.com/gofiber/fiber/v2/utils" ) -func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { - if key == c.Locals("ContextKey") { - return true, nil - } - return false, ErrMissingOrMalformedAPIKey -} func TestKeyAuth(t *testing.T) { @@ -28,8 +22,14 @@ func TestKeyAuth(t *testing.T) { // use keyauth.New and keyauth.Config outside of testing app.Use(New(Config{ KeyLookup: "header:key", - Validator: validateAPIKey, - ContextKey: "MySecretPassword", + Validator: func(c *fiber.Ctx, key string) (bool, error) { + if key == c.Locals("token") { + return true, nil + } + return false, ErrMissingOrMalformedAPIKey + }, + ContextKey: "token", + ApiKey: "MySecretPassword", })) app.Get("/", func(c *fiber.Ctx) error { From 4e1fb0964159395068f3787ea6a17adeb1237485 Mon Sep 17 00:00:00 2001 From: DavZim Date: Wed, 14 Dec 2022 09:01:34 +0100 Subject: [PATCH 06/15] add test for multiple middleware --- main_test.go | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/main_test.go b/main_test.go index bfd3b5a..7b14506 100644 --- a/main_test.go +++ b/main_test.go @@ -84,3 +84,140 @@ func TestKeyAuth(t *testing.T) { utils.AssertEqual(t, test.expectedBody, string(body), test.description) } } + + +func TestMultipleKeyAuth(t *testing.T) { + + // setup the fiber endpoint + app := fiber.New() + + // setup keyauth for /auth1 + app.Use(New(Config{ + Filter: func(c *fiber.Ctx) bool { + return c.OriginalURL() != "/auth1" + }, + KeyLookup: "header:key", + Validator: func(c *fiber.Ctx, key string) (bool, error) { + if key == c.Locals("token_auth1") { + return true, nil + } + return false, ErrMissingOrMalformedAPIKey + }, + ContextKey: "token_auth1", + ApiKey: "password1", + })) + + // setup keyauth for /auth2 + app.Use(New(Config{ + Filter: func(c *fiber.Ctx) bool { + return c.OriginalURL() != "/auth2" + }, + KeyLookup: "header:key", + Validator: func(c *fiber.Ctx, key string) (bool, error) { + if key == c.Locals("token_auth2") { + return true, nil + } + return false, ErrMissingOrMalformedAPIKey + }, + ContextKey: "token_auth2", + ApiKey: "password2", + })) + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("No auth needed!") + }) + + app.Get("/auth1", func(c *fiber.Ctx) error { + return c.SendString("Successfully authenticated for auth1!") + }) + + app.Get("/auth2", func(c *fiber.Ctx) error { + return c.SendString("Successfully authenticated for auth2!") + }) + + // define test cases + tests := []struct { + route string + description string + apiKey string + expectedCode int + expectedBody string + }{ + // No auth needed for / + { + route: "/", + description: "No password needed", + apiKey: "", + expectedCode: 200, + expectedBody: "No auth needed!", + }, + + // auth needed for auth1 + { + route: "/auth1", + description: "Normal Authentication Case", + apiKey: "password1", + expectedCode: 200, + expectedBody: "Successfully authenticated for auth1!", + }, + { + route: "/auth1", + description: "Wrong API Key", + apiKey: "WRONG KEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + { + route: "/auth1", + description: "Wrong API Key", + apiKey: "", // NO KEY + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + + // Auth 2 has a different password + { + route: "/auth2", + description: "Normal Authentication Case for auth2", + apiKey: "password2", + expectedCode: 200, + expectedBody: "Successfully authenticated for auth2!", + }, + { + route: "/auth2", + description: "Wrong API Key", + apiKey: "WRONG KEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + { + route: "/auth2", + description: "Wrong API Key", + apiKey: "", // NO KEY + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + + } + + // run the tests + for _, test := range tests { + var req *http.Request + req, _ = http.NewRequest("GET", test.route, nil) + if test.apiKey != "" { + req.Header.Set("key", test.apiKey) + } + + res, err := app.Test(req, -1) + + utils.AssertEqual(t, nil, err, test.description) + + // test the body of the request + body, err := ioutil.ReadAll(res.Body) + utils.AssertEqual(t, test.expectedCode, res.StatusCode, test.description) + + // body + utils.AssertEqual(t, nil, err, test.description) + utils.AssertEqual(t, test.expectedBody, string(body), test.description) + } +} From 48f689dc88033d17dcdc322f7d7733944f6e4e51 Mon Sep 17 00:00:00 2001 From: DavZim Date: Wed, 14 Dec 2022 09:56:38 +0100 Subject: [PATCH 07/15] rename ApiKey to APIKey (fixes hound bot review) --- main.go | 8 ++++---- main_test.go | 38 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/main.go b/main.go index cb2fd62..3def921 100644 --- a/main.go +++ b/main.go @@ -53,7 +53,7 @@ type Config struct { ContextKey string // API Key which is used by the Validator function for authentication - ApiKey string + APIKey string } // New ... @@ -90,8 +90,8 @@ func New(config ...Config) fiber.Handler { if cfg.ContextKey == "" { cfg.ContextKey = "token" } - if cfg.ApiKey == "" { - panic("fiber: keyauth middleware requires an ApiKey") + if cfg.APIKey == "" { + panic("fiber: keyauth middleware requires an APIKey") } // Initialize @@ -122,7 +122,7 @@ func New(config ...Config) fiber.Handler { } // first set the ContextKey as Locals so the Validator can be checked correctly - c.Locals(cfg.ContextKey, cfg.ApiKey) + c.Locals(cfg.ContextKey, cfg.APIKey) valid, err := cfg.Validator(c, key) if err == nil && valid { diff --git a/main_test.go b/main_test.go index 7b14506..ab31339 100644 --- a/main_test.go +++ b/main_test.go @@ -29,7 +29,7 @@ func TestKeyAuth(t *testing.T) { return false, ErrMissingOrMalformedAPIKey }, ContextKey: "token", - ApiKey: "MySecretPassword", + APIKey: "MySecretPassword", })) app.Get("/", func(c *fiber.Ctx) error { @@ -39,25 +39,25 @@ func TestKeyAuth(t *testing.T) { // define test cases tests := []struct { description string - apiKey string + APIKey string expectedCode int expectedBody string }{ { description: "Normal Authentication Case", - apiKey: "MySecretPassword", + APIKey: "MySecretPassword", expectedCode: 200, expectedBody: "Successfully authenticated!", }, { description: "Wrong API Key", - apiKey: "WRONG KEY", + APIKey: "WRONG KEY", expectedCode: 401, expectedBody: "missing or malformed API Key", }, { description: "Wrong API Key", - apiKey: "", // NO KEY + APIKey: "", // NO KEY expectedCode: 401, expectedBody: "missing or malformed API Key", }, @@ -67,8 +67,8 @@ func TestKeyAuth(t *testing.T) { for _, test := range tests { var req *http.Request req, _ = http.NewRequest("GET", "/", nil) - if test.apiKey != "" { - req.Header.Set("key", test.apiKey) + if test.APIKey != "" { + req.Header.Set("key", test.APIKey) } res, err := app.Test(req, -1) @@ -104,7 +104,7 @@ func TestMultipleKeyAuth(t *testing.T) { return false, ErrMissingOrMalformedAPIKey }, ContextKey: "token_auth1", - ApiKey: "password1", + APIKey: "password1", })) // setup keyauth for /auth2 @@ -120,7 +120,7 @@ func TestMultipleKeyAuth(t *testing.T) { return false, ErrMissingOrMalformedAPIKey }, ContextKey: "token_auth2", - ApiKey: "password2", + APIKey: "password2", })) app.Get("/", func(c *fiber.Ctx) error { @@ -139,7 +139,7 @@ func TestMultipleKeyAuth(t *testing.T) { tests := []struct { route string description string - apiKey string + APIKey string expectedCode int expectedBody string }{ @@ -147,7 +147,7 @@ func TestMultipleKeyAuth(t *testing.T) { { route: "/", description: "No password needed", - apiKey: "", + APIKey: "", expectedCode: 200, expectedBody: "No auth needed!", }, @@ -156,21 +156,21 @@ func TestMultipleKeyAuth(t *testing.T) { { route: "/auth1", description: "Normal Authentication Case", - apiKey: "password1", + APIKey: "password1", expectedCode: 200, expectedBody: "Successfully authenticated for auth1!", }, { route: "/auth1", description: "Wrong API Key", - apiKey: "WRONG KEY", + APIKey: "WRONG KEY", expectedCode: 401, expectedBody: "missing or malformed API Key", }, { route: "/auth1", description: "Wrong API Key", - apiKey: "", // NO KEY + APIKey: "", // NO KEY expectedCode: 401, expectedBody: "missing or malformed API Key", }, @@ -179,21 +179,21 @@ func TestMultipleKeyAuth(t *testing.T) { { route: "/auth2", description: "Normal Authentication Case for auth2", - apiKey: "password2", + APIKey: "password2", expectedCode: 200, expectedBody: "Successfully authenticated for auth2!", }, { route: "/auth2", description: "Wrong API Key", - apiKey: "WRONG KEY", + APIKey: "WRONG KEY", expectedCode: 401, expectedBody: "missing or malformed API Key", }, { route: "/auth2", description: "Wrong API Key", - apiKey: "", // NO KEY + APIKey: "", // NO KEY expectedCode: 401, expectedBody: "missing or malformed API Key", }, @@ -204,8 +204,8 @@ func TestMultipleKeyAuth(t *testing.T) { for _, test := range tests { var req *http.Request req, _ = http.NewRequest("GET", test.route, nil) - if test.apiKey != "" { - req.Header.Set("key", test.apiKey) + if test.APIKey != "" { + req.Header.Set("key", test.APIKey) } res, err := app.Test(req, -1) From ba365fd29393270d93caaa8efd002c4c1efa2438 Mon Sep 17 00:00:00 2001 From: DavZim Date: Fri, 23 Dec 2022 10:38:28 +0100 Subject: [PATCH 08/15] remove APIKey again --- main.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/main.go b/main.go index 3def921..9306c33 100644 --- a/main.go +++ b/main.go @@ -51,9 +51,6 @@ type Config struct { // Context key to store the bearertoken from the token into context. // Optional. Default: "token". ContextKey string - - // API Key which is used by the Validator function for authentication - APIKey string } // New ... @@ -90,9 +87,6 @@ func New(config ...Config) fiber.Handler { if cfg.ContextKey == "" { cfg.ContextKey = "token" } - if cfg.APIKey == "" { - panic("fiber: keyauth middleware requires an APIKey") - } // Initialize parts := strings.Split(cfg.KeyLookup, ":") @@ -121,11 +115,10 @@ func New(config ...Config) fiber.Handler { return cfg.ErrorHandler(c, err) } - // first set the ContextKey as Locals so the Validator can be checked correctly - c.Locals(cfg.ContextKey, cfg.APIKey) valid, err := cfg.Validator(c, key) if err == nil && valid { + c.Locals(cfg.ContextKey, key) return cfg.SuccessHandler(c) } return cfg.ErrorHandler(c, err) From f6c3c4cdacd7ff228fde1563494fd44840ccda0d Mon Sep 17 00:00:00 2001 From: DavZim Date: Fri, 23 Dec 2022 10:39:41 +0100 Subject: [PATCH 09/15] fix + add tests for all auth sources --- main_test.go | 195 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 184 insertions(+), 11 deletions(-) diff --git a/main_test.go b/main_test.go index ab31339..e9723ff 100644 --- a/main_test.go +++ b/main_test.go @@ -7,6 +7,7 @@ package keyauth import ( "io/ioutil" "net/http" + "net/url" "testing" "github.com/gofiber/fiber/v2" @@ -19,17 +20,14 @@ func TestKeyAuth(t *testing.T) { // setup the fiber endpoint app := fiber.New() - // use keyauth.New and keyauth.Config outside of testing app.Use(New(Config{ KeyLookup: "header:key", Validator: func(c *fiber.Ctx, key string) (bool, error) { - if key == c.Locals("token") { + if key == "MySecretPassword" { return true, nil } return false, ErrMissingOrMalformedAPIKey }, - ContextKey: "token", - APIKey: "MySecretPassword", })) app.Get("/", func(c *fiber.Ctx) error { @@ -85,6 +83,186 @@ func TestKeyAuth(t *testing.T) { } } +func TestAuthSources(t *testing.T) { + + var CorrectKey = "correct horse battery staple" + // define test cases + tests := []struct { + route string + authSource string + authTokenName string + description string + APIKey string + expectedCode int + expectedBody string + }{ + // header:access_token auth + { + route: "/", + authSource: "header", + authTokenName: "access_token", + description: "Testing Header:access_token", + APIKey: CorrectKey, + expectedCode: 200, + expectedBody: "Success!", + }, + { + route: "/", + authSource: "header", + authTokenName: "access_token", + description: "Testing Header:access_token with a wrong key", + APIKey: "WRONGKEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + + // cookie:access_token auth + { + route: "/", + authSource: "cookie", + authTokenName: "access_token", + description: "Testing cookie:access_token", + APIKey: CorrectKey, + expectedCode: 200, + expectedBody: "Success!", + }, + { + route: "/", + authSource: "cookie", + authTokenName: "access_token", + description: "Testing cookie:access_token with a wrong key", + APIKey: "WRONGKEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + + // query:access_token auth + { + route: "/", + authSource: "query", + authTokenName: "access_token", + description: "Testing query:access_token", + APIKey: CorrectKey, + expectedCode: 200, + expectedBody: "Success!", + }, + { + route: "/", + authSource: "query", + authTokenName: "access_token", + description: "Testing query:access_token with a wrong key", + APIKey: "WRONGKEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + + // param:access_token auth + // TOOD: somehow the params are not handed-off to c.Params() when keyauth is used + /*{ + route: "/key/", + authSource: "param", + authTokenName: "access_token", + description: "Testing param:access_token", + APIKey: CorrectKey, + expectedCode: 200, + expectedBody: "Success!", + }, + { + route: "/key/", + authSource: "param", + authTokenName: "access_token", + description: "Testing param:access_token with a wrong key", + APIKey: "WRONGKEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + },*/ + + // form:access_token auth + { + route: "/", + authSource: "form", + authTokenName: "access_token", + description: "Testing form:access_token", + APIKey: CorrectKey, + expectedCode: 200, + expectedBody: "Success!", + }, + { + route: "/", + authSource: "form", + authTokenName: "access_token", + description: "Testing form:access_token with a wrong key", + APIKey: "WRONGKEY", + expectedCode: 401, + expectedBody: "missing or malformed API Key", + }, + } + + + for _, test := range tests { + // setup the fiber endpoint + app := fiber.New() + + app.Use(New(Config{ + KeyLookup: test.authSource + ":" + test.authTokenName, + Validator: func(c *fiber.Ctx, key string) (bool, error) { + if key == CorrectKey { + return true, nil + } + return false, ErrMissingOrMalformedAPIKey + }, + })) + + + app.Get("/", func(c *fiber.Ctx) error { + return c.SendString("Success!") + }) + app.Get("/key/:" + test.authTokenName, func(c *fiber.Ctx) error { + return c.SendString("Success!") + }) + + + // construct the test HTTP request + var req *http.Request + req, _ = http.NewRequest("GET", test.route, nil) + + // setup the apikey for the different auth schemes + if test.authSource == "header" { + + req.Header.Set(test.authTokenName, test.APIKey) + + } else if test.authSource == "cookie" { + + req.Header.Set("Cookie", test.authTokenName + "=" + test.APIKey) + + } else if test.authSource == "query" || test.authSource == "form" { + + q := req.URL.Query() + q.Add(test.authTokenName, test.APIKey) + req.URL.RawQuery = q.Encode() + + } else if test.authSource == "param" { + + r := req.URL.Path + r = r + url.QueryEscape(test.APIKey) + req.URL.Path = r + + } + + res, err := app.Test(req, -1) + + utils.AssertEqual(t, nil, err, test.description) + + // test the body of the request + body, err := ioutil.ReadAll(res.Body) + utils.AssertEqual(t, test.expectedCode, res.StatusCode, test.description) + + // body + utils.AssertEqual(t, nil, err, test.description) + utils.AssertEqual(t, test.expectedBody, string(body), test.description) + } +} + func TestMultipleKeyAuth(t *testing.T) { @@ -98,13 +276,11 @@ func TestMultipleKeyAuth(t *testing.T) { }, KeyLookup: "header:key", Validator: func(c *fiber.Ctx, key string) (bool, error) { - if key == c.Locals("token_auth1") { + if key == "password1" { return true, nil } return false, ErrMissingOrMalformedAPIKey }, - ContextKey: "token_auth1", - APIKey: "password1", })) // setup keyauth for /auth2 @@ -114,13 +290,11 @@ func TestMultipleKeyAuth(t *testing.T) { }, KeyLookup: "header:key", Validator: func(c *fiber.Ctx, key string) (bool, error) { - if key == c.Locals("token_auth2") { + if key == "password2" { return true, nil } return false, ErrMissingOrMalformedAPIKey }, - ContextKey: "token_auth2", - APIKey: "password2", })) app.Get("/", func(c *fiber.Ctx) error { @@ -197,7 +371,6 @@ func TestMultipleKeyAuth(t *testing.T) { expectedCode: 401, expectedBody: "missing or malformed API Key", }, - } // run the tests From 279f4b7d1bec8957ef8c078ff8efefffb6b83c2f Mon Sep 17 00:00:00 2001 From: DavZim Date: Fri, 23 Dec 2022 10:42:23 +0100 Subject: [PATCH 10/15] update Readme --- README.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 080cb9a..d73b86b 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,12 @@ import ( "github.com/gofiber/keyauth/v2" ) +var ( + APIKey = "correct horse battery staple" +) + func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { - if key == c.Locals("ContextKey") { + if key == APIKey { return true, nil } return false, keyauth.ErrMissingOrMalformedAPIKey @@ -31,11 +35,11 @@ func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { func main() { app := fiber.New() - + + // note that the keyauth middleware needs to be defined before the routes are defined! app.Use(keyauth.New(keyauth.Config{ KeyLookup: "cookie:access_token", Validator: validateAPIKey, - ContextKey: "my-super-secret-key-123" })) app.Get("/", func(c *fiber.Ctx) error { @@ -53,7 +57,7 @@ func main() { curl http://localhost:3000 #> missing or malformed API Key -curl --cookie "access_token=my-super-secret-key-123" http://localhost:3000 +curl --cookie "access_token=correct horse battery staple" http://localhost:3000 #> Successfully authenticated! curl --cookie "access_token=Clearly A Wrong Key" http://localhost:3000 @@ -74,9 +78,12 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/keyauth/v2" ) +var ( + APIKey = "correct horse battery staple" +) func validateAPIKey(c *fiber.Ctx, key string) (bool, error) { - if key == c.Locals("ContextKey") { + if key == APIKey { return true, nil } return false, keyauth.ErrMissingOrMalformedAPIKey @@ -95,7 +102,6 @@ func main() { Filter: authFilter, KeyLookup: "cookie:access_token", Validator: validateAPIKey, - ContextKey: "my-super-secret-key-123" })) app.Get("/", func(c *fiber.Ctx) error { @@ -120,10 +126,10 @@ curl http://localhost:3000 #> Welcome # /authenticated needs to be authenticated -curl --cookie "access_token=my-super-secret-key-123" http://localhost:3000/authenticated +curl --cookie "access_token=correct horse battery staple" http://localhost:3000/authenticated #> Successfully authenticated! # /auth2 needs to be authenticated too -curl --cookie "access_token=my-super-secret-key-123" http://localhost:3000/auth2 +curl --cookie "access_token=correct horse battery staple" http://localhost:3000/auth2 #> Successfully authenticated 2! ``` From b7591809b4da49c4884dd562dffc3ebe8c73831f Mon Sep 17 00:00:00 2001 From: DavZim Date: Fri, 23 Dec 2022 13:16:25 +0100 Subject: [PATCH 11/15] fix param extraction in keyFromParam --- main.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 9306c33..dad9a8b 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ package keyauth import ( "errors" + "net/url" "strings" "github.com/gofiber/fiber/v2" @@ -165,8 +166,14 @@ func keyFromForm(param string) func(c *fiber.Ctx) (string, error) { // keyFromParam returns a function that extracts api key from the url param string. func keyFromParam(param string) func(c *fiber.Ctx) (string, error) { return func(c *fiber.Ctx) (string, error) { - key := c.Params(param) - if key == "" { + // somehow two path unescapes are needed to get the original key again + // see also tests for param in main_test.go + key, err := url.PathUnescape(c.Params(param)) + if err != nil { + return "", ErrMissingOrMalformedAPIKey + } + key, err = url.PathUnescape(key) + if key == "" || err != nil { return "", ErrMissingOrMalformedAPIKey } return key, nil From 8dba14ac13d05c149ff5e5e655f19e42dc9b4c49 Mon Sep 17 00:00:00 2001 From: DavZim Date: Fri, 23 Dec 2022 13:16:47 +0100 Subject: [PATCH 12/15] add test cases for param auth --- main_test.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/main_test.go b/main_test.go index e9723ff..a82c2c1 100644 --- a/main_test.go +++ b/main_test.go @@ -85,7 +85,7 @@ func TestKeyAuth(t *testing.T) { func TestAuthSources(t *testing.T) { - var CorrectKey = "correct horse battery staple" + var CorrectKey = "specials: !$%,.#\"!?~`<>@$^*(){}[]|/\\123" // define test cases tests := []struct { route string @@ -157,9 +157,8 @@ func TestAuthSources(t *testing.T) { }, // param:access_token auth - // TOOD: somehow the params are not handed-off to c.Params() when keyauth is used - /*{ - route: "/key/", + { + route: "/key/", // will end as '/key/:access_token' authSource: "param", authTokenName: "access_token", description: "Testing param:access_token", @@ -175,7 +174,7 @@ func TestAuthSources(t *testing.T) { APIKey: "WRONGKEY", expectedCode: 401, expectedBody: "missing or malformed API Key", - },*/ + }, // form:access_token auth { @@ -203,7 +202,7 @@ func TestAuthSources(t *testing.T) { // setup the fiber endpoint app := fiber.New() - app.Use(New(Config{ + authMiddleware := New(Config{ KeyLookup: test.authSource + ":" + test.authTokenName, Validator: func(c *fiber.Ctx, key string) (bool, error) { if key == CorrectKey { @@ -211,13 +210,18 @@ func TestAuthSources(t *testing.T) { } return false, ErrMissingOrMalformedAPIKey }, - })) + }) + var route string + if test.authSource == "param" { + route = test.route + ":" + test.authTokenName + app.Use(route, authMiddleware) + } else { + route = test.route + app.Use(authMiddleware) + } - app.Get("/", func(c *fiber.Ctx) error { - return c.SendString("Success!") - }) - app.Get("/key/:" + test.authTokenName, func(c *fiber.Ctx) error { + app.Get(route, func(c *fiber.Ctx) error { return c.SendString("Success!") }) @@ -244,7 +248,7 @@ func TestAuthSources(t *testing.T) { } else if test.authSource == "param" { r := req.URL.Path - r = r + url.QueryEscape(test.APIKey) + r = r + url.PathEscape(test.APIKey) req.URL.Path = r } From e28a017331cb9c1663203a4b13d7f18a03bde8d9 Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 2 Jan 2023 11:35:38 +0100 Subject: [PATCH 13/15] remove unneeded second PathUnescape and fix test --- main.go | 6 ------ main_test.go | 5 +++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index dad9a8b..fa1d98c 100644 --- a/main.go +++ b/main.go @@ -166,16 +166,10 @@ func keyFromForm(param string) func(c *fiber.Ctx) (string, error) { // keyFromParam returns a function that extracts api key from the url param string. func keyFromParam(param string) func(c *fiber.Ctx) (string, error) { return func(c *fiber.Ctx) (string, error) { - // somehow two path unescapes are needed to get the original key again - // see also tests for param in main_test.go key, err := url.PathUnescape(c.Params(param)) if err != nil { return "", ErrMissingOrMalformedAPIKey } - key, err = url.PathUnescape(key) - if key == "" || err != nil { - return "", ErrMissingOrMalformedAPIKey - } return key, nil } } diff --git a/main_test.go b/main_test.go index a82c2c1..59783e0 100644 --- a/main_test.go +++ b/main_test.go @@ -200,8 +200,9 @@ func TestAuthSources(t *testing.T) { for _, test := range tests { // setup the fiber endpoint - app := fiber.New() - + // note that if UnescapePath: false (the default) + // escaped characters (such as `\"`) will not be handled correctly in the tests + app := fiber.New(fiber.Config{UnescapePath: true}) authMiddleware := New(Config{ KeyLookup: test.authSource + ":" + test.authTokenName, Validator: func(c *fiber.Ctx, key string) (bool, error) { From 425c89ca9561555430b44bae4c6d4ddcf36fca18 Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 2 Jan 2023 12:51:31 +0100 Subject: [PATCH 14/15] restructure nested tests to use subtests --- main_test.go | 209 ++++++++++++++++++--------------------------------- 1 file changed, 73 insertions(+), 136 deletions(-) diff --git a/main_test.go b/main_test.go index 59783e0..b8e0d86 100644 --- a/main_test.go +++ b/main_test.go @@ -87,110 +87,36 @@ func TestAuthSources(t *testing.T) { var CorrectKey = "specials: !$%,.#\"!?~`<>@$^*(){}[]|/\\123" // define test cases + testSources := []string {"header", "cookie", "query", "param", "form"} + tests := []struct { - route string - authSource string + route string authTokenName string description string APIKey string expectedCode int expectedBody string }{ - // header:access_token auth - { - route: "/", - authSource: "header", - authTokenName: "access_token", - description: "Testing Header:access_token", - APIKey: CorrectKey, - expectedCode: 200, - expectedBody: "Success!", - }, - { - route: "/", - authSource: "header", - authTokenName: "access_token", - description: "Testing Header:access_token with a wrong key", - APIKey: "WRONGKEY", - expectedCode: 401, - expectedBody: "missing or malformed API Key", - }, - - // cookie:access_token auth { route: "/", - authSource: "cookie", authTokenName: "access_token", - description: "Testing cookie:access_token", + description: "auth with correct key", APIKey: CorrectKey, expectedCode: 200, expectedBody: "Success!", }, { route: "/", - authSource: "cookie", authTokenName: "access_token", - description: "Testing cookie:access_token with a wrong key", - APIKey: "WRONGKEY", - expectedCode: 401, + description: "auth with no key", + APIKey: "", + expectedCode: 401, // 404 in case of param authentication expectedBody: "missing or malformed API Key", }, - - // query:access_token auth { route: "/", - authSource: "query", - authTokenName: "access_token", - description: "Testing query:access_token", - APIKey: CorrectKey, - expectedCode: 200, - expectedBody: "Success!", - }, - { - route: "/", - authSource: "query", - authTokenName: "access_token", - description: "Testing query:access_token with a wrong key", - APIKey: "WRONGKEY", - expectedCode: 401, - expectedBody: "missing or malformed API Key", - }, - - // param:access_token auth - { - route: "/key/", // will end as '/key/:access_token' - authSource: "param", - authTokenName: "access_token", - description: "Testing param:access_token", - APIKey: CorrectKey, - expectedCode: 200, - expectedBody: "Success!", - }, - { - route: "/key/", - authSource: "param", authTokenName: "access_token", - description: "Testing param:access_token with a wrong key", - APIKey: "WRONGKEY", - expectedCode: 401, - expectedBody: "missing or malformed API Key", - }, - - // form:access_token auth - { - route: "/", - authSource: "form", - authTokenName: "access_token", - description: "Testing form:access_token", - APIKey: CorrectKey, - expectedCode: 200, - expectedBody: "Success!", - }, - { - route: "/", - authSource: "form", - authTokenName: "access_token", - description: "Testing form:access_token with a wrong key", + description: "auth with wrong key", APIKey: "WRONGKEY", expectedCode: 401, expectedBody: "missing or malformed API Key", @@ -198,73 +124,84 @@ func TestAuthSources(t *testing.T) { } - for _, test := range tests { - // setup the fiber endpoint - // note that if UnescapePath: false (the default) - // escaped characters (such as `\"`) will not be handled correctly in the tests - app := fiber.New(fiber.Config{UnescapePath: true}) - authMiddleware := New(Config{ - KeyLookup: test.authSource + ":" + test.authTokenName, - Validator: func(c *fiber.Ctx, key string) (bool, error) { - if key == CorrectKey { - return true, nil + for _, authSource := range testSources { + t.Run(authSource, func(t *testing.T) { + for _, test := range tests { + // setup the fiber endpoint + // note that if UnescapePath: false (the default) + // escaped characters (such as `\"`) will not be handled correctly in the tests + app := fiber.New(fiber.Config{UnescapePath: true}) + + authMiddleware := New(Config{ + KeyLookup: authSource + ":" + test.authTokenName, + Validator: func(c *fiber.Ctx, key string) (bool, error) { + if key == CorrectKey { + return true, nil + } + return false, ErrMissingOrMalformedAPIKey + }, + }) + + var route string + if authSource == "param" { + route = test.route + ":" + test.authTokenName + app.Use(route, authMiddleware) + } else { + route = test.route + app.Use(authMiddleware) } - return false, ErrMissingOrMalformedAPIKey - }, - }) - var route string - if test.authSource == "param" { - route = test.route + ":" + test.authTokenName - app.Use(route, authMiddleware) - } else { - route = test.route - app.Use(authMiddleware) - } + app.Get(route, func(c *fiber.Ctx) error { + return c.SendString("Success!") + }) - app.Get(route, func(c *fiber.Ctx) error { - return c.SendString("Success!") - }) + // construct the test HTTP request + var req *http.Request + req, _ = http.NewRequest("GET", test.route, nil) + + // setup the apikey for the different auth schemes + if authSource == "header" { - - // construct the test HTTP request - var req *http.Request - req, _ = http.NewRequest("GET", test.route, nil) - - // setup the apikey for the different auth schemes - if test.authSource == "header" { + req.Header.Set(test.authTokenName, test.APIKey) - req.Header.Set(test.authTokenName, test.APIKey) + } else if authSource == "cookie" { - } else if test.authSource == "cookie" { - - req.Header.Set("Cookie", test.authTokenName + "=" + test.APIKey) + req.Header.Set("Cookie", test.authTokenName + "=" + test.APIKey) - } else if test.authSource == "query" || test.authSource == "form" { - - q := req.URL.Query() - q.Add(test.authTokenName, test.APIKey) - req.URL.RawQuery = q.Encode() + } else if authSource == "query" || authSource == "form" { - } else if test.authSource == "param" { - - r := req.URL.Path - r = r + url.PathEscape(test.APIKey) - req.URL.Path = r + q := req.URL.Query() + q.Add(test.authTokenName, test.APIKey) + req.URL.RawQuery = q.Encode() - } + } else if authSource == "param" { - res, err := app.Test(req, -1) + r := req.URL.Path + r = r + url.PathEscape(test.APIKey) + req.URL.Path = r - utils.AssertEqual(t, nil, err, test.description) + } - // test the body of the request - body, err := ioutil.ReadAll(res.Body) - utils.AssertEqual(t, test.expectedCode, res.StatusCode, test.description) + res, err := app.Test(req, -1) - // body - utils.AssertEqual(t, nil, err, test.description) - utils.AssertEqual(t, test.expectedBody, string(body), test.description) + utils.AssertEqual(t, nil, err, test.description) + + // test the body of the request + body, err := ioutil.ReadAll(res.Body) + // for param authentication, the route would be /:access_token + // when the access_token is empty, it leads to a 404 (not found) + // not a 401 (auth error) + if authSource == "param" && test.APIKey == "" { + test.expectedCode = 404 + test.expectedBody = "Cannot GET /" + } + utils.AssertEqual(t, test.expectedCode, res.StatusCode, test.description) + + // body + utils.AssertEqual(t, nil, err, test.description) + utils.AssertEqual(t, test.expectedBody, string(body), test.description) + } + }) } } From 2d84d5bfca0d3a488689810e38d167cefd0dab8b Mon Sep 17 00:00:00 2001 From: DavZim Date: Mon, 2 Jan 2023 13:47:10 +0100 Subject: [PATCH 15/15] remove redundant tests --- main_test.go | 68 ---------------------------------------------------- 1 file changed, 68 deletions(-) diff --git a/main_test.go b/main_test.go index b8e0d86..3cb3d33 100644 --- a/main_test.go +++ b/main_test.go @@ -15,74 +15,6 @@ import ( ) -func TestKeyAuth(t *testing.T) { - - // setup the fiber endpoint - app := fiber.New() - - app.Use(New(Config{ - KeyLookup: "header:key", - Validator: func(c *fiber.Ctx, key string) (bool, error) { - if key == "MySecretPassword" { - return true, nil - } - return false, ErrMissingOrMalformedAPIKey - }, - })) - - app.Get("/", func(c *fiber.Ctx) error { - return c.SendString("Successfully authenticated!") - }) - - // define test cases - tests := []struct { - description string - APIKey string - expectedCode int - expectedBody string - }{ - { - description: "Normal Authentication Case", - APIKey: "MySecretPassword", - expectedCode: 200, - expectedBody: "Successfully authenticated!", - }, - { - description: "Wrong API Key", - APIKey: "WRONG KEY", - expectedCode: 401, - expectedBody: "missing or malformed API Key", - }, - { - description: "Wrong API Key", - APIKey: "", // NO KEY - expectedCode: 401, - expectedBody: "missing or malformed API Key", - }, - } - - // run the tests - for _, test := range tests { - var req *http.Request - req, _ = http.NewRequest("GET", "/", nil) - if test.APIKey != "" { - req.Header.Set("key", test.APIKey) - } - - res, err := app.Test(req, -1) - - utils.AssertEqual(t, nil, err, test.description) - - // test the body of the request - body, err := ioutil.ReadAll(res.Body) - utils.AssertEqual(t, test.expectedCode, res.StatusCode, test.description) - - // body - utils.AssertEqual(t, nil, err, test.description) - utils.AssertEqual(t, test.expectedBody, string(body), test.description) - } -} - func TestAuthSources(t *testing.T) { var CorrectKey = "specials: !$%,.#\"!?~`<>@$^*(){}[]|/\\123"