Skip to content

Commit 4b931bd

Browse files
committed
init
1 parent b1454ef commit 4b931bd

File tree

4 files changed

+341
-0
lines changed

4 files changed

+341
-0
lines changed

go.mod

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module github.com/bootjp/echo_middleware_path_auth
2+
3+
go 1.19
4+
5+
require (
6+
github.com/labstack/echo/v4 v4.9.1
7+
github.com/stretchr/testify v1.7.0
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
13+
github.com/labstack/gommon v0.4.0 // indirect
14+
github.com/mattn/go-colorable v0.1.11 // indirect
15+
github.com/mattn/go-isatty v0.0.14 // indirect
16+
github.com/pmezard/go-difflib v1.0.0 // indirect
17+
github.com/valyala/bytebufferpool v1.0.0 // indirect
18+
github.com/valyala/fasttemplate v1.2.1 // indirect
19+
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
20+
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
21+
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
22+
golang.org/x/text v0.3.7 // indirect
23+
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
24+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
25+
)

go.sum

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
5+
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
6+
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
7+
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
8+
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
9+
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
10+
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
11+
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
12+
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
13+
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
14+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
15+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
16+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
17+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
18+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
19+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
20+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
21+
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
22+
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
23+
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
24+
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
25+
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
26+
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
27+
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
28+
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
29+
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
30+
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
31+
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
32+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
33+
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
34+
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
35+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
36+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
37+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
38+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
39+
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

path_auth.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package echo_middleware_path_auth
2+
3+
import (
4+
"github.com/labstack/echo/v4"
5+
"github.com/labstack/echo/v4/middleware"
6+
"net/http"
7+
)
8+
9+
type (
10+
// PathAuthConfig defines the config for PathAuth middleware.
11+
PathAuthConfig struct {
12+
// Skipper defines a function to skip middleware.
13+
Skipper middleware.Skipper
14+
15+
// Validator is a function to validate key.
16+
// Required.
17+
Validator PathAuthValidator
18+
19+
// ErrorHandler defines a function which is executed for an invalid key.
20+
// It may be used to define a custom error.
21+
ErrorHandler PathAuthErrorHandler
22+
23+
Param string
24+
}
25+
26+
// PathAuthValidator defines a function to validate PathAuth credentials.
27+
PathAuthValidator func(auth string, c echo.Context) (bool, error)
28+
29+
// PathAuthErrorHandler defines a function which is executed for an invalid key.
30+
PathAuthErrorHandler func(err error, c echo.Context) error
31+
)
32+
33+
var (
34+
// DefaultKeyAuthConfig is the default PathAuth middleware config.
35+
DefaultKeyAuthConfig = PathAuthConfig{
36+
Skipper: middleware.DefaultSkipper,
37+
}
38+
)
39+
40+
// ErrKeyAuthMissing is error type when PathAuth middleware is unable to extract value from lookups
41+
type ErrKeyAuthMissing struct {
42+
Err error
43+
}
44+
45+
// Error returns errors text
46+
func (e *ErrKeyAuthMissing) Error() string {
47+
return e.Err.Error()
48+
}
49+
50+
// Unwrap unwraps error
51+
func (e *ErrKeyAuthMissing) Unwrap() error {
52+
return e.Err
53+
}
54+
55+
// PathAuth returns an PathAuth middleware.
56+
//
57+
// For valid key it calls the next handler.
58+
// For invalid key, it sends "401 - Unauthorized" response.
59+
// For missing key, it sends "400 - Bad Request" response.
60+
func PathAuth(param string, fn PathAuthValidator) echo.MiddlewareFunc {
61+
c := DefaultKeyAuthConfig
62+
c.Validator = fn
63+
c.Param = param
64+
return PathAuthWithConfig(c)
65+
}
66+
67+
func PathAuthWithConfig(config PathAuthConfig) echo.MiddlewareFunc {
68+
if config.Skipper == nil {
69+
config.Skipper = DefaultKeyAuthConfig.Skipper
70+
}
71+
if config.Validator == nil {
72+
panic("PathAuth: requires a validator function")
73+
}
74+
75+
if len(config.Param) == 0 {
76+
panic("PathAuth: requires a param")
77+
}
78+
79+
return func(next echo.HandlerFunc) echo.HandlerFunc {
80+
return func(c echo.Context) error {
81+
if config.Skipper(c) {
82+
return next(c)
83+
}
84+
valid, err := config.Validator(c.Param(config.Param), c)
85+
if err != nil {
86+
return &echo.HTTPError{
87+
Code: http.StatusUnauthorized,
88+
Message: "Unauthorized",
89+
Internal: err,
90+
}
91+
}
92+
93+
if valid {
94+
return next(c)
95+
}
96+
97+
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
98+
}
99+
}
100+
}

