在Nest.js中访问Stripe Webhook的原始正文

the*_*eva 3 node.js express stripe-payments typescript nestjs

我需要从Nest.js应用程序中的Stripe访问webhook请求的原始正文。

示例之后,我将以下内容添加到具有需要原始主体的控制器方法的模块中。

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');
  }
}
Run Code Online (Sandbox Code Playgroud)

在控制器中,我正在使用@Req() req然后req.rawBody获取原始主体。我需要原始主体,因为Stripe api的constructEvent正在使用它来验证请求。

问题是请求被卡住了。似乎没有为数据或结束事件调用req.on。因此next()在中间件中不被称为。

我也确实尝试过raw-body这里一样使用但是得到的结果几乎相同。在这种情况下,req.read总是错误的,所以我也被困在那里。

我想这是Nest.js的问题,但我不确定...

Joe*_*aju 46

For anyone looking for a more elegant solution, turn off the bodyParser in main.ts. Create two middleware functions, 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

And apply the middleware functions to appropriate routes in app.module.ts.

app.module.ts

[...]

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

[...]
Run Code Online (Sandbox Code Playgroud)

BTW req.rawbody has been removed from express long ago.

https://github.com/expressjs/express/issues/897

  • 这是一个很好的答案,但请记住调整 Nestjs 的初始化以关闭 bodyParser: const app = wait NestFactory.create(AppModule, { bodyParser: false, }) (11认同)
  • 正文解析器已弃用。“json”和“raw”现在都可以在“express”中使用。例如:`import {Request, Response, raw, json } from 'express';` (7认同)

小智 11

今天,

因为我正在使用 NestJS 和 Stripe

我安装了 body-parser (npm),然后在 main.ts 中,添加

 app.use('/payment/hooks', bodyParser.raw({type: 'application/json'}));
Run Code Online (Sandbox Code Playgroud)

并且将被限制在这条路线上!没有超载

  • 如果您配置了多个解析器,请确保更具体的解析器位于通用解析器之前。`app.use('/ payment/hooks', bodyParser.raw({type: 'application/json'})); app.use(bodyParser.json());` (2认同)

cha*_*fir 10

2022 年第三季度更新

rawBody现在可以通过专用选项 开箱即用:https: //docs.nestjs.com/faq/raw-body

ps 只是不要忘记将您的嵌套依赖项更新到最新版本:

npm update @nestjs/core
npm update @nestjs/common
npm update @nestjs/common
npm update @nestjs/platform-express //if you are using express
Run Code Online (Sandbox Code Playgroud)

  • @Jeremiah我在本地也遇到了这个问题。我的问题是,我使用的是为我设置的 webhook 提供的签名密钥,但如果您使用 CLI 转发事件,则需要在调用 stripe Listen 时使用终端中提供的密钥。 (3认同)

Mik*_*ing 7

昨晚我尝试验证Slack令牌时遇到了类似的问题。

我们使用的解决方案确实需要从核心Nest App禁用bodyParser,然后rawBody在使用原始请求正文向请求中添加新密钥后重新启用它。

    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 }));

Run Code Online (Sandbox Code Playgroud)

然后在我的中间件中,我可以像这样访问它:

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();
}
Run Code Online (Sandbox Code Playgroud)

发光!


And*_*dré 7

我用一行修好了它:)

main.ts

import * as express from 'express';

async function bootstrap() {
...
  app.use('/your-stripe-webhook', express.raw({ type: "*/*" })); // <- add this!
...
  await app.listen(8080)
}
Run Code Online (Sandbox Code Playgroud)

...不必添加任何中间件。...不必禁用 bodyParser