Access raw body of Stripe webhook in Nest.js

允我心安 提交于 2020-05-27 05:34:11

问题


I need to access the raw body of the webhook request from Stripe in my Nest.js application.

Following this example, I added the below to the module which has a controller method that is needing the raw body.

function addRawBody(req, res, next) {
  req.setEncoding('utf8');

  let data = '';

  req.on('data', (chunk) => {
    data += chunk;
  });

  req.on('end', () => {
    req.rawBody = data;

    next();
  });
}

export class SubscriptionModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(addRawBody)
      .forRoutes('subscriptions/stripe');
  }
}

In the controller I am using @Req() reqand then req.rawBody to get the raw body. I need the raw body because the constructEvent of the Stripe api is using it to verify the request.

The problem is that the request is stuck. It seems that the req.on is not called either for data nor for the end event. So next() is not called in the middleware.

I did also try to use raw-body like here but I got pretty much the same result. In that case the req.readable is always false, so I am stuck there as well.

I guess this is an issue with Nest.js but I am not sure...


回答1:


I ran into a similar problem last night trying to authenticate a Slack token.

The solution we wound up using did require disabling the bodyParser from the core Nest App then re-enabling it after adding a new rawBody key to the request with the raw request body.

    const app = await NestFactory.create(AppModule, {
        bodyParser: false
    });

    const rawBodyBuffer = (req, res, buf, encoding) => {
        if (buf && buf.length) {
            req.rawBody = buf.toString(encoding || 'utf8');
        }
    };

    app.use(bodyParser.urlencoded({verify: rawBodyBuffer, extended: true }));
    app.use(bodyParser.json({ verify: rawBodyBuffer }));

Then in my middleware I could access it like so:

const isVerified = (req) => {
    const signature = req.headers['x-slack-signature'];
    const timestamp = req.headers['x-slack-request-timestamp'];
    const hmac = crypto.createHmac('sha256', 'somekey');
    const [version, hash] = signature.split('=');

    // Check if the timestamp is too old
    // tslint:disable-next-line:no-bitwise
    const fiveMinutesAgo = ~~(Date.now() / 1000) - (60 * 5);
    if (timestamp < fiveMinutesAgo) { return false; }

    hmac.update(`${version}:${timestamp}:${req.rawBody}`);

    // check that the request signature matches expected value
    return timingSafeCompare(hmac.digest('hex'), hash);
};

export async function slackTokenAuthentication(req, res, next) {
    if (!isVerified(req)) {
        next(new HttpException('Not Authorized Slack', HttpStatus.FORBIDDEN));
    }
    next();
}

Shine On!




回答2:


For anyone looking for a more elegant solution, turn off the bodyParser in main.ts

Create 2 middlewares one for rawbody and the other for json-parsed-body

json-body.middleware.ts

import {Request, Response} from 'express';
import * as bodyParser from 'body-parser';
import {Injectable, NestMiddleware} from '@nestjs/common';

@Injectable()
export class JsonBodyMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => any) {
    bodyParser.json()(req, res, next);
  }
}

raw-body.middleware.ts

import {Injectable, NestMiddleware} from '@nestjs/common';
import {Request, Response} from 'express';
import * as bodyParser from 'body-parser';

@Injectable()
export class RawBodyMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: () => any) {
    bodyParser.raw({type: '*/*'})(req, res, next);
  }
}

And apply the middlewares to appropriate routes in app.module.ts.

app.module.ts

...
public configure(consumer: MiddlewareConsumer): void {
    consumer
      .apply(RawBodyMiddleware)
      .forRoutes({
        path: '/stripe-webhooks',
        method: RequestMethod.POST,
      })
      .apply(JsonBodyMiddleware)
      .forRoutes('*');
 }

BTW req.rawbody has been removed from express long back - https://github.com/expressjs/express/issues/897




回答3:


Today,

as I am using NestJS and Stripe

I installed body-parser (npm), then in the main.ts, just add

 app.use('/payment/hooks', bodyParser.raw({type: 'application/json'}));

and it will be restricted to this route ! no overload




回答4:


This is because the bodyParser middleware is used by default and has already consumed the body when your middleware starts. You can turn off bodyParser in your main.ts:

const app = await NestFactory.create(AppModule, {bodyParser: false});
                                                 ^^^^^^^^^^^^^^^^^^

You should note though, that in most other cases you will probably want to use bodyParser.json so add this to all the other routes. You can exclude one particular route from middleware by using a negative regex, see e.g. this thread.



来源:https://stackoverflow.com/questions/54346465/access-raw-body-of-stripe-webhook-in-nest-js

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!