Skip to content

Commit 77f34e1

Browse files
authored
Merge pull request #339 from authorizerdev/fix/webhooks
fix: allow multiple hooks for same event
2 parents f324976 + 1613693 commit 77f34e1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+622
-395
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ test-all-db:
5151
docker rm -vf authorizer_mongodb_db
5252
docker rm -vf authorizer_arangodb
5353
docker rm -vf dynamodb-local-test
54-
# docker rm -vf couchbase-local-test
54+
docker rm -vf couchbase-local-test
5555
generate:
5656
cd server && go run github.com/99designs/gqlgen generate && go mod tidy

dashboard/src/components/UpdateWebhookModal.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ interface headersValidatorDataType {
6363
interface selecetdWebhookDataTypes {
6464
[WebhookInputDataFields.ID]: string;
6565
[WebhookInputDataFields.EVENT_NAME]: string;
66+
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
6667
[WebhookInputDataFields.ENDPOINT]: string;
6768
[WebhookInputDataFields.ENABLED]: boolean;
6869
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
@@ -86,6 +87,7 @@ const initHeadersValidatorData: headersValidatorDataType = {
8687

8788
interface webhookDataType {
8889
[WebhookInputDataFields.EVENT_NAME]: string;
90+
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
8991
[WebhookInputDataFields.ENDPOINT]: string;
9092
[WebhookInputDataFields.ENABLED]: boolean;
9193
[WebhookInputDataFields.HEADERS]: headersDataType[];
@@ -98,6 +100,7 @@ interface validatorDataType {
98100

99101
const initWebhookData: webhookDataType = {
100102
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames['User login'],
103+
[WebhookInputDataFields.EVENT_DESCRIPTION]: '',
101104
[WebhookInputDataFields.ENDPOINT]: '',
102105
[WebhookInputDataFields.ENABLED]: true,
103106
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
@@ -144,6 +147,9 @@ const UpdateWebhookModal = ({
144147
case WebhookInputDataFields.EVENT_NAME:
145148
setWebhook({ ...webhook, [inputType]: value });
146149
break;
150+
case WebhookInputDataFields.EVENT_DESCRIPTION:
151+
setWebhook({ ...webhook, [inputType]: value });
152+
break;
147153
case WebhookInputDataFields.ENDPOINT:
148154
setWebhook({ ...webhook, [inputType]: value });
149155
setValidator({
@@ -246,6 +252,8 @@ const UpdateWebhookModal = ({
246252
let params: any = {
247253
[WebhookInputDataFields.EVENT_NAME]:
248254
webhook[WebhookInputDataFields.EVENT_NAME],
255+
[WebhookInputDataFields.EVENT_DESCRIPTION]:
256+
webhook[WebhookInputDataFields.EVENT_DESCRIPTION],
249257
[WebhookInputDataFields.ENDPOINT]:
250258
webhook[WebhookInputDataFields.ENDPOINT],
251259
[WebhookInputDataFields.ENABLED]: webhook[WebhookInputDataFields.ENABLED],
@@ -402,7 +410,9 @@ const UpdateWebhookModal = ({
402410
<Flex flex="3">
403411
<Select
404412
size="md"
405-
value={webhook[WebhookInputDataFields.EVENT_NAME]}
413+
value={
414+
webhook[WebhookInputDataFields.EVENT_NAME].split('-')[0]
415+
}
406416
onChange={(e) =>
407417
inputChangehandler(
408418
WebhookInputDataFields.EVENT_NAME,
@@ -420,6 +430,30 @@ const UpdateWebhookModal = ({
420430
</Select>
421431
</Flex>
422432
</Flex>
433+
<Flex
434+
width="100%"
435+
justifyContent="start"
436+
alignItems="center"
437+
marginBottom="5%"
438+
>
439+
<Flex flex="1">Event Description</Flex>
440+
<Flex flex="3">
441+
<InputGroup size="md">
442+
<Input
443+
pr="4.5rem"
444+
type="text"
445+
placeholder="User event"
446+
value={webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
447+
onChange={(e) =>
448+
inputChangehandler(
449+
WebhookInputDataFields.EVENT_DESCRIPTION,
450+
e.currentTarget.value,
451+
)
452+
}
453+
/>
454+
</InputGroup>
455+
</Flex>
456+
</Flex>
423457
<Flex
424458
width="100%"
425459
justifyContent="start"

dashboard/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ export const envSubViews = {
179179

180180
export enum WebhookInputDataFields {
181181
ID = 'id',
182+
EVENT_DESCRIPTION = 'event_description',
182183
EVENT_NAME = 'event_name',
183184
ENDPOINT = 'endpoint',
184185
ENABLED = 'enabled',

dashboard/src/graphql/queries/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export const WebhooksDataQuery = `
118118
_webhooks(params: $params){
119119
webhooks{
120120
id
121+
event_description
121122
event_name
122123
endpoint
123124
enabled

dashboard/src/pages/Webhooks.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ interface paginationPropTypes {
5656
interface webhookDataTypes {
5757
[WebhookInputDataFields.ID]: string;
5858
[WebhookInputDataFields.EVENT_NAME]: string;
59+
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
5960
[WebhookInputDataFields.ENDPOINT]: string;
6061
[WebhookInputDataFields.ENABLED]: boolean;
6162
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
@@ -117,6 +118,7 @@ const Webhooks = () => {
117118
useEffect(() => {
118119
fetchWebookData();
119120
}, [paginationProps.page, paginationProps.limit]);
121+
console.log({ webhookData });
120122
return (
121123
<Box m="5" py="5" px="10" bg="white" rounded="md">
122124
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
@@ -134,6 +136,7 @@ const Webhooks = () => {
134136
<Thead>
135137
<Tr>
136138
<Th>Event Name</Th>
139+
<Th>Event Description</Th>
137140
<Th>Endpoint</Th>
138141
<Th>Enabled</Th>
139142
<Th>Headers</Th>
@@ -147,7 +150,10 @@ const Webhooks = () => {
147150
style={{ fontSize: 14 }}
148151
>
149152
<Td maxW="300">
150-
{webhook[WebhookInputDataFields.EVENT_NAME]}
153+
{webhook[WebhookInputDataFields.EVENT_NAME].split('-')[0]}
154+
</Td>
155+
<Td maxW="300">
156+
{webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
151157
</Td>
152158
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
153159
<Td>
@@ -264,7 +270,7 @@ const Webhooks = () => {
264270
</Text>
265271
</Text>
266272
<Flex alignItems="center">
267-
<Text flexShrink="0">Go to page:</Text>{' '}
273+
<Text>Go to page:</Text>{' '}
268274
<NumberInput
269275
ml={2}
270276
mr={8}

server/db/models/webhook.go

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,42 @@ import (
1010

1111
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
1212

13+
// Event name has been kept unique as per initial design. But later on decided that we can have
14+
// multiple hooks for same event so will be in a pattern `event_name-TIMESTAMP`
15+
1316
// Webhook model for db
1417
type Webhook struct {
15-
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
16-
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
17-
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
18-
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
19-
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
20-
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
21-
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
22-
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
18+
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
19+
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
20+
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
21+
EventDescription string `json:"event_description" bson:"event_description" cql:"event_description" dynamo:"event_description"`
22+
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
23+
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
24+
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
25+
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
26+
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
2327
}
2428

2529
// AsAPIWebhook to return webhook as graphql response object
2630
func (w *Webhook) AsAPIWebhook() *model.Webhook {
2731
headersMap := make(map[string]interface{})
2832
json.Unmarshal([]byte(w.Headers), &headersMap)
29-
3033
id := w.ID
3134
if strings.Contains(id, Collections.Webhook+"/") {
3235
id = strings.TrimPrefix(id, Collections.Webhook+"/")
3336
}
34-
37+
// set default title to event name without dot(.)
38+
if w.EventDescription == "" {
39+
w.EventDescription = strings.Join(strings.Split(w.EventName, "."), " ")
40+
}
3541
return &model.Webhook{
36-
ID: id,
37-
EventName: refs.NewStringRef(w.EventName),
38-
Endpoint: refs.NewStringRef(w.EndPoint),
39-
Headers: headersMap,
40-
Enabled: refs.NewBoolRef(w.Enabled),
41-
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
42-
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
42+
ID: id,
43+
EventName: refs.NewStringRef(w.EventName),
44+
EventDescription: refs.NewStringRef(w.EventDescription),
45+
Endpoint: refs.NewStringRef(w.EndPoint),
46+
Headers: headersMap,
47+
Enabled: refs.NewBoolRef(w.Enabled),
48+
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
49+
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
4350
}
4451
}

server/db/providers/arangodb/webhook.go

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package arangodb
33
import (
44
"context"
55
"fmt"
6+
"strings"
67
"time"
78

9+
"github.com/arangodb/go-driver"
810
arangoDriver "github.com/arangodb/go-driver"
911
"github.com/authorizerdev/authorizer/server/db/models"
1012
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -17,8 +19,9 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
1719
webhook.ID = uuid.New().String()
1820
webhook.Key = webhook.ID
1921
}
20-
2122
webhook.Key = webhook.ID
23+
// Add timestamp to make event name unique for legacy version
24+
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
2225
webhook.CreatedAt = time.Now().Unix()
2326
webhook.UpdatedAt = time.Now().Unix()
2427
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
@@ -32,12 +35,15 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
3235
// UpdateWebhook to update webhook
3336
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
3437
webhook.UpdatedAt = time.Now().Unix()
38+
// Event is changed
39+
if !strings.Contains(webhook.EventName, "-") {
40+
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
41+
}
3542
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
3643
meta, err := webhookCollection.UpdateDocument(ctx, webhook.Key, webhook)
3744
if err != nil {
3845
return nil, err
3946
}
40-
4147
webhook.Key = meta.Key
4248
webhook.ID = meta.ID.String()
4349
return webhook.AsAPIWebhook(), nil
@@ -55,10 +61,8 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
5561
return nil, err
5662
}
5763
defer cursor.Close()
58-
5964
paginationClone := pagination
6065
paginationClone.Total = cursor.Statistics().FullCount()
61-
6266
for {
6367
var webhook models.Webhook
6468
meta, err := cursor.ReadDocument(ctx, &webhook)
@@ -87,13 +91,11 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
8791
bindVars := map[string]interface{}{
8892
"webhook_id": webhookID,
8993
}
90-
9194
cursor, err := p.db.Query(ctx, query, bindVars)
9295
if err != nil {
9396
return nil, err
9497
}
9598
defer cursor.Close()
96-
9799
for {
98100
if !cursor.HasMore() {
99101
if webhook.Key == "" {
@@ -110,32 +112,28 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
110112
}
111113

112114
// GetWebhookByEventName to get webhook by event_name
113-
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
114-
var webhook models.Webhook
115-
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.Webhook)
115+
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
116+
query := fmt.Sprintf("FOR d in %s FILTER d.event_name LIKE @event_name RETURN d", models.Collections.Webhook)
116117
bindVars := map[string]interface{}{
117-
"event_name": eventName,
118+
"event_name": eventName + "%",
118119
}
119-
120120
cursor, err := p.db.Query(ctx, query, bindVars)
121121
if err != nil {
122122
return nil, err
123123
}
124124
defer cursor.Close()
125-
125+
webhooks := []*model.Webhook{}
126126
for {
127-
if !cursor.HasMore() {
128-
if webhook.Key == "" {
129-
return nil, fmt.Errorf("webhook not found")
130-
}
127+
var webhook models.Webhook
128+
if _, err := cursor.ReadDocument(ctx, &webhook); driver.IsNoMoreDocuments(err) {
129+
// We're done
131130
break
132-
}
133-
_, err := cursor.ReadDocument(ctx, &webhook)
134-
if err != nil {
131+
} else if err != nil {
135132
return nil, err
136133
}
134+
webhooks = append(webhooks, webhook.AsAPIWebhook())
137135
}
138-
return webhook.AsAPIWebhook(), nil
136+
return webhooks, nil
139137
}
140138

141139
// DeleteWebhook to delete webhook
@@ -145,17 +143,14 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
145143
if err != nil {
146144
return err
147145
}
148-
149146
query := fmt.Sprintf("FOR d IN %s FILTER d.webhook_id == @webhook_id REMOVE { _key: d._key } IN %s", models.Collections.WebhookLog, models.Collections.WebhookLog)
150147
bindVars := map[string]interface{}{
151148
"webhook_id": webhook.ID,
152149
}
153-
154150
cursor, err := p.db.Query(ctx, query, bindVars)
155151
if err != nil {
156152
return err
157153
}
158154
defer cursor.Close()
159-
160155
return nil
161156
}

server/db/providers/cassandradb/provider.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ func NewProvider() (*provider, error) {
207207
if err != nil {
208208
return nil, err
209209
}
210+
// add event_description to webhook table
211+
webhookAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (event_description text);`, KeySpace, models.Collections.Webhook)
212+
err = session.Query(webhookAlterQuery).Exec()
213+
if err != nil {
214+
log.Debug("Failed to alter table as column exists: ", err)
215+
// continue
216+
}
210217

211218
webhookLogCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, http_status bigint, response text, request text, webhook_id text,updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.WebhookLog)
212219
err = session.Query(webhookLogCollectionQuery).Exec()

0 commit comments

Comments
 (0)