From bead49b870e60c8e15b161f9af5057ea516a1e95 Mon Sep 17 00:00:00 2001 From: nate Date: Wed, 18 Mar 2026 22:46:57 +0400 Subject: [PATCH] fix: replace error() with set.status across all API routes (fixes undefined error helper) --- apps/api/src/routes/auth.ts | 26 ++++++++++++------------ apps/api/src/routes/internal.ts | 14 ++++++++++--- apps/api/src/routes/monitors.ts | 35 ++++++++++++++++++--------------- apps/api/src/routes/pings.ts | 6 +++--- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/apps/api/src/routes/auth.ts b/apps/api/src/routes/auth.ts index 92317ec..2fdfaaf 100644 --- a/apps/api/src/routes/auth.ts +++ b/apps/api/src/routes/auth.ts @@ -70,9 +70,9 @@ const COOKIE_OPTS = { export const account = new Elysia({ prefix: "/account" }) - .post("/login", async ({ body, cookie, set, request, error }) => { + .post("/login", async ({ body, cookie, set, request }) => { const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown"; - if (!checkAuthRateLimit(ip, 10)) return error(429, { error: "Too many login attempts. Try again later." }); + if (!checkAuthRateLimit(ip, 10)) { set.status = 429; return { error: "Too many login attempts. Try again later." }; } const key = (body.key as string)?.trim(); if (!key) { set.status = 400; return { error: "Key required" }; } @@ -94,9 +94,9 @@ export const account = new Elysia({ prefix: "/account" }) set.redirect = "/dashboard"; }, { detail: { hide: true } }) - .post("/register", async ({ body, cookie, request, error }) => { + .post("/register", async ({ body, cookie, request, set }) => { const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "unknown"; - if (!checkAuthRateLimit(ip, 5)) return error(429, { error: "Too many registrations. Try again later." }); + if (!checkAuthRateLimit(ip, 5)) { set.status = 429; return { error: "Too many registrations. Try again later." }; } const key = generateKey(); const emailHash = body.email ? hashEmail(body.email) : null; @@ -130,8 +130,8 @@ export const account = new Elysia({ prefix: "/account" }) }; }) - .post("/email", async ({ accountId, keyId, body, error }) => { - if (keyId) return error(403, { error: "Sub-keys cannot modify account email" }); + .post("/email", async ({ accountId, keyId, body, set }) => { + if (keyId) { set.status = 403; return { error: "Sub-keys cannot modify account email" }; } const emailHash = body.email ? hashEmail(body.email) : null; await sql`UPDATE accounts SET email_hash = ${emailHash} WHERE id = ${accountId}`; return { ok: true }; @@ -141,16 +141,16 @@ export const account = new Elysia({ prefix: "/account" }) }), }) - .post("/reset-key", async ({ accountId, keyId, cookie, error }) => { - if (keyId) return error(403, { error: "Sub-keys cannot rotate the account key" }); + .post("/reset-key", async ({ accountId, keyId, cookie, set }) => { + if (keyId) { set.status = 403; return { error: "Sub-keys cannot rotate the account key" }; } const key = generateKey(); await sql`UPDATE accounts SET key = ${key} WHERE id = ${accountId}`; cookie.pingql_key.set({ value: key, ...COOKIE_OPTS }); return { key, message: "Primary key rotated. Your old key is now invalid." }; }) - .post("/keys", async ({ accountId, keyId, body, error }) => { - if (keyId) return error(403, { error: "Sub-keys cannot create other sub-keys" }); + .post("/keys", async ({ accountId, keyId, body, set }) => { + if (keyId) { set.status = 403; return { error: "Sub-keys cannot create other sub-keys" }; } const key = generateKey(); const [created] = await sql`INSERT INTO api_keys (key, account_id, label) VALUES (${key}, ${accountId}, ${body.label}) RETURNING id`; return { key, id: created.id, label: body.label }; @@ -160,11 +160,11 @@ export const account = new Elysia({ prefix: "/account" }) }), }) - .delete("/keys/:id", async ({ accountId, keyId, params, error }) => { - if (keyId) return error(403, { error: "Sub-keys cannot revoke other sub-keys" }); + .delete("/keys/:id", async ({ accountId, keyId, params, set }) => { + if (keyId) { set.status = 403; return { error: "Sub-keys cannot revoke other sub-keys" }; } const [deleted] = await sql` DELETE FROM api_keys WHERE id = ${params.id} AND account_id = ${accountId} RETURNING id `; - if (!deleted) return error(404, { error: "Key not found" }); + if (!deleted) { set.status = 404; return { error: "Key not found" }; } return { deleted: true }; }); diff --git a/apps/api/src/routes/internal.ts b/apps/api/src/routes/internal.ts index 38187fd..9ea4f88 100644 --- a/apps/api/src/routes/internal.ts +++ b/apps/api/src/routes/internal.ts @@ -17,11 +17,19 @@ setInterval(() => { }, 60 * 60 * 1000); export const internal = new Elysia({ prefix: "/internal", detail: { hide: true } }) - .derive(({ headers, error }) => { - if (!safeTokenCompare(headers["x-monitor-token"], process.env.MONITOR_TOKEN)) - return error(401, { error: "Unauthorized" }); + .derive(({ headers, set }) => { + if (!safeTokenCompare(headers["x-monitor-token"], process.env.MONITOR_TOKEN)) { + set.status = 401; + return { _unauthorized: true }; + } return {}; }) + .onBeforeHandle(({ _unauthorized, set }: any) => { + if (_unauthorized) { + set.status = 401; + return { error: "Unauthorized" }; + } + }) // Returns monitors due within the next `lookahead_ms` milliseconds (default 2000). // Nodes receive scheduled_at as an exact unix ms timestamp and sleep until that diff --git a/apps/api/src/routes/monitors.ts b/apps/api/src/routes/monitors.ts index 490fb5b..cf24334 100644 --- a/apps/api/src/routes/monitors.ts +++ b/apps/api/src/routes/monitors.ts @@ -25,24 +25,26 @@ export const monitors = new Elysia({ prefix: "/monitors" }) }, { detail: { summary: "List monitors", tags: ["monitors"] } }) // Create monitor - .post("/", async ({ accountId, plan, body, error }) => { + .post("/", async ({ accountId, plan, body, set }) => { const limits = getPlanLimits(plan); // Enforce monitor count limit const [{ count }] = await sql`SELECT COUNT(*)::int as count FROM monitors WHERE account_id = ${accountId}`; if (count >= limits.maxMonitors) { - return error(403, { error: `Plan limit reached: ${limits.maxMonitors} monitors (${plan}). Upgrade to create more.` }); + set.status = 403; + return { error: `Plan limit reached: ${limits.maxMonitors} monitors (${plan}). Upgrade to create more.` }; } // Enforce minimum interval for plan const interval = body.interval_s ?? 30; if (interval < limits.minIntervalS) { - return error(400, { error: `Minimum interval for ${plan} plan is ${limits.minIntervalS}s` }); + set.status = 400; + return { error: `Minimum interval for ${plan} plan is ${limits.minIntervalS}s` }; } // SSRF protection const ssrfError = await validateMonitorUrl(body.url); - if (ssrfError) return error(400, { error: ssrfError }); + if (ssrfError) { set.status = 400; return { error: ssrfError }; } const regions = body.regions ?? []; const [monitor] = await sql` @@ -63,11 +65,11 @@ export const monitors = new Elysia({ prefix: "/monitors" }) }, { body: MonitorBody, detail: { summary: "Create monitor", tags: ["monitors"] } }) // Get monitor + recent status - .get("/:id", async ({ accountId, params, error }) => { + .get("/:id", async ({ accountId, params, set }) => { const [monitor] = await sql` SELECT * FROM monitors WHERE id = ${params.id} AND account_id = ${accountId} `; - if (!monitor) return error(404, { error: "Not found" }); + if (!monitor) { set.status = 404; return { error: "Not found" }; } const results = await sql` SELECT * FROM pings WHERE monitor_id = ${params.id} @@ -77,19 +79,20 @@ export const monitors = new Elysia({ prefix: "/monitors" }) }, { detail: { summary: "Get monitor with results", tags: ["monitors"] } }) // Update monitor - .patch("/:id", async ({ accountId, plan, params, body, error }) => { + .patch("/:id", async ({ accountId, plan, params, body, set }) => { // Enforce minimum interval for plan if (body.interval_s != null) { const limits = getPlanLimits(plan); if (body.interval_s < limits.minIntervalS) { - return error(400, { error: `Minimum interval for ${plan} plan is ${limits.minIntervalS}s` }); + set.status = 400; + return { error: `Minimum interval for ${plan} plan is ${limits.minIntervalS}s` }; } } // SSRF protection on URL change if (body.url) { const ssrfError = await validateMonitorUrl(body.url); - if (ssrfError) return error(400, { error: ssrfError }); + if (ssrfError) { set.status = 400; return { error: ssrfError }; } } const [monitor] = await sql` @@ -106,36 +109,36 @@ export const monitors = new Elysia({ prefix: "/monitors" }) WHERE id = ${params.id} AND account_id = ${accountId} RETURNING * `; - if (!monitor) return error(404, { error: "Not found" }); + if (!monitor) { set.status = 404; return { error: "Not found" }; } return monitor; }, { body: t.Partial(MonitorBody), detail: { summary: "Update monitor", tags: ["monitors"] } }) // Delete monitor - .delete("/:id", async ({ accountId, params, error }) => { + .delete("/:id", async ({ accountId, params, set }) => { const [deleted] = await sql` DELETE FROM monitors WHERE id = ${params.id} AND account_id = ${accountId} RETURNING id `; - if (!deleted) return error(404, { error: "Not found" }); + if (!deleted) { set.status = 404; return { error: "Not found" }; } return { deleted: true }; }, { detail: { summary: "Delete monitor", tags: ["monitors"] } }) // Toggle enabled - .post("/:id/toggle", async ({ accountId, params, error }) => { + .post("/:id/toggle", async ({ accountId, params, set }) => { const [monitor] = await sql` UPDATE monitors SET enabled = NOT enabled WHERE id = ${params.id} AND account_id = ${accountId} RETURNING id, enabled `; - if (!monitor) return error(404, { error: "Not found" }); + if (!monitor) { set.status = 404; return { error: "Not found" }; } return monitor; }, { detail: { summary: "Toggle monitor on/off", tags: ["monitors"] } }) // Check history - .get("/:id/pings", async ({ accountId, params, query, error }) => { + .get("/:id/pings", async ({ accountId, params, query, set }) => { const [monitor] = await sql` SELECT id FROM monitors WHERE id = ${params.id} AND account_id = ${accountId} `; - if (!monitor) return error(404, { error: "Not found" }); + if (!monitor) { set.status = 404; return { error: "Not found" }; } const limit = Math.min(Number(query.limit) || 100, 1000); return sql` SELECT * FROM pings diff --git a/apps/api/src/routes/pings.ts b/apps/api/src/routes/pings.ts index 3d2fbf4..8938f22 100644 --- a/apps/api/src/routes/pings.ts +++ b/apps/api/src/routes/pings.ts @@ -54,13 +54,13 @@ function makeSSEStream(accountId: string): Response { export const ingest = new Elysia() // Internal: called by Rust monitor runner - .post("/internal/ingest", async ({ body, headers, error }) => { + .post("/internal/ingest", async ({ body, headers, set }) => { const token = headers["x-monitor-token"]; - if (!safeTokenCompare(token, process.env.MONITOR_TOKEN)) return error(401, { error: "Unauthorized" }); + if (!safeTokenCompare(token, process.env.MONITOR_TOKEN)) { set.status = 401; return { error: "Unauthorized" }; } // Validate monitor exists const [monitor_check] = await sql`SELECT id FROM monitors WHERE id = ${body.monitor_id}`; - if (!monitor_check) return error(404, { error: "Monitor not found" }); + if (!monitor_check) { set.status = 404; return { error: "Monitor not found" }; } const meta = body.meta ? { ...body.meta } : {}; if (body.cert_expiry_days != null) meta.cert_expiry_days = body.cert_expiry_days;