Skip to content

Commit 16e0e3b

Browse files
committed
feat(*): add plugins
1 parent c8d1618 commit 16e0e3b

File tree

27 files changed

+963
-128
lines changed

27 files changed

+963
-128
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";

examples/function/webhookx-function-sample.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ endpoints:
1414

1515
sources:
1616
- name: github-source
17-
path: /github
18-
methods: [ "POST" ]
19-
response:
20-
code: 200
21-
content_type: application/json
22-
body: '{"message": "OK"}'
17+
type: http
18+
config:
19+
http:
20+
path: /github
21+
methods: [ "POST" ]
22+
response:
23+
code: 200
24+
content_type: application/json
25+
body: '{"message": "OK"}'
2326
plugins:
2427
- name: function
2528
config:

openapi.yml

Lines changed: 41 additions & 24 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:
@@ -915,8 +902,8 @@ components:
915902
type: integer
916903
readOnly: true
917904
required:
918-
- path
919-
- methods
905+
- type
906+
- config
920907

921908
Plugin:
922909
type: object
@@ -988,3 +975,33 @@ components:
988975
required:
989976
- quota
990977
- period
978+
979+
HTTPSourceConfig:
980+
type: object
981+
default:
982+
methods: [ "POST" ]
983+
properties:
984+
path:
985+
type: string
986+
methods:
987+
type: array
988+
items:
989+
type: string
990+
enum: [ GET, POST, PUT, DELETE, PATCH ]
991+
minItems: 1
992+
default: [ "POST" ]
993+
response:
994+
type: object
995+
nullable: true
996+
properties:
997+
code:
998+
type: integer
999+
minimum: 200
1000+
maximum: 599
1001+
content_type:
1002+
type: string
1003+
body:
1004+
type: string
1005+
required:
1006+
- code
1007+
- content_type

plugins/basic-auth/plugin.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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"`
11+
Password string `json:"password"`
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, ok := inbound.Request.BasicAuth()
37+
if !ok || username != p.Config.Username || password != p.Config.Password {
38+
response.JSON(inbound.Response, 401, `{"message":"Unauthorized"}`)
39+
result.Terminated = true
40+
}
41+
42+
result.Payload = inbound.RawBody
43+
return
44+
}

plugins/hmac-auth/plugin.go

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

0 commit comments

Comments
 (0)