Reputation: 1434
Is there a way to access the raw body of the request? Not that has been parsed into json?
@Injectable()
export class WooGuard implements CanActivate {
secret: string;
constructor(
private readonly reflector: Reflector,
private configService: ConfigService,
) {
this.secret = this.configService.get<string>("woocommerce.webhook.secret");
}
async canActivate(
context: ExecutionContext,
): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
request.body // this is parsed json
// I am calculating the sha256 hash of the body with a secret for a webhook.
// due to how the raw json is vs. the JSON.stringify(request.body), the signature is never the same.
}
}
Upvotes: 9
Views: 6872
Reputation: 111
Adding these options in Main.ts worked for me
const app = await NestFactory.create(AppModule, {
rawBody: true,
bodyParser: true,
});
And in the guard, you can access raw body as:
const request = context.switchToHttp().getRequest();
const rawBody = request.rawBody;
Or in the controller, you can access it as:
@Post("webhooks")
async webhooks(
@Req() req: RawBodyRequest<Request>,
@Res() res: Response,
) {
// do stuff with it
Upvotes: 0
Reputation: 1889
You can add a middleware in main.ts
(after app
was initialized) to prevent NestJS from parsing the body to JSON for a specific route you need.
import { raw } from 'body-parser'
const app = await NestFactory.create(AppModule);
// Will keep the raw body
app.use('/users', raw({type: 'application/json'}));
All requests under the /users
route will have the raw body.
Pro: fast and easy for 1-2 endpoints, testing, etc.
Cons: may become cumbersome for many routes. Can disable body-parser instead and create 2 functional middlewares (one for raw and one for JSON parse).
Upvotes: 6
Reputation: 356
Shopify has a similar way to validate requests, this code worked for me, maybe you can change it.
First you need to install crypto:
npm install --save crypto
Then:
import { Injectable, CanActivate, ExecutionContext, HttpStatus } from '@nestjs/common';
const crypto = require('crypto');
@Injectable()
export class ShopifyAuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const secretKey = <MY_KEY>;
const hmac = request.headers["x-shopify-hmac-sha256"];
const hash = crypto
.createHmac('sha256', secretKey)
.update(request.body)
.digest('base64');
if (hmac === hash) {
return true;
} else {
throw new ForbiddenException("Not allowed");
}
}
}
And finally on your controller:
@Post()
@UseGuards(ShopifyAuthGuard)
async createNewOrder(@Body() orderDto: OrderDto) {}
Hope it helps!
Upvotes: 3