path_auth_test.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package echo_middleware_path_auth
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/labstack/echo/v4"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func testKeyValidator(key string, _ echo.Context) (bool, error) {
14+
switch key {
15+
case "valid-key":
16+
return true, nil
17+
case "error-key":
18+
return false, errors.New("some user defined error")
19+
default:
20+
return false, nil
21+
}
22+
}
23+
24+
func TestKeyAuth(t *testing.T) {
25+
t.Run("auth ok", func(t *testing.T) {
26+
handlerCalled := false
27+
handler := func(c echo.Context) error {
28+
handlerCalled = true
29+
return c.String(http.StatusOK, "test")
30+
}
31+
middlewareChain := PathAuth("apikey", testKeyValidator)(handler)
32+
33+
e := echo.New()
34+
e.GET("/:apikey", middlewareChain)
35+
36+
req := httptest.NewRequest(http.MethodGet, "/", nil)
37+
rec := httptest.NewRecorder()
38+
39+
c := e.NewContext(req, rec)
40+
e.Router().Find(http.MethodGet, "/valid-key", c)
41+
err := middlewareChain(c)
42+
43+
assert.NoError(t, err)
44+
assert.True(t, handlerCalled)
45+
})
46+
47+
t.Run("auth nok", func(t *testing.T) {
48+
handlerCalled := false
49+
handler := func(c echo.Context) error {
50+
handlerCalled = true
51+
return c.String(http.StatusOK, "test")
52+
}
53+
middlewareChain := PathAuth("apikey", testKeyValidator)(handler)
54+
55+
e := echo.New()
56+
e.GET("/:apikey", middlewareChain)
57+
58+
req := httptest.NewRequest(http.MethodGet, "/", nil)
59+
rec := httptest.NewRecorder()
60+
61+
c := e.NewContext(req, rec)
62+
e.Router().Find(http.MethodGet, "/error-key", c)
63+
err := middlewareChain(c)
64+
65+
assert.Error(t, err)
66+
assert.False(t, handlerCalled)
67+
})
68+
69+
}
70+
71+
func TestPathAuthWithConfig(t *testing.T) {
72+
var testCases = []struct {
73+
name string
74+
givenRequestFunc func() *http.Request
75+
givenRequest func(req *http.Request)
76+
whenConfig func(conf *PathAuthConfig)
77+
pathName string
78+
expectHandlerCalled bool
79+
expectError string
80+
}{
81+
{
82+
name: "ok, default config",
83+
givenRequestFunc: func() *http.Request {
84+
req := httptest.NewRequest(http.MethodPost, "/valid-key", nil)
85+
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
86+
return req
87+
},
88+
expectHandlerCalled: true,
89+
expectError: "",
90+
},
91+
}
92+
93+
for _, tc := range testCases {
94+
t.Run(tc.name, func(t *testing.T) {
95+
handlerCalled := false
96+
handler := func(c echo.Context) error {
97+
handlerCalled = true
98+
return c.String(http.StatusOK, "test")
99+
}
100+
config := PathAuthConfig{
101+
Validator: testKeyValidator,
102+
Param: "apikey",
103+
}
104+
if tc.whenConfig != nil {
105+
tc.whenConfig(&config)
106+
}
107+
middlewareChain := PathAuthWithConfig(config)(handler)
108+
109+
e := echo.New()
110+
req := httptest.NewRequest(http.MethodGet, "/", nil)
111+
if tc.givenRequestFunc != nil {
112+
req = tc.givenRequestFunc()
113+
}
114+
if tc.givenRequest != nil {
115+
tc.givenRequest(req)
116+
}
117+
e.GET("/:apikey", middlewareChain)
118+
rec := httptest.NewRecorder()
119+
c := e.NewContext(req, rec)
120+
e.Router().Find(http.MethodGet, "/valid-key", c)
121+
err := middlewareChain(c)
122+
123+
assert.Equal(t, tc.expectHandlerCalled, handlerCalled)
124+
if tc.expectError != "" {
125+
assert.EqualError(t, err, tc.expectError)
126+
} else {
127+
assert.NoError(t, err)
128+
}
129+
})
130+
}
131+
}
132+
133+
func TestPathAuthWithConfig_panicsOnEmptyValidator(t *testing.T) {
134+
assert.PanicsWithValue(
135+
t,
136+
"PathAuth: requires a validator function",
137+
func() {
138+
handler := func(c echo.Context) error {
139+
return c.String(http.StatusOK, "test")
140+
}
141+
PathAuthWithConfig(PathAuthConfig{
142+
Validator: nil,
143+
})(handler)
144+
},
145+
)
146+
}
147+
148+
func TestPathAuthWithConfig_panicsOnEmptyParam(t *testing.T) {
149+
assert.PanicsWithValue(
150+
t,
151+
"PathAuth: requires a param",
152+
func() {
153+
handler := func(c echo.Context) error {
154+
return c.String(http.StatusOK, "test")
155+
}
156+
PathAuthWithConfig(PathAuthConfig{
157+
Validator: func(auth string, c echo.Context) (bool, error) {
158+
return true, nil
159+
},
160+
Param: "",
161+
})(handler)
162+
},
163+
)
164+
165+
assert.PanicsWithValue(
166+
t,
167+
"PathAuth: requires a param",
168+
func() {
169+
handler := func(c echo.Context) error {
170+
return c.String(http.StatusOK, "test")
171+
}
172+
PathAuth("", func(auth string, c echo.Context) (bool, error) {
173+
return true, nil
174+
})(handler)
175+
},
176+
)
177+
}

0 commit comments

Comments
 (0)