Skip to content

Commit

Permalink
Fix: storyblok webhook signature (#412)
Browse files Browse the repository at this point in the history
* fix signature

* fix: tests for webhooks

---------

Co-authored-by: Ellie Re'em <eleanor.reem@hotmail.co.uk>
  • Loading branch information
annarhughes and eleanorreem committed May 16, 2024
1 parent 44c11f1 commit d0968a6
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 11 deletions.
6 changes: 3 additions & 3 deletions src/webhooks/webhooks.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Body, Controller, Headers, Logger, Post, UseGuards } from '@nestjs/common';
import { Body, Controller, Headers, Logger, Post, Request, UseGuards } from '@nestjs/common';
import { ApiBody, ApiTags } from '@nestjs/swagger';
import { EventLogEntity } from 'src/entities/event-log.entity';
import { TherapySessionEntity } from 'src/entities/therapy-session.entity';
Expand Down Expand Up @@ -46,8 +46,8 @@ export class WebhooksController {

@Post('storyblok')
@ApiBody({ type: StoryDto })
async updateStory(@Body() data: StoryDto, @Headers() headers) {
async updateStory(@Request() req, @Body() data: StoryDto, @Headers() headers) {
const signature: string | undefined = headers['webhook-signature'];
return this.webhooksService.updateStory(data, signature);
return this.webhooksService.updateStory(req, data, signature);
}
}
20 changes: 14 additions & 6 deletions src/webhooks/webhooks.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,14 @@ import { WebhooksService } from './webhooks.service';
const webhookSecret = process.env.STORYBLOK_WEBHOOK_SECRET;

const getWebhookSignature = (body) => {
return createHmac('sha1', webhookSecret).update(JSON.stringify(body)).digest('hex');
return createHmac('sha1', webhookSecret).update(""+body).digest('hex');
};
const createRequestObject = (body) => {
return {
rawBody: "" + body,
setEncoding: ()=>{},
encoding: "utf8"
}}

// Difficult to mock classes as well as node modules.
// This seemed the best approach
Expand Down Expand Up @@ -228,7 +234,7 @@ describe('WebhooksService', () => {
text: '',
};

return expect(service.updateStory(body, getWebhookSignature(body))).rejects.toThrow(
return expect(service.updateStory(createRequestObject(body), body, getWebhookSignature(body))).rejects.toThrow(
'STORYBLOK STORY NOT FOUND',
);
});
Expand All @@ -241,6 +247,7 @@ describe('WebhooksService', () => {
};

const deletedStory = (await service.updateStory(
createRequestObject(body),
body,
getWebhookSignature(body),
)) as SessionEntity;
Expand All @@ -256,6 +263,7 @@ describe('WebhooksService', () => {
};

const unpublished = (await service.updateStory(
createRequestObject(body),
body,
getWebhookSignature(body),
)) as SessionEntity;
Expand Down Expand Up @@ -306,7 +314,7 @@ describe('WebhooksService', () => {
text: '',
};

const session = (await service.updateStory(body, getWebhookSignature(body))) as SessionEntity;
const session = (await service.updateStory(createRequestObject(body), body, getWebhookSignature(body))) as SessionEntity;

expect(courseFindOneSpy).toHaveBeenCalledWith({
storyblokUuid: 'anotherCourseUuId',
Expand Down Expand Up @@ -349,7 +357,7 @@ describe('WebhooksService', () => {
text: '',
};

const session = (await service.updateStory(body, getWebhookSignature(body))) as SessionEntity;
const session = (await service.updateStory(createRequestObject(body), body, getWebhookSignature(body))) as SessionEntity;

expect(session).toEqual(mockSession);
expect(courseFindOneSpy).toHaveBeenCalledWith({
Expand Down Expand Up @@ -408,7 +416,7 @@ describe('WebhooksService', () => {
text: '',
};

const session = (await service.updateStory(body, getWebhookSignature(body))) as SessionEntity;
const session = (await service.updateStory(createRequestObject(body), body, getWebhookSignature(body))) as SessionEntity;

expect(session).toEqual(mockSession);
expect(sessionSaveRepoSpy).toHaveBeenCalledWith({
Expand Down Expand Up @@ -442,7 +450,7 @@ describe('WebhooksService', () => {
text: '',
};

const course = (await service.updateStory(body, getWebhookSignature(body))) as CourseEntity;
const course = (await service.updateStory(createRequestObject(body), body, getWebhookSignature(body))) as CourseEntity;

expect(course).toEqual(mockCourse);
expect(courseFindOneRepoSpy).toHaveBeenCalledWith({
Expand Down
8 changes: 6 additions & 2 deletions src/webhooks/webhooks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,7 @@ export class WebhooksService {
}
}

async updateStory(data: StoryDto, signature: string | undefined) {
async updateStory(req, data: StoryDto, signature: string | undefined) {
// Verify storyblok signature uses storyblok webhook secret - see https://www.storyblok.com/docs/guide/in-depth/webhooks#securing-a-webhook
if (!signature) {
const error = `Storyblok webhook error - no signature provided`;
Expand All @@ -543,7 +543,11 @@ export class WebhooksService {
}

const webhookSecret = process.env.STORYBLOK_WEBHOOK_SECRET;
const bodyHmac = createHmac('sha1', webhookSecret).update(JSON.stringify(data)).digest('hex');

req.rawBody = '' + data;
req.setEncoding('utf8');

const bodyHmac = createHmac('sha1', webhookSecret).update(req.rawBody).digest('hex');

if (bodyHmac !== signature) {
const error = `Storyblok webhook error - signature mismatch`;
Expand Down

0 comments on commit d0968a6

Please sign in to comment.