mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 07:12:12 +03:00
fix(api/settings): prevent cached /api/settings responses (port from 9router#951) (#4566)
Integrated into release/v3.8.34 (rebuilt onto tip)
This commit is contained in:
committed by
GitHub
parent
2bdf37796e
commit
fc3b417f40
@@ -26,6 +26,19 @@ import { isCliTokenAuthValid } from "@/lib/middleware/cliTokenAuth";
|
||||
import { extractApiKey } from "@/sse/services/auth";
|
||||
import { getApiKeyMetadata } from "@/lib/db/apiKeys";
|
||||
|
||||
/**
|
||||
* Force this route to run dynamically per-request and never be cached/prerendered.
|
||||
* Combined with the `Cache-Control: no-store` response header below, this keeps
|
||||
* persisted settings (e.g. dashboard preferences, debugMode, hidden sidebar
|
||||
* items) visible immediately after refresh or restart instead of falling back
|
||||
* to stale Next.js fetch cache. Ported from upstream decolua/9router#951.
|
||||
*/
|
||||
export const dynamic = "force-dynamic";
|
||||
export const revalidate = 0;
|
||||
|
||||
/** Response headers applied to every successful GET/PATCH on /api/settings. */
|
||||
const SETTINGS_RESPONSE_HEADERS = { "Cache-Control": "no-store" } as const;
|
||||
|
||||
/**
|
||||
* Settings keys whose change broadens attack surface. Spec §Security:
|
||||
* password re-auth is required when any of these is present in a PATCH body.
|
||||
@@ -162,19 +175,22 @@ export async function GET(request: Request) {
|
||||
// best effort — don't fail GET /api/settings if this lookup fails
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
...safeSettings,
|
||||
hasPassword: hasManagementPasswordConfigured(settings),
|
||||
runtimePorts,
|
||||
apiPort: runtimePorts.apiPort,
|
||||
dashboardPort: runtimePorts.dashboardPort,
|
||||
cloudConfigured: Boolean(cloudUrl),
|
||||
cloudUrl,
|
||||
machineId,
|
||||
...(cliproxyapiModelMapping !== null
|
||||
? { cliproxyapi_model_mapping: cliproxyapiModelMapping }
|
||||
: {}),
|
||||
});
|
||||
return NextResponse.json(
|
||||
{
|
||||
...safeSettings,
|
||||
hasPassword: hasManagementPasswordConfigured(settings),
|
||||
runtimePorts,
|
||||
apiPort: runtimePorts.apiPort,
|
||||
dashboardPort: runtimePorts.dashboardPort,
|
||||
cloudConfigured: Boolean(cloudUrl),
|
||||
cloudUrl,
|
||||
machineId,
|
||||
...(cliproxyapiModelMapping !== null
|
||||
? { cliproxyapi_model_mapping: cliproxyapiModelMapping }
|
||||
: {}),
|
||||
},
|
||||
{ headers: SETTINGS_RESPONSE_HEADERS }
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting settings:", error);
|
||||
return NextResponse.json({ error: "Failed to load settings" }, { status: 500 });
|
||||
@@ -376,7 +392,7 @@ export async function PATCH(request: Request) {
|
||||
}
|
||||
|
||||
const { password, ...safeSettings } = settings;
|
||||
return NextResponse.json(safeSettings);
|
||||
return NextResponse.json(safeSettings, { headers: SETTINGS_RESPONSE_HEADERS });
|
||||
} catch (error) {
|
||||
console.log("Error updating settings:", error);
|
||||
return NextResponse.json({ error: "Failed to update settings" }, { status: 500 });
|
||||
|
||||
@@ -209,6 +209,35 @@ describe("Settings API - persisted preferences", () => {
|
||||
assert.equal(settings.responsesPreviousResponseIdMode, "strip");
|
||||
});
|
||||
|
||||
test("GET /api/settings returns Cache-Control: no-store (ported from upstream #951)", async () => {
|
||||
const response = await harness.settingsRoute.GET(
|
||||
await makeManagementSessionRequest("http://localhost/api/settings", {
|
||||
method: "GET",
|
||||
})
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(
|
||||
response.headers.get("Cache-Control"),
|
||||
"no-store",
|
||||
"GET /api/settings must return Cache-Control: no-store so persisted settings stay fresh after refresh/restart"
|
||||
);
|
||||
});
|
||||
|
||||
test("PATCH /api/settings returns Cache-Control: no-store (ported from upstream #951)", async () => {
|
||||
const response = await harness.settingsRoute.PATCH(
|
||||
await makeManagementSessionRequest("http://localhost/api/settings", {
|
||||
method: "PATCH",
|
||||
body: { debugMode: true },
|
||||
})
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(
|
||||
response.headers.get("Cache-Control"),
|
||||
"no-store",
|
||||
"PATCH /api/settings must return Cache-Control: no-store"
|
||||
);
|
||||
});
|
||||
|
||||
test("PUT /api/settings reuses the PATCH update flow", async () => {
|
||||
const response = await harness.settingsRoute.PUT(
|
||||
await makeManagementSessionRequest("http://localhost/api/settings", {
|
||||
|
||||
Reference in New Issue
Block a user