Skip to content

Commit ef71522

Browse files
committed
feat(plugins): add plugins
1 parent 76f82cf commit ef71522

File tree

9 files changed

+756
-0
lines changed

9 files changed

+756
-0
lines changed

openapi.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,58 @@ components:
10371037
format: jsonschema
10381038
maxLength: 1048576
10391039

1040+
BasicAuthPluginConfiguration:
1041+
description: "The basic auth plugin configuration"
1042+
type: object
1043+
properties:
1044+
username:
1045+
type: string
1046+
password:
1047+
type: string
1048+
required:
1049+
- username
1050+
- password
1051+
1052+
KeyAuthPluginConfiguration:
1053+
description: ""
1054+
type: object
1055+
properties:
1056+
param_name:
1057+
type: string
1058+
param_sources:
1059+
type: array
1060+
items:
1061+
type: string
1062+
enum: [ "header", "query" ]
1063+
key:
1064+
type: string
1065+
required:
1066+
- param_name
1067+
- param_sources
1068+
- key
1069+
1070+
HmacAuthPluginConfiguration:
1071+
description: "The hmac-auth plugin configuration"
1072+
type: object
1073+
properties:
1074+
hash_method:
1075+
type: string
1076+
enum: [ "md5", "sha1", "sha256", "sha512" ]
1077+
default: "sha256"
1078+
hash_encoding:
1079+
type: string
1080+
enum: [ "hex", "base64", "base64url" ]
1081+
default: "hex"
1082+
signature_header:
1083+
type: string
1084+
key:
1085+
type: string
1086+
required:
1087+
- hash_method
1088+
- hash_encoding
1089+
- signature_header
1090+
- key
1091+
10401092
HTTPSourceConfig:
10411093
type: object
10421094
default:

plugins/basic-auth/plugin.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package basic_auth
2+
3+
import (
4+
"context"
5+
6+
"github.com/getkin/kin-openapi/openapi3"
7+
"github.com/webhookx-io/webhookx/db/entities"
8+
"github.com/webhookx-io/webhookx/pkg/http/response"
9+
"github.com/webhookx-io/webhookx/pkg/plugin"
10+
)
11+
12+
type Config struct {
13+
Username string `json:"username"`
14+
Password string `json:"password"`
15+
}
16+
17+
func (c Config) Schema() *openapi3.Schema {
18+
return entities.LookupSchema("BasicAuthPluginConfiguration")
19+
}
20+
21+
type BasicAuthPlugin struct {
22+
plugin.BasePlugin[Config]
23+
}
24+
25+
func (p *BasicAuthPlugin) Name() string {
26+
return "basic-auth"
27+
}
28+
29+
func (p *BasicAuthPlugin) ExecuteInbound(ctx context.Context, inbound *plugin.Inbound) (result plugin.InboundResult, err error) {
30+
username, password, ok := inbound.Request.BasicAuth()
31+
if !ok || username != p.Config.Username || password != p.Config.Password {
32+
response.JSON(inbound.Response, 401, `{"message":"Unauthorized"}`)
33+
result.Terminated = true
34+
}
35+
36+
result.Payload = inbound.RawBody
37+
return
38+
}

plugins/hmac-auth/plugin.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package hmac_auth
2+
3+
import (
4+
"context"
5+
"crypto/hmac"
6+
"crypto/md5"
7+
"crypto/sha1"
8+
"crypto/sha256"
9+
"crypto/sha512"
10+
"crypto/subtle"
11+
"encoding/base64"
12+
"encoding/hex"
13+
"errors"
14+
"fmt"
15+
"hash"
16+
17+
"github.com/getkin/kin-openapi/openapi3"
18+
"github.com/webhookx-io/webhookx/db/entities"
19+
"github.com/webhookx-io/webhookx/pkg/http/response"
20+
"github.com/webhookx-io/webhookx/pkg/plugin"
21+
)
22+
23+
var (
24+
ErrInvalidHashMethod = errors.New("invalid hash method")
25+
ErrInvalidEncodingMethod = errors.New("invalid encoding method")
26+
)
27+
28+
var hashMethods = map[string]func() hash.Hash{
29+
"md5": md5.New,
30+
"sha1": sha1.New,
31+
"sha256": sha256.New,
32+
"sha512": sha512.New,
33+
}
34+
35+
func Hmac(algorithm string, key string, data string) []byte {
36+
fn, ok := hashMethods[algorithm]
37+
if !ok {
38+
panic(fmt.Errorf("%w: %s", ErrInvalidHashMethod, algorithm))
39+
}
40+
h := hmac.New(fn, []byte(key))
41+
h.Write([]byte(data))
42+
return h.Sum(nil)
43+
}
44+
45+
func encode(encoding string, data []byte) string {
46+
switch encoding {
47+
case "hex":
48+
return hex.EncodeToString(data)
49+
case "base64":
50+
return base64.StdEncoding.EncodeToString(data)
51+
case "base64url":
52+
return base64.RawURLEncoding.EncodeToString(data)
53+
default:
54+
panic(fmt.Errorf("%w: %s", ErrInvalidEncodingMethod, encoding))
55+
}
56+
}
57+
58+
type Config struct {
59+
HashMethod string `json:"hash_method"`
60+
HashEncoding string `json:"hash_encoding"`
61+
SignatureHeader string `json:"signature_header"`
62+
Key string `json:"key"`
63+
}
64+
65+
func (c Config) Schema() *openapi3.Schema {
66+
return entities.LookupSchema("HmacAuthPluginConfiguration")
67+
}
68+
69+
type HmacAuthPlugin struct {
70+
plugin.BasePlugin[Config]
71+
}
72+
73+
func (p *HmacAuthPlugin) Name() string {
74+
return "hmac-auth"
75+
}
76+
77+
func (p *HmacAuthPlugin) ExecuteInbound(ctx context.Context, inbound *plugin.Inbound) (result plugin.InboundResult, err error) {
78+
matched := false
79+
signature := inbound.Request.Header.Get(p.Config.SignatureHeader)
80+
if len(signature) > 0 {
81+
bytes := Hmac(p.Config.HashMethod, p.Config.Key, string(inbound.RawBody))
82+
expectedSignature := encode(p.Config.HashEncoding, bytes)
83+
matched = subtle.ConstantTimeCompare([]byte(signature), []byte(expectedSignature)) == 1
84+
}
85+
86+
if !matched {
87+
response.JSON(inbound.Response, 401, `{"message":"Unauthorized"}`)
88+
result.Terminated = true
89+
}
90+
result.Payload = inbound.RawBody
91+
92+
return
93+
}

