UNPKG

2.57 kBPlain TextView Raw
1import * as http from "node:http";
2import { Buffer } from "node:buffer";
3import { JSONParseError, SignatureValidationFailed } from "./exceptions.js";
4import * as Types from "./types.js";
5import validateSignature from "./validate-signature.js";
6
7export type Request = http.IncomingMessage & { body: any };
8export type Response = http.ServerResponse;
9export type NextCallback = (err?: Error) => void;
10
11export type Middleware = (
12 req: Request,
13 res: Response,
14 next: NextCallback,
15) => void | Promise<void>;
16
17function isValidBody(body?: any): body is string | Buffer {
18 return (body && typeof body === "string") || Buffer.isBuffer(body);
19}
20
21const readRequestBody = async (req: http.IncomingMessage): Promise<Buffer> => {
22 const chunks: Buffer[] = [];
23 for await (const chunk of req) {
24 chunks.push(chunk as Buffer);
25 }
26 return Buffer.concat(chunks);
27};
28
29export default function middleware(config: Types.MiddlewareConfig): Middleware {
30 if (!config.channelSecret) {
31 throw new Error("no channel secret");
32 }
33
34 const secret = config.channelSecret;
35
36 const _middleware: Middleware = async (req, res, next) => {
37 // header names are lower-cased
38 // https://nodejs.org/api/http.html#http_message_headers
39 const signature = req.headers[
40 Types.LINE_SIGNATURE_HTTP_HEADER_NAME
41 ] as string;
42
43 if (!signature) {
44 next(new SignatureValidationFailed("no signature"));
45 return;
46 }
47
48 const body = await (async (): Promise<string | Buffer> => {
49 if (isValidBody((req as any).rawBody)) {
50 // rawBody is provided in Google Cloud Functions and others
51 return (req as any).rawBody;
52 } else if (isValidBody(req.body)) {
53 return req.body;
54 } else {
55 // body may not be parsed yet, parse it to a buffer
56 const rawBody = await readRequestBody(req);
57 if (isValidBody(rawBody)) {
58 return rawBody;
59 } else {
60 throw new JSONParseError("Invalid body", { raw: rawBody });
61 }
62 }
63 })();
64
65 if (!validateSignature(body, secret, signature)) {
66 next(
67 new SignatureValidationFailed("signature validation failed", {
68 signature,
69 }),
70 );
71 return;
72 }
73
74 const strBody = Buffer.isBuffer(body) ? body.toString() : body;
75
76 try {
77 req.body = JSON.parse(strBody);
78 next();
79 } catch (err) {
80 const { message } = err;
81
82 next(new JSONParseError(message, { raw: strBody }));
83 }
84 };
85 return (req, res, next): void => {
86 (<Promise<void>>_middleware(req, res, next)).catch(next);
87 };
88}