Skip to content

Commit

Permalink
Merge pull request #339 from authorizerdev/fix/webhooks
Browse files Browse the repository at this point in the history
fix: allow multiple hooks for same event
  • Loading branch information
lakhansamani authored Mar 29, 2023
2 parents f324976 + 1613693 commit 77f34e1
Show file tree
Hide file tree
Showing 50 changed files with 622 additions and 395 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ test-all-db:
docker rm -vf authorizer_mongodb_db
docker rm -vf authorizer_arangodb
docker rm -vf dynamodb-local-test
# docker rm -vf couchbase-local-test
docker rm -vf couchbase-local-test
generate:
cd server && go run github.com/99designs/gqlgen generate && go mod tidy
36 changes: 35 additions & 1 deletion dashboard/src/components/UpdateWebhookModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ interface headersValidatorDataType {
interface selecetdWebhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
Expand All @@ -86,6 +87,7 @@ const initHeadersValidatorData: headersValidatorDataType = {

interface webhookDataType {
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]: headersDataType[];
Expand All @@ -98,6 +100,7 @@ interface validatorDataType {

const initWebhookData: webhookDataType = {
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames['User login'],
[WebhookInputDataFields.EVENT_DESCRIPTION]: '',
[WebhookInputDataFields.ENDPOINT]: '',
[WebhookInputDataFields.ENABLED]: true,
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
Expand Down Expand Up @@ -144,6 +147,9 @@ const UpdateWebhookModal = ({
case WebhookInputDataFields.EVENT_NAME:
setWebhook({ ...webhook, [inputType]: value });
break;
case WebhookInputDataFields.EVENT_DESCRIPTION:
setWebhook({ ...webhook, [inputType]: value });
break;
case WebhookInputDataFields.ENDPOINT:
setWebhook({ ...webhook, [inputType]: value });
setValidator({
Expand Down Expand Up @@ -246,6 +252,8 @@ const UpdateWebhookModal = ({
let params: any = {
[WebhookInputDataFields.EVENT_NAME]:
webhook[WebhookInputDataFields.EVENT_NAME],
[WebhookInputDataFields.EVENT_DESCRIPTION]:
webhook[WebhookInputDataFields.EVENT_DESCRIPTION],
[WebhookInputDataFields.ENDPOINT]:
webhook[WebhookInputDataFields.ENDPOINT],
[WebhookInputDataFields.ENABLED]: webhook[WebhookInputDataFields.ENABLED],
Expand Down Expand Up @@ -402,7 +410,9 @@ const UpdateWebhookModal = ({
<Flex flex="3">
<Select
size="md"
value={webhook[WebhookInputDataFields.EVENT_NAME]}
value={
webhook[WebhookInputDataFields.EVENT_NAME].split('-')[0]
}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.EVENT_NAME,
Expand All @@ -420,6 +430,30 @@ const UpdateWebhookModal = ({
</Select>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="start"
alignItems="center"
marginBottom="5%"
>
<Flex flex="1">Event Description</Flex>
<Flex flex="3">
<InputGroup size="md">
<Input
pr="4.5rem"
type="text"
placeholder="User event"
value={webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
onChange={(e) =>
inputChangehandler(
WebhookInputDataFields.EVENT_DESCRIPTION,
e.currentTarget.value,
)
}
/>
</InputGroup>
</Flex>
</Flex>
<Flex
width="100%"
justifyContent="start"
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export const envSubViews = {

export enum WebhookInputDataFields {
ID = 'id',
EVENT_DESCRIPTION = 'event_description',
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/graphql/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export const WebhooksDataQuery = `
_webhooks(params: $params){
webhooks{
id
event_description
event_name
endpoint
enabled
Expand Down
10 changes: 8 additions & 2 deletions dashboard/src/pages/Webhooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ interface paginationPropTypes {
interface webhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]?: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
Expand Down Expand Up @@ -117,6 +118,7 @@ const Webhooks = () => {
useEffect(() => {
fetchWebookData();
}, [paginationProps.page, paginationProps.limit]);
console.log({ webhookData });
return (
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
Expand All @@ -134,6 +136,7 @@ const Webhooks = () => {
<Thead>
<Tr>
<Th>Event Name</Th>
<Th>Event Description</Th>
<Th>Endpoint</Th>
<Th>Enabled</Th>
<Th>Headers</Th>
Expand All @@ -147,7 +150,10 @@ const Webhooks = () => {
style={{ fontSize: 14 }}
>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_NAME]}
{webhook[WebhookInputDataFields.EVENT_NAME].split('-')[0]}
</Td>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
</Td>
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
<Td>
Expand Down Expand Up @@ -264,7 +270,7 @@ const Webhooks = () => {
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<Text>Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
Expand Down
41 changes: 24 additions & 17 deletions server/db/models/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,42 @@ import (

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

// Event name has been kept unique as per initial design. But later on decided that we can have
// multiple hooks for same event so will be in a pattern `event_name-TIMESTAMP`

// Webhook model for db
type Webhook struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
EventDescription string `json:"event_description" bson:"event_description" cql:"event_description" dynamo:"event_description"`
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

// AsAPIWebhook to return webhook as graphql response object
func (w *Webhook) AsAPIWebhook() *model.Webhook {
headersMap := make(map[string]interface{})
json.Unmarshal([]byte(w.Headers), &headersMap)

id := w.ID
if strings.Contains(id, Collections.Webhook+"/") {
id = strings.TrimPrefix(id, Collections.Webhook+"/")
}

// set default title to event name without dot(.)
if w.EventDescription == "" {
w.EventDescription = strings.Join(strings.Split(w.EventName, "."), " ")
}
return &model.Webhook{
ID: id,
EventName: refs.NewStringRef(w.EventName),
Endpoint: refs.NewStringRef(w.EndPoint),
Headers: headersMap,
Enabled: refs.NewBoolRef(w.Enabled),
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
ID: id,
EventName: refs.NewStringRef(w.EventName),
EventDescription: refs.NewStringRef(w.EventDescription),
Endpoint: refs.NewStringRef(w.EndPoint),
Headers: headersMap,
Enabled: refs.NewBoolRef(w.Enabled),
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
}
}
41 changes: 18 additions & 23 deletions server/db/providers/arangodb/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package arangodb
import (
"context"
"fmt"
"strings"
"time"

"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
Expand All @@ -17,8 +19,9 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.ID = uuid.New().String()
webhook.Key = webhook.ID
}

webhook.Key = webhook.ID
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix()
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
Expand All @@ -32,12 +35,15 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
// UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
meta, err := webhookCollection.UpdateDocument(ctx, webhook.Key, webhook)
if err != nil {
return nil, err
}

webhook.Key = meta.Key
webhook.ID = meta.ID.String()
return webhook.AsAPIWebhook(), nil
Expand All @@ -55,10 +61,8 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
return nil, err
}
defer cursor.Close()

paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount()

for {
var webhook models.Webhook
meta, err := cursor.ReadDocument(ctx, &webhook)
Expand Down Expand Up @@ -87,13 +91,11 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
bindVars := map[string]interface{}{
"webhook_id": webhookID,
}

cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()

for {
if !cursor.HasMore() {
if webhook.Key == "" {
Expand All @@ -110,32 +112,28 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
}

// GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
var webhook models.Webhook
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.Webhook)
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
query := fmt.Sprintf("FOR d in %s FILTER d.event_name LIKE @event_name RETURN d", models.Collections.Webhook)
bindVars := map[string]interface{}{
"event_name": eventName,
"event_name": eventName + "%",
}

cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return nil, err
}
defer cursor.Close()

webhooks := []*model.Webhook{}
for {
if !cursor.HasMore() {
if webhook.Key == "" {
return nil, fmt.Errorf("webhook not found")
}
var webhook models.Webhook
if _, err := cursor.ReadDocument(ctx, &webhook); driver.IsNoMoreDocuments(err) {
// We're done
break
}
_, err := cursor.ReadDocument(ctx, &webhook)
if err != nil {
} else if err != nil {
return nil, err
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
return webhook.AsAPIWebhook(), nil
return webhooks, nil
}

// DeleteWebhook to delete webhook
Expand All @@ -145,17 +143,14 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
if err != nil {
return err
}

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)
bindVars := map[string]interface{}{
"webhook_id": webhook.ID,
}

cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil {
return err
}
defer cursor.Close()

return nil
}
7 changes: 7 additions & 0 deletions server/db/providers/cassandradb/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ func NewProvider() (*provider, error) {
if err != nil {
return nil, err
}
// add event_description to webhook table
webhookAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (event_description text);`, KeySpace, models.Collections.Webhook)
err = session.Query(webhookAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter table as column exists: ", err)
// continue
}

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)
err = session.Query(webhookLogCollectionQuery).Exec()
Expand Down
Loading

0 comments on commit 77f34e1

Please sign in to comment.