feat(agent,server): v0.2.7 - mu-plugin WordPress robuste, réparation wp-config, proxy cookies/headers
- Agent: mu-plugin embarqué amélioré (HTTPS forcé, filtres URL, localhost:port) - Agent: suppression des WP_HOME/WP_SITEURL hardcodés au démarrage des instances - Server/proxy: envoi X-Forwarded-Port, réécriture headers/body élargie - Server/proxy: sanitization des Set-Cookie (Secure, SameSite, Domain) - Dashboard: version agent 0.2.7, action Supprimer complète - Cleanup: binaires agent 0.2.3-0.2.6 remplacés par 0.2.7
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const AGENT_VERSION = "0.2.3";
|
||||
const AGENT_VERSION = "0.2.7";
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -99,7 +99,7 @@ export async function PATCH(req: NextRequest) {
|
||||
const publicUrl = domain ? `https://${instance.id}.${domain}` : null;
|
||||
|
||||
if (action === "stop") {
|
||||
sendToNode(instance.nodeId, { action: "stop", instanceId: instance.id });
|
||||
sendToNode(instance.nodeId, { action: "delete", instanceId: instance.id });
|
||||
await prisma.instance.update({ where: { id }, data: { status: "stopped" } });
|
||||
} else if (action === "start") {
|
||||
const sent = sendToNode(instance.nodeId, {
|
||||
@@ -126,7 +126,7 @@ export async function DELETE(req: NextRequest) {
|
||||
const id = searchParams.get("id");
|
||||
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
|
||||
const instance = await prisma.instance.findUnique({ where: { id } });
|
||||
if (instance) sendToNode(instance.nodeId, { action: "stop", instanceId: instance.id });
|
||||
if (instance) sendToNode(instance.nodeId, { action: "delete", instanceId: instance.id });
|
||||
await prisma.instance.delete({ where: { id } });
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
|
||||
@@ -25,12 +25,22 @@ async function proxyRequest(req: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
const publicUrl = `https://${cleanHost}`;
|
||||
const targetUrl = new URL(req.url);
|
||||
const upstream = `http://${instance.node.tailscaleIp}:${instance.port}${targetUrl.pathname}${targetUrl.search}`;
|
||||
// The middleware rewrites /foo to /api/proxy/foo; strip the prefix before forwarding
|
||||
let pathname = targetUrl.pathname;
|
||||
if (pathname.startsWith("/api/proxy")) {
|
||||
pathname = pathname.slice("/api/proxy".length) || "/";
|
||||
}
|
||||
const upstream = `http://${instance.node.tailscaleIp}:${instance.port}${pathname}${targetUrl.search}`;
|
||||
|
||||
const headers = new Headers(req.headers);
|
||||
headers.delete("host");
|
||||
headers.set("host", cleanHost);
|
||||
headers.set("x-forwarded-host", cleanHost);
|
||||
headers.set("x-forwarded-proto", "https");
|
||||
headers.set("x-forwarded-port", "443");
|
||||
headers.set("x-forwarded-for", req.headers.get("x-forwarded-for") || "unknown");
|
||||
|
||||
const upstreamRes = await fetch(upstream, {
|
||||
method: req.method,
|
||||
@@ -47,7 +57,78 @@ async function proxyRequest(req: NextRequest) {
|
||||
responseHeaders.delete("content-encoding");
|
||||
responseHeaders.delete("content-length");
|
||||
|
||||
return new Response(upstreamRes.body, {
|
||||
// Rewrite any header values that point to localhost or the internal Tailscale address
|
||||
const localPatterns = [
|
||||
`http://${instance.node.tailscaleIp}:${instance.port}`,
|
||||
`https://${instance.node.tailscaleIp}:${instance.port}`,
|
||||
`http://localhost:${instance.port}`,
|
||||
`https://localhost:${instance.port}`,
|
||||
`http://localhost`,
|
||||
`https://localhost`,
|
||||
];
|
||||
responseHeaders.forEach((value, key) => {
|
||||
let newValue = value;
|
||||
for (const pattern of localPatterns) {
|
||||
newValue = newValue.replaceAll(pattern, publicUrl);
|
||||
}
|
||||
if (newValue !== value) {
|
||||
responseHeaders.set(key, newValue);
|
||||
}
|
||||
});
|
||||
|
||||
// Sanitize Set-Cookie headers so sessions work through the public domain.
|
||||
// WordPress may issue cookies for "localhost" or without Secure flag; fix it.
|
||||
const setCookies = responseHeaders.getSetCookie();
|
||||
if (setCookies.length > 0) {
|
||||
responseHeaders.delete("set-cookie");
|
||||
for (let cookie of setCookies) {
|
||||
// Drop any Domain=... set for localhost/internal domains
|
||||
cookie = cookie.replace(/;\s*Domain=[^;]+/gi, "");
|
||||
// Ensure Secure is present for HTTPS public URLs
|
||||
if (!/;\s*Secure\b/i.test(cookie)) {
|
||||
cookie += "; Secure";
|
||||
}
|
||||
// Make sure cookies are sent on sub-domain navigations
|
||||
if (!/;\s*SameSite\b/i.test(cookie)) {
|
||||
cookie += "; SameSite=Lax";
|
||||
}
|
||||
responseHeaders.append("set-cookie", cookie);
|
||||
}
|
||||
}
|
||||
|
||||
const contentType = responseHeaders.get("content-type") || "";
|
||||
const shouldRewriteBody =
|
||||
contentType.includes("text/html") ||
|
||||
contentType.includes("text/css") ||
|
||||
contentType.includes("application/javascript") ||
|
||||
contentType.includes("application/json");
|
||||
|
||||
if (!shouldRewriteBody) {
|
||||
return new Response(upstreamRes.body, {
|
||||
status: upstreamRes.status,
|
||||
statusText: upstreamRes.statusText,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
// For text responses, rewrite localhost/internal URLs to the public URL
|
||||
let body = await upstreamRes.text();
|
||||
const localBase = `http://${instance.node.tailscaleIp}:${instance.port}`;
|
||||
const localBaseHttps = `https://${instance.node.tailscaleIp}:${instance.port}`;
|
||||
const localLocalhostHttp = `http://localhost:${instance.port}`;
|
||||
const localLocalhostHttps = `https://localhost:${instance.port}`;
|
||||
const localLocalhostPlainHttp = `http://localhost`;
|
||||
const localLocalhostPlainHttps = `https://localhost`;
|
||||
|
||||
body = body
|
||||
.replaceAll(localBase, publicUrl)
|
||||
.replaceAll(localBaseHttps, publicUrl)
|
||||
.replaceAll(localLocalhostHttp, publicUrl)
|
||||
.replaceAll(localLocalhostHttps, publicUrl)
|
||||
.replaceAll(localLocalhostPlainHttp, publicUrl)
|
||||
.replaceAll(localLocalhostPlainHttps, publicUrl);
|
||||
|
||||
return new Response(body, {
|
||||
status: upstreamRes.status,
|
||||
statusText: upstreamRes.statusText,
|
||||
headers: responseHeaders,
|
||||
|
||||
Reference in New Issue
Block a user