Skip to content

Commit 96fbba5

Browse files
committed
feat(*): add plugins
1 parent 2fc3eb0 commit 96fbba5

File tree

15 files changed

+327
-72
lines changed

15 files changed

+327
-72
lines changed

admin/api/sources.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ func (api *API) CreateSource(w http.ResponseWriter, r *http.Request) {
4040
return
4141
}
4242

43+
if source.Type == "http" {
44+
if source.Config.HTTP.Path == "" {
45+
source.Config.HTTP.Path = "/" + utils.UUIDShort()
46+
}
47+
}
48+
4349
source.WorkspaceId = ucontext.GetWorkspaceID(r.Context())
4450
err := api.db.SourcesWS.Insert(r.Context(), &source)
4551
api.assert(err)

db/entities/source.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,33 @@ func (m CustomResponse) Value() (driver.Value, error) {
1919
return json.Marshal(m)
2020
}
2121

22+
type SourceConfig struct {
23+
HTTP HttpSourceConfig `json:"http"`
24+
}
25+
26+
func (m *SourceConfig) Scan(src interface{}) error {
27+
return json.Unmarshal(src.([]byte), m)
28+
}
29+
30+
func (m SourceConfig) Value() (driver.Value, error) {
31+
return json.Marshal(m)
32+
}
33+
34+
type HttpSourceConfig struct {
35+
Path string `json:"path"`
36+
Methods Strings `json:"methods"`
37+
Response *CustomResponse `json:"response"`
38+
}
39+
2240
type Source struct {
23-
ID string `json:"id" db:"id"`
24-
Name *string `json:"name" db:"name"`
25-
Enabled bool `json:"enabled" db:"enabled"`
26-
Path string `json:"path" db:"path"`
27-
Methods Strings `json:"methods" db:"methods"`
28-
Async bool `json:"async" db:"async"`
29-
Response *CustomResponse `json:"response" db:"response"`
30-
Metadata Metadata `json:"metadata" db:"metadata"`
31-
RateLimit *RateLimit `json:"rate_limit" yaml:"rate_limit" db:"rate_limit"`
41+
ID string `json:"id" db:"id"`
42+
Name *string `json:"name" db:"name"`
43+
Enabled bool `json:"enabled" db:"enabled"`
44+
Type string `json:"type" db:"type"`
45+
Config SourceConfig `json:"config" db:"config"`
46+
Async bool `json:"async" db:"async"`
47+
Metadata Metadata `json:"metadata" db:"metadata"`
48+
RateLimit *RateLimit `json:"rate_limit" yaml:"rate_limit" db:"rate_limit"`
3249

3350
BaseModel `yaml:"-"`
3451
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
ALTER TABLE IF EXISTS ONLY "sources" ADD COLUMN IF NOT EXISTS "path" TEXT;
2+
ALTER TABLE IF EXISTS ONLY "sources" ADD COLUMN IF NOT EXISTS "methods" TEXT[];
3+
ALTER TABLE IF EXISTS ONLY "sources" ADD COLUMN IF NOT EXISTS "response" JSONB;
4+
5+
UPDATE sources SET
6+
"path" = (config->'http'->>'path'),
7+
"methods" = ARRAY(SELECT jsonb_array_elements_text(config->'http'->'methods')),
8+
"response" = (config->'http'->'response');
9+
10+
ALTER TABLE IF EXISTS ONLY "sources" DROP COLUMN IF EXISTS "type";
11+
ALTER TABLE IF EXISTS ONLY "sources" DROP COLUMN IF EXISTS "config";
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ALTER TABLE IF EXISTS ONLY "sources" ADD COLUMN IF NOT EXISTS "type" varchar(20);
2+
ALTER TABLE IF EXISTS ONLY "sources" ADD COLUMN IF NOT EXISTS "config" JSONB NOT NULL DEFAULT '{}'::jsonb;
3+
4+
UPDATE sources SET "type" = 'http', "config" = jsonb_build_object('http', jsonb_build_object('methods', methods, 'path', path, 'response', response));
5+
6+
ALTER TABLE IF EXISTS ONLY "sources" DROP COLUMN IF EXISTS "path";
7+
ALTER TABLE IF EXISTS ONLY "sources" DROP COLUMN IF EXISTS "methods";
8+
ALTER TABLE IF EXISTS ONLY "sources" DROP COLUMN IF EXISTS "response";

openapi.yml

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -877,33 +877,20 @@ components:
877877
enabled:
878878
type: boolean
879879
default: true
880-
path:
880+
type:
881881
type: string
882-
methods:
883-
type: array
884-
items:
885-
type: string
886-
enum: [ GET, POST, PUT, DELETE, PATCH ]
887-
minItems: 1
882+
enum: [ http ]
883+
config:
884+
type: object
885+
properties:
886+
http:
887+
$ref: "#/components/schemas/HTTPSourceConfig"
888+
required:
889+
- http
888890
async:
889891
type: boolean
890892
description: "Whether to ingest events asynchronously through the queue"
891893
default: false
892-
response:
893-
type: object
894-
nullable: true
895-
properties:
896-
code:
897-
type: integer
898-
minimum: 200
899-
maximum: 599
900-
content_type:
901-
type: string
902-
body:
903-
type: string
904-
required:
905-
- code
906-
- content_type
907894
metadata:
908895
$ref: "#/components/schemas/Metadata"
909896
rate_limit:
@@ -914,9 +901,6 @@ components:
914901
updated_at:
915902
type: integer
916903
readOnly: true
917-
required:
918-
- path
919-
- methods
920904

921905
Plugin:
922906
type: object
@@ -988,3 +972,31 @@ components:
988972
required:
989973
- quota
990974
- period
975+
976+
HTTPSourceConfig:
977+
type: object
978+
properties:
979+
path:
980+
type: string
981+
methods:
982+
type: array
983+
items:
984+
type: string
985+
enum: [ GET, POST, PUT, DELETE, PATCH ]
986+
minItems: 1
987+
default: [ "POST" ]
988+
response:
989+
type: object
990+
nullable: true
991+
properties:
992+
code:
993+
type: integer
994+
minimum: 200
995+
maximum: 599
996+
content_type:
997+
type: string
998+
body:
999+
type: string
1000+
required:
1001+
- code
1002+
- content_type

plugins/basic-auth/plugin.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package basic_auth
2+
3+
import (
4+
"github.com/webhookx-io/webhookx/pkg/http/response"
5+
"github.com/webhookx-io/webhookx/pkg/plugin"
6+
"github.com/webhookx-io/webhookx/utils"
7+
)
8+
9+
type Config struct {
10+
Username string `json:"username" validate:"required"`
11+
Password string `json:"password" validate:"required"`
12+
}
13+
14+
type BasicAuthPlugin struct {
15+
plugin.BasePlugin[Config]
16+
}
17+
18+
func New(config []byte) (plugin.Plugin, error) {
19+
p := &BasicAuthPlugin{}
20+
p.Name = "basic-auth"
21+
22+
if config != nil {
23+
if err := p.UnmarshalConfig(config); err != nil {
24+
return nil, err
25+
}
26+
}
27+
28+
return p, nil
29+
}
30+
31+
func (p *BasicAuthPlugin) ValidateConfig() error {
32+
return utils.Validate(p.Config)
33+
}
34+
35+
func (p *BasicAuthPlugin) ExecuteInbound(inbound *plugin.Inbound) (result plugin.InboundResult, err error) {
36+
username, password, _ := inbound.Request.BasicAuth()
37+
if username != p.Config.Username || password != p.Config.Password {
38+
response.JSON(inbound.Response, 401, `{"message":"Unauthorized"}`)
39+
result.Terminated = true
40+
}
41+
42+
return
43+
}

plugins/hmac-auth/plugin.go

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

plugins/key-auth/plugin.go

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

plugins/plugins.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ 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"
8+
key_auth "github.com/webhookx-io/webhookx/plugins/key-auth"
69
"github.com/webhookx-io/webhookx/plugins/wasm"
710
"github.com/webhookx-io/webhookx/plugins/webhookx_signature"
811
)
@@ -11,4 +14,8 @@ func LoadPlugins() {
1114
plugin.RegisterPlugin(plugin.TypeInbound, "function", function.New)
1215
plugin.RegisterPlugin(plugin.TypeOutbound, "wasm", wasm.New)
1316
plugin.RegisterPlugin(plugin.TypeOutbound, "webhookx-signature", webhookx_signature.New)
17+
plugin.RegisterPlugin(plugin.TypeInbound, "key-auth", key_auth.New)
18+
plugin.RegisterPlugin(plugin.TypeInbound, "basic-auth", basic_auth.New)
19+
plugin.RegisterPlugin(plugin.TypeInbound, "hmac-auth", hmac_auth.New)
20+
1421
}

0 commit comments

Comments
 (0)