1
0
Fork 0

Adding upstream version 2.52.6.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-17 06:50:16 +02:00
parent a960158181
commit 6d002e9543
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
441 changed files with 95392 additions and 0 deletions

View file

@ -0,0 +1,95 @@
package keyauth
import (
"errors"
"github.com/gofiber/fiber/v2"
)
// Config defines the config for middleware.
type Config struct {
// Next defines a function to skip middleware.
// Optional. Default: nil
Next func(*fiber.Ctx) bool
// SuccessHandler defines a function which is executed for a valid key.
// Optional. Default: nil
SuccessHandler fiber.Handler
// ErrorHandler defines a function which is executed for an invalid key.
// It may be used to define a custom error.
// Optional. Default: 401 Invalid or expired key
ErrorHandler fiber.ErrorHandler
// KeyLookup is a string in the form of "<source>:<name>" that is used
// to extract key from the request.
// Optional. Default value "header:Authorization".
// Possible values:
// - "header:<name>"
// - "query:<name>"
// - "form:<name>"
// - "param:<name>"
// - "cookie:<name>"
KeyLookup string
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
// Validator is a function to validate key.
Validator func(*fiber.Ctx, string) (bool, error)
// Context key to store the bearertoken from the token into context.
// Optional. Default: "token".
ContextKey interface{}
}
// ConfigDefault is the default config
var ConfigDefault = Config{
SuccessHandler: func(c *fiber.Ctx) error {
return c.Next()
},
ErrorHandler: func(c *fiber.Ctx, err error) error {
if errors.Is(err, ErrMissingOrMalformedAPIKey) {
return c.Status(fiber.StatusUnauthorized).SendString(err.Error())
}
return c.Status(fiber.StatusUnauthorized).SendString("Invalid or expired API Key")
},
KeyLookup: "header:" + fiber.HeaderAuthorization,
AuthScheme: "Bearer",
ContextKey: "token",
}
// Helper function to set default values
func configDefault(config ...Config) Config {
// Return default config if nothing provided
if len(config) < 1 {
return ConfigDefault
}
// Override default config
cfg := config[0]
// Set default values
if cfg.SuccessHandler == nil {
cfg.SuccessHandler = ConfigDefault.SuccessHandler
}
if cfg.ErrorHandler == nil {
cfg.ErrorHandler = ConfigDefault.ErrorHandler
}
if cfg.KeyLookup == "" {
cfg.KeyLookup = ConfigDefault.KeyLookup
// set AuthScheme as "Bearer" only if KeyLookup is set to default.
if cfg.AuthScheme == "" {
cfg.AuthScheme = ConfigDefault.AuthScheme
}
}
if cfg.Validator == nil {
panic("fiber: keyauth middleware requires a validator function")
}
if cfg.ContextKey == nil {
cfg.ContextKey = ConfigDefault.ContextKey
}
return cfg
}

View file

@ -0,0 +1,121 @@
// Special thanks to Echo: https://github.com/labstack/echo/blob/master/middleware/key_auth.go
package keyauth
import (
"errors"
"net/url"
"strings"
"github.com/gofiber/fiber/v2"
)
// When there is no request of the key thrown ErrMissingOrMalformedAPIKey
var ErrMissingOrMalformedAPIKey = errors.New("missing or malformed API Key")
const (
query = "query"
form = "form"
param = "param"
cookie = "cookie"
)
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Init config
cfg := configDefault(config...)
// Initialize
parts := strings.Split(cfg.KeyLookup, ":")
extractor := keyFromHeader(parts[1], cfg.AuthScheme)
switch parts[0] {
case query:
extractor = keyFromQuery(parts[1])
case form:
extractor = keyFromForm(parts[1])
case param:
extractor = keyFromParam(parts[1])
case cookie:
extractor = keyFromCookie(parts[1])
}
// Return middleware handler
return func(c *fiber.Ctx) error {
// Filter request to skip middleware
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Extract and verify key
key, err := extractor(c)
if err != nil {
return cfg.ErrorHandler(c, err)
}
valid, err := cfg.Validator(c, key)
if err == nil && valid {
c.Locals(cfg.ContextKey, key)
return cfg.SuccessHandler(c)
}
return cfg.ErrorHandler(c, err)
}
}
// keyFromHeader returns a function that extracts api key from the request header.
func keyFromHeader(header, authScheme string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
auth := c.Get(header)
l := len(authScheme)
if len(auth) > 0 && l == 0 {
return auth, nil
}
if len(auth) > l+1 && auth[:l] == authScheme {
return auth[l+1:], nil
}
return "", ErrMissingOrMalformedAPIKey
}
}
// keyFromQuery returns a function that extracts api key from the query string.
func keyFromQuery(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
key := c.Query(param)
if key == "" {
return "", ErrMissingOrMalformedAPIKey
}
return key, nil
}
}
// keyFromForm returns a function that extracts api key from the form.
func keyFromForm(param string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
key := c.FormValue(param)
if key == "" {
return "", ErrMissingOrMalformedAPIKey
}
return key, nil
}
}
// 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, err := url.PathUnescape(c.Params(param))
if err != nil {
return "", ErrMissingOrMalformedAPIKey
}
return key, nil
}
}
// keyFromCookie returns a function that extracts api key from the named cookie.
func keyFromCookie(name string) func(c *fiber.Ctx) (string, error) {
return func(c *fiber.Ctx) (string, error) {
key := c.Cookies(name)
if key == "" {
return "", ErrMissingOrMalformedAPIKey
}
return key, nil
}
}

