Openstatus www.openstatus.dev

security fixes

+114 -10
+38 -3
apps/server/src/routes/v1/maintenance/post.ts
··· 2 2 import { trackMiddleware } from "@/libs/middlewares"; 3 3 import { createRoute } from "@hono/zod-openapi"; 4 4 import { Events } from "@openstatus/analytics"; 5 - import { db } from "@openstatus/db"; 5 + import { and, isNull, eq, inArray, db } from "@openstatus/db"; 6 6 import { 7 7 maintenance, 8 8 maintenancesToMonitors, 9 9 } from "@openstatus/db/src/schema/maintenances"; 10 10 import type { maintenanceApi } from "./index"; 11 11 import { MaintenanceSchema } from "./schema"; 12 + import { monitor, page } from "@openstatus/db/src/schema"; 12 13 13 14 const postRoute = createRoute({ 14 15 method: "post", ··· 43 44 const workspaceId = c.get("workspace").id; 44 45 const input = c.req.valid("json"); 45 46 47 + const { monitorIds, pageId } = input; 48 + 49 + const _monitors = await db 50 + .select() 51 + .from(monitor) 52 + .where( 53 + and( 54 + inArray(monitor.id, monitorIds), 55 + eq(monitor.workspaceId, workspaceId), 56 + isNull(monitor.deletedAt) 57 + ) 58 + ) 59 + .all(); 60 + 61 + if (_monitors.length !== monitorIds.length) { 62 + throw new OpenStatusApiError({ 63 + code: "BAD_REQUEST", 64 + message: `Some of the monitors ${monitorIds.join(", ")} not found`, 65 + }); 66 + } 67 + 68 + const _page = await db 69 + .select() 70 + .from(page) 71 + .where(and(eq(page.id, pageId), eq(page.workspaceId, workspaceId))) 72 + .get(); 73 + 74 + if (!_page) { 75 + throw new OpenStatusApiError({ 76 + code: "BAD_REQUEST", 77 + message: `Page ${pageId} not found`, 78 + }); 79 + } 80 + 46 81 const _maintenance = await db.transaction(async (tx) => { 47 82 const newMaintenance = await tx 48 83 .insert(maintenance) ··· 53 88 .returning() 54 89 .get(); 55 90 56 - if (input.monitorIds?.length) { 91 + if (monitorIds?.length) { 57 92 await tx 58 93 .insert(maintenancesToMonitors) 59 94 .values( 60 95 input.monitorIds.map((monitorId) => ({ 61 96 maintenanceId: newMaintenance.id, 62 97 monitorId, 63 - })), 98 + })) 64 99 ) 65 100 .run(); 66 101 }
+30
apps/server/src/routes/v1/maintenance/put.test.ts
··· 67 67 68 68 expect(res.status).toBe(401); 69 69 }); 70 + 71 + test("update with invalid monitor ids should return 400", async () => { 72 + const res = await app.request("/v1/maintenance/1", { 73 + method: "PUT", 74 + headers: { 75 + "x-openstatus-key": "1", 76 + "Content-Type": "application/json", 77 + }, 78 + body: JSON.stringify({ 79 + monitorIds: [999], // Non-existent monitor 80 + }), 81 + }); 82 + 83 + expect(res.status).toBe(400); 84 + }); 85 + 86 + test("update with invalid page id should return 400", async () => { 87 + const res = await app.request("/v1/maintenance/1", { 88 + method: "PUT", 89 + headers: { 90 + "x-openstatus-key": "1", 91 + "Content-Type": "application/json", 92 + }, 93 + body: JSON.stringify({ 94 + pageId: 999, // Non-existent page 95 + }), 96 + }); 97 + 98 + expect(res.status).toBe(400); 99 + });
+46 -7
apps/server/src/routes/v1/maintenance/put.ts
··· 2 2 import { trackMiddleware } from "@/libs/middlewares"; 3 3 import { createRoute } from "@hono/zod-openapi"; 4 4 import { Events } from "@openstatus/analytics"; 5 - import { and, db, eq } from "@openstatus/db"; 5 + import { and, isNull, eq, inArray, db } from "@openstatus/db"; 6 6 import { 7 7 maintenance, 8 8 maintenancesToMonitors, 9 9 } from "@openstatus/db/src/schema/maintenances"; 10 + import { monitor, page } from "@openstatus/db/src/schema"; 10 11 import type { maintenanceApi } from "./index"; 11 12 import { MaintenanceSchema, ParamsSchema } from "./schema"; 12 13 ··· 45 46 const { id } = c.req.valid("param"); 46 47 const input = c.req.valid("json"); 47 48 49 + const { monitorIds, pageId } = input; 50 + 48 51 const _maintenance = await db.query.maintenance.findFirst({ 49 52 with: { 50 53 maintenancesToMonitors: true, 51 54 }, 52 55 where: and( 53 56 eq(maintenance.id, Number(id)), 54 - eq(maintenance.workspaceId, workspaceId), 57 + eq(maintenance.workspaceId, workspaceId) 55 58 ), 56 59 }); 57 60 ··· 62 65 }); 63 66 } 64 67 68 + if (monitorIds?.length) { 69 + const _monitors = await db 70 + .select() 71 + .from(monitor) 72 + .where( 73 + and( 74 + inArray(monitor.id, monitorIds), 75 + eq(monitor.workspaceId, workspaceId), 76 + isNull(monitor.deletedAt) 77 + ) 78 + ) 79 + .all(); 80 + 81 + if (_monitors.length !== monitorIds.length) { 82 + throw new OpenStatusApiError({ 83 + code: "BAD_REQUEST", 84 + message: `Some of the monitors ${monitorIds.join(", ")} not found`, 85 + }); 86 + } 87 + } 88 + 89 + if (pageId) { 90 + const _page = await db 91 + .select() 92 + .from(page) 93 + .where(and(eq(page.id, pageId), eq(page.workspaceId, workspaceId))) 94 + .get(); 95 + 96 + if (!_page) { 97 + throw new OpenStatusApiError({ 98 + code: "BAD_REQUEST", 99 + message: `Page ${pageId} not found`, 100 + }); 101 + } 102 + } 103 + 65 104 const updatedMaintenance = await db.transaction(async (tx) => { 66 105 const updated = await tx 67 106 .update(maintenance) ··· 73 112 .returning() 74 113 .get(); 75 114 76 - if (input.monitorIds) { 115 + if (monitorIds) { 77 116 // Delete existing monitor associations 78 117 await tx 79 118 .delete(maintenancesToMonitors) ··· 81 120 .run(); 82 121 83 122 // Add new monitor associations 84 - if (input.monitorIds.length > 0) { 123 + if (monitorIds.length > 0) { 85 124 await tx 86 125 .insert(maintenancesToMonitors) 87 126 .values( 88 - input.monitorIds.map((monitorId) => ({ 127 + monitorIds.map((monitorId) => ({ 89 128 maintenanceId: Number(id), 90 129 monitorId, 91 - })), 130 + })) 92 131 ) 93 132 .run(); 94 133 } ··· 100 139 const data = MaintenanceSchema.parse({ 101 140 ...updatedMaintenance, 102 141 monitorIds: 103 - input.monitorIds ?? 142 + monitorIds ?? 104 143 _maintenance.maintenancesToMonitors.map((m) => m.monitorId), 105 144 }); 106 145