mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
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.
120 lines
4.2 KiB
TypeScript
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);
|
|
}
|