diff --git a/README.md b/README.md index 35b3dfa..d345dbe 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,13 @@ Just a fun little project to take a REST API input and generate an output to a n # TO DO - Print Queue for incoming jobs (create background loop that checks queue) -- Fix JWT, I think maybe this version of Golang broke it? ## Example Usage: ``` curl --request POST \ --url http://127.0.0.1:8080/api/v1/print \ - --header 'Authorization: Bearer ' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidWJlcmJyaW5nZXIifQ.TWB3oK8fuoad7oP6FHTP7HhAQB7iRhZRtL1N71irVNc' \ --header 'Content-Type: application/json' \ --header 'User-Agent: Insomnia/2023.5.5' \ --data '{ @@ -49,4 +48,4 @@ curl --request POST \ } ] }' -``` \ No newline at end of file +``` diff --git a/config.toml b/config.toml index bda12d3..e6853bf 100644 --- a/config.toml +++ b/config.toml @@ -3,7 +3,7 @@ port = 8080 [api] - secret = "4ce3ea58ff83fd4e77a9b6031d24b103fcbc7e90889dc73846da7801fc75a52f" + secret = "xTw8qdxTy7Ng4DiptTqmeVy506xWLht3OhFn4kdawmM=" [printer] address = "192.168.1.245" diff --git a/go.mod b/go.mod index 6939190..d2015a9 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/getsentry/sentry-go/echo v0.31.1 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/justinmichaelvieira/escpos v0.1.1 + github.com/labstack/echo-jwt/v4 v4.3.0 github.com/labstack/echo/v4 v4.13.3 github.com/liamg/tml v0.7.0 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index a9907bd..91978b2 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/justinmichaelvieira/escpos v0.1.1 h1:FzvdSWbuR/6tVt6UKXPAxtS+VM7XGxLW github.com/justinmichaelvieira/escpos v0.1.1/go.mod h1:BTLZkBKl4F53lYM/S635//HCltbEoUHuVfHHw+tC9os= github.com/justinmichaelvieira/iconv v0.1.0 h1:nL7d7cHijfoBYHa804+5SvLwaWHO6+OFyJfWHrPnaOw= github.com/justinmichaelvieira/iconv v0.1.0/go.mod h1:Mm+28owXWTkJx2w+ftVX/RUnzWK7hM1hyUYkrgUerts= +github.com/labstack/echo-jwt/v4 v4.3.0 h1:8JcvVCrK9dRkPx/aWY3ZempZLO336Bebh4oAtBcxAv4= +github.com/labstack/echo-jwt/v4 v4.3.0/go.mod h1:OlWm3wqfnq3Ma8DLmmH7GiEAz2S7Bj23im2iPMEAR+Q= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= diff --git a/service/webserver.go b/service/webserver.go index c449516..bb51123 100644 --- a/service/webserver.go +++ b/service/webserver.go @@ -4,6 +4,7 @@ import ( fifo "github.com/foize/go.fifo" sentryecho "github.com/getsentry/sentry-go/echo" "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "golang.org/x/time/rate" @@ -84,17 +85,15 @@ func StartEcho(server chan bool, webserver_addr string, webserver_port int) { api_v1 := e.Group("/api/v1") // Echo JWT - Won't accept valid keys? Bug To-Do - /* - api_v1.Use(echojwt.WithConfig(echojwt.Config{ - SigningKey: []byte(config.JWT_Secret), - SigningMethod: "HS256", - TokenLookup: "header:Authorization:Bearer ,query:token, param:token", - ContextKey: "User", - NewClaimsFunc: func(c echo.Context) jwt.Claims { - return new(jwtCustomClaims) - }, - })) - */ + api_v1.Use(echojwt.WithConfig(echojwt.Config{ + SigningKey: []byte(config.JWT_Secret), + SigningMethod: "HS256", + TokenLookup: "header:Authorization:Bearer ,query:token, param:token", + ContextKey: "User", + NewClaimsFunc: func(c echo.Context) jwt.Claims { + return new(jwtCustomClaims) + }, + })) api_v1.POST("/print", func(c echo.Context) error { diff --git a/vendor/github.com/labstack/echo-jwt/v4/.editorconfig b/vendor/github.com/labstack/echo-jwt/v4/.editorconfig new file mode 100644 index 0000000..17ae50d --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig coding styles definitions. For more information about the +# properties used in this file, please see the EditorConfig documentation: +# http://editorconfig.org/ + +# indicate this is the root of the project +root = true + +[*] +charset = utf-8 + +end_of_line = LF +insert_final_newline = true +trim_trailing_whitespace = true + +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +[*.go] +indent_style = tab diff --git a/vendor/github.com/labstack/echo-jwt/v4/.gitattributes b/vendor/github.com/labstack/echo-jwt/v4/.gitattributes new file mode 100644 index 0000000..d3f4026 --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/.gitattributes @@ -0,0 +1,16 @@ +# Automatically normalize line endings for all text-based files +# http://git-scm.com/docs/gitattributes#_end_of_line_conversion +* text=auto + +# For the following file types, normalize line endings to LF on checking and +# prevent conversion to CRLF when they are checked out (this is required in +# order to prevent newline related issues) +.* text eol=lf +*.go text eol=lf +*.yml text eol=lf +*.html text eol=lf +*.css text eol=lf +*.js text eol=lf +*.json text eol=lf +LICENSE text eol=lf + diff --git a/vendor/github.com/labstack/echo-jwt/v4/.gitignore b/vendor/github.com/labstack/echo-jwt/v4/.gitignore new file mode 100644 index 0000000..dbadf3b --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +coverage.txt +_test +vendor +.idea +*.iml +*.out +.vscode diff --git a/vendor/github.com/labstack/echo-jwt/v4/CHANGELOG.md b/vendor/github.com/labstack/echo-jwt/v4/CHANGELOG.md new file mode 100644 index 0000000..e2b19ba --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/CHANGELOG.md @@ -0,0 +1,50 @@ +# Changelog + +## v4.3.0 - 2024-12-04 + +**Enhancements** + +* Update Echo dependency to v4.13.0 by @aldas in [#28](https://github.com/labstack/echo-jwt/pull/28) + + +## v4.2.1 - 2024-12-04 + +**Enhancements** + +* Return HTTP status 400 if missing JWT by @kitloong in [#13](https://github.com/labstack/echo-jwt/pull/13) +* Update dependencies and CI flow by @aldas in [#21](https://github.com/labstack/echo-jwt/pull/21), [#24](https://github.com/labstack/echo-jwt/pull/24), [#27](https://github.com/labstack/echo-jwt/pull/27) +* Improve readme formatting by @aldas in [#25](https://github.com/labstack/echo-jwt/pull/25) + + +## v4.2.0 - 2023-01-26 + +**Breaking change:** [JWT](github.com/golang-jwt/jwt) has been upgraded to `v5`. Check/test all your code involved with JWT tokens/claims. Search for `github.com/golang-jwt/jwt/v4` +and replace it with `github.com/golang-jwt/jwt/v5` + +**Enhancements** + +* Upgrade `golang-jwt/jwt` library to `v5` [#9](https://github.com/labstack/echo-jwt/pull/9) + + +## v4.1.0 - 2023-01-26 + +**Enhancements** + +* Add TokenExtractionError and TokenParsingError types to help distinguishing error source in ErrorHandler [#6](https://github.com/labstack/echo-jwt/pull/6) + + +## v4.0.1 - 2023-01-24 + +**Fixes** + +* Fix data race in error path [#4](https://github.com/labstack/echo-jwt/pull/4) + + +**Enhancements** + +* add TokenError as error returned when parsing fails [#3](https://github.com/labstack/echo-jwt/pull/3) + + +## v4.0.0 - 2022-12-27 + +* First release diff --git a/vendor/github.com/labstack/echo-jwt/v4/LICENSE b/vendor/github.com/labstack/echo-jwt/v4/LICENSE new file mode 100644 index 0000000..983641a --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 LabStack and Echo contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/labstack/echo-jwt/v4/Makefile b/vendor/github.com/labstack/echo-jwt/v4/Makefile new file mode 100644 index 0000000..dad1d89 --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/Makefile @@ -0,0 +1,36 @@ +PKG := "github.com/labstack/echo-jwt" +PKG_LIST := $(shell go list ${PKG}/...) + +.DEFAULT_GOAL := check +check: lint vet race ## Check project + + +init: + @go install golang.org/x/lint/golint@latest + @go install honnef.co/go/tools/cmd/staticcheck@latest + +lint: ## Lint the files + @staticcheck ${PKG_LIST} + @golint -set_exit_status ${PKG_LIST} + +vet: ## Vet the files + @go vet ${PKG_LIST} + +test: ## Run tests + @go test -short ${PKG_LIST} + +race: ## Run tests with data race detector + @go test -race ${PKG_LIST} + +benchmark: ## Run benchmarks + @go test -run="-" -bench=".*" ${PKG_LIST} + +format: ## Format the source code + @find ./ -type f -name "*.go" -exec gofmt -w {} \; + +help: ## Display this help screen + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +goversion ?= "1.20" +test_version: ## Run tests inside Docker with given version (defaults to 1.20 oldest supported). Example: make test_version goversion=1.20 + @docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make race" diff --git a/vendor/github.com/labstack/echo-jwt/v4/README.md b/vendor/github.com/labstack/echo-jwt/v4/README.md new file mode 100644 index 0000000..cf4e19c --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/README.md @@ -0,0 +1,165 @@ +[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo-jwt/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo-jwt?badge) +[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo-jwt/v4) +[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo-jwt?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo-jwt) +[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo-jwt.svg?style=flat-square)](https://codecov.io/gh/labstack/echo-jwt) +[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo-jwt/master/LICENSE) + +# Echo JWT middleware + +JWT middleware for [Echo](https://github.com/labstack/echo) framework. This middleware uses by default [golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) +as JWT implementation. + +## Versioning + +This repository does not use semantic versioning. MAJOR version tracks which Echo version should be used. MINOR version +tracks API changes (possibly backwards incompatible) and PATCH version is incremented for fixes. + +NB: When `golang-jwt` MAJOR version changes this library will release MINOR version with **breaking change**. Always +add at least one integration test in your project. + +For Echo `v4` use `v4.x.y` releases. +Minimal needed Echo versions: +* `v4.0.0` needs Echo `v4.7.0+` + +`main` branch is compatible with the latest Echo version. + +## Usage + +Add JWT middleware dependency with go modules +```bash +go get github.com/labstack/echo-jwt/v4 +``` + +Use as import statement +```go +import "github.com/labstack/echo-jwt/v4" +``` + +Add middleware in simplified form, by providing only the secret key +```go +e.Use(echojwt.JWT([]byte("secret"))) +``` + +Add middleware with configuration options +```go +e.Use(echojwt.WithConfig(echojwt.Config{ + // ... + SigningKey: []byte("secret"), + // ... +})) +``` + +Extract token in handler +```go +import "github.com/golang-jwt/jwt/v5" + +// ... + +e.GET("/", func(c echo.Context) error { + token, ok := c.Get("user").(*jwt.Token) // by default token is stored under `user` key + if !ok { + return errors.New("JWT token missing or invalid") + } + claims, ok := token.Claims.(jwt.MapClaims) // by default claims is of type `jwt.MapClaims` + if !ok { + return errors.New("failed to cast claims as jwt.MapClaims") + } + return c.JSON(http.StatusOK, claims) +}) +``` + +## IMPORTANT: Integration Testing with JWT Library + +Ensure that your project includes at least one integration test to detect changes in major versions of the `golang-jwt/jwt` library early. +This is crucial because type assertions like `token := c.Get("user").(*jwt.Token)` may fail silently if the imported version of the JWT library (e.g., `import "github.com/golang-jwt/jwt/v5"`) differs from the version used internally by dependencies (e.g., echo-jwt may now use `v6`). Such discrepancies can lead to invalid casts, causing your handlers to panic or throw errors. Integration tests help safeguard against these version mismatches. + +```go +func TestIntegrationMiddlewareWithHandler(t *testing.T) { + e := echo.New() + e.Use(echojwt.WithConfig(echojwt.Config{ + SigningKey: []byte("secret"), + })) + + // use handler that gets token from context to fail your CI flow when `golang-jwt/jwt` library version changes + // a) `token, ok := c.Get("user").(*jwt.Token)` + // b) `token := c.Get("user").(*jwt.Token)` + e.GET("/example", exampleHandler) + + req := httptest.NewRequest(http.MethodGet, "/example", nil) + req.Header.Set(echo.HeaderAuthorization, "Bearer ") + res := httptest.NewRecorder() + + e.ServeHTTP(res, req) + + if res.Code != 200 { + t.Failed() + } +} +``` + + +## Full example + +```go +package main + +import ( + "errors" + "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo-jwt/v4" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "log" + "net/http" +) + +func main() { + e := echo.New() + e.Use(middleware.Logger()) + e.Use(middleware.Recover()) + + e.Use(echojwt.WithConfig(echojwt.Config{ + SigningKey: []byte("secret"), + })) + + e.GET("/", func(c echo.Context) error { + token, ok := c.Get("user").(*jwt.Token) // by default token is stored under `user` key + if !ok { + return errors.New("JWT token missing or invalid") + } + claims, ok := token.Claims.(jwt.MapClaims) // by default claims is of type `jwt.MapClaims` + if !ok { + return errors.New("failed to cast claims as jwt.MapClaims") + } + return c.JSON(http.StatusOK, claims) + }) + + if err := e.Start(":8080"); err != http.ErrServerClosed { + log.Fatal(err) + } +} +``` + +Test with +```bash +curl -v -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" http://localhost:8080 +``` + +Output should be +```bash +* Trying 127.0.0.1:8080... +* Connected to localhost (127.0.0.1) port 8080 (#0) +> GET / HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.81.0 +> Accept: */* +> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ +> +* Mark bundle as not supporting multiuse +< HTTP/1.1 200 OK +< Content-Type: application/json; charset=UTF-8 +< Date: Sun, 27 Nov 2022 21:34:17 GMT +< Content-Length: 52 +< +{"admin":true,"name":"John Doe","sub":"1234567890"} +``` diff --git a/vendor/github.com/labstack/echo-jwt/v4/codecov.yml b/vendor/github.com/labstack/echo-jwt/v4/codecov.yml new file mode 100644 index 0000000..c275d81 --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: + threshold: 1.0% + patch: + default: + threshold: 1.0% + +comment: + require_changes: true diff --git a/vendor/github.com/labstack/echo-jwt/v4/extractors.go b/vendor/github.com/labstack/echo-jwt/v4/extractors.go new file mode 100644 index 0000000..f72537e --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/extractors.go @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2016 LabStack and Echo contributors + +package echojwt + +import ( + "errors" + "fmt" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + "net/textproto" + "strings" +) + +const ( + // extractorLimit is arbitrary number to limit values extractor can return. this limits possible resource exhaustion + // attack vector + extractorLimit = 20 +) + +// TokenExtractionError is catch all type for all errors that occur when the token is extracted from the request. This +// helps to distinguish extractor errors from token parsing errors even if custom extractors or token parsing functions +// are being used that have their own custom errors. +type TokenExtractionError struct { + Err error +} + +// Is checks if target error is same as TokenExtractionError +func (e TokenExtractionError) Is(target error) bool { return target == ErrJWTMissing } // to provide some compatibility with older error handling logic + +func (e *TokenExtractionError) Error() string { return e.Err.Error() } +func (e *TokenExtractionError) Unwrap() error { return e.Err } + +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") + +// CreateExtractors creates ValuesExtractors from given lookups. +// Lookups is a string in the form of ":" or ":,:" that is used +// to extract key from the request. +// Possible values: +// - "header:" or "header::" +// `` is argument value to cut/trim prefix of the extracted value. This is useful if header +// value has static prefix like `Authorization: ` where part that we +// want to cut is ` ` note the space at the end. +// In case of basic authentication `Authorization: Basic ` prefix we want to remove is `Basic `. +// - "query:" +// - "param:" +// - "form:" +// - "cookie:" +// +// Multiple sources example: +// - "header:Authorization,header:X-Api-Key" +func CreateExtractors(lookups string) ([]middleware.ValuesExtractor, error) { + if lookups == "" { + return nil, nil + } + sources := strings.Split(lookups, ",") + var extractors = make([]middleware.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] + } + 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: ` where part that we want to remove is ` ` +// note the space at the end. In case of basic authentication `Authorization: Basic ` prefix we want to remove +// is `Basic `. In case of JWT tokens `Authorization: Bearer ` prefix is `Bearer `. +// If prefix is left empty the whole value is returned. +func valuesFromHeader(header string, valuePrefix string) middleware.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) middleware.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) middleware.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) middleware.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) middleware.ValuesExtractor { + return func(c echo.Context) ([]string, error) { + if c.Request().Form == nil { + _ = c.Request().ParseMultipartForm(32 << 20) // same what `c.Request().FormValue(name)` does + } + 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-jwt/v4/jwt.go b/vendor/github.com/labstack/echo-jwt/v4/jwt.go new file mode 100644 index 0000000..1e97d30 --- /dev/null +++ b/vendor/github.com/labstack/echo-jwt/v4/jwt.go @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: © 2016 LabStack and Echo contributors + +package echojwt + +import ( + "errors" + "fmt" + "net/http" + + "github.com/golang-jwt/jwt/v5" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +// Config defines the config for JWT middleware. +type Config struct { + // Skipper defines a function to skip middleware. + Skipper middleware.Skipper + + // BeforeFunc defines a function which is executed just before the middleware. + BeforeFunc middleware.BeforeFunc + + // SuccessHandler defines a function which is executed for a valid token. + SuccessHandler func(c echo.Context) + + // ErrorHandler defines a function which is executed when all lookups have been done and none of them passed Validator + // function. ErrorHandler is executed with last missing (ErrExtractionValueMissing) or an invalid key. + // It may be used to define a custom JWT error. + // + // Note: when error handler swallows the error (returns nil) middleware continues handler chain execution towards handler. + // This is useful in cases when portion of your site/api is publicly accessible and has extra features for authorized users + // In that case you can use ErrorHandler to set default public JWT token value to request and continue with handler chain. + ErrorHandler func(c echo.Context, err error) error + + // 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 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 + + // Context key to store user information from the token into context. + // Optional. Default value "user". + ContextKey string + + // 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. + // Required if neither user-defined KeyFunc nor SigningKeys is provided. + SigningKey interface{} + + // Map of signing keys to validate token with kid field usage. + // 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. + // Required if neither user-defined KeyFunc nor SigningKey is provided. + SigningKeys map[string]interface{} + + // Signing method used to check the token's signing algorithm. + // Optional. Default value HS256. + SigningMethod string + + // KeyFunc defines a user-defined function that supplies the public key for a token validation. + // The function shall take care of verifying the signing algorithm and selecting the proper key. + // A user-defined KeyFunc can be useful if tokens are issued by an external party. + // Used by default ParseTokenFunc implementation. + // + // When a user-defined KeyFunc is provided, SigningKey, SigningKeys, and SigningMethod are ignored. + // 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. + // Required if neither SigningKeys nor SigningKey is provided. + // Not used if custom ParseTokenFunc is set. + // Default to an internal implementation verifying the signing algorithm and selecting the proper key. + KeyFunc jwt.Keyfunc + + // TokenLookup is a string in the form of ":" or ":,:" that is used + // to extract token from the request. + // Optional. Default value "header:Authorization". + // Possible values: + // - "header:" or "header::" + // `` is argument value to cut/trim prefix of the extracted value. This is useful if header + // value has static prefix like `Authorization: ` where part that we + // want to cut is ` ` note the space at the end. + // In case of JWT tokens `Authorization: Bearer ` prefix we cut is `Bearer `. + // If prefix is left empty the whole value is returned. + // - "query:" + // - "param:" + // - "cookie:" + // - "form:" + // Multiple sources example: + // - "header:Authorization:Bearer ,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 []middleware.ValuesExtractor + + // ParseTokenFunc defines a user-defined function that parses token from given auth. Returns an error when token + // parsing fails or parsed token is invalid. + // Defaults to implementation using `github.com/golang-jwt/jwt` as JWT implementation library + ParseTokenFunc func(c echo.Context, auth string) (interface{}, error) + + // Claims are extendable claims data defining token content. Used by default ParseTokenFunc implementation. + // Not used if custom ParseTokenFunc is set. + // Optional. Defaults to function returning jwt.MapClaims + NewClaimsFunc func(c echo.Context) jwt.Claims +} + +const ( + // AlgorithmHS256 is token signing algorithm + AlgorithmHS256 = "HS256" +) + +// ErrJWTMissing denotes an error raised when JWT token value could not be extracted from request +var ErrJWTMissing = echo.NewHTTPError(http.StatusUnauthorized, "missing or malformed jwt") + +// ErrJWTInvalid denotes an error raised when JWT token value is invalid or expired +var ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt") + +// TokenParsingError is catch all type for all errors that occur when token is parsed. In case of library default +// token parsing functions are being used this error instance wraps TokenError. This helps to distinguish extractor +// errors from token parsing errors even if custom extractors or token parsing functions are being used that have +// their own custom errors. +type TokenParsingError struct { + Err error +} + +// Is checks if target error is same as TokenParsingError +func (e TokenParsingError) Is(target error) bool { return target == ErrJWTInvalid } // to provide some compatibility with older error handling logic + +func (e *TokenParsingError) Error() string { return e.Err.Error() } +func (e *TokenParsingError) Unwrap() error { return e.Err } + +// TokenError is used to return error with error occurred JWT token when processing JWT token +type TokenError struct { + Token *jwt.Token + Err error +} + +func (e *TokenError) Error() string { return e.Err.Error() } + +func (e *TokenError) Unwrap() error { return e.Err } + +// JWT returns a JSON Web Token (JWT) auth middleware. +// +// For valid token, it sets the user in context and calls next handler. +// For invalid token, it returns "401 - Unauthorized" error. +// For missing token, it returns "400 - Bad Request" error. +// +// See: https://jwt.io/introduction +func JWT(signingKey interface{}) echo.MiddlewareFunc { + return WithConfig(Config{SigningKey: signingKey}) +} + +// WithConfig returns a JSON Web Token (JWT) auth middleware or panics if configuration is invalid. +// +// For valid token, it sets the user in context and calls next handler. +// For invalid token, it returns "401 - Unauthorized" error. +// For missing token, it returns "400 - Bad Request" error. +// +// See: https://jwt.io/introduction +func WithConfig(config Config) echo.MiddlewareFunc { + mw, err := config.ToMiddleware() + if err != nil { + panic(err) + } + return mw +} + +// ToMiddleware converts Config to middleware or returns an error for invalid configuration +func (config Config) ToMiddleware() (echo.MiddlewareFunc, error) { + if config.Skipper == nil { + config.Skipper = middleware.DefaultSkipper + } + if config.ContextKey == "" { + config.ContextKey = "user" + } + if config.TokenLookup == "" && len(config.TokenLookupFuncs) == 0 { + config.TokenLookup = "header:Authorization:Bearer " + } + if config.SigningMethod == "" { + config.SigningMethod = AlgorithmHS256 + } + + if config.NewClaimsFunc == nil { + config.NewClaimsFunc = func(c echo.Context) jwt.Claims { + return jwt.MapClaims{} + } + } + if config.SigningKey == nil && len(config.SigningKeys) == 0 && config.KeyFunc == nil && config.ParseTokenFunc == nil { + return nil, errors.New("jwt middleware requires signing key") + } + if config.KeyFunc == nil { + config.KeyFunc = config.defaultKeyFunc + } + if config.ParseTokenFunc == nil { + config.ParseTokenFunc = config.defaultParseTokenFunc + } + extractors, ceErr := CreateExtractors(config.TokenLookup) + if ceErr != nil { + return nil, ceErr + } + if len(config.TokenLookupFuncs) > 0 { + extractors = append(config.TokenLookupFuncs, extractors...) + } + + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if config.Skipper(c) { + return next(c) + } + + if config.BeforeFunc != nil { + config.BeforeFunc(c) + } + var lastExtractorErr error + var lastTokenErr error + for _, extractor := range extractors { + auths, extrErr := extractor(c) + if extrErr != nil { + lastExtractorErr = extrErr + continue + } + for _, auth := range auths { + token, err := config.ParseTokenFunc(c, auth) + 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) + } + } + + // prioritize token errors over extracting errors as parsing is occurs further in process, meaning we managed to + // extract at least one token and failed to parse it + var err error + if lastTokenErr != nil { + err = &TokenParsingError{Err: lastTokenErr} + } else if lastExtractorErr != nil { + err = &TokenExtractionError{Err: lastExtractorErr} + } + if config.ErrorHandler != nil { + tmpErr := config.ErrorHandler(c, err) + if config.ContinueOnIgnoredError && tmpErr == nil { + return next(c) + } + return tmpErr + } + + if lastTokenErr == nil { + return echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt").SetInternal(err) + } + + return echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt").SetInternal(err) + } + }, nil +} + +// defaultKeyFunc creates JWTGo implementation for KeyFunc. +// +// error returns TokenError. +func (config Config) defaultKeyFunc(token *jwt.Token) (interface{}, error) { + if token.Method.Alg() != config.SigningMethod { + return nil, &TokenError{Token: token, Err: fmt.Errorf("unexpected jwt signing method=%v", token.Header["alg"])} + } + if len(config.SigningKeys) == 0 { + return config.SigningKey, nil + } + + if kid, ok := token.Header["kid"].(string); ok { + if key, ok := config.SigningKeys[kid]; ok { + return key, nil + } + } + return nil, &TokenError{Token: token, Err: fmt.Errorf("unexpected jwt key id=%v", token.Header["kid"])} +} + +// defaultParseTokenFunc creates JWTGo implementation for ParseTokenFunc. +// +// error returns TokenError. +func (config Config) defaultParseTokenFunc(c echo.Context, auth string) (interface{}, error) { + token, err := jwt.ParseWithClaims(auth, config.NewClaimsFunc(c), config.KeyFunc) + if err != nil { + return nil, &TokenError{Token: token, Err: err} + } + if !token.Valid { + return nil, &TokenError{Token: token, Err: errors.New("invalid token")} + } + return token, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 7f887c1..b6391cc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -27,6 +27,9 @@ github.com/justinmichaelvieira/escpos # github.com/justinmichaelvieira/iconv v0.1.0 ## explicit github.com/justinmichaelvieira/iconv +# github.com/labstack/echo-jwt/v4 v4.3.0 +## explicit; go 1.20 +github.com/labstack/echo-jwt/v4 # github.com/labstack/echo/v4 v4.13.3 ## explicit; go 1.20 github.com/labstack/echo/v4