feat: add comprehensive REST API with token auth, OpenAPI docs, and full test coverage

- API token model (SHA-256 hashed, debounced lastUsedAt) with Bearer auth
- Dual auth middleware (session + API token) in src/lib/api-auth.ts
- 23 REST endpoints under /api/v1/ covering all functionality:
  tokens, proxy-hosts, l4-proxy-hosts, certificates, ca-certificates,
  client-certificates, access-lists, settings, instances, users,
  audit-log, caddy/apply
- OpenAPI 3.1 spec at /api/v1/openapi.json with fully typed schemas
- Swagger UI docs page at /api-docs in the dashboard
- API token management integrated into the Profile page
- Fix: next build now works under Node.js (bun:sqlite aliased to better-sqlite3)
- 89 new API route unit tests + 11 integration tests (592 total)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fuomag9
2026-03-26 09:45:45 +01:00
parent 0acb430ebb
commit de28478a42
49 changed files with 5160 additions and 6 deletions

View File

@@ -0,0 +1,66 @@
"use client";
import { useEffect, useRef } from "react";
export default function ApiDocsClient() {
const containerRef = useRef<HTMLDivElement>(null);
const initializedRef = useRef(false);
useEffect(() => {
if (initializedRef.current) return;
initializedRef.current = true;
// Load Swagger UI CSS
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css";
document.head.appendChild(link);
// Inject overrides for dark-mode compatibility
const style = document.createElement("style");
style.textContent = `
.dark .swagger-ui {
filter: invert(88%) hue-rotate(180deg);
}
.dark .swagger-ui .highlight-code,
.dark .swagger-ui pre {
filter: invert(100%) hue-rotate(180deg);
}
.swagger-ui .topbar { display: none; }
.swagger-ui .information-container { padding: 1rem 0; }
`;
document.head.appendChild(style);
// Load Swagger UI bundle script
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js";
script.onload = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const SwaggerUIBundle = (window as any).SwaggerUIBundle;
if (SwaggerUIBundle && containerRef.current) {
SwaggerUIBundle({
url: "/api/v1/openapi.json",
domNode: containerRef.current,
presets: [SwaggerUIBundle.presets.apis],
deepLinking: true,
defaultModelsExpandDepth: 1,
});
}
};
document.body.appendChild(script);
return () => {
// Cleanup on unmount
link.remove();
style.remove();
script.remove();
};
}, []);
return (
<div
ref={containerRef}
className="w-full min-h-[600px] -mx-4 md:-mx-8 -my-6 px-4 md:px-8 py-6"
/>
);
}