Closes checklist items F-01..F-06, D-01..D-28, S-01..S-10, C-01..C-07, B-01..B-07, R-01..R-02, O-03. Security (28 deletions + 10 auth gates): - Delete 28 unauthenticated debug/cursor/firebase/test routes - Gate ai/chat, ai/conversation, context/summarize, work-completed with withTenantProject/withAuth - Add HMAC-SHA256 signature verification to webhooks/coolify - Switch all admin secret comparisons to timingSafeStringEq Foundations (lib/server/*): - api-handler.ts: withAuth, withTenantProject, withWorkspace, withAdminSecret, withRateLimit - logger.ts: structured request-scoped logging with turnId - audit-log.ts: writeAuditLog helper + audit_log table - rate-limit.ts: Postgres sliding window rate limiter - coolify-webhook.ts: verifyCoolifySignature - timing-safe.ts: timingSafeStringEq Chat hardening (chat/route.ts): - MAX_TOOL_ROUNDS 15 → 8 (C-01) - Loop detection: hard-break at 3 identical fingerprints (was 5) (C-02) - Add 6-consecutive-tool-call hard-break (C-02) - Mode: respond first, act second prompt block (C-03) - SSE heartbeat every 25s via setInterval (C-04) - Per-tool 45s timeout via Promise.race (C-05) - turnId per-turn UUID for log correlation (C-06) - Recovery fires when roundsSinceText >= 4 (C-07) - SSE plan event on plan_task_add/edit (B-05) Beta features: - invites table + GET/POST /api/invites (P4.8) - invites/[token] validate + redeem (P4.8) - fs_project_dev_servers table + lib/server/dev-server-state.ts (P6.B1) - fs_project_secrets table + CRUD routes (P6.D2) - lib/integrations/brief-extract.ts (P3.7) Documentation: - app/api/ROUTES.md: full route map with auth + tenant
54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import {
|
|
isCoolifyInfraOperational,
|
|
runCoolifyInfraHealthProbe,
|
|
} from "@/lib/server/infra-coolify-health";
|
|
import { timingSafeStringEq } from "@/lib/server/timing-safe";
|
|
|
|
/**
|
|
* Authenticated infrastructure probe for Coolify API + SSH→Docker.
|
|
*
|
|
* Configure on deploy:
|
|
* INFRA_HEALTH_SECRET — required; pass as Authorization: Bearer <secret>
|
|
*
|
|
* Use from Cron / UptimeRobot / Better Stack:
|
|
* curl -sf -H "Authorization: Bearer $INFRA_HEALTH_SECRET" \
|
|
* https://<your-app>/api/internal/infra-health
|
|
*
|
|
* Returns 200 only when Coolify API and SSH+docker on the host are OK.
|
|
*/
|
|
export async function GET(req: NextRequest) {
|
|
const secret = process.env.INFRA_HEALTH_SECRET?.trim();
|
|
if (!secret) {
|
|
return NextResponse.json(
|
|
{
|
|
ok: false,
|
|
error:
|
|
"INFRA_HEALTH_SECRET is not set — configure it on vibn-frontend to enable this probe.",
|
|
},
|
|
{ status: 503 },
|
|
);
|
|
}
|
|
|
|
const auth = req.headers.get("authorization");
|
|
const bearer = auth?.startsWith("Bearer ") ? auth.slice(7).trim() : "";
|
|
const headerSecret = req.headers.get("x-vibn-infra-secret")?.trim() ?? "";
|
|
const token = bearer || headerSecret;
|
|
|
|
if (!token || !timingSafeStringEq(secret, token)) {
|
|
return NextResponse.json(
|
|
{ ok: false, error: "Unauthorized" },
|
|
{ status: 401 },
|
|
);
|
|
}
|
|
|
|
try {
|
|
const report = await runCoolifyInfraHealthProbe();
|
|
const ok = isCoolifyInfraOperational(report);
|
|
return NextResponse.json({ ok, report }, { status: ok ? 200 : 503 });
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
return NextResponse.json({ ok: false, error: message }, { status: 503 });
|
|
}
|
|
}
|