plugins/key-auth/plugin.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package key_auth
2+
3+
import (
4+
"context"
5+
6+
"github.com/getkin/kin-openapi/openapi3"
7+
"github.com/webhookx-io/webhookx/db/entities"
8+
"github.com/webhookx-io/webhookx/pkg/http/response"
9+
"github.com/webhookx-io/webhookx/pkg/plugin"
10+
)
11+
12+
// apikey-verifier ?
13+
14+
type Config struct {
15+
ParamName string `json:"param_name" validate:"required"`
16+
ParamSources []string `json:"param_sources" validate:"required"`
17+
Key string `json:"key" validate:"required"`
18+
}
19+
20+
func (c Config) Schema() *openapi3.Schema {
21+
return entities.LookupSchema("KeyAuthPluginConfiguration")
22+
}
23+
24+
type KeyAuthPlugin struct {
25+
plugin.BasePlugin[Config]
26+
}
27+
28+
func (p *KeyAuthPlugin) Name() string {
29+
return "key-auth"
30+
}
31+
32+
func (p *KeyAuthPlugin) ExecuteInbound(ctx context.Context, inbound *plugin.Inbound) (result plugin.InboundResult, err error) {
33+
name := p.Config.ParamName
34+
key := p.Config.Key
35+
sources := p.Config.ParamSources
36+
37+
querys := inbound.Request.URL.Query()
38+
headers := inbound.Request.Header
39+
40+
found := false
41+
for _, source := range sources {
42+
var value string
43+
switch source {
44+
case "query":
45+
value = querys.Get(name)
46+
case "header":
47+
value = headers.Get(name)
48+
}
49+
if value == key {
50+
found = true
51+
break
52+
}
53+
}
54+
55+
if !found {
56+
response.JSON(inbound.Response, 401, `{"message":"Unauthorized"}`)
57+
result.Terminated = true
58+
}
59+
60+
result.Payload = inbound.RawBody
61+
return
62+
}

plugins/plugins.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package plugins
22

33
import (
44
"github.com/webhookx-io/webhookx/pkg/plugin"
5+
basic_auth "github.com/webhookx-io/webhookx/plugins/basic-auth"
56
"github.com/webhookx-io/webhookx/plugins/function"
7+
hmac_auth "github.com/webhookx-io/webhookx/plugins/hmac-auth"
68
"github.com/webhookx-io/webhookx/plugins/jsonschema_validator"
9+
key_auth "github.com/webhookx-io/webhookx/plugins/key-auth"
710
"github.com/webhookx-io/webhookx/plugins/wasm"
811
"github.com/webhookx-io/webhookx/plugins/webhookx_signature"
912
)
@@ -21,4 +24,13 @@ func LoadPlugins() {
2124
plugin.RegisterPlugin(plugin.TypeInbound, "jsonschema-validator", func() plugin.Plugin {
2225
return &jsonschema_validator.SchemaValidatorPlugin{}
2326
})
27+
plugin.RegisterPlugin(plugin.TypeInbound, "basic-auth", func() plugin.Plugin {
28+
return &basic_auth.BasicAuthPlugin{}
29+
})
30+
plugin.RegisterPlugin(plugin.TypeInbound, "key-auth", func() plugin.Plugin {
31+
return &key_auth.KeyAuthPlugin{}
32+
})
33+
plugin.RegisterPlugin(plugin.TypeInbound, "hmac-auth", func() plugin.Plugin {
34+
return &hmac_auth.HmacAuthPlugin{}
35+
})
2436
}

