mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 06:21:26 +00:00
fix(line): return 200 for webhook verification requests without signature
LINE Platform sends POST {"events":[]} without an X-Line-Signature
header when the user clicks 'Verify' in the LINE Developers Console.
Both webhook.ts and monitor.ts rejected this with 400 'Missing
X-Line-Signature header', causing verification to fail.
Now detect the verification pattern (no signature + empty events array)
and return 200 OK immediately, while still requiring valid signatures
for all real webhook deliveries with non-empty events.
Fixes #16425
This commit is contained in:
committed by
Peter Steinberger
parent
3189430ad0
commit
4f2f641950
@@ -322,8 +322,24 @@ export async function monitorLineProvider(
|
|||||||
const rawBody = await readLineWebhookRequestBody(req, LINE_WEBHOOK_MAX_BODY_BYTES);
|
const rawBody = await readLineWebhookRequestBody(req, LINE_WEBHOOK_MAX_BODY_BYTES);
|
||||||
const signature = req.headers["x-line-signature"];
|
const signature = req.headers["x-line-signature"];
|
||||||
|
|
||||||
// Validate signature
|
// LINE webhook verification sends POST {"events":[]} without a
|
||||||
|
// signature header. Return 200 so the LINE Developers Console
|
||||||
|
// "Verify" button succeeds.
|
||||||
if (!signature || typeof signature !== "string") {
|
if (!signature || typeof signature !== "string") {
|
||||||
|
try {
|
||||||
|
const verifyBody = JSON.parse(rawBody) as WebhookRequestBody;
|
||||||
|
if (Array.isArray(verifyBody.events) && verifyBody.events.length === 0) {
|
||||||
|
logVerbose(
|
||||||
|
"line: webhook verification request (empty events, no signature) — 200 OK",
|
||||||
|
);
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.setHeader("Content-Type", "application/json");
|
||||||
|
res.end(JSON.stringify({ status: "ok" }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Not valid JSON — fall through to the error below.
|
||||||
|
}
|
||||||
logVerbose("line: webhook missing X-Line-Signature header");
|
logVerbose("line: webhook missing X-Line-Signature header");
|
||||||
res.statusCode = 400;
|
res.statusCode = 400;
|
||||||
res.setHeader("Content-Type", "application/json");
|
res.setHeader("Content-Type", "application/json");
|
||||||
|
|||||||
@@ -98,6 +98,48 @@ describe("createLineWebhookMiddleware", () => {
|
|||||||
expect(onEvents).not.toHaveBeenCalled();
|
expect(onEvents).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns 200 for verification request (empty events, no signature)", async () => {
|
||||||
|
const onEvents = vi.fn(async () => {});
|
||||||
|
const secret = "secret";
|
||||||
|
const rawBody = JSON.stringify({ events: [] });
|
||||||
|
const middleware = createLineWebhookMiddleware({ channelSecret: secret, onEvents });
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
headers: {},
|
||||||
|
body: rawBody,
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
} as any;
|
||||||
|
const res = createRes();
|
||||||
|
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
await middleware(req, res, {} as any);
|
||||||
|
|
||||||
|
expect(res.status).toHaveBeenCalledWith(200);
|
||||||
|
expect(res.json).toHaveBeenCalledWith({ status: "ok" });
|
||||||
|
expect(onEvents).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects missing signature when events are non-empty", async () => {
|
||||||
|
const onEvents = vi.fn(async () => {});
|
||||||
|
const secret = "secret";
|
||||||
|
const rawBody = JSON.stringify({ events: [{ type: "message" }] });
|
||||||
|
const middleware = createLineWebhookMiddleware({ channelSecret: secret, onEvents });
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
headers: {},
|
||||||
|
body: rawBody,
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
} as any;
|
||||||
|
const res = createRes();
|
||||||
|
|
||||||
|
// oxlint-disable-next-line typescript/no-explicit-any
|
||||||
|
await middleware(req, res, {} as any);
|
||||||
|
|
||||||
|
expect(res.status).toHaveBeenCalledWith(400);
|
||||||
|
expect(res.json).toHaveBeenCalledWith({ error: "Missing X-Line-Signature header" });
|
||||||
|
expect(onEvents).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects webhooks with signatures computed using wrong secret", async () => {
|
it("rejects webhooks with signatures computed using wrong secret", async () => {
|
||||||
const onEvents = vi.fn(async () => {});
|
const onEvents = vi.fn(async () => {});
|
||||||
const correctSecret = "correct-secret";
|
const correctSecret = "correct-secret";
|
||||||
|
|||||||
@@ -39,13 +39,24 @@ export function createLineWebhookMiddleware(
|
|||||||
return async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
|
return async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const signature = req.headers["x-line-signature"];
|
const signature = req.headers["x-line-signature"];
|
||||||
|
const rawBody = readRawBody(req);
|
||||||
|
|
||||||
|
// LINE webhook verification sends POST {"events":[]} without a
|
||||||
|
// signature header. Return 200 immediately so the LINE Developers
|
||||||
|
// Console "Verify" button succeeds.
|
||||||
if (!signature || typeof signature !== "string") {
|
if (!signature || typeof signature !== "string") {
|
||||||
|
if (rawBody) {
|
||||||
|
const body = parseWebhookBody(req, rawBody);
|
||||||
|
if (body && Array.isArray(body.events) && body.events.length === 0) {
|
||||||
|
logVerbose("line: webhook verification request (empty events, no signature) — 200 OK");
|
||||||
|
res.status(200).json({ status: "ok" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
res.status(400).json({ error: "Missing X-Line-Signature header" });
|
res.status(400).json({ error: "Missing X-Line-Signature header" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawBody = readRawBody(req);
|
|
||||||
if (!rawBody) {
|
if (!rawBody) {
|
||||||
res.status(400).json({ error: "Missing raw request body for signature verification" });
|
res.status(400).json({ error: "Missing raw request body for signature verification" });
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user