summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/labstack
diff options
context:
space:
mode:
authorWim <wim@42.be>2022-03-12 19:41:07 +0100
committerGitHub <noreply@github.com>2022-03-12 19:41:07 +0100
commitb3be2e208cb373207d6199cac5a9fc92be073e7a (patch)
tree6de6d444737edee8f0476eea87c233fa980f0002 /vendor/github.com/labstack
parentc30e90ff3f7e9ff96ac79ed4b7d90d6346216a15 (diff)
downloadmatterbridge-msglm-b3be2e208cb373207d6199cac5a9fc92be073e7a.tar.gz
matterbridge-msglm-b3be2e208cb373207d6199cac5a9fc92be073e7a.tar.bz2
matterbridge-msglm-b3be2e208cb373207d6199cac5a9fc92be073e7a.zip
Update dependencies and vendor (#1761)
Diffstat (limited to 'vendor/github.com/labstack')
-rw-r--r--vendor/github.com/labstack/echo/v4/CHANGELOG.md21
-rw-r--r--vendor/github.com/labstack/echo/v4/README.md16
-rw-r--r--vendor/github.com/labstack/echo/v4/bind.go10
-rw-r--r--vendor/github.com/labstack/echo/v4/context.go32
-rw-r--r--vendor/github.com/labstack/echo/v4/context_fs.go33
-rw-r--r--vendor/github.com/labstack/echo/v4/context_fs_go1.16.go52
-rw-r--r--vendor/github.com/labstack/echo/v4/echo.go85
-rw-r--r--vendor/github.com/labstack/echo/v4/echo_fs.go62
-rw-r--r--vendor/github.com/labstack/echo/v4/echo_fs_go1.16.go145
-rw-r--r--vendor/github.com/labstack/echo/v4/group.go5
-rw-r--r--vendor/github.com/labstack/echo/v4/group_fs.go9
-rw-r--r--vendor/github.com/labstack/echo/v4/group_fs_go1.16.go33
-rw-r--r--vendor/github.com/labstack/echo/v4/ip.go142
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/cors.go69
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/csrf.go110
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/extractor.go184
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/jwt.go209
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/key_auth.go173
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/middleware.go4
-rw-r--r--vendor/github.com/labstack/echo/v4/middleware/recover.go21
-rw-r--r--vendor/github.com/labstack/echo/v4/router.go95
21 files changed, 1081 insertions, 429 deletions
diff --git a/vendor/github.com/labstack/echo/v4/CHANGELOG.md b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
index 372ed13c..461ac89c 100644
--- a/vendor/github.com/labstack/echo/v4/CHANGELOG.md
+++ b/vendor/github.com/labstack/echo/v4/CHANGELOG.md
@@ -1,5 +1,26 @@
# Changelog
+## v4.7.0 - 2022-03-01
+
+**Enhancements**
+
+* Add JWT, KeyAuth, CSRF multivalue extractors [#2060](https://github.com/labstack/echo/pull/2060)
+* Add LogErrorFunc to recover middleware [#2072](https://github.com/labstack/echo/pull/2072)
+* Add support for HEAD method query params binding [#2027](https://github.com/labstack/echo/pull/2027)
+* Improve filesystem support with echo.FileFS, echo.StaticFS, group.FileFS, group.StaticFS [#2064](https://github.com/labstack/echo/pull/2064)
+
+**Fixes**
+
+* Fix X-Real-IP bug, improve tests [#2007](https://github.com/labstack/echo/pull/2007)
+* Minor syntax fixes [#1994](https://github.com/labstack/echo/pull/1994), [#2102](https://github.com/labstack/echo/pull/2102), [#2102](https://github.com/labstack/echo/pull/2102)
+
+**General**
+
+* Add cache-control and connection headers [#2103](https://github.com/labstack/echo/pull/2103)
+* Add Retry-After header constant [#2078](https://github.com/labstack/echo/pull/2078)
+* Upgrade `go` directive in `go.mod` to 1.17 [#2049](https://github.com/labstack/echo/pull/2049)
+* Add Pagoda [#2077](https://github.com/labstack/echo/pull/2077) and Souin [#2069](https://github.com/labstack/echo/pull/2069) to 3rd-party middlewares in README
+
## v4.6.3 - 2022-01-10
**Fixes**
diff --git a/vendor/github.com/labstack/echo/v4/README.md b/vendor/github.com/labstack/echo/v4/README.md
index 930cb034..8b2321f0 100644
--- a/vendor/github.com/labstack/echo/v4/README.md
+++ b/vendor/github.com/labstack/echo/v4/README.md
@@ -5,7 +5,6 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
-[![Join the chat at https://gitter.im/labstack/echo](https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg?style=flat-square)](https://gitter.im/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
@@ -92,10 +91,23 @@ func hello(c echo.Context) error {
}
```
+# Third-party middlewares
+
+| Repository | Description |
+|------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [github.com/labstack/echo-contrib](https://github.com/labstack/echo-contrib) | (by Echo team) [casbin](https://github.com/casbin/casbin), [gorilla/sessions](https://github.com/gorilla/sessions), [jaegertracing](github.com/uber/jaeger-client-go), [prometheus](https://github.com/prometheus/client_golang/), [pprof](https://pkg.go.dev/net/http/pprof), [zipkin](https://github.com/openzipkin/zipkin-go) middlewares |
+| [deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen) | Automatically generate RESTful API documentation with [OpenAPI](https://swagger.io/specification/) Client and Server Code Generator |
+| [github.com/swaggo/echo-swagger](https://github.com/swaggo/echo-swagger) | Automatically generate RESTful API documentation with [Swagger](https://swagger.io/) 2.0. |
+| [github.com/ziflex/lecho](https://github.com/ziflex/lecho) | [Zerolog](https://github.com/rs/zerolog) logging library wrapper for Echo logger interface. |
+| [github.com/brpaz/echozap](https://github.com/brpaz/echozap) | UberĀ“s [Zap](https://github.com/uber-go/zap) logging library wrapper for Echo logger interface. |
+| [github.com/darkweak/souin/plugins/echo](https://github.com/darkweak/souin/tree/master/plugins/echo) | HTTP cache system based on [Souin](https://github.com/darkweak/souin) to automatically get your endpoints cached. It supports some distributed and non-distributed storage systems depending your needs. |
+| [github.com/mikestefanello/pagoda](https://github.com/mikestefanello/pagoda) | Rapid, easy full-stack web development starter kit built with Echo.
+
+Please send a PR to add your own library here.
+
## Help
- [Forum](https://github.com/labstack/echo/discussions)
-- [Chat](https://gitter.im/labstack/echo)
## Contribute
diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go
index fdf0524c..c841ca01 100644
--- a/vendor/github.com/labstack/echo/v4/bind.go
+++ b/vendor/github.com/labstack/echo/v4/bind.go
@@ -111,11 +111,11 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.BindPathParams(c, i); err != nil {
return err
}
- // Issue #1670 - Query params are binded only for GET/DELETE and NOT for usual request with body (POST/PUT/PATCH)
- // Reasoning here is that parameters in query and bind destination struct could have UNEXPECTED matches and results due that.
- // i.e. is `&id=1&lang=en` from URL same as `{"id":100,"lang":"de"}` request body and which one should have priority when binding.
- // This HTTP method check restores pre v4.1.11 behavior and avoids different problems when query is mixed with body
- if c.Request().Method == http.MethodGet || c.Request().Method == http.MethodDelete {
+ // Only bind query parameters for GET/DELETE/HEAD to avoid unexpected behavior with destination struct binding from body.
+ // For example a request URL `&id=1&lang=en` with body `{"id":100,"lang":"de"}` would lead to precedence issues.
+ // The HTTP method check restores pre-v4.1.11 behavior to avoid these problems (see issue #1670)
+ method := c.Request().Method
+ if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
if err = b.BindQueryParams(c, i); err != nil {
return err
}
diff --git a/vendor/github.com/labstack/echo/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go
index 91ab6e48..a4ecfadf 100644
--- a/vendor/github.com/labstack/echo/v4/context.go
+++ b/vendor/github.com/labstack/echo/v4/context.go
@@ -9,8 +9,6 @@ import (
"net"
"net/http"
"net/url"
- "os"
- "path/filepath"
"strings"
"sync"
)
@@ -211,6 +209,13 @@ type (
)
const (
+ // ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
+ // Allow header is mandatory for status 405 (method not found) and useful for OPTIONS method requests.
+ // It is added to context only when Router does not find matching method handler for request.
+ ContextKeyHeaderAllow = "echo_header_allow"
+)
+
+const (
defaultMemory = 32 << 20 // 32 MB
indexPage = "index.html"
defaultIndent = " "
@@ -562,29 +567,6 @@ func (c *context) Stream(code int, contentType string, r io.Reader) (err error)
return
}
-func (c *context) File(file string) (err error) {
- f, err := os.Open(file)
- if err != nil {
- return NotFoundHandler(c)
- }
- defer f.Close()
-
- fi, _ := f.Stat()
- if fi.IsDir() {
- file = filepath.Join(file, indexPage)
- f, err = os.Open(file)
- if err != nil {
- return NotFoundHandler(c)
- }
- defer f.Close()
- if fi, err = f.Stat(); err != nil {
- return
- }
- }
- http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
- return
-}
-
func (c *context) Attachment(file, name string) error {
return c.contentDisposition(file, name, "attachment")
}
diff --git a/vendor/github.com/labstack/echo/v4/context_fs.go b/vendor/github.com/labstack/echo/v4/context_fs.go
new file mode 100644
index 00000000..11ee84bc
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/context_fs.go
@@ -0,0 +1,33 @@
+//go:build !go1.16
+// +build !go1.16
+
+package echo
+
+import (
+ "net/http"
+ "os"
+ "path/filepath"
+)
+
+func (c *context) File(file string) (err error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return NotFoundHandler(c)
+ }
+ defer f.Close()
+
+ fi, _ := f.Stat()
+ if fi.IsDir() {
+ file = filepath.Join(file, indexPage)
+ f, err = os.Open(file)
+ if err != nil {
+ return NotFoundHandler(c)
+ }
+ defer f.Close()
+ if fi, err = f.Stat(); err != nil {
+ return
+ }
+ }
+ http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
+ return
+}
diff --git a/vendor/github.com/labstack/echo/v4/context_fs_go1.16.go b/vendor/github.com/labstack/echo/v4/context_fs_go1.16.go
new file mode 100644
index 00000000..c1c724af
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/context_fs_go1.16.go
@@ -0,0 +1,52 @@
+//go:build go1.16
+// +build go1.16
+
+package echo
+
+import (
+ "errors"
+ "io"
+ "io/fs"
+ "net/http"
+ "path/filepath"
+)
+
+func (c *context) File(file string) error {
+ return fsFile(c, file, c.echo.Filesystem)
+}
+
+// FileFS serves file from given file system.
+//
+// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
+// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
+// including `assets/images` as their prefix.
+func (c *context) FileFS(file string, filesystem fs.FS) error {
+ return fsFile(c, file, filesystem)
+}
+
+func fsFile(c Context, file string, filesystem fs.FS) error {
+ f, err := filesystem.Open(file)
+ if err != nil {
+ return ErrNotFound
+ }
+ defer f.Close()
+
+ fi, _ := f.Stat()
+ if fi.IsDir() {
+ file = filepath.ToSlash(filepath.Join(file, indexPage)) // ToSlash is necessary for Windows. fs.Open and os.Open are different in that aspect.
+ f, err = filesystem.Open(file)
+ if err != nil {
+ return ErrNotFound
+ }
+ defer f.Close()
+ if fi, err = f.Stat(); err != nil {
+ return err
+ }
+ }
+ ff, ok := f.(io.ReadSeeker)
+ if !ok {
+ return errors.New("file does not implement io.ReadSeeker")
+ }
+ http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), ff)
+ return nil
+}
diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go
index 1a60fb07..143f9ffe 100644
--- a/vendor/github.com/labstack/echo/v4/echo.go
+++ b/vendor/github.com/labstack/echo/v4/echo.go
@@ -47,9 +47,6 @@ import (
stdLog "log"
"net"
"net/http"
- "net/url"
- "os"
- "path/filepath"
"reflect"
"runtime"
"sync"
@@ -66,6 +63,7 @@ import (
type (
// Echo is the top-level framework instance.
Echo struct {
+ filesystem
common
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// listener address info (on which interface/port was listener binded) without having data races.
@@ -77,7 +75,6 @@ type (
maxParam *int
router *Router
routers map[string]*Router
- notFoundHandler HandlerFunc
pool sync.Pool
Server *http.Server
TLSServer *http.Server
@@ -113,10 +110,10 @@ type (
}
// MiddlewareFunc defines a function to process middleware.
- MiddlewareFunc func(HandlerFunc) HandlerFunc
+ MiddlewareFunc func(next HandlerFunc) HandlerFunc
// HandlerFunc defines a function to serve HTTP requests.
- HandlerFunc func(Context) error
+ HandlerFunc func(c Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, Context)
@@ -190,8 +187,12 @@ const (
// Headers
const (
- HeaderAccept = "Accept"
- HeaderAcceptEncoding = "Accept-Encoding"
+ HeaderAccept = "Accept"
+ HeaderAcceptEncoding = "Accept-Encoding"
+ // HeaderAllow is the name of the "Allow" header field used to list the set of methods
+ // advertised as supported by the target resource. Returning an Allow header is mandatory
+ // for status 405 (method not found) and useful for the OPTIONS method in responses.
+ // See RFC 7231: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1
HeaderAllow = "Allow"
HeaderAuthorization = "Authorization"
HeaderContentDisposition = "Content-Disposition"
@@ -203,6 +204,7 @@ const (
HeaderIfModifiedSince = "If-Modified-Since"
HeaderLastModified = "Last-Modified"
HeaderLocation = "Location"
+ HeaderRetryAfter = "Retry-After"
HeaderUpgrade = "Upgrade"
HeaderVary = "Vary"
HeaderWWWAuthenticate = "WWW-Authenticate"
@@ -212,12 +214,14 @@ const (
HeaderXForwardedSsl = "X-Forwarded-Ssl"
HeaderXUrlScheme = "X-Url-Scheme"
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
- HeaderXRealIP = "X-Real-IP"
- HeaderXRequestID = "X-Request-ID"
- HeaderXCorrelationID = "X-Correlation-ID"
+ HeaderXRealIP = "X-Real-Ip"
+ HeaderXRequestID = "X-Request-Id"
+ HeaderXCorrelationID = "X-Correlation-Id"
HeaderXRequestedWith = "X-Requested-With"
HeaderServer = "Server"
HeaderOrigin = "Origin"
+ HeaderCacheControl = "Cache-Control"
+ HeaderConnection = "Connection"
// Access control
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
@@ -242,7 +246,7 @@ const (
const (
// Version of Echo
- Version = "4.6.3"
+ Version = "4.7.0"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
@@ -302,6 +306,12 @@ var (
}
MethodNotAllowedHandler = func(c Context) error {
+ // See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
+ // response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
+ routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
+ if ok && routerAllowMethods != "" {
+ c.Response().Header().Set(HeaderAllow, routerAllowMethods)
+ }
return ErrMethodNotAllowed
}
)
@@ -309,8 +319,9 @@ var (
// New creates an instance of Echo.
func New() (e *Echo) {
e = &Echo{
- Server: new(http.Server),
- TLSServer: new(http.Server),
+ filesystem: createFilesystem(),
+ Server: new(http.Server),
+ TLSServer: new(http.Server),
AutoTLSManager: autocert.Manager{
Prompt: autocert.AcceptTOS,
},
@@ -489,50 +500,6 @@ func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middlew
return routes
}
-// Static registers a new route with path prefix to serve static files from the
-// provided root directory.
-func (e *Echo) Static(prefix, root string) *Route {
- if root == "" {
- root = "." // For security we want to restrict to CWD.
- }
- return e.static(prefix, root, e.GET)
-}
-
-func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
- h := func(c Context) error {
- p, err := url.PathUnescape(c.Param("*"))
- if err != nil {
- return err
- }
-
- name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
- fi, err := os.Stat(name)
- if err != nil {
- // The access path does not exist
- return NotFoundHandler(c)
- }
-
- // If the request is for a directory and does not end with "/"
- p = c.Request().URL.Path // path must not be empty.
- if fi.IsDir() && p[len(p)-1] != '/' {
- // Redirect to ends with "/"
- return c.Redirect(http.StatusMovedPermanently, p+"/")
- }
- return c.File(name)
- }
- // Handle added routes based on trailing slash:
- // /prefix => exact route "/prefix" + any route "/prefix/*"
- // /prefix/ => only any route "/prefix/*"
- if prefix != "" {
- if prefix[len(prefix)-1] == '/' {
- // Only add any route for intentional trailing slash
- return get(prefix+"*", h)
- }
- get(prefix, h)
- }
- return get(prefix+"/*", h)
-}
-
func (common) file(path, file string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route,
m ...MiddlewareFunc) *Route {
return get(path, func(c Context) error {
@@ -643,7 +610,7 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context
c := e.pool.Get().(*context)
c.Reset(r, w)
- h := NotFoundHandler
+ var h func(Context) error
if e.premiddleware == nil {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
diff --git a/vendor/github.com/labstack/echo/v4/echo_fs.go b/vendor/github.com/labstack/echo/v4/echo_fs.go
new file mode 100644
index 00000000..c3790545
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/echo_fs.go
@@ -0,0 +1,62 @@
+//go:build !go1.16
+// +build !go1.16
+
+package echo
+
+import (
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+)
+
+type filesystem struct {
+}
+
+func createFilesystem() filesystem {
+ return filesystem{}
+}
+
+// Static registers a new route with path prefix to serve static files from the
+// provided root directory.
+func (e *Echo) Static(prefix, root string) *Route {
+ if root == "" {
+ root = "." // For security we want to restrict to CWD.
+ }
+ return e.static(prefix, root, e.GET)
+}
+
+func (common) static(prefix, root string, get func(string, HandlerFunc, ...MiddlewareFunc) *Route) *Route {
+ h := func(c Context) error {
+ p, err := url.PathUnescape(c.Param("*"))
+ if err != nil {
+ return err
+ }
+
+ name := filepath.Join(root, filepath.Clean("/"+p)) // "/"+ for security
+ fi, err := os.Stat(name)
+ if err != nil {
+ // The access path does not exist
+ return NotFoundHandler(c)
+ }
+
+ // If the request is for a directory and does not end with "/"
+ p = c.Request().URL.Path // path must not be empty.
+ if fi.IsDir() && p[len(p)-1] != '/' {
+ // Redirect to ends with "/"
+ return c.Redirect(http.StatusMovedPermanently, p+"/")
+ }
+ return c.File(name)
+ }
+ // Handle added routes based on trailing slash:
+ // /prefix => exact route "/prefix" + any route "/prefix/*"
+ // /prefix/ => only any route "/prefix/*"
+ if prefix != "" {
+ if prefix[len(prefix)-1] == '/' {
+ // Only add any route for intentional trailing slash
+ return get(prefix+"*", h)
+ }
+ get(prefix, h)
+ }
+ return get(prefix+"/*", h)
+}
diff --git a/vendor/github.com/labstack/echo/v4/echo_fs_go1.16.go b/vendor/github.com/labstack/echo/v4/echo_fs_go1.16.go
new file mode 100644
index 00000000..435459de
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/echo_fs_go1.16.go
@@ -0,0 +1,145 @@
+//go:build go1.16
+// +build go1.16
+
+package echo
+
+import (
+ "fmt"
+ "io/fs"
+ "net/http"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+type filesystem struct {
+ // Filesystem is file system used by Static and File handlers to access files.
+ // Defaults to os.DirFS(".")
+ //
+ // When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
+ // prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
+ // including `assets/images` as their prefix.
+ Filesystem fs.FS
+}
+
+func createFilesystem() filesystem {
+ return filesystem{
+ Filesystem: newDefaultFS(),
+ }
+}
+
+// Static registers a new route with path prefix to serve static files from the provided root directory.
+func (e *Echo) Static(pathPrefix, fsRoot string) *Route {
+ subFs := MustSubFS(e.Filesystem, fsRoot)
+ return e.Add(
+ http.MethodGet,
+ pathPrefix+"*",
+ StaticDirectoryHandler(subFs, false),
+ )
+}
+
+// StaticFS registers a new route with path prefix to serve static files from the provided file system.
+//
+// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
+// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
+// including `assets/images` as their prefix.
+func (e *Echo) StaticFS(pathPrefix string, filesystem fs.FS) *Route {
+ return e.Add(
+ http.MethodGet,
+ pathPrefix+"*",
+ StaticDirectoryHandler(filesystem, false),
+ )
+}
+
+// StaticDirectoryHandler creates handler function to serve files from provided file system
+// When disablePathUnescaping is set then file name from path is not unescaped and is served as is.
+func StaticDirectoryHandler(fileSystem fs.FS, disablePathUnescaping bool) HandlerFunc {
+ return func(c Context) error {
+ p := c.Param("*")
+ if !disablePathUnescaping { // when router is already unescaping we do not want to do is twice
+ tmpPath, err := url.PathUnescape(p)
+ if err != nil {
+ return fmt.Errorf("failed to unescape path variable: %w", err)
+ }
+ p = tmpPath
+ }
+
+ // fs.FS.Open() already assumes that file names are relative to FS root path and considers name with prefix `/` as invalid
+ name := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(p, "/")))
+ fi, err := fs.Stat(fileSystem, name)
+ if err != nil {
+ return ErrNotFound
+ }
+
+ // If the request is for a directory and does not end with "/"
+ p = c.Request().URL.Path // path must not be empty.
+ if fi.IsDir() && len(p) > 0 && p[len(p)-1] != '/' {
+ // Redirect to ends with "/"
+ return c.Redirect(http.StatusMovedPermanently, p+"/")
+ }
+ return fsFile(c, name, fileSystem)
+ }
+}
+
+// FileFS registers a new route with path to serve file from the provided file system.
+func (e *Echo) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
+ return e.GET(path, StaticFileHandler(file, filesystem), m...)
+}
+
+// StaticFileHandler creates handler function to serve file from provided file system
+func StaticFileHandler(file string, filesystem fs.FS) HandlerFunc {
+ return func(c Context) error {
+ return fsFile(c, file, filesystem)
+ }
+}
+
+// defaultFS emulates os.Open behaviour with filesystem opened by `os.DirFs`. Difference between `os.Open` and `fs.Open`
+// is that FS does not allow to open path that start with `..` or `/` etc. For example previously you could have `../images`
+// in your application but `fs := os.DirFS("./")` would not allow you to use `fs.Open("../images")` and this would break
+// all old applications that rely on being able to traverse up from current executable run path.
+// NB: private because you really should use fs.FS implementation instances
+type defaultFS struct {
+ prefix string
+ fs fs.FS
+}
+
+func newDefaultFS() *defaultFS {
+ dir, _ := os.Getwd()
+ return &defaultFS{
+ prefix: dir,
+ fs: os.DirFS(dir),
+ }
+}
+
+func (fs defaultFS) Open(name string) (fs.File, error) {
+ return fs.fs.Open(name)
+}
+
+func subFS(currentFs fs.FS, root string) (fs.FS, error) {
+ root = filepath.ToSlash(filepath.Clean(root)) // note: fs.FS operates only with slashes. `ToSlash` is necessary for Windows
+ if dFS, ok := currentFs.(*defaultFS); ok {
+ // we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS to
+ // allow cases when root is given as `../somepath` which is not valid for fs.FS
+ root = filepath.Join(dFS.prefix, root)
+ return &defaultFS{
+ prefix: root,
+ fs: os.DirFS(root),
+ }, nil
+ }
+ return fs.Sub(currentFs, root)
+}
+
+// MustSubFS creates sub FS from current filesystem or panic on failure.
+// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
+//
+// MustSubFS is helpful when dealing with `embed.FS` because for example `//go:embed assets/images` embeds files with
+// paths including `assets/images` as their prefix. In that case use `fs := echo.MustSubFS(fs, "rootDirectory") to
+// create sub fs which uses necessary prefix for directory path.
+func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS {
+ subFs, err := subFS(currentFs, fsRoot)
+ if err != nil {
+ panic(fmt.Errorf("can not create sub FS, invalid root given, err: %w", err))
+ }
+ return subFs
+}
diff --git a/vendor/github.com/labstack/echo/v4/group.go b/vendor/github.com/labstack/echo/v4/group.go
index 426bef9e..bba470ce 100644
--- a/vendor/github.com/labstack/echo/v4/group.go
+++ b/vendor/github.com/labstack/echo/v4/group.go
@@ -102,11 +102,6 @@ func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) (sg *Group) {
return
}
-// Static implements `Echo#Static()` for sub-routes within the Group.
-func (g *Group) Static(prefix, root string) {
- g.static(prefix, root, g.GET)
-}
-
// File implements `Echo#File()` for sub-routes within the Group.
func (g *Group) File(path, file string) {
g.file(path, file, g.GET)
diff --git a/vendor/github.com/labstack/echo/v4/group_fs.go b/vendor/github.com/labstack/echo/v4/group_fs.go
new file mode 100644
index 00000000..0a1ce4a9
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/group_fs.go
@@ -0,0 +1,9 @@
+//go:build !go1.16
+// +build !go1.16
+
+package echo
+
+// Static implements `Echo#Static()` for sub-routes within the Group.
+func (g *Group) Static(prefix, root string) {
+ g.static(prefix, root, g.GET)
+}
diff --git a/vendor/github.com/labstack/echo/v4/group_fs_go1.16.go b/vendor/github.com/labstack/echo/v4/group_fs_go1.16.go
new file mode 100644
index 00000000..2ba52b5e
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/group_fs_go1.16.go
@@ -0,0 +1,33 @@
+//go:build go1.16
+// +build go1.16
+
+package echo
+
+import (
+ "io/fs"
+ "net/http"
+)
+
+// Static implements `Echo#Static()` for sub-routes within the Group.
+func (g *Group) Static(pathPrefix, fsRoot string) {
+ subFs := MustSubFS(g.echo.Filesystem, fsRoot)
+ g.StaticFS(pathPrefix, subFs)
+}
+
+// StaticFS implements `Echo#StaticFS()` for sub-routes within the Group.
+//
+// When dealing with `embed.FS` use `fs := echo.MustSubFS(fs, "rootDirectory") to create sub fs which uses necessary
+// prefix for directory path. This is necessary as `//go:embed assets/images` embeds files with paths
+// including `assets/images` as their prefix.
+func (g *Group) StaticFS(pathPrefix string, filesystem fs.FS) {
+ g.Add(
+ http.MethodGet,
+ pathPrefix+"*",
+ StaticDirectoryHandler(filesystem, false),
+ )
+}
+
+// FileFS implements `Echo#FileFS()` for sub-routes within the Group.
+func (g *Group) FileFS(path, file string, filesystem fs.FS, m ...MiddlewareFunc) *Route {
+ return g.GET(path, StaticFileHandler(file, filesystem), m...)
+}
diff --git a/vendor/github.com/labstack/echo/v4/ip.go b/vendor/github.com/labstack/echo/v4/ip.go
index 39cb421f..46d464cf 100644
--- a/vendor/github.com/labstack/echo/v4/ip.go
+++ b/vendor/github.com/labstack/echo/v4/ip.go
@@ -6,6 +6,130 @@ import (
"strings"
)
+/**
+By: https://github.com/tmshn (See: https://github.com/labstack/echo/pull/1478 , https://github.com/labstack/echox/pull/134 )
+Source: https://echo.labstack.com/guide/ip-address/
+
+IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more.
+Echo provides handy method [`Context#RealIP()`](https://godoc.org/github.com/labstack/echo#Context) for that.
+
+However, it is not trivial to retrieve the _real_ IP address from requests especially when you put L7 proxies before the application.
+In such situation, _real_ IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.
+Otherwise, you might give someone a chance of deceiving you. **A security risk!**
+
+To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructure.
+In Echo, this can be done by configuring `Echo#IPExtractor` appropriately.
+This guides show you why and how.
+
+> Note: if you dont' set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.
+
+Let's start from two questions to know the right direction:
+
+1. Do you put any HTTP (L7) proxy in front of the application?
+ - It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway).
+2. If yes, what HTTP header do your proxies use to pass client IP to the application?
+
+## Case 1. With no proxy
+
+If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer.
+Any HTTP header is untrustable because the clients have full control what headers to be set.
+
+In this case, use `echo.ExtractIPDirect()`.
+
+```go
+e.IPExtractor = echo.ExtractIPDirect()
+```
+
+## Case 2. With proxies using `X-Forwarded-For` header
+
+[`X-Forwared-For` (XFF)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) is the popular header
+to relay clients' IP addresses.
+At each hop on the proxies, they append the request IP address at the end of the header.
+
+Following example diagram illustrates this behavior.
+
+```text
+ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā” ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
+ā”‚ "Origin" ā”‚ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>ā”‚ Proxy 1 ā”‚ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>ā”‚ Proxy 2 ā”‚ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€>ā”‚ Your app ā”‚
+ā”‚ (IP: a) ā”‚ ā”‚ (IP: b) ā”‚ ā”‚ (IP: c) ā”‚ ā”‚ ā”‚
+ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
+
+Case 1.
+XFF: "" "a" "a, b"
+ ~~~~~~
+Case 2.
+XFF: "x" "x, a" "x, a, b"
+ ~~~~~~~~~
+ ā†‘ What your app will see
+```
+
+In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
+configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre".
+In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
+
+In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
+
+```go
+e.IPExtractor = echo.ExtractIPFromXFFHeader()
+```
+
+By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address
+from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
+[RFC4193](https://tools.ietf.org/html/rfc4193)).
+To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
+
+E.g.:
+
+```go
+e.IPExtractor = echo.ExtractIPFromXFFHeader(
+ TrustLinkLocal(false),
+ TrustIPRanges(lbIPRange),
+)
+```
+
+- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
+
+## Case 3. With proxies using `X-Real-IP` header
+
+`X-Real-IP` is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF.
+
+If your proxies set this header, use `ExtractIPFromRealIPHeader(...TrustOption)`.
+
+```go
+e.IPExtractor = echo.ExtractIPFromRealIPHeader()
+```
+
+Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address
+from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
+[RFC4193](https://tools.ietf.org/html/rfc4193)).
+To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
+
+- Ref: https://godoc.org/github.com/labstack/echo#TrustOption
+
+> **Never forget** to configure the outermost proxy (i.e.; at the edge of your infrastructure) **not to pass through incoming headers**.
+> Otherwise there is a chance of fraud, as it is what clients can control.
+
+## About default behavior
+
+In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer.
+
+As you might already notice, after reading this article, this is not good.
+Sole reason this is default is just backward compatibility.
+
+## Private IP ranges
+
+See: https://en.wikipedia.org/wiki/Private_network
+
+Private IPv4 address ranges (RFC 1918):
+* 10.0.0.0 ā€“ 10.255.255.255 (24-bit block)
+* 172.16.0.0 ā€“ 172.31.255.255 (20-bit block)
+* 192.168.0.0 ā€“ 192.168.255.255 (16-bit block)
+
+Private IPv6 address ranges:
+* fc00::/7 address block = RFC 4193 Unique Local Addresses (ULA)
+
+*/
+
type ipChecker struct {
trustLoopback bool
trustLinkLocal bool
@@ -52,6 +176,7 @@ func newIPChecker(configs []TrustOption) *ipChecker {
return checker
}
+// Go1.16+ added `ip.IsPrivate()` but until that use this implementation
func isPrivateIPRange(ip net.IP) bool {
if ip4 := ip.To4(); ip4 != nil {
return ip4[0] == 10 ||
@@ -87,10 +212,12 @@ type IPExtractor func(*http.Request) string
// ExtractIPDirect extracts IP address using actual IP address.
// Use this if your server faces to internet directory (i.e.: uses no proxy).
func ExtractIPDirect() IPExtractor {
- return func(req *http.Request) string {
- ra, _, _ := net.SplitHostPort(req.RemoteAddr)
- return ra
- }
+ return extractIP
+}
+
+func extractIP(req *http.Request) string {
+ ra, _, _ := net.SplitHostPort(req.RemoteAddr)
+ return ra
}
// ExtractIPFromRealIPHeader extracts IP address using x-real-ip header.
@@ -98,14 +225,13 @@ func ExtractIPDirect() IPExtractor {
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
- directIP := ExtractIPDirect()(req)
realIP := req.Header.Get(HeaderXRealIP)
if realIP != "" {
- if ip := net.ParseIP(directIP); ip != nil && checker.trust(ip) {
+ if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) {
return realIP
}
}
- return directIP
+ return extractIP(req)
}
}
@@ -115,7 +241,7 @@ func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
- directIP := ExtractIPDirect()(req)
+ directIP := extractIP(req)
xffs := req.Header[HeaderXForwardedFor]
if len(xffs) == 0 {
return directIP
diff --git a/vendor/github.com/labstack/echo/v4/middleware/cors.go b/vendor/github.com/labstack/echo/v4/middleware/cors.go
index d6ef8964..16259512 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/cors.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/cors.go
@@ -29,6 +29,8 @@ type (
// AllowMethods defines a list methods allowed when accessing the resource.
// This is used in response to a preflight request.
// Optional. Default value DefaultCORSConfig.AllowMethods.
+ // If `allowMethods` is left empty will fill for preflight request `Access-Control-Allow-Methods` header value
+ // from `Allow` header that echo.Router set into context.
AllowMethods []string `yaml:"allow_methods"`
// AllowHeaders defines a list of request headers that can be used when
@@ -41,6 +43,8 @@ type (
// a response to a preflight request, this indicates whether or not the
// actual request can be made using credentials.
// Optional. Default value false.
+ // Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`.
+ // See http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
AllowCredentials bool `yaml:"allow_credentials"`
// ExposeHeaders defines a whitelist headers that clients are allowed to
@@ -80,7 +84,9 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
if len(config.AllowOrigins) == 0 {
config.AllowOrigins = DefaultCORSConfig.AllowOrigins
}
+ hasCustomAllowMethods := true
if len(config.AllowMethods) == 0 {
+ hasCustomAllowMethods = false
config.AllowMethods = DefaultCORSConfig.AllowMethods
}
@@ -109,10 +115,28 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
origin := req.Header.Get(echo.HeaderOrigin)
allowOrigin := ""
- preflight := req.Method == http.MethodOptions
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
- // No Origin provided
+ // Preflight request is an OPTIONS request, using three HTTP request headers: Access-Control-Request-Method,
+ // Access-Control-Request-Headers, and the Origin header. See: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
+ // For simplicity we just consider method type and later `Origin` header.
+ preflight := req.Method == http.MethodOptions
+
+ // Although router adds special handler in case of OPTIONS method we avoid calling next for OPTIONS in this middleware
+ // as CORS requests do not have cookies / authentication headers by default, so we could get stuck in auth
+ // middlewares by calling next(c).
+ // But we still want to send `Allow` header as response in case of Non-CORS OPTIONS request as router default
+ // handler does.
+ routerAllowMethods := ""
+ if preflight {
+ tmpAllowMethods, ok := c.Get(echo.ContextKeyHeaderAllow).(string)
+ if ok && tmpAllowMethods != "" {
+ routerAllowMethods = tmpAllowMethods
+ c.Response().Header().Set(echo.HeaderAllow, routerAllowMethods)
+ }
+ }
+
+ // No Origin provided. This is (probably) not request from actual browser - proceed executing middleware chain
if origin == "" {
if !preflight {
return next(c)
@@ -145,19 +169,15 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
}
}
- // Check allowed origin patterns
- for _, re := range allowOriginPatterns {
- if allowOrigin == "" {
- didx := strings.Index(origin, "://")
- if didx == -1 {
- continue
- }
- domAuth := origin[didx+3:]
- // to avoid regex cost by invalid long domain
- if len(domAuth) > 253 {
- break
- }
-
+ checkPatterns := false
+ if allowOrigin == "" {
+ // to avoid regex cost by invalid (long) domains (253 is domain name max limit)
+ if len(origin) <= (253+3+5) && strings.Contains(origin, "://") {
+ checkPatterns = true
+ }
+ }
+ if checkPatterns {
+ for _, re := range allowOriginPatterns {
if match, _ := regexp.MatchString(re, origin); match {
allowOrigin = origin
break
@@ -174,12 +194,13 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
return c.NoContent(http.StatusNoContent)
}
+ res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
+ if config.AllowCredentials {
+ res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
+ }
+
// Simple request
if !preflight {
- res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
- if config.AllowCredentials {
- res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
- }
if exposeHeaders != "" {
res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
}
@@ -189,11 +210,13 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
// Preflight request
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
- res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
- res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
- if config.AllowCredentials {
- res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
+
+ if !hasCustomAllowMethods && routerAllowMethods != "" {
+ res.Header().Set(echo.HeaderAccessControlAllowMethods, routerAllowMethods)
+ } else {
+ res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
}
+
if allowHeaders != "" {
res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
} else {
diff --git a/vendor/github.com/labstack/echo/v4/middleware/csrf.go b/vendor/github.com/labstack/echo/v4/middleware/csrf.go
index 7804997d..61299f5c 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/csrf.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/csrf.go
@@ -2,9 +2,7 @@ package middleware
import (
"crypto/subtle"
- "errors"
"net/http"
- "strings"
"time"
"github.com/labstack/echo/v4"
@@ -21,13 +19,15 @@ type (
TokenLength uint8 `yaml:"token_length"`
// Optional. Default value 32.
- // TokenLookup is a string in the form of "<source>:<key>" that is used
+ // TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract token from the request.
// Optional. Default value "header:X-CSRF-Token".
// Possible values:
- // - "header:<name>"
- // - "form:<name>"
+ // - "header:<name>" or "header:<name>:<cut-prefix>"
// - "query:<name>"
+ // - "form:<name>"
+ // Multiple sources example:
+ // - "header:X-CSRF-Token,query:csrf"
TokenLookup string `yaml:"token_lookup"`
// Context key to store generated CSRF token into context.
@@ -62,12 +62,11 @@ type (
// Optional. Default value SameSiteDefaultMode.
CookieSameSite http.SameSite `yaml:"cookie_same_site"`
}
-
- // csrfTokenExtractor defines a function that takes `echo.Context` and returns
- // either a token or an error.
- csrfTokenExtractor func(echo.Context) (string, error)
)
+// ErrCSRFInvalid is returned when CSRF check fails
+var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
+
var (
// DefaultCSRFConfig is the default CSRF middleware config.
DefaultCSRFConfig = CSRFConfig{
@@ -114,14 +113,9 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
config.CookieSecure = true
}
- // Initialize
- parts := strings.Split(config.TokenLookup, ":")
- extractor := csrfTokenFromHeader(parts[1])
- switch parts[0] {
- case "form":
- extractor = csrfTokenFromForm(parts[1])
- case "query":
- extractor = csrfTokenFromQuery(parts[1])
+ extractors, err := createExtractors(config.TokenLookup, "")
+ if err != nil {
+ panic(err)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -130,28 +124,50 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
return next(c)
}
- req := c.Request()
- k, err := c.Cookie(config.CookieName)
token := ""
-
- // Generate token
- if err != nil {
- token = random.String(config.TokenLength)
+ if k, err := c.Cookie(config.CookieName); err != nil {
+ token = random.String(config.TokenLength) // Generate token
} else {
- // Reuse token
- token = k.Value
+ token = k.Value // Reuse token
}
- switch req.Method {
+ switch c.Request().Method {
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
default:
// Validate token only for requests which are not defined as 'safe' by RFC7231
- clientToken, err := extractor(c)
- if err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+ var lastExtractorErr error
+ var lastTokenErr error
+ outer:
+ for _, extractor := range extractors {
+ clientTokens, err := extractor(c)
+ if err != nil {
+ lastExtractorErr = err
+ continue
+ }
+
+ for _, clientToken := range clientTokens {
+ if validateCSRFToken(token, clientToken) {
+ lastTokenErr = nil
+ lastExtractorErr = nil
+ break outer
+ }
+ lastTokenErr = ErrCSRFInvalid
+ }
}
- if !validateCSRFToken(token, clientToken) {
- return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
+ if lastTokenErr != nil {
+ return lastTokenErr
+ } else if lastExtractorErr != nil {
+ // ugly part to preserve backwards compatible errors. someone could rely on them
+ if lastExtractorErr == errQueryExtractorValueMissing {
+ lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the query string")
+ } else if lastExtractorErr == errFormExtractorValueMissing {
+ lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in the form parameter")
+ } else if lastExtractorErr == errHeaderExtractorValueMissing {
+ lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, "missing csrf token in request header")
+ } else {
+ lastExtractorErr = echo.NewHTTPError(http.StatusBadRequest, lastExtractorErr.Error())
+ }
+ return lastExtractorErr
}
}
@@ -184,38 +200,6 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
}
}
-// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
-// provided request header.
-func csrfTokenFromHeader(header string) csrfTokenExtractor {
- return func(c echo.Context) (string, error) {
- return c.Request().Header.Get(header), nil
- }
-}
-
-// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
-// provided form parameter.
-func csrfTokenFromForm(param string) csrfTokenExtractor {
- return func(c echo.Context) (string, error) {
- token := c.FormValue(param)
- if token == "" {
- return "", errors.New("missing csrf token in the form parameter")
- }
- return token, nil
- }
-}
-
-// csrfTokenFromQuery returns a `csrfTokenExtractor` that extracts token from the
-// provided query parameter.
-func csrfTokenFromQuery(param string) csrfTokenExtractor {
- return func(c echo.Context) (string, error) {
- token := c.QueryParam(param)
- if token == "" {
- return "", errors.New("missing csrf token in the query string")
- }
- return token, nil
- }
-}
-
func validateCSRFToken(token, clientToken string) bool {
return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/extractor.go b/vendor/github.com/labstack/echo/v4/middleware/extractor.go
new file mode 100644
index 00000000..a57ed4e1
--- /dev/null
+++ b/vendor/github.com/labstack/echo/v4/middleware/extractor.go
@@ -0,0 +1,184 @@
+package middleware
+
+import (
+ "errors"
+ "fmt"
+ "github.com/labstack/echo/v4"
+ "net/textproto"
+ "strings"
+)
+
+const (
+ // extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion
+ // attack vector
+ extractorLimit = 20
+)
+
+var errHeaderExtractorValueMissing = errors.New("missing value in request header")
+var errHeaderExtractorValueInvalid = errors.New("invalid value in request header")
+var errQueryExtractorValueMissing = errors.New("missing value in the query string")
+var errParamExtractorValueMissing = errors.New("missing value in path params")
+var errCookieExtractorValueMissing = errors.New("missing value in cookies")
+var errFormExtractorValueMissing = errors.New("missing value in the form")
+
+// ValuesExtractor defines a function for extracting values (keys/tokens) from the given context.
+type ValuesExtractor func(c echo.Context) ([]string, error)
+
+func createExtractors(lookups string, authScheme string) ([]ValuesExtractor, error) {
+ if lookups == "" {
+ return nil, nil
+ }
+ sources := strings.Split(lookups, ",")
+ var extractors = make([]ValuesExtractor, 0)
+ for _, source := range sources {
+ parts := strings.Split(source, ":")
+ if len(parts) < 2 {
+ return nil, fmt.Errorf("extractor source for lookup could not be split into needed parts: %v", source)
+ }
+
+ switch parts[0] {
+ case "query":
+ extractors = append(extractors, valuesFromQuery(parts[1]))
+ case "param":
+ extractors = append(extractors, valuesFromParam(parts[1]))
+ case "cookie":
+ extractors = append(extractors, valuesFromCookie(parts[1]))
+ case "form":
+ extractors = append(extractors, valuesFromForm(parts[1]))
+ case "header":
+ prefix := ""
+ if len(parts) > 2 {
+ prefix = parts[2]
+ } else if authScheme != "" && parts[1] == echo.HeaderAuthorization {
+ // backwards compatibility for JWT and KeyAuth:
+ // * we only apply this fix to Authorization as header we use and uses prefixes like "Bearer <token-value>" etc
+ // * previously header extractor assumed that auth-scheme/prefix had a space as suffix we need to retain that
+ // behaviour for default values and Authorization header.
+ prefix = authScheme
+ if !strings.HasSuffix(prefix, " ") {
+ prefix += " "
+ }
+ }
+ extractors = append(extractors, valuesFromHeader(parts[1], prefix))
+ }
+ }
+ return extractors, nil
+}
+
+// valuesFromHeader returns a functions that extracts values from the request header.
+// valuePrefix is parameter to remove first part (prefix) of the extracted value. This is useful if header value has static
+// prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we want to remove is `<auth-scheme> `
+// note the space at the end. In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove
+// is `Basic `. In case of JWT tokens `Authorization: Bearer <token>` prefix is `Bearer `.
+// If prefix is left empty the whole value is returned.
+func valuesFromHeader(header string, valuePrefix string) ValuesExtractor {
+ prefixLen := len(valuePrefix)
+ // standard library parses http.Request header keys in canonical form but we may provide something else so fix this
+ header = textproto.CanonicalMIMEHeaderKey(header)
+ return func(c echo.Context) ([]string, error) {
+ values := c.Request().Header.Values(header)
+ if len(values) == 0 {
+ return nil, errHeaderExtractorValueMissing
+ }
+
+ result := make([]string, 0)
+ for i, value := range values {
+ if prefixLen == 0 {
+ result = append(result, value)
+ if i >= extractorLimit-1 {
+ break
+ }
+ continue
+ }
+ if len(value) > prefixLen && strings.EqualFold(value[:prefixLen], valuePrefix) {
+ result = append(result, value[prefixLen:])
+ if i >= extractorLimit-1 {
+ break
+ }
+ }
+ }
+
+ if len(result) == 0 {
+ if prefixLen > 0 {
+ return nil, errHeaderExtractorValueInvalid
+ }
+ return nil, errHeaderExtractorValueMissing
+ }
+ return result, nil
+ }
+}
+
+// valuesFromQuery returns a function that extracts values from the query string.
+func valuesFromQuery(param string) ValuesExtractor {
+ return func(c echo.Context) ([]string, error) {
+ result := c.QueryParams()[param]
+ if len(result) == 0 {
+ return nil, errQueryExtractorValueMissing
+ } else if len(result) > extractorLimit-1 {
+ result = result[:extractorLimit]
+ }
+ return result, nil
+ }
+}
+
+// valuesFromParam returns a function that extracts values from the url param string.
+func valuesFromParam(param string) ValuesExtractor {
+ return func(c echo.Context) ([]string, error) {
+ result := make([]string, 0)
+ paramVales := c.ParamValues()
+ for i, p := range c.ParamNames() {
+ if param == p {
+ result = append(result, paramVales[i])
+ if i >= extractorLimit-1 {
+ break
+ }
+ }
+ }
+ if len(result) == 0 {
+ return nil, errParamExtractorValueMissing
+ }
+ return result, nil
+ }
+}
+
+// valuesFromCookie returns a function that extracts values from the named cookie.
+func valuesFromCookie(name string) ValuesExtractor {
+ return func(c echo.Context) ([]string, error) {
+ cookies := c.Cookies()
+ if len(cookies) == 0 {
+ return nil, errCookieExtractorValueMissing
+ }
+
+ result := make([]string, 0)
+ for i, cookie := range cookies {
+ if name == cookie.Name {
+ result = append(result, cookie.Value)
+ if i >= extractorLimit-1 {
+ break
+ }
+ }
+ }
+ if len(result) == 0 {
+ return nil, errCookieExtractorValueMissing
+ }
+ return result, nil
+ }
+}
+
+// valuesFromForm returns a function that extracts values from the form field.
+func valuesFromForm(name string) ValuesExtractor {
+ return func(c echo.Context) ([]string, error) {
+ if parseErr := c.Request().ParseForm(); parseErr != nil {
+ return nil, fmt.Errorf("valuesFromForm parse form failed: %w", parseErr)
+ }
+ values := c.Request().Form[name]
+ if len(values) == 0 {
+ return nil, errFormExtractorValueMissing
+ }
+ if len(values) > extractorLimit-1 {
+ values = values[:extractorLimit]
+ }
+ result := append([]string{}, values...)
+ return result, nil
+ }
+}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/jwt.go b/vendor/github.com/labstack/echo/v4/middleware/jwt.go
index 21e33ab8..bec5167e 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/jwt.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/jwt.go
@@ -1,3 +1,4 @@
+//go:build go1.15
// +build go1.15
package middleware
@@ -5,12 +6,10 @@ package middleware
import (
"errors"
"fmt"
- "net/http"
- "reflect"
- "strings"
-
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
+ "net/http"
+ "reflect"
)
type (
@@ -22,7 +21,8 @@ type (
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc BeforeFunc
- // SuccessHandler defines a function which is executed for a valid token.
+ // SuccessHandler defines a function which is executed for a valid token before middleware chain continues with next
+ // middleware or handler.
SuccessHandler JWTSuccessHandler
// ErrorHandler defines a function which is executed for an invalid token.
@@ -32,6 +32,13 @@ type (
// ErrorHandlerWithContext is almost identical to ErrorHandler, but it's passed the current context.
ErrorHandlerWithContext JWTErrorHandlerWithContext
+ // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandlerWithContext decides to
+ // ignore the error (by returning `nil`).
+ // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
+ // In that case you can use ErrorHandlerWithContext to set a default public JWT token value in the request context
+ // and continue. Some logic down the remaining execution chain needs to check that (public) token value then.
+ ContinueOnIgnoredError bool
+
// Signing key to validate token.
// This is one of the three options to provide a token validation key.
// The order of precedence is a user-defined KeyFunc, SigningKeys and SigningKey.
@@ -61,16 +68,26 @@ type (
// to extract token from the request.
// Optional. Default value "header:Authorization".
// Possible values:
- // - "header:<name>"
+ // - "header:<name>" or "header:<name>:<cut-prefix>"
+ // `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
+ // value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
+ // want to cut is `<auth-scheme> ` note the space at the end.
+ // In case of JWT tokens `Authorization: Bearer <token>` prefix we cut is `Bearer `.
+ // If prefix is left empty the whole value is returned.
// - "query:<name>"
// - "param:<name>"
// - "cookie:<name>"
// - "form:<name>"
- // Multiply sources example:
- // - "header: Authorization,cookie: myowncookie"
-
+ // Multiple sources example:
+ // - "header:Authorization,cookie:myowncookie"
TokenLookup string
+ // TokenLookupFuncs defines a list of user-defined functions that extract JWT token from the given context.
+ // This is one of the two options to provide a token extractor.
+ // The order of precedence is user-defined TokenLookupFuncs, and TokenLookup.
+ // You can also provide both if you want.
+ TokenLookupFuncs []ValuesExtractor
+
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
AuthScheme string
@@ -95,15 +112,13 @@ type (
}
// JWTSuccessHandler defines a function which is executed for a valid token.
- JWTSuccessHandler func(echo.Context)
+ JWTSuccessHandler func(c echo.Context)
// JWTErrorHandler defines a function which is executed for an invalid token.
- JWTErrorHandler func(error) error
+ JWTErrorHandler func(err error) error
// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context.
- JWTErrorHandlerWithContext func(error, echo.Context) error
-
- jwtExtractor func(echo.Context) (string, error)
+ JWTErrorHandlerWithContext func(err error, c echo.Context) error
)
// Algorithms
@@ -120,13 +135,14 @@ var (
var (
// DefaultJWTConfig is the default JWT auth middleware config.
DefaultJWTConfig = JWTConfig{
- Skipper: DefaultSkipper,
- SigningMethod: AlgorithmHS256,
- ContextKey: "user",
- TokenLookup: "header:" + echo.HeaderAuthorization,
- AuthScheme: "Bearer",
- Claims: jwt.MapClaims{},
- KeyFunc: nil,
+ Skipper: DefaultSkipper,
+ SigningMethod: AlgorithmHS256,
+ ContextKey: "user",
+ TokenLookup: "header:" + echo.HeaderAuthorization,
+ TokenLookupFuncs: nil,
+ AuthScheme: "Bearer",
+ Claims: jwt.MapClaims{},
+ KeyFunc: nil,
}
)
@@ -163,7 +179,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
if config.Claims == nil {
config.Claims = DefaultJWTConfig.Claims
}
- if config.TokenLookup == "" {
+ if config.TokenLookup == "" && len(config.TokenLookupFuncs) == 0 {
config.TokenLookup = DefaultJWTConfig.TokenLookup
}
if config.AuthScheme == "" {
@@ -176,25 +192,12 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
config.ParseTokenFunc = config.defaultParseToken
}
- // Initialize
- // Split sources
- sources := strings.Split(config.TokenLookup, ",")
- var extractors []jwtExtractor
- for _, source := range sources {
- parts := strings.Split(source, ":")
-
- switch parts[0] {
- case "query":
- extractors = append(extractors, jwtFromQuery(parts[1]))
- case "param":
- extractors = append(extractors, jwtFromParam(parts[1]))
- case "cookie":
- extractors = append(extractors, jwtFromCookie(parts[1]))
- case "form":
- extractors = append(extractors, jwtFromForm(parts[1]))
- case "header":
- extractors = append(extractors, jwtFromHeader(parts[1], config.AuthScheme))
- }
+ extractors, err := createExtractors(config.TokenLookup, config.AuthScheme)
+ if err != nil {
+ panic(err)
+ }
+ if len(config.TokenLookupFuncs) > 0 {
+ extractors = append(config.TokenLookupFuncs, extractors...)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -206,48 +209,54 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
if config.BeforeFunc != nil {
config.BeforeFunc(c)
}
- var auth string
- var err error
+
+ var lastExtractorErr error
+ var lastTokenErr error
for _, extractor := range extractors {
- // Extract token from extractor, if it's not fail break the loop and
- // set auth
- auth, err = extractor(c)
- if err == nil {
- break
+ auths, err := extractor(c)
+ if err != nil {
+ lastExtractorErr = ErrJWTMissing // backwards compatibility: all extraction errors are same (unlike KeyAuth)
+ continue
}
- }
- // If none of extractor has a token, handle error
- if err != nil {
- if config.ErrorHandler != nil {
- return config.ErrorHandler(err)
- }
-
- if config.ErrorHandlerWithContext != nil {
- return config.ErrorHandlerWithContext(err, c)
+ for _, auth := range auths {
+ token, err := config.ParseTokenFunc(auth, c)
+ if err != nil {
+ lastTokenErr = err
+ continue
+ }
+ // Store user information from token into context.
+ c.Set(config.ContextKey, token)
+ if config.SuccessHandler != nil {
+ config.SuccessHandler(c)
+ }
+ return next(c)
}
- return err
}
-
- token, err := config.ParseTokenFunc(auth, c)
- if err == nil {
- // Store user information from token into context.
- c.Set(config.ContextKey, token)
- if config.SuccessHandler != nil {
- config.SuccessHandler(c)
- }
- return next(c)
+ // we are here only when we did not successfully extract or parse any of the tokens
+ err := lastTokenErr
+ if err == nil { // prioritize token errors over extracting errors
+ err = lastExtractorErr
}
if config.ErrorHandler != nil {
return config.ErrorHandler(err)
}
if config.ErrorHandlerWithContext != nil {
- return config.ErrorHandlerWithContext(err, c)
+ tmpErr := config.ErrorHandlerWithContext(err, c)
+ if config.ContinueOnIgnoredError && tmpErr == nil {
+ return next(c)
+ }
+ return tmpErr
}
- return &echo.HTTPError{
- Code: ErrJWTInvalid.Code,
- Message: ErrJWTInvalid.Message,
- Internal: err,
+
+ // backwards compatible errors codes
+ if lastTokenErr != nil {
+ return &echo.HTTPError{
+ Code: ErrJWTInvalid.Code,
+ Message: ErrJWTInvalid.Message,
+ Internal: err,
+ }
}
+ return err // this is lastExtractorErr value
}
}
}
@@ -289,59 +298,3 @@ func (config *JWTConfig) defaultKeyFunc(t *jwt.Token) (interface{}, error) {
return config.SigningKey, nil
}
-
-// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
-func jwtFromHeader(header string, authScheme string) jwtExtractor {
- return func(c echo.Context) (string, error) {
- auth := c.Request().Header.Get(header)
- l := len(authScheme)
- if len(auth) > l+1 && strings.EqualFold(auth[:l], authScheme) {
- return auth[l+1:], nil
- }
- return "", ErrJWTMissing
- }
-}
-
-// jwtFromQuery returns a `jwtExtractor` that extracts token from the query string.
-func jwtFromQuery(param string) jwtExtractor {
- return func(c echo.Context) (string, error) {
- token := c.QueryParam(param)
- if token == "" {
- return "", ErrJWTMissing
- }
- return token, nil
- }
-}
-
-// jwtFromParam returns a `jwtExtractor` that extracts token from the url param string.
-func jwtFromParam(param string) jwtExtractor {
- return func(c echo.Context) (string, error) {
- token := c.Param(param)
- if token == "" {
- return "", ErrJWTMissing
- }
- return token, nil
- }
-}
-
-// jwtFromCookie returns a `jwtExtractor` that extracts token from the named cookie.
-func jwtFromCookie(name string) jwtExtractor {
- return func(c echo.Context) (string, error) {
- cookie, err := c.Cookie(name)
- if err != nil {
- return "", ErrJWTMissing
- }
- return cookie.Value, nil
- }
-}
-
-// jwtFromForm returns a `jwtExtractor` that extracts token from the form field.
-func jwtFromForm(name string) jwtExtractor {
- return func(c echo.Context) (string, error) {
- field := c.FormValue(name)
- if field == "" {
- return "", ErrJWTMissing
- }
- return field, nil
- }
-}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/key_auth.go b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go
index 54f3b47f..e8a6b085 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/key_auth.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/key_auth.go
@@ -2,11 +2,8 @@ package middleware
import (
"errors"
- "fmt"
- "net/http"
- "strings"
-
"github.com/labstack/echo/v4"
+ "net/http"
)
type (
@@ -15,15 +12,21 @@ type (
// Skipper defines a function to skip middleware.
Skipper Skipper
- // KeyLookup is a string in the form of "<source>:<name>" that is used
+ // KeyLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract key from the request.
// Optional. Default value "header:Authorization".
// Possible values:
- // - "header:<name>"
+ // - "header:<name>" or "header:<name>:<cut-prefix>"
+ // `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
+ // value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
+ // want to cut is `<auth-scheme> ` note the space at the end.
+ // In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
// - "query:<name>"
// - "form:<name>"
// - "cookie:<name>"
- KeyLookup string `yaml:"key_lookup"`
+ // Multiple sources example:
+ // - "header:Authorization,header:X-Api-Key"
+ KeyLookup string
// AuthScheme to be used in the Authorization header.
// Optional. Default value "Bearer".
@@ -36,15 +39,20 @@ type (
// ErrorHandler defines a function which is executed for an invalid key.
// It may be used to define a custom error.
ErrorHandler KeyAuthErrorHandler
+
+ // ContinueOnIgnoredError allows the next middleware/handler to be called when ErrorHandler decides to
+ // ignore the error (by returning `nil`).
+ // This is useful when parts of your site/api allow public access and some authorized routes provide extra functionality.
+ // In that case you can use ErrorHandler to set a default public key auth value in the request context
+ // and continue. Some logic down the remaining execution chain needs to check that (public) key auth value then.
+ ContinueOnIgnoredError bool
}
// KeyAuthValidator defines a function to validate KeyAuth credentials.
- KeyAuthValidator func(string, echo.Context) (bool, error)
-
- keyExtractor func(echo.Context) (string, error)
+ KeyAuthValidator func(auth string, c echo.Context) (bool, error)
// KeyAuthErrorHandler defines a function which is executed for an invalid key.
- KeyAuthErrorHandler func(error, echo.Context) error
+ KeyAuthErrorHandler func(err error, c echo.Context) error
)
var (
@@ -56,6 +64,21 @@ var (
}
)
+// ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups
+type ErrKeyAuthMissing struct {
+ Err error
+}
+
+// Error returns errors text
+func (e *ErrKeyAuthMissing) Error() string {
+ return e.Err.Error()
+}
+
+// Unwrap unwraps error
+func (e *ErrKeyAuthMissing) Unwrap() error {
+ return e.Err
+}
+
// KeyAuth returns an KeyAuth middleware.
//
// For valid key it calls the next handler.
@@ -85,16 +108,9 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
panic("echo: key-auth middleware requires a validator function")
}
- // Initialize
- parts := strings.Split(config.KeyLookup, ":")
- extractor := keyFromHeader(parts[1], config.AuthScheme)
- switch parts[0] {
- case "query":
- extractor = keyFromQuery(parts[1])
- case "form":
- extractor = keyFromForm(parts[1])
- case "cookie":
- extractor = keyFromCookie(parts[1])
+ extractors, err := createExtractors(config.KeyLookup, config.AuthScheme)
+ if err != nil {
+ panic(err)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -103,79 +119,62 @@ func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
return next(c)
}
- // Extract and verify key
- key, err := extractor(c)
- if err != nil {
- if config.ErrorHandler != nil {
- return config.ErrorHandler(err, c)
+ var lastExtractorErr error
+ var lastValidatorErr error
+ for _, extractor := range extractors {
+ keys, err := extractor(c)
+ if err != nil {
+ lastExtractorErr = err
+ continue
+ }
+ for _, key := range keys {
+ valid, err := config.Validator(key, c)
+ if err != nil {
+ lastValidatorErr = err
+ continue
+ }
+ if valid {
+ return next(c)
+ }
+ lastValidatorErr = errors.New("invalid key")
}
- return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
- valid, err := config.Validator(key, c)
- if err != nil {
- if config.ErrorHandler != nil {
- return config.ErrorHandler(err, c)
+
+ // we are here only when we did not successfully extract and validate any of keys
+ err := lastValidatorErr
+ if err == nil { // prioritize validator errors over extracting errors
+ // ugly part to preserve backwards compatible errors. someone could rely on them
+ if lastExtractorErr == errQueryExtractorValueMissing {
+ err = errors.New("missing key in the query string")
+ } else if lastExtractorErr == errCookieExtractorValueMissing {
+ err = errors.New("missing key in cookies")
+ } else if lastExtractorErr == errFormExtractorValueMissing {
+ err = errors.New("missing key in the form")
+ } else if lastExtractorErr == errHeaderExtractorValueMissing {
+ err = errors.New("missing key in request header")
+ } else if lastExtractorErr == errHeaderExtractorValueInvalid {
+ err = errors.New("invalid key in the request header")
+ } else {
+ err = lastExtractorErr
}
+ err = &ErrKeyAuthMissing{Err: err}
+ }
+
+ if config.ErrorHandler != nil {
+ tmpErr := config.ErrorHandler(err, c)
+ if config.ContinueOnIgnoredError && tmpErr == nil {
+ return next(c)
+ }
+ return tmpErr
+ }
+ if lastValidatorErr != nil { // prioritize validator errors over extracting errors
return &echo.HTTPError{
Code: http.StatusUnauthorized,
- Message: "invalid key",
- Internal: err,
+ Message: "Unauthorized",
+ Internal: lastValidatorErr,
}
- } else if valid {
- return next(c)
}
- return echo.ErrUnauthorized
- }
- }
-}
-
-// keyFromHeader returns a `keyExtractor` that extracts key from the request header.
-func keyFromHeader(header string, authScheme string) keyExtractor {
- return func(c echo.Context) (string, error) {
- auth := c.Request().Header.Get(header)
- if auth == "" {
- return "", errors.New("missing key in request header")
- }
- if header == echo.HeaderAuthorization {
- l := len(authScheme)
- if len(auth) > l+1 && auth[:l] == authScheme {
- return auth[l+1:], nil
- }
- return "", errors.New("invalid key in the request header")
- }
- return auth, nil
- }
-}
-
-// keyFromQuery returns a `keyExtractor` that extracts key from the query string.
-func keyFromQuery(param string) keyExtractor {
- return func(c echo.Context) (string, error) {
- key := c.QueryParam(param)
- if key == "" {
- return "", errors.New("missing key in the query string")
- }
- return key, nil
- }
-}
-
-// keyFromForm returns a `keyExtractor` that extracts key from the form.
-func keyFromForm(param string) keyExtractor {
- return func(c echo.Context) (string, error) {
- key := c.FormValue(param)
- if key == "" {
- return "", errors.New("missing key in the form")
- }
- return key, nil
- }
-}
-
-// keyFromCookie returns a `keyExtractor` that extracts key from the form.
-func keyFromCookie(cookieName string) keyExtractor {
- return func(c echo.Context) (string, error) {
- key, err := c.Cookie(cookieName)
- if err != nil {
- return "", fmt.Errorf("missing key in cookies: %w", err)
+ return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
- return key.Value, nil
}
}
diff --git a/vendor/github.com/labstack/echo/v4/middleware/middleware.go b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
index a7ad73a5..f250ca49 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/middleware.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/middleware.go
@@ -12,10 +12,10 @@ import (
type (
// Skipper defines a function to skip middleware. Returning true skips processing
// the middleware.
- Skipper func(echo.Context) bool
+ Skipper func(c echo.Context) bool
// BeforeFunc defines a function which is executed just before the middleware.
- BeforeFunc func(echo.Context)
+ BeforeFunc func(c echo.Context)
)
func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
diff --git a/vendor/github.com/labstack/echo/v4/middleware/recover.go b/vendor/github.com/labstack/echo/v4/middleware/recover.go
index 0dbe740d..a621a9ef 100644
--- a/vendor/github.com/labstack/echo/v4/middleware/recover.go
+++ b/vendor/github.com/labstack/echo/v4/middleware/recover.go
@@ -9,6 +9,9 @@ import (
)
type (
+ // LogErrorFunc defines a function for custom logging in the middleware.
+ LogErrorFunc func(c echo.Context, err error, stack []byte) error
+
// RecoverConfig defines the config for Recover middleware.
RecoverConfig struct {
// Skipper defines a function to skip middleware.
@@ -30,6 +33,10 @@ type (
// LogLevel is log level to printing stack trace.
// Optional. Default value 0 (Print).
LogLevel log.Lvl
+
+ // LogErrorFunc defines a function for custom logging in the middleware.
+ // If it's set you don't need to provide LogLevel for config.
+ LogErrorFunc LogErrorFunc
}
)
@@ -41,6 +48,7 @@ var (
DisableStackAll: false,
DisablePrintStack: false,
LogLevel: 0,
+ LogErrorFunc: nil,
}
)
@@ -73,9 +81,18 @@ func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
if !ok {
err = fmt.Errorf("%v", r)
}
- stack := make([]byte, config.StackSize)
- length := runtime.Stack(stack, !config.DisableStackAll)
+ var stack []byte
+ var length int
+
if !config.DisablePrintStack {
+ stack = make([]byte, config.StackSize)
+ length = runtime.Stack(stack, !config.DisableStackAll)
+ stack = stack[:length]
+ }
+
+ if config.LogErrorFunc != nil {
+ err = config.LogErrorFunc(c, err, stack)
+ } else if !config.DisablePrintStack {
msg := fmt.Sprintf("[PANIC RECOVER] %v %s\n", err, stack[:length])
switch config.LogLevel {
case log.DEBUG:
diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go
index dc93e29c..a1de2d6e 100644
--- a/vendor/github.com/labstack/echo/v4/router.go
+++ b/vendor/github.com/labstack/echo/v4/router.go
@@ -1,6 +1,7 @@
package echo
import (
+ "bytes"
"net/http"
)
@@ -31,17 +32,18 @@ type (
kind uint8
children []*node
methodHandler struct {
- connect HandlerFunc
- delete HandlerFunc
- get HandlerFunc
- head HandlerFunc
- options HandlerFunc
- patch HandlerFunc
- post HandlerFunc
- propfind HandlerFunc
- put HandlerFunc
- trace HandlerFunc
- report HandlerFunc
+ connect HandlerFunc
+ delete HandlerFunc
+ get HandlerFunc
+ head HandlerFunc
+ options HandlerFunc
+ patch HandlerFunc
+ post HandlerFunc
+ propfind HandlerFunc
+ put HandlerFunc
+ trace HandlerFunc
+ report HandlerFunc
+ allowHeader string
}
)
@@ -68,6 +70,51 @@ func (m *methodHandler) isHandler() bool {
m.report != nil
}
+func (m *methodHandler) updateAllowHeader() {
+ buf := new(bytes.Buffer)
+ buf.WriteString(http.MethodOptions)
+
+ if m.connect != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodConnect)
+ }
+ if m.delete != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodDelete)
+ }
+ if m.get != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodGet)
+ }
+ if m.head != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodHead)
+ }
+ if m.patch != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodPatch)
+ }
+ if m.post != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodPost)
+ }
+ if m.propfind != nil {
+ buf.WriteString(", PROPFIND")
+ }
+ if m.put != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodPut)
+ }
+ if m.trace != nil {
+ buf.WriteString(", ")
+ buf.WriteString(http.MethodTrace)
+ }
+ if m.report != nil {
+ buf.WriteString(", REPORT")
+ }
+ m.allowHeader = buf.String()
+}
+
// NewRouter returns a new Router instance.
func NewRouter(e *Echo) *Router {
return &Router{
@@ -326,6 +373,7 @@ func (n *node) addHandler(method string, h HandlerFunc) {
n.methodHandler.report = h
}
+ n.methodHandler.updateAllowHeader()
if h != nil {
n.isHandler = true
} else {
@@ -362,13 +410,14 @@ func (n *node) findHandler(method string) HandlerFunc {
}
}
-func (n *node) checkMethodNotAllowed() HandlerFunc {
- for _, m := range methods {
- if h := n.findHandler(m); h != nil {
- return MethodNotAllowedHandler
- }
+func optionsMethodHandler(allowMethods string) func(c Context) error {
+ return func(c Context) error {
+ // Note: we are not handling most of the CORS headers here. CORS is handled by CORS middleware
+ // 'OPTIONS' method RFC: https://httpwg.org/specs/rfc7231.html#OPTIONS
+ // 'Allow' header RFC: https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1
+ c.Response().Header().Add(HeaderAllow, allowMethods)
+ return c.NoContent(http.StatusNoContent)
}
- return NotFoundHandler
}
// Find lookup a handler registered for method and path. It also parses URL for path
@@ -563,10 +612,16 @@ func (r *Router) Find(method, path string, c Context) {
// use previous match as basis. although we have no matching handler we have path match.
// so we can send http.StatusMethodNotAllowed (405) instead of http.StatusNotFound (404)
currentNode = previousBestMatchNode
- ctx.handler = currentNode.checkMethodNotAllowed()
+
+ ctx.handler = NotFoundHandler
+ if currentNode.isHandler {
+ ctx.Set(ContextKeyHeaderAllow, currentNode.methodHandler.allowHeader)
+ ctx.handler = MethodNotAllowedHandler
+ if method == http.MethodOptions {
+ ctx.handler = optionsMethodHandler(currentNode.methodHandler.allowHeader)
+ }
+ }
}
ctx.path = currentNode.ppath
ctx.pnames = currentNode.pnames
-
- return
}