fix: replace error() with set.status across all API routes (fixes undefined error helper)

This commit is contained in:
nate 2026-03-18 22:46:57 +04:00
parent c89b63bd97
commit bead49b870
4 changed files with 46 additions and 35 deletions

View File

@ -70,9 +70,9 @@ const COOKIE_OPTS = {
export const account = new Elysia({ prefix: "/account" }) 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"; 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(); const key = (body.key as string)?.trim();
if (!key) { set.status = 400; return { error: "Key required" }; } if (!key) { set.status = 400; return { error: "Key required" }; }
@ -94,9 +94,9 @@ export const account = new Elysia({ prefix: "/account" })
set.redirect = "/dashboard"; set.redirect = "/dashboard";
}, { detail: { hide: true } }) }, { 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"; 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 key = generateKey();
const emailHash = body.email ? hashEmail(body.email) : null; 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 }) => { .post("/email", async ({ accountId, keyId, body, set }) => {
if (keyId) return error(403, { error: "Sub-keys cannot modify account email" }); if (keyId) { set.status = 403; return { error: "Sub-keys cannot modify account email" }; }
const emailHash = body.email ? hashEmail(body.email) : null; const emailHash = body.email ? hashEmail(body.email) : null;
await sql`UPDATE accounts SET email_hash = ${emailHash} WHERE id = ${accountId}`; await sql`UPDATE accounts SET email_hash = ${emailHash} WHERE id = ${accountId}`;
return { ok: true }; return { ok: true };
@ -141,16 +141,16 @@ export const account = new Elysia({ prefix: "/account" })
}), }),
}) })
.post("/reset-key", async ({ accountId, keyId, cookie, error }) => { .post("/reset-key", async ({ accountId, keyId, cookie, set }) => {
if (keyId) return error(403, { error: "Sub-keys cannot rotate the account key" }); if (keyId) { set.status = 403; return { error: "Sub-keys cannot rotate the account key" }; }
const key = generateKey(); const key = generateKey();
await sql`UPDATE accounts SET key = ${key} WHERE id = ${accountId}`; await sql`UPDATE accounts SET key = ${key} WHERE id = ${accountId}`;
cookie.pingql_key.set({ value: key, ...COOKIE_OPTS }); cookie.pingql_key.set({ value: key, ...COOKIE_OPTS });
return { key, message: "Primary key rotated. Your old key is now invalid." }; return { key, message: "Primary key rotated. Your old key is now invalid." };
}) })
.post("/keys", async ({ accountId, keyId, body, error }) => { .post("/keys", async ({ accountId, keyId, body, set }) => {
if (keyId) return error(403, { error: "Sub-keys cannot create other sub-keys" }); if (keyId) { set.status = 403; return { error: "Sub-keys cannot create other sub-keys" }; }
const key = generateKey(); const key = generateKey();
const [created] = await sql`INSERT INTO api_keys (key, account_id, label) VALUES (${key}, ${accountId}, ${body.label}) RETURNING id`; 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 }; 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 }) => { .delete("/keys/:id", async ({ accountId, keyId, params, set }) => {
if (keyId) return error(403, { error: "Sub-keys cannot revoke other sub-keys" }); if (keyId) { set.status = 403; return { error: "Sub-keys cannot revoke other sub-keys" }; }
const [deleted] = await sql` const [deleted] = await sql`
DELETE FROM api_keys WHERE id = ${params.id} AND account_id = ${accountId} RETURNING id 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 }; return { deleted: true };
}); });

View File

@ -17,11 +17,19 @@ setInterval(() => {
}, 60 * 60 * 1000); }, 60 * 60 * 1000);
export const internal = new Elysia({ prefix: "/internal", detail: { hide: true } }) export const internal = new Elysia({ prefix: "/internal", detail: { hide: true } })
.derive(({ headers, error }) => { .derive(({ headers, set }) => {
if (!safeTokenCompare(headers["x-monitor-token"], process.env.MONITOR_TOKEN)) if (!safeTokenCompare(headers["x-monitor-token"], process.env.MONITOR_TOKEN)) {
return error(401, { error: "Unauthorized" }); set.status = 401;
return { _unauthorized: true };
}
return {}; return {};
}) })
.onBeforeHandle(({ _unauthorized, set }: any) => {
if (_unauthorized) {
set.status = 401;
return { error: "Unauthorized" };
}
})
// Returns monitors due within the next `lookahead_ms` milliseconds (default 2000). // 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 // Nodes receive scheduled_at as an exact unix ms timestamp and sleep until that

View File

