fix(ui): correct Quick Start API key link to /dashboard/endpoint

- HomePageClient.tsx: 'Settings → API Keys' → 'Endpoint → Registered Keys'
- docs/page.tsx: same fix in Quick Start step 2
- Also includes user's manual fixes: bodySizeGuard, container imports, migrationRunner ESM compat
This commit is contained in:
diegosouzapw
2026-02-18 16:40:33 -03:00
parent 0279341221
commit 5a645973eb
12 changed files with 123 additions and 52 deletions

View File

@@ -158,10 +158,10 @@ export default function HomePageClient({ machineId }) {
<span className="font-semibold">1. Create API key</span>
<p className="text-text-muted mt-0.5">
Go to{" "}
<Link href="/dashboard/settings" className="text-primary hover:underline">
Settings
<Link href="/dashboard/endpoint" className="text-primary hover:underline">
Endpoint
</Link>{" "}
API Keys. Generate one key per environment.
Registered Keys. Generate one key per environment.
</p>
</div>
</li>

View File

@@ -23,26 +23,51 @@ export default function SessionInfoCard() {
const [loading, setLoading] = useState(true);
useEffect(() => {
// Build session info from client-side data
const loginTime = sessionStorage.getItem("omniroute_login_time");
const now = Date.now();
let cancelled = false;
let sessionAge = "Unknown";
if (loginTime) {
const elapsed = now - parseInt(loginTime, 10);
const hours = Math.floor(elapsed / 3600000);
const minutes = Math.floor((elapsed % 3600000) / 60000);
sessionAge = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
async function loadSession() {
// Build session info from client-side data
const loginTime = sessionStorage.getItem("omniroute_login_time");
const now = Date.now();
let sessionAge = "Unknown";
if (loginTime) {
const elapsed = now - parseInt(loginTime, 10);
const hours = Math.floor(elapsed / 3600000);
const minutes = Math.floor((elapsed % 3600000) / 60000);
sessionAge = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
}
let authenticated = false;
try {
const res = await fetch("/api/auth/status", {
method: "GET",
cache: "no-store",
});
if (res.ok) {
const data = await res.json();
authenticated = data.authenticated === true;
}
} catch {
// Keep unauthenticated fallback on network errors.
}
if (cancelled) return;
setSession({
authenticated,
loginTime: loginTime ? new Date(parseInt(loginTime, 10)).toLocaleString() : null,
sessionAge,
ipAddress: "—", // Server-side only
userAgent: navigator.userAgent.split(" ").slice(-2).join(" ") || "Unknown",
});
setLoading(false);
}
setSession({
authenticated: !!document.cookie.includes("omniroute"),
loginTime: loginTime ? new Date(parseInt(loginTime, 10)).toLocaleString() : null,
sessionAge,
ipAddress: "—", // Server-side only
userAgent: navigator.userAgent.split(" ").slice(-2).join(" ") || "Unknown",
});
setLoading(false);
loadSession();
return () => {
cancelled = true;
};
}, []);
const handleLogout = async () => {

View File

@@ -0,0 +1,23 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { jwtVerify } from "jose";
const SECRET = process.env.JWT_SECRET
? new TextEncoder().encode(process.env.JWT_SECRET)
: null;
export async function GET() {
try {
const cookieStore = await cookies();
const token = cookieStore.get("auth_token")?.value;
if (!token || !SECRET) {
return NextResponse.json({ authenticated: false });
}
await jwtVerify(token, SECRET);
return NextResponse.json({ authenticated: true });
} catch {
return NextResponse.json({ authenticated: false });
}
}

View File

@@ -204,7 +204,7 @@ export default function DocsPage() {
<li className="rounded-lg border border-border p-3 bg-bg">
<span className="font-semibold">2. Create API key</span>
<p className="text-text-muted mt-1">
Go to Settings API Keys. Generate one key per environment.
Go to Endpoint Registered Keys. Generate one key per environment.
</p>
</li>
<li className="rounded-lg border border-border p-3 bg-bg">

View File

@@ -56,6 +56,7 @@ export default function LoginPage() {
});
if (res.ok) {
sessionStorage.setItem("omniroute_login_time", String(Date.now()));
router.push("/dashboard");
router.refresh();
} else {

View File

@@ -15,6 +15,18 @@
* @module lib/container
*/
import { evaluateFirstAllowed, evaluateRequest, PolicyEngine } from "../domain/policyEngine";
import { getDbInstance } from "./db/core";
import {
decrypt,
decryptConnectionFields,
encrypt,
encryptConnectionFields,
} from "./db/encryption";
import { getSettings } from "./localDb";
import { getCircuitBreaker } from "../shared/utils/circuitBreaker";
import { recordTelemetry, RequestTelemetry } from "../shared/utils/requestTelemetry";
type Factory<T = any> = () => T;
class Container {
@@ -76,41 +88,34 @@ class Container {
export const container = new Container();
// ── Default registrations ──
// These lazy-load the actual modules so the container can be imported
// without triggering all side effects at module load time.
// Services are still lazily instantiated on first resolve().
container.register("settings", () => {
const { getSettings } = require("@/lib/localDb");
return { get: getSettings };
});
container.register("db", () => {
const { getDbInstance } = require("@/lib/db/core");
return getDbInstance();
});
container.register("encryption", () => {
const enc = require("@/lib/db/encryption");
return {
encrypt: enc.encrypt,
decrypt: enc.decrypt,
encryptConnectionFields: enc.encryptConnectionFields,
decryptConnectionFields: enc.decryptConnectionFields,
encrypt,
decrypt,
encryptConnectionFields,
decryptConnectionFields,
};
});
container.register("policyEngine", () => {
const { evaluateRequest, evaluateFirstAllowed, PolicyEngine } = require("@/domain/policyEngine");
return { evaluateRequest, evaluateFirstAllowed, PolicyEngine };
});
container.register("circuitBreaker", () => {
const { getCircuitBreaker } = require("@/shared/utils/circuitBreaker");
return { get: getCircuitBreaker };
});
container.register("telemetry", () => {
const { RequestTelemetry, recordTelemetry } = require("@/shared/utils/requestTelemetry");
return { RequestTelemetry, recordTelemetry };
});

View File

@@ -8,6 +8,7 @@ import Database from "better-sqlite3";
import path from "node:path";
import fs from "node:fs";
import { resolveDataDir, getLegacyDotDataDir } from "../dataPaths";
import { runMigrations } from "./migrationRunner";
// ──────────────── Environment Detection ────────────────
@@ -355,21 +356,16 @@ export function getDbInstance() {
// ── Versioned Migrations ──
// Auto-seed 001 as applied (the inline SCHEMA_SQL already created these tables)
// then run any new migrations (002+)
try {
const { runMigrations } = require("./migrationRunner");
_db.exec(`
CREATE TABLE IF NOT EXISTS _omniroute_migrations (
version TEXT PRIMARY KEY,
name TEXT NOT NULL,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT OR IGNORE INTO _omniroute_migrations (version, name)
VALUES ('001', 'initial_schema');
`);
runMigrations(_db);
} catch (err: any) {
console.warn("[DB] Migration runner unavailable:", err.message);
}
_db.exec(`
CREATE TABLE IF NOT EXISTS _omniroute_migrations (
version TEXT PRIMARY KEY,
name TEXT NOT NULL,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT OR IGNORE INTO _omniroute_migrations (version, name)
VALUES ('001', 'initial_schema');
`);
runMigrations(_db);
// Auto-migrate from db.json if exists
if (JSON_DB_FILE && fs.existsSync(JSON_DB_FILE)) {

View File

@@ -11,8 +11,11 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type Database from "better-sqlite3";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const MIGRATIONS_DIR = path.join(__dirname, "migrations");
/**

View File

@@ -8,7 +8,7 @@
* @module lib/evals/scheduler
*/
import { runSuite, listSuites, createScorecard } from "./evalRunner";
import { runSuite, listSuites, createScorecard, getSuite } from "./evalRunner";
// ── Types ──
@@ -159,7 +159,6 @@ async function executeScheduledRun(suiteId: string): Promise<EvalRunResult | nul
// Get outputs from provider
const outputs: Record<string, string> = {};
// We use the suite's cases to get the case IDs
const { getSuite } = require("./evalRunner");
const suite = getSuite(suiteId);
if (!suite?.cases) return null;

View File

@@ -3,7 +3,7 @@ import { jwtVerify } from "jose";
import { generateRequestId } from "./shared/utils/requestId";
import { getSettings } from "./lib/localDb";
import { isPublicRoute, verifyAuth, isAuthRequired } from "./shared/utils/apiAuth";
import { checkBodySize } from "./shared/middleware/bodySizeGuard";
import { checkBodySize, getBodySizeLimit } from "./shared/middleware/bodySizeGuard";
import { isDraining } from "./lib/gracefulShutdown";
// FASE-01: Fail-fast — no hardcoded fallback. Server must have JWT_SECRET configured.
@@ -37,7 +37,7 @@ export async function proxy(request) {
// ──────────────── Pre-flight: Reject oversized bodies ────────────────
if (pathname.startsWith("/api/") && request.method !== "GET" && request.method !== "OPTIONS") {
const bodySizeRejection = checkBodySize(request);
const bodySizeRejection = checkBodySize(request, getBodySizeLimit(pathname));
if (bodySizeRejection) return bodySizeRejection;
}

View File

@@ -19,12 +19,30 @@ const DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
/** Larger limit for backup/import routes: 100 MB */
export const MAX_BODY_BYTES_IMPORT = 100 * 1024 * 1024;
/** Larger limit for audio transcription uploads: 100 MB */
export const MAX_BODY_BYTES_AUDIO = 100 * 1024 * 1024;
/** Configured limit — reads from env or falls back to 10 MB */
export const MAX_BODY_BYTES = parseInt(
process.env.MAX_BODY_SIZE_BYTES || String(DEFAULT_MAX_BODY_BYTES),
10
);
type BodySizeRule = { prefix: string; limit: number };
const ROUTE_LIMITS: BodySizeRule[] = [
{ prefix: "/api/db-backups/import", limit: MAX_BODY_BYTES_IMPORT },
{ prefix: "/api/v1/audio/transcriptions", limit: MAX_BODY_BYTES_AUDIO },
];
/**
* Resolve the body size limit for a request path.
*/
export function getBodySizeLimit(pathname: string): number {
const customRule = ROUTE_LIMITS.find((rule) => pathname.startsWith(rule.prefix));
return customRule?.limit ?? MAX_BODY_BYTES;
}
/**
* Check Content-Length header against the configured limit.
* Returns a 413 Response if the body is too large, or null if OK.

View File

@@ -20,6 +20,7 @@ const PUBLIC_API_ROUTES = [
// Auth flow — must be accessible to unauthenticated users
"/api/auth/login",
"/api/auth/logout",
"/api/auth/status",
// Settings check — used by login page / onboarding
"/api/settings/require-login",