feat(api): comprehensive QA hardening — security gates, chat improvements, beta scaffolds
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
This commit is contained in:
@@ -9,10 +9,20 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export interface Anatomy {
|
||||
project: { id: string; name: string; gitea?: string; coolifyProjectUuid?: string };
|
||||
project: {
|
||||
id: string;
|
||||
name: string;
|
||||
gitea?: string;
|
||||
coolifyProjectUuid?: string;
|
||||
};
|
||||
codebasesReason?: "no_repo" | "empty_repo";
|
||||
product: {
|
||||
codebases: Array<{ id: string; label: string; path: string; hint?: string }>;
|
||||
codebases: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
path: string;
|
||||
hint?: string;
|
||||
}>;
|
||||
images: Array<{
|
||||
uuid: string;
|
||||
name: string;
|
||||
@@ -104,7 +114,10 @@ export interface UseAnatomyOptions {
|
||||
pollMs?: number;
|
||||
}
|
||||
|
||||
export function useAnatomy(projectId: string, options: UseAnatomyOptions = {}): UseAnatomyResult {
|
||||
export function useAnatomy(
|
||||
projectId: string,
|
||||
options: UseAnatomyOptions = {},
|
||||
): UseAnatomyResult {
|
||||
const { pollMs } = options;
|
||||
const [anatomy, setAnatomy] = useState<Anatomy | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -131,22 +144,30 @@ export function useAnatomy(projectId: string, options: UseAnatomyOptions = {}):
|
||||
fetch(`/api/projects/${projectId}/anatomy`, {
|
||||
credentials: "include",
|
||||
signal: controller.signal,
|
||||
cache: "no-store",
|
||||
})
|
||||
.then(async r => {
|
||||
.then(async (r) => {
|
||||
let body: unknown = {};
|
||||
try { body = await r.json(); } catch { /* keep {} */ }
|
||||
try {
|
||||
body = await r.json();
|
||||
} catch {
|
||||
/* keep {} */
|
||||
}
|
||||
if (!r.ok) {
|
||||
const msg = (body as { error?: string }).error || `HTTP ${r.status} ${r.statusText}`.trim();
|
||||
const msg =
|
||||
(body as { error?: string }).error ||
|
||||
`HTTP ${r.status} ${r.statusText}`.trim();
|
||||
throw new Error(msg);
|
||||
}
|
||||
return body as Anatomy;
|
||||
})
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
if (!cancelled) setAnatomy(data);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
if (cancelled) return;
|
||||
if (err?.name === "AbortError") setError("Request timed out after 10s.");
|
||||
if (err?.name === "AbortError")
|
||||
setError("Request timed out after 10s.");
|
||||
else setError(err?.message || "Failed to load project anatomy");
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -161,5 +182,5 @@ export function useAnatomy(projectId: string, options: UseAnatomyOptions = {}):
|
||||
};
|
||||
}, [projectId, tick]);
|
||||
|
||||
return { anatomy, loading, error, reload: () => setTick(t => t + 1) };
|
||||
return { anatomy, loading, error, reload: () => setTick((t) => t + 1) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user