@ -25,24 +25,26 @@ export const monitors = new Elysia({ prefix: "/monitors" })
}, { detail: { summary: "List monitors", tags: ["monitors"] } }) }, { detail: { summary: "List monitors", tags: ["monitors"] } })
// Create monitor // Create monitor
.post("/", async ({ accountId, plan, body, error }) => { .post("/", async ({ accountId, plan, body, set }) => {
const limits = getPlanLimits(plan); const limits = getPlanLimits(plan);
// Enforce monitor count limit // Enforce monitor count limit
const [{ count }] = await sql`SELECT COUNT(*)::int as count FROM monitors WHERE account_id = ${accountId}`; const [{ count }] = await sql`SELECT COUNT(*)::int as count FROM monitors WHERE account_id = ${accountId}`;
if (count >= limits.maxMonitors) { 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 // Enforce minimum interval for plan
const interval = body.interval_s ?? 30; const interval = body.interval_s ?? 30;
if (interval < limits.minIntervalS) { 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 // SSRF protection
const ssrfError = await validateMonitorUrl(body.url); 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 regions = body.regions ?? [];
const [monitor] = await sql` const [monitor] = await sql`
@ -63,11 +65,11 @@ export const monitors = new Elysia({ prefix: "/monitors" })
}, { body: MonitorBody, detail: { summary: "Create monitor", tags: ["monitors"] } }) }, { body: MonitorBody, detail: { summary: "Create monitor", tags: ["monitors"] } })
// Get monitor + recent status // Get monitor + recent status
.get("/:id", async ({ accountId, params, error }) => { .get("/:id", async ({ accountId, params, set }) => {
const [monitor] = await sql` const [monitor] = await sql`
SELECT * FROM monitors WHERE id = ${params.id} AND account_id = ${accountId} 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` const results = await sql`
SELECT * FROM pings WHERE monitor_id = ${params.id} 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"] } }) }, { detail: { summary: "Get monitor with results", tags: ["monitors"] } })
// Update monitor // Update monitor
.patch("/:id", async ({ accountId, plan, params, body, error }) => { .patch("/:id", async ({ accountId, plan, params, body, set }) => {
// Enforce minimum interval for plan // Enforce minimum interval for plan
if (body.interval_s != null) { if (body.interval_s != null) {
const limits = getPlanLimits(plan); const limits = getPlanLimits(plan);
if (body.interval_s < limits.minIntervalS) { 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 // SSRF protection on URL change
if (body.url) { if (body.url) {
const ssrfError = await validateMonitorUrl(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` const [monitor] = await sql`
@ -106,36 +109,36 @@ export const monitors = new Elysia({ prefix: "/monitors" })
WHERE id = ${params.id} AND account_id = ${accountId} WHERE id = ${params.id} AND account_id = ${accountId}
RETURNING * RETURNING *
`; `;
if (!monitor) return error(404, { error: "Not found" }); if (!monitor) { set.status = 404; return { error: "Not found" }; }
return monitor; return monitor;
}, { body: t.Partial(MonitorBody), detail: { summary: "Update monitor", tags: ["monitors"] } }) }, { body: t.Partial(MonitorBody), detail: { summary: "Update monitor", tags: ["monitors"] } })
// Delete monitor // Delete monitor
.delete("/:id", async ({ accountId, params, error }) => { .delete("/:id", async ({ accountId, params, set }) => {
const [deleted] = await sql` const [deleted] = await sql`
DELETE FROM monitors WHERE id = ${params.id} AND account_id = ${accountId} RETURNING id 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 }; return { deleted: true };
}, { detail: { summary: "Delete monitor", tags: ["monitors"] } }) }, { detail: { summary: "Delete monitor", tags: ["monitors"] } })
// Toggle enabled // Toggle enabled
.post("/:id/toggle", async ({ accountId, params, error }) => { .post("/:id/toggle", async ({ accountId, params, set }) => {
const [monitor] = await sql` const [monitor] = await sql`
UPDATE monitors SET enabled = NOT enabled UPDATE monitors SET enabled = NOT enabled
WHERE id = ${params.id} AND account_id = ${accountId} WHERE id = ${params.id} AND account_id = ${accountId}
RETURNING id, enabled RETURNING id, enabled
`; `;
if (!monitor) return error(404, { error: "Not found" }); if (!monitor) { set.status = 404; return { error: "Not found" }; }
return monitor; return monitor;
}, { detail: { summary: "Toggle monitor on/off", tags: ["monitors"] } }) }, { detail: { summary: "Toggle monitor on/off", tags: ["monitors"] } })
// Check history // Check history
.get("/:id/pings", async ({ accountId, params, query, error }) => { .get("/:id/pings", async ({ accountId, params, query, set }) => {
const [monitor] = await sql` const [monitor] = await sql`
SELECT id FROM monitors WHERE id = ${params.id} AND account_id = ${accountId} 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); const limit = Math.min(Number(query.limit) || 100, 1000);
return sql` return sql`
SELECT * FROM pings SELECT * FROM pings

View File

@ -54,13 +54,13 @@ function makeSSEStream(accountId: string): Response {
export const ingest = new Elysia() export const ingest = new Elysia()
// Internal: called by Rust monitor runner // 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"]; 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 // Validate monitor exists
const [monitor_check] = await sql`SELECT id FROM monitors WHERE id = ${body.monitor_id}`; 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 } : {}; const meta = body.meta ? { ...body.meta } : {};
if (body.cert_expiry_days != null) meta.cert_expiry_days = body.cert_expiry_days; if (body.cert_expiry_days != null) meta.cert_expiry_days = body.cert_expiry_days;