test/admin/plugins_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/webhookx-io/webhookx/db"
1111
"github.com/webhookx-io/webhookx/db/entities"
1212
"github.com/webhookx-io/webhookx/pkg/plugin"
13+
key_auth "github.com/webhookx-io/webhookx/plugins/key-auth"
1314
"github.com/webhookx-io/webhookx/test/fixtures/plugins/hello"
1415
"github.com/webhookx-io/webhookx/test/fixtures/plugins/inbound"
1516
"github.com/webhookx-io/webhookx/test/fixtures/plugins/outbound"
@@ -213,6 +214,95 @@ var _ = Describe("/plugins", Ordered, func() {
213214
})
214215
})
215216

217+
Context("basic-auth plugin", func() {
218+
It("return 201", func() {
219+
source := factory.SourceP()
220+
assert.Nil(GinkgoT(), db.Sources.Insert(context.TODO(), source))
221+
resp, err := adminClient.R().
222+
SetBody(map[string]interface{}{
223+
"name": "basic-auth",
224+
"source_id": source.ID,
225+
"config": map[string]string{
226+
"username": "foo",
227+
"password": "bar",
228+
},
229+
}).
230+
SetResult(entities.Plugin{}).
231+
Post("/workspaces/default/plugins")
232+
233+
assert.Nil(GinkgoT(), err)
234+
assert.Equal(GinkgoT(), 201, resp.StatusCode())
235+
236+
result := resp.Result().(*entities.Plugin)
237+
assert.Equal(GinkgoT(), "basic-auth", result.Name)
238+
assert.Equal(GinkgoT(), source.ID, *result.SourceId)
239+
assert.Equal(GinkgoT(), "foo", result.Config["username"])
240+
assert.Equal(GinkgoT(), "bar", result.Config["password"])
241+
})
242+
})
243+
244+
Context("key-auth plugin", func() {
245+
It("return 201", func() {
246+
source := factory.SourceP()
247+
assert.Nil(GinkgoT(), db.Sources.Insert(context.TODO(), source))
248+
resp, err := adminClient.R().
249+
SetBody(map[string]interface{}{
250+
"name": "key-auth",
251+
"source_id": source.ID,
252+
"config": map[string]interface{}{
253+
"param_name": "apikey",
254+
"param_sources": []string{"header", "query"},
255+
"key": "mykey",
256+
},
257+
}).
258+
SetResult(entities.Plugin{}).
259+
Post("/workspaces/default/plugins")
260+
261+
assert.Nil(GinkgoT(), err)
262+
assert.Equal(GinkgoT(), 201, resp.StatusCode())
263+
264+
result := resp.Result().(*entities.Plugin)
265+
assert.Equal(GinkgoT(), "key-auth", result.Name)
266+
assert.Equal(GinkgoT(), source.ID, *result.SourceId)
267+
cfg := key_auth.Config{}
268+
utils.MapToStruct(result.Config, &cfg)
269+
assert.Equal(GinkgoT(), "apikey", cfg.ParamName)
270+
assert.Equal(GinkgoT(), []string{"header", "query"}, cfg.ParamSources)
271+
assert.Equal(GinkgoT(), "mykey", cfg.Key)
272+
})
273+
})
274+
275+
Context("hmac-auth plugin", func() {
276+
It("return 201", func() {
277+
source := factory.SourceP()
278+
assert.Nil(GinkgoT(), db.Sources.Insert(context.TODO(), source))
279+
resp, err := adminClient.R().
280+
SetBody(map[string]interface{}{
281+
"name": "hmac-auth",
282+
"source_id": source.ID,
283+
"config": map[string]interface{}{
284+
"hash_method": "sha256",
285+
"hash_encoding": "base64",
286+
"signature_header": "x-signature",
287+
"key": "mykey",
288+
},
289+
}).
290+
SetResult(entities.Plugin{}).
291+
Post("/workspaces/default/plugins")
292+
293+
assert.Nil(GinkgoT(), err)
294+
assert.Equal(GinkgoT(), 201, resp.StatusCode())
295+
296+
result := resp.Result().(*entities.Plugin)
297+
assert.Equal(GinkgoT(), "hmac-auth", result.Name)
298+
assert.Equal(GinkgoT(), source.ID, *result.SourceId)
299+
assert.Equal(GinkgoT(), "sha256", result.Config["hash_method"])
300+
assert.Equal(GinkgoT(), "base64", result.Config["hash_encoding"])
301+
assert.Equal(GinkgoT(), "x-signature", result.Config["signature_header"])
302+
assert.Equal(GinkgoT(), "mykey", result.Config["key"])
303+
})
304+
})
305+
216306
Context("errors", func() {
217307
It("return HTTP 400", func() {
218308
resp, err := adminClient.R().

0 commit comments

Comments
 (0)