我需要在我的Nest.js应用程序中从Stripe访问webhook请求的原始正文。
在this示例之后,我将以下内容添加到具有控制器方法的模块中,该方法需要原始主体。
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');
}
}
在控制器中,我先使用@Req() req
然后使用req.rawBody
来获取原始内容。我需要原始主体,因为Stripe api的ConstructEvent正在使用它来验证请求。
问题是请求被卡住了。似乎没有为数据或结束事件调用req.on。因此next()
在中间件中没有被调用。
我也确实尝试像here一样使用raw-body
,但是得到的结果几乎相同。在这种情况下,req。可读性总是为假,所以我也被困在那里。
我想这是Nest.js的问题,但我不确定...
答案 0 :(得分:9)
今天
我正在使用NestJS和Stripe
我安装了body解析器(npm), 然后在main.ts中, 只需添加
app.use('/payment/hooks', bodyParser.raw({type: 'application/json'}));
,它将仅限于此路线!没有过载
答案 1 :(得分:3)
昨晚我尝试验证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 }));
然后在我的中间件中,我可以这样访问它:
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();
}
发光!
答案 2 :(得分:0)
对于任何寻求更优雅解决方案的人,请关闭bodyParser
中的main.ts
创建2个middlewares
,一个用于rawbody,另一个用于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);
}
}
并将中间件应用于app.module.ts
中的适当路由。
app.module.ts
...
public configure(consumer: MiddlewareConsumer): void {
consumer
.apply(RawBodyMiddleware)
.forRoutes({
path: '/stripe-webhooks',
method: RequestMethod.POST,
})
.apply(JsonBodyMiddleware)
.forRoutes('*');
}
顺便说一句req.rawbody
已从express
的后背移除-https://github.com/expressjs/express/issues/897
答案 3 :(得分:0)
这是我在NestJS的处理程序中获取raw(text)body的观点:
preserveRawBodyInRequest
配置应用程序(仅使用Stripe Webhook限制使用"stripe-signature"
作为过滤器头)RawBody
装饰器来检索raw(text)body raw-request.decorator.ts:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { NestExpressApplication } from "@nestjs/platform-express";
import { json, urlencoded } from "express";
import type { Request } from "express";
import type http from "http";
export const HTTP_REQUEST_RAW_BODY = "rawBody";
/**
* make sure you configure the nest app with <code>preserveRawBodyInRequest</code>
* @example
* webhook(@RawBody() rawBody: string): Record<string, unknown> {
* return { received: true };
* }
* @see preserveRawBodyInRequest
*/
export const RawBody = createParamDecorator(
async (data: unknown, context: ExecutionContext) => {
const request = context
.switchToHttp()
.getRequest<Request>()
;
if (!(HTTP_REQUEST_RAW_BODY in request)) {
throw new Error(
`RawBody not preserved for request in handler: ${context.getClass().name}::${context.getHandler().name}`,
);
}
const rawBody = request[HTTP_REQUEST_RAW_BODY];
return rawBody;
},
);
/**
* @example
* const app = await NestFactory.create<NestExpressApplication>(
* AppModule,
* {
* bodyParser: false, // it is prerequisite to disable nest's default body parser
* },
* );
* preserveRawBodyInRequest(
* app,
* "signature-header",
* );
* @param app
* @param ifRequestContainsHeader
*/
export function preserveRawBodyInRequest(
app: NestExpressApplication,
...ifRequestContainsHeader: string[]
): void {
const rawBodyBuffer = (
req: http.IncomingMessage,
res: http.ServerResponse,
buf: Buffer,
): void => {
if (
buf?.length
&& (ifRequestContainsHeader.length === 0
|| ifRequestContainsHeader.some(filterHeader => req.headers[filterHeader])
)
) {
req[HTTP_REQUEST_RAW_BODY] = buf.toString("utf8");
}
};
app.use(
urlencoded(
{
verify: rawBodyBuffer,
extended: true,
},
),
);
app.use(
json(
{
verify: rawBodyBuffer,
},
),
);
}
答案 4 :(得分:0)
在模块上应用中间件并分配控制器。
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'
import { raw } from 'body-parser'
import { PaymentIntentController } from './payment-intent.controller'
import { PaymentIntentService } from './payment-intent.service'
@Module({
controllers: [PaymentIntentController],
providers: [PaymentIntentService]
})
export class PaymentIntentModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(raw({ type: 'application/json' })).forRoutes(PaymentIntentController)
}
}
bodyParser
选项在引导时false。
import { NestFactory } from '@nestjs/core'
import { AppModule } from './module'
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true, bodyParser: false })
await app.listen(8080)
}
bootstrap()
参考:
答案 5 :(得分:0)
我发现由于某种原因,主体解析器未能移交给链中的下一个处理程序。
当内容类型为“text/plain”时,NestJS 已经支持原始正文,所以我的解决方案是这样的:
import { Injectable, NestMiddleware } from "@nestjs/common";
import { Request, Response } from "express";
@Injectable()
export class RawBodyMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => unknown) {
req.headers["content-type"] = "text/plain";
next();
}
}
答案 6 :(得分:-2)
这是因为默认情况下使用bodyParser
中间件,并且在中间件启动时已经消耗了主体。您可以在bodyParser
中关闭main.ts
:
const app = await NestFactory.create(AppModule, {bodyParser: false});
^^^^^^^^^^^^^^^^^^
不过,您应该注意,在大多数其他情况下,您可能希望使用bodyParser.json
,因此请将其添加到所有其他路由中。您可以使用否定的正则表达式从中间件中排除一条特定的路由,例如this thread。