singularity-forge/web/app/api/update/route.ts
Jeremy McSpadden ab03677567 fix(security): activate auth middleware and harden shutdown/update routes (#4023)
The Next.js auth middleware (proxy.ts) was never wired in — it exported
`proxy` from a file named proxy.ts, but Next.js requires a `middleware`
export from middleware.ts. The middleware-manifest.json was empty,
leaving all 42 API routes accessible without authentication.

Fixes:
- Rename web/proxy.ts → web/middleware.ts, export `middleware` not `proxy`
- Add defense-in-depth auth-guard to /api/shutdown and /api/update routes
- Remove shell: true from update-service spawn (command injection surface)
- Update contract tests to verify middleware file name and export

Closes #4014

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:52:43 -04:00

79 lines
2.1 KiB
TypeScript

import {
checkForUpdate,
getUpdateStatus,
triggerUpdate,
} from "../../../../src/web/update-service.ts"
import { verifyAuthToken } from "../../../lib/auth-guard";
export const runtime = "nodejs"
export const dynamic = "force-dynamic"
export async function GET(request: Request): Promise<Response> {
// Defense-in-depth: verify auth token even though middleware should catch it.
const authError = verifyAuthToken(request);
if (authError) return authError;
try {
const versionInfo = await checkForUpdate()
const { status, error, targetVersion } = getUpdateStatus()
return Response.json(
{
currentVersion: versionInfo.currentVersion,
latestVersion: versionInfo.latestVersion,
updateAvailable: versionInfo.updateAvailable,
updateStatus: status,
...(error ? { error } : {}),
...(targetVersion ? { targetVersion } : {}),
},
{
headers: { "Cache-Control": "no-store" },
},
)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return Response.json(
{ error: message },
{
status: 500,
headers: { "Cache-Control": "no-store" },
},
)
}
}
export async function POST(request: Request): Promise<Response> {
// Defense-in-depth: verify auth token even though middleware should catch it.
const authError = verifyAuthToken(request);
if (authError) return authError;
try {
const versionInfo = await checkForUpdate()
const started = triggerUpdate(versionInfo.latestVersion)
if (!started) {
return Response.json(
{ error: "Update already in progress" },
{
status: 409,
headers: { "Cache-Control": "no-store" },
},
)
}
return Response.json(
{ triggered: true },
{
status: 202,
headers: { "Cache-Control": "no-store" },
},
)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
return Response.json(
{ error: message },
{
status: 500,
headers: { "Cache-Control": "no-store" },
},
)
}
}