Files
OmniRoute/src/app/api/settings/route.ts
diegosouzapw 8cf78ddf00 feat(auth): enforce dashboard sessions for management routes
Require dashboard session cookies on protected management APIs and
reject bearer API keys with explicit 403 responses to prevent
privilege escalation across provider, settings, and model alias routes.

Add a dedicated payload rules management surface with dashboard UI,
OpenAPI documentation, route normalization, and tests for hot-reloaded
runtime updates.

Consolidate provider catalog metadata for dashboard pages, add
Perplexity web-cookie provider support, retire the legacy provider
creation page, and improve upstream proxy handling.

Harden startup and runtime behavior by moving cloud sync bootstrap to
server instrumentation, skipping background services during build/test,
making models.dev sync abortable, pruning isolated build artifacts, and
improving DB backup and recovery safeguards.
2026-04-17 16:45:27 -03:00

120 lines
4.2 KiB
TypeScript

import { NextResponse } from "next/server";
import { getSettings, updateSettings } from "@/lib/localDb";
import { getRuntimePorts } from "@/lib/runtime/ports";
import { updateSettingsSchema } from "@/shared/validation/settingsSchemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { getConsistentMachineId } from "@/shared/utils/machineId";
import { validateProxyUrl, upsertUpstreamProxyConfig } from "@/lib/db/upstreamProxy";
import {
ensurePersistentManagementPasswordHash,
getStoredManagementPassword,
hasManagementPasswordConfigured,
hashManagementPassword,
verifyManagementPassword,
} from "@/lib/auth/managementPassword";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
export async function GET(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
const settings = await getSettings();
const { password, ...safeSettings } = settings;
const runtimePorts = getRuntimePorts();
const cloudUrl = process.env.CLOUD_URL || process.env.NEXT_PUBLIC_CLOUD_URL || null;
const machineId = await getConsistentMachineId();
return NextResponse.json({
...safeSettings,
hasPassword: hasManagementPasswordConfigured(settings),
runtimePorts,
apiPort: runtimePorts.apiPort,
dashboardPort: runtimePorts.dashboardPort,
cloudConfigured: Boolean(cloudUrl),
cloudUrl,
machineId,
});
} catch (error) {
console.log("Error getting settings:", error);
return NextResponse.json({ error: "Failed to load settings" }, { status: 500 });
}
}
export async function PATCH(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
const rawBody = await request.json();
// Zod validation
const validation = validateBody(updateSettingsSchema, rawBody);
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const body: typeof validation.data & { password?: string } = { ...validation.data };
// If updating password, hash it
if (body.newPassword) {
const settings = await getSettings();
const passwordState = await ensurePersistentManagementPasswordHash({
settings,
source: "settings.password_change",
});
const currentHash = getStoredManagementPassword(passwordState.settings);
if (currentHash) {
if (!body.currentPassword) {
return NextResponse.json({ error: "Current password required" }, { status: 400 });
}
const isValid = await verifyManagementPassword(body.currentPassword, currentHash);
if (!isValid) {
return NextResponse.json({ error: "Invalid current password" }, { status: 401 });
}
}
body.password = await hashManagementPassword(body.newPassword);
delete body.newPassword;
delete body.currentPassword;
}
const settings = await updateSettings(body);
// Sync CLIProxyAPI settings to upstream_proxy_config table
const cpaUrl = rawBody.cliproxyapi_url as string | undefined;
const cpaFallback = rawBody.cliproxyapi_fallback_enabled as boolean | undefined;
if (cpaUrl && typeof cpaUrl === "string") {
const urlValidation = validateProxyUrl(cpaUrl);
if (urlValidation.valid === false) {
return NextResponse.json(
{ error: `Invalid CLIProxyAPI URL: ${urlValidation.error}` },
{ status: 400 }
);
}
}
if (cpaFallback !== undefined || cpaUrl !== undefined) {
const enabled =
cpaFallback ?? (settings as Record<string, unknown>).cliproxyapi_fallback_enabled;
const mode = enabled ? "fallback" : "native";
await upsertUpstreamProxyConfig({
providerId: "cliproxyapi",
mode,
enabled: !!enabled,
});
}
const { password, ...safeSettings } = settings;
return NextResponse.json(safeSettings);
} catch (error) {
console.log("Error updating settings:", error);
return NextResponse.json({ error: "Failed to update settings" }, { status: 500 });
}
}
export async function PUT(request: Request) {
return PATCH(request);
}