import { Hono } from "jsr:@hono/hono@4"; import { sqlWrapper } from "../../db.ts"; import { auditWrapper } from "../../audit.ts"; import { getAuthenticatedUser } from "../../auth-session.ts"; import { getClientIp } from "../../middleware.ts"; export const appsAdminRoutes = new Hono(); appsAdminRoutes.get("/", async (c) => { const auth = await getAuthenticatedUser(c); if (!auth) return c.json({ error: "Unauthorized" }, 401); const apps = await sqlWrapper.sql` SELECT a.id, a.name, a.spiffe_id, a.description, a.created_at, COUNT(g.id) AS active_grants_count FROM apps a LEFT JOIN grants g ON a.id = g.app_id GROUP BY a.id, a.name, a.spiffe_id, a.description, a.created_at ORDER BY a.created_at ASC `; return c.json({ apps }); }); appsAdminRoutes.post("/", async (c) => { const auth = await getAuthenticatedUser(c); if (!auth) return c.json({ error: "Unauthorized" }, 401); const { name, spiffeId, description, domain, is_public, bypass_paths, allowed_cidrs, } = await c.req.json(); if (!name || !spiffeId) { return c.json({ error: "Name and SPIFFE ID are required" }, 400); } try { const newApp = await sqlWrapper.sql` INSERT INTO apps (name, spiffe_id, description, domain, is_public, bypass_paths, allowed_cidrs) VALUES (${name.trim()}, ${spiffeId.trim()}, ${ description?.trim() || null }, ${domain?.trim() || null}, ${is_public || false}, ${ bypass_paths || [] }, ${allowed_cidrs || []}) RETURNING id, name, spiffe_id, description, created_at `.then((res: any) => res[0]); auditWrapper.auditLog(auth.userId, "app_registered", newApp.id, { name: newApp.name, spiffe_id: newApp.spiffe_id, }, getClientIp(c)); return c.json({ success: true, app: newApp }); } catch (err: any) { if (err.code === "23505") { return c.json({ error: "An application with this SPIFFE ID already exists", }, 409); } return c.json({ error: "Failed to register application" }, 500); } }); appsAdminRoutes.put("/:id", async (c) => { const auth = await getAuthenticatedUser(c); if (!auth) return c.json({ error: "Unauthorized" }, 401); const appId = c.req.param("id"); const { name, description, domain, is_public, bypass_paths, allowed_cidrs, } = await c.req.json(); if (!name) { return c.json({ error: "Application name is required" }, 400); } try { const updatedApp = await sqlWrapper.sql` UPDATE apps SET name = ${name.trim()}, description = ${description?.trim() || null}, domain = ${domain?.trim() || null}, is_public = ${is_public || false}, bypass_paths = ${bypass_paths || []}, allowed_cidrs = ${allowed_cidrs || []} WHERE id = ${appId} RETURNING id, name, spiffe_id, description, domain, is_public, bypass_paths, allowed_cidrs `.then((res: any) => res[0]); if (!updatedApp) return c.json({ error: "Application not found" }, 404); auditWrapper.auditLog(auth.userId, "app_updated", updatedApp.id, { name: updatedApp.name, }, getClientIp(c)); return c.json({ success: true, app: updatedApp }); } catch (_err: any) { return c.json({ error: "Failed to update application" }, 500); } }); appsAdminRoutes.delete("/:id", async (c) => { const auth = await getAuthenticatedUser(c); if (!auth) return c.json({ error: "Unauthorized" }, 401); const appId = c.req.param("id"); const app = await sqlWrapper .sql`DELETE FROM apps WHERE id = ${appId} RETURNING id, name` .then((res: any) => res[0]); if (app) { auditWrapper.auditLog( auth.userId, "app_deleted", appId, { name: app.name }, getClientIp(c), ); return c.json({ success: true }); } return c.json({ error: "Application not found" }, 404); });