Archived
fix: log all webhook host requests
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
|||||||
import { formatError, requireEnv } from "../../core/contracts.js";
|
import { formatError, requireEnv } from "../../core/contracts.js";
|
||||||
import { type PublicationContext, safeFailure } from "../publication/status.js";
|
import { type PublicationContext, safeFailure } from "../publication/status.js";
|
||||||
import { log, pumpDeliveries, pumpOutbox } from "./handlers/workers.js";
|
import { log, pumpDeliveries, pumpOutbox } from "./handlers/workers.js";
|
||||||
import { handleHttp } from "./server.js";
|
import { handleHttp, observeHttpRequest } from "./server.js";
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const serverUrl = validateServerUrl(requireEnv("GITEA_SERVER_URL"));
|
const serverUrl = validateServerUrl(requireEnv("GITEA_SERVER_URL"));
|
||||||
@@ -97,6 +97,10 @@ async function main(): Promise<void> {
|
|||||||
retentionTimer.unref();
|
retentionTimer.unref();
|
||||||
|
|
||||||
const server = createServer((request, response) => {
|
const server = createServer((request, response) => {
|
||||||
|
observeHttpRequest(request, response, (entry) => {
|
||||||
|
const level = entry.statusCode >= 500 ? "error" : "info";
|
||||||
|
console.log(log(level, "HTTP request", { ...entry }));
|
||||||
|
});
|
||||||
handleHttp(request, response, {
|
handleHttp(request, response, {
|
||||||
store,
|
store,
|
||||||
webhookSecret,
|
webhookSecret,
|
||||||
|
|||||||
@@ -10,6 +10,36 @@ import {
|
|||||||
const bodyLimit = 1024 * 1024;
|
const bodyLimit = 1024 * 1024;
|
||||||
class BodyTooLarge extends Error {}
|
class BodyTooLarge extends Error {}
|
||||||
|
|
||||||
|
export interface HttpRequestLog {
|
||||||
|
method: string;
|
||||||
|
path: string;
|
||||||
|
statusCode: number;
|
||||||
|
durationMs: number;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function observeHttpRequest(
|
||||||
|
request: IncomingMessage,
|
||||||
|
response: ServerResponse,
|
||||||
|
write: (entry: HttpRequestLog) => void,
|
||||||
|
): void {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
let observed = false;
|
||||||
|
const observe = () => {
|
||||||
|
if (observed) return;
|
||||||
|
observed = true;
|
||||||
|
write({
|
||||||
|
method: request.method || "UNKNOWN",
|
||||||
|
path: request.url || "",
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
completed: response.writableFinished,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
response.once("finish", observe);
|
||||||
|
response.once("close", observe);
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleHttp(
|
export async function handleHttp(
|
||||||
request: IncomingMessage,
|
request: IncomingMessage,
|
||||||
response: ServerResponse,
|
response: ServerResponse,
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ import { tmpdir } from "node:os";
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import test from "node:test";
|
import test from "node:test";
|
||||||
import { AgentStore } from "../../adapters/database/store.js";
|
import { AgentStore } from "../../adapters/database/store.js";
|
||||||
import { handleHttp } from "../../application/controller/server.js";
|
import {
|
||||||
|
type HttpRequestLog,
|
||||||
|
handleHttp,
|
||||||
|
observeHttpRequest,
|
||||||
|
} from "../../application/controller/server.js";
|
||||||
|
|
||||||
test("signed webhooks are durably admitted before acknowledgment", async () => {
|
test("signed webhooks are durably admitted before acknowledgment", async () => {
|
||||||
const root = await mkdtemp(join(tmpdir(), "agent-http-"));
|
const root = await mkdtemp(join(tmpdir(), "agent-http-"));
|
||||||
@@ -62,7 +66,9 @@ test("signed webhooks are durably admitted before acknowledgment", async () => {
|
|||||||
test("invalid webhook signatures are rejected without persistence", async () => {
|
test("invalid webhook signatures are rejected without persistence", async () => {
|
||||||
const root = await mkdtemp(join(tmpdir(), "agent-http-auth-"));
|
const root = await mkdtemp(join(tmpdir(), "agent-http-auth-"));
|
||||||
const store = new AgentStore(join(root, "agent.db"));
|
const store = new AgentStore(join(root, "agent.db"));
|
||||||
|
const requests: HttpRequestLog[] = [];
|
||||||
const server = createServer((request, response) => {
|
const server = createServer((request, response) => {
|
||||||
|
observeHttpRequest(request, response, (entry) => requests.push(entry));
|
||||||
handleHttp(request, response, {
|
handleHttp(request, response, {
|
||||||
store,
|
store,
|
||||||
webhookSecret: "test-secret",
|
webhookSecret: "test-secret",
|
||||||
@@ -97,6 +103,22 @@ test("invalid webhook signatures are rejected without persistence", async () =>
|
|||||||
);
|
);
|
||||||
assert.equal(response.status, 401);
|
assert.equal(response.status, 401);
|
||||||
assert.equal(store.leaseDelivery(), undefined);
|
assert.equal(store.leaseDelivery(), undefined);
|
||||||
|
assert.equal(requests.length, 1);
|
||||||
|
assert.deepEqual(
|
||||||
|
{
|
||||||
|
method: requests[0]?.method,
|
||||||
|
path: requests[0]?.path,
|
||||||
|
statusCode: requests[0]?.statusCode,
|
||||||
|
completed: requests[0]?.completed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
path: "/webhooks/gitea",
|
||||||
|
statusCode: 401,
|
||||||
|
completed: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.ok((requests[0]?.durationMs ?? -1) >= 0);
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||||
store.close();
|
store.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user