ai_api / src /app /api /settings /mitm /route.ts
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
8.7 kB
export const runtime = "nodejs";
import fs from "fs";
import path from "path";
import { z } from "zod";
import { NextResponse } from "next/server";
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
import { resolveApiKey } from "@/shared/services/apiKeyResolver";
import { resolveMitmDataDir } from "@/mitm/dataDir";
import { KIRO_MITM_PROFILE } from "@/mitm/targets/kiro";
import { ANTIGRAVITY_MITM_PROFILE } from "@/mitm/targets/antigravity";
type MitmTargetRoute = {
id: string;
name: string;
targetHost: string;
targetPort: number;
localPort: number;
endpoints: string[];
enabled: boolean;
};
type MitmStats = {
startedAt: string | null;
totalRequests: number;
interceptedRequests: number;
activeConnections: number;
lastRequestAt: string | null;
lastInterceptAt: string | null;
};
type MitmConfig = {
port: number;
targets: MitmTargetRoute[];
};
const DEFAULT_PORT = 443;
const MITM_PORT_ERROR =
"Transparent MITM interception currently requires port 443 because DNS override does not redirect destination ports.";
const updateMitmSchema = z.object({
enabled: z.boolean().optional(),
apiKey: z.string().optional(),
keyId: z.string().optional(),
sudoPassword: z.string().optional(),
port: z.coerce.number().int().min(1).max(65535).optional(),
});
const regenerateSchema = z.object({
action: z.literal("regenerate-cert").optional(),
});
function getMitmDir() {
return path.join(resolveMitmDataDir(), "mitm");
}
function getConfigPath() {
return path.join(getMitmDir(), "settings.json");
}
function getStatsPath() {
return path.join(getMitmDir(), "stats.json");
}
function getCertPath() {
return path.join(getMitmDir(), "server.crt");
}
function getKeyPath() {
return path.join(getMitmDir(), "server.key");
}
function defaultTargets(port = DEFAULT_PORT): MitmTargetRoute[] {
const allHosts = [
ANTIGRAVITY_MITM_PROFILE.targetHost,
...(ANTIGRAVITY_MITM_PROFILE.additionalHosts || []),
];
return [
{
id: ANTIGRAVITY_MITM_PROFILE.id,
name: ANTIGRAVITY_MITM_PROFILE.name,
targetHost: allHosts.join(", "),
targetPort: ANTIGRAVITY_MITM_PROFILE.targetPort,
localPort: port,
endpoints: ANTIGRAVITY_MITM_PROFILE.apiEndpoints,
enabled: true,
},
{
id: KIRO_MITM_PROFILE.id,
name: KIRO_MITM_PROFILE.name,
targetHost: KIRO_MITM_PROFILE.targetHost,
targetPort: KIRO_MITM_PROFILE.targetPort,
localPort: KIRO_MITM_PROFILE.localPort,
endpoints: KIRO_MITM_PROFILE.apiEndpoints,
enabled: false,
},
];
}
function readConfig(): MitmConfig {
try {
JSON.parse(fs.readFileSync(getConfigPath(), "utf8"));
return {
port: DEFAULT_PORT,
targets: defaultTargets(DEFAULT_PORT),
};
} catch {
return {
port: DEFAULT_PORT,
targets: defaultTargets(DEFAULT_PORT),
};
}
}
function writeConfig() {
const mitmDir = getMitmDir();
fs.mkdirSync(mitmDir, { recursive: true });
fs.writeFileSync(getConfigPath(), JSON.stringify({ port: DEFAULT_PORT }, null, 2));
}
function readStats(): MitmStats {
try {
const raw = JSON.parse(fs.readFileSync(getStatsPath(), "utf8"));
return {
startedAt: typeof raw.startedAt === "string" ? raw.startedAt : null,
totalRequests: Number(raw.totalRequests || 0),
interceptedRequests: Number(raw.interceptedRequests || 0),
activeConnections: Number(raw.activeConnections || 0),
lastRequestAt: typeof raw.lastRequestAt === "string" ? raw.lastRequestAt : null,
lastInterceptAt: typeof raw.lastInterceptAt === "string" ? raw.lastInterceptAt : null,
};
} catch {
return {
startedAt: null,
totalRequests: 0,
interceptedRequests: 0,
activeConnections: 0,
lastRequestAt: null,
lastInterceptAt: null,
};
}
}
async function buildMitmResponse() {
const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager.runtime");
const status = await getMitmStatus();
const config = readConfig();
const stats = readStats();
return {
running: status.running,
pid: status.pid || null,
dnsConfigured: status.dnsConfigured || false,
certExists: status.certExists || fs.existsSync(getCertPath()),
hasCachedPassword: !!getCachedPassword(),
port: config.port,
targets: config.targets,
stats,
};
}
export async function GET(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
const { searchParams } = new URL(request.url);
if (searchParams.get("download") === "cert") {
const certPath = getCertPath();
if (!fs.existsSync(certPath)) {
return NextResponse.json({ error: "MITM certificate not found" }, { status: 404 });
}
return new NextResponse(fs.readFileSync(certPath), {
headers: {
"Content-Type": "application/x-pem-file",
"Content-Disposition": 'attachment; filename="omniroute-mitm-ca.crt"',
},
});
}
return NextResponse.json(await buildMitmResponse());
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to load MITM settings";
return NextResponse.json({ error: message }, { status: 500 });
}
}
export async function PUT(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
const rawBody = await request.json().catch(() => ({}));
const parsed = updateMitmSchema.safeParse(rawBody);
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
}
const config = readConfig();
if (parsed.data.port !== undefined && parsed.data.port !== DEFAULT_PORT) {
return NextResponse.json({ error: MITM_PORT_ERROR }, { status: 400 });
}
if (parsed.data.port !== undefined) {
config.port = DEFAULT_PORT;
config.targets = defaultTargets(config.port);
writeConfig();
}
if (typeof parsed.data.enabled === "boolean") {
const { getCachedPassword, setCachedPassword, startMitm, stopMitm } =
await import("@/mitm/manager.runtime");
const { isRoot } = await import("@/mitm/systemCommands");
const isWin = process.platform === "win32";
const isRootUser = !isWin && isRoot();
const sudoPassword = parsed.data.sudoPassword || getCachedPassword() || "";
if (parsed.data.enabled) {
const apiKey = await resolveApiKey(parsed.data.keyId || null, parsed.data.apiKey || null);
if (!apiKey || (!isWin && !isRootUser && !sudoPassword)) {
return NextResponse.json(
{ error: isWin ? "Missing apiKey" : "Missing apiKey or sudoPassword" },
{ status: 400 }
);
}
await startMitm(apiKey, sudoPassword, { port: config.port });
if (!isWin) setCachedPassword(sudoPassword);
} else {
if (!isWin && !isRootUser && !sudoPassword) {
return NextResponse.json({ error: "Missing sudoPassword" }, { status: 400 });
}
await stopMitm(sudoPassword);
if (!isWin && parsed.data.sudoPassword) setCachedPassword(parsed.data.sudoPassword);
}
}
return NextResponse.json(await buildMitmResponse());
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to update MITM settings";
return NextResponse.json({ error: message }, { status: 500 });
}
}
export async function POST(request: Request) {
const authError = await requireManagementAuth(request);
if (authError) return authError;
try {
const rawBody = await request.json().catch(() => ({}));
const parsed = regenerateSchema.safeParse(rawBody);
if (!parsed.success) {
return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
}
const { getMitmStatus } = await import("@/mitm/manager.runtime");
const status = await getMitmStatus();
if (status.running) {
return NextResponse.json(
{ error: "Stop the MITM proxy before regenerating certificates" },
{ status: 409 }
);
}
for (const filePath of [getCertPath(), getKeyPath()]) {
try {
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
} catch {
/* ignore */
}
}
const { generateCert } = await import("@/mitm/cert/generate");
await generateCert();
return NextResponse.json(await buildMitmResponse());
} catch (error) {
const message =
error instanceof Error ? error.message : "Failed to regenerate MITM certificate";
return NextResponse.json({ error: message }, { status: 500 });
}
}