View file

@ -0,0 +1,461 @@
//nolint:bodyclose // Much easier to just ignore memory leaks in tests
package keyauth
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/utils"
)
const CorrectKey = "specials: !$%,.#\"!?~`<>@$^*(){}[]|/\\123"
func TestAuthSources(t *testing.T) {
// define test cases
testSources := []string{"header", "cookie", "query", "param", "form"}
tests := []struct {
route string
authTokenName string
description string
APIKey string
expectedCode int
expectedBody string
}{
{
route: "/",
authTokenName: "access_token",
description: "auth with correct key",
APIKey: CorrectKey,
expectedCode: 200,
expectedBody: "Success!",
},
{
route: "/",
authTokenName: "access_token",
description: "auth with no key",
APIKey: "",
expectedCode: 401, // 404 in case of param authentication
expectedBody: "missing or malformed API Key",
},
{
route: "/",
authTokenName: "access_token",
description: "auth with wrong key",
APIKey: "WRONGKEY",
expectedCode: 401,
expectedBody: "missing or malformed API Key",
},
}
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)
}
app.Get(route, func(c *fiber.Ctx) error {
return c.SendString("Success!")
})
// construct the test HTTP request
var req *http.Request
req, err := http.NewRequestWithContext(context.Background(), fiber.MethodGet, test.route, nil)
utils.AssertEqual(t, err, nil)
// setup the apikey for the different auth schemes
if authSource == "header" {
req.Header.Set(test.authTokenName, test.APIKey)
} else if authSource == "cookie" {
req.Header.Set("Cookie", test.authTokenName+"="+test.APIKey)
} else if authSource == "query" || authSource == "form" {
q := req.URL.Query()
q.Add(test.authTokenName, test.APIKey)
req.URL.RawQuery = q.Encode()
} else if authSource == "param" {
r := req.URL.Path
r += url.PathEscape(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 := io.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)
err = res.Body.Close()
utils.AssertEqual(t, err, nil)
}
})
}
}
func TestMultipleKeyAuth(t *testing.T) {
// setup the fiber endpoint
app := fiber.New()
// setup keyauth for /auth1
app.Use(New(Config{
Next: func(c *fiber.Ctx) bool {
return c.OriginalURL() != "/auth1"
},
KeyLookup: "header:key",
Validator: func(c *fiber.Ctx, key string) (bool, error) {
if key == "password1" {
return true, nil
}
return false, ErrMissingOrMalformedAPIKey
},
}))
// setup keyauth for /auth2
app.Use(New(Config{
Next: func(c *fiber.Ctx) bool {
return c.OriginalURL() != "/auth2"
},
KeyLookup: "header:key",
Validator: func(c *fiber.Ctx, key string) (bool, error) {
if key == "password2" {
return true, nil
}
return false, ErrMissingOrMalformedAPIKey
},
}))
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, err := http.NewRequestWithContext(context.Background(), fiber.MethodGet, test.route, nil)
utils.AssertEqual(t, err, 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 := io.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 TestCustomSuccessAndFailureHandlers(t *testing.T) {
app := fiber.New()
app.Use(New(Config{
SuccessHandler: func(c *fiber.Ctx) error {
return c.Status(fiber.StatusOK).SendString("API key is valid and request was handled by custom success handler")
},
ErrorHandler: func(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusUnauthorized).SendString("API key is invalid and request was handled by custom error handler")
},
Validator: func(c *fiber.Ctx, key string) (bool, error) {
if key == CorrectKey {
return true, nil
}
return false, ErrMissingOrMalformedAPIKey
},
}))
// Define a test handler that should not be called
app.Get("/", func(c *fiber.Ctx) error {
t.Error("Test handler should not be called")
return nil
})
// Create a request without an API key and send it to the app
res, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err := io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusUnauthorized)
utils.AssertEqual(t, string(body), "API key is invalid and request was handled by custom error handler")
// Create a request with a valid API key in the Authorization header
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", CorrectKey))
// Send the request to the app
res, err = app.Test(req)
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err = io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusOK)
utils.AssertEqual(t, string(body), "API key is valid and request was handled by custom success handler")
}
func TestCustomNextFunc(t *testing.T) {
app := fiber.New()
app.Use(New(Config{
Next: func(c *fiber.Ctx) bool {
return c.Path() == "/allowed"
},
Validator: func(c *fiber.Ctx, key string) (bool, error) {
if key == CorrectKey {
return true, nil
}
return false, ErrMissingOrMalformedAPIKey
},
}))
// Define a test handler
app.Get("/allowed", func(c *fiber.Ctx) error {
return c.SendString("API key is valid and request was allowed by custom filter")
})
// Create a request with the "/allowed" path and send it to the app
req := httptest.NewRequest(fiber.MethodGet, "/allowed", nil)
res, err := app.Test(req)
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err := io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusOK)
utils.AssertEqual(t, string(body), "API key is valid and request was allowed by custom filter")
// Create a request with a different path and send it to the app without correct key
req = httptest.NewRequest(fiber.MethodGet, "/not-allowed", nil)
res, err = app.Test(req)
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err = io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusUnauthorized)
utils.AssertEqual(t, string(body), ErrMissingOrMalformedAPIKey.Error())
// Create a request with a different path and send it to the app with correct key
req = httptest.NewRequest(fiber.MethodGet, "/not-allowed", nil)
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", CorrectKey))
res, err = app.Test(req)
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err = io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusUnauthorized)
utils.AssertEqual(t, string(body), ErrMissingOrMalformedAPIKey.Error())
}
func TestAuthSchemeToken(t *testing.T) {
app := fiber.New()
app.Use(New(Config{
AuthScheme: "Token",
Validator: func(c *fiber.Ctx, key string) (bool, error) {
if key == CorrectKey {
return true, nil
}
return false, ErrMissingOrMalformedAPIKey
},
}))
// Define a test handler
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("API key is valid")
})
// Create a request with a valid API key in the "Token" Authorization header
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Header.Add("Authorization", fmt.Sprintf("Token %s", CorrectKey))
// Send the request to the app
res, err := app.Test(req)
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err := io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusOK)
utils.AssertEqual(t, string(body), "API key is valid")
}
func TestAuthSchemeBasic(t *testing.T) {
app := fiber.New()
app.Use(New(Config{
KeyLookup: "header:Authorization",
AuthScheme: "Basic",
Validator: func(c *fiber.Ctx, key string) (bool, error) {
if key == CorrectKey {
return true, nil
}
return false, ErrMissingOrMalformedAPIKey
},
}))
// Define a test handler
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("API key is valid")
})
// Create a request without an API key and Send the request to the app
res, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err := io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusUnauthorized)
utils.AssertEqual(t, string(body), ErrMissingOrMalformedAPIKey.Error())
// Create a request with a valid API key in the "Authorization" header using the "Basic" scheme
req := httptest.NewRequest(fiber.MethodGet, "/", nil)
req.Header.Add("Authorization", fmt.Sprintf("Basic %s", CorrectKey))
// Send the request to the app
res, err = app.Test(req)
utils.AssertEqual(t, err, nil)
// Read the response body into a string
body, err = io.ReadAll(res.Body)
utils.AssertEqual(t, err, nil)
// Check that the response has the expected status code and body
utils.AssertEqual(t, res.StatusCode, http.StatusOK)
utils.AssertEqual(t, string(body), "API key is valid")
}