generated from Dicken/dickendock
95 lines
3.8 KiB
TypeScript
95 lines
3.8 KiB
TypeScript
import type { FastifyInstance } from "fastify";
|
|
import * as serviceRepo from "../db/repositories/services.js";
|
|
import * as apiRepo from "../db/repositories/apis.js";
|
|
import * as logRepo from "../db/repositories/logs.js";
|
|
import { detectApis } from "../scanner/apiDetector.js";
|
|
import { startJob, updateJobProgress, finishJob, failJob, getJobStatus, isJobRunning } from "../scanJobs.js";
|
|
|
|
/**
|
|
* API-Scanner: eigener, von den Geräte-/FritzBox-Scannern komplett
|
|
* unabhängiger manueller Scan (siehe Scanner-Seite) - durchsucht bereits
|
|
* bekannte Dienste nach üblichen API-Pfaden (siehe scanner/apiDetector.ts)
|
|
* und speichert die Funde. Läuft NIE automatisch, nur auf Knopfdruck.
|
|
*
|
|
* Läuft als Hintergrund-Job im Backend (siehe scanJobs.ts), nicht mehr
|
|
* innerhalb einer einzigen HTTP-Anfrage: bei vielen Diensten (jeweils ~40
|
|
* geprüfte Pfade) kann das insgesamt länger dauern, als ein
|
|
* Reverse-Proxy/Browser auf eine Antwort wartet (führte zu HTTP 504). Der
|
|
* POST-Aufruf startet den Job nur und antwortet sofort, der Fortschritt wird
|
|
* über GET .../status abgefragt - das macht den laufenden Scan zudem
|
|
* geräteübergreifend sichtbar (der Status lebt im Backend, nicht im Browser).
|
|
*/
|
|
export async function apiRoutes(app: FastifyInstance): Promise<void> {
|
|
app.get("/api/detected-apis", async () => {
|
|
return apiRepo.listDetectedApis();
|
|
});
|
|
|
|
app.get("/api/scan/apis/status", async () => {
|
|
return getJobStatus("apis");
|
|
});
|
|
|
|
app.post("/api/scan/apis", async (_request, reply) => {
|
|
if (isJobRunning("apis")) {
|
|
return reply.code(409).send({ error: "Es läuft bereits ein API-Scan." });
|
|
}
|
|
|
|
const services = serviceRepo.listServices();
|
|
startJob("apis", services.length);
|
|
|
|
// Bewusst NICHT awaited - der Job läuft im Hintergrund weiter, die
|
|
// HTTP-Antwort kommt sofort zurück (siehe Doku oben).
|
|
void (async () => {
|
|
let servicesWithApi = 0;
|
|
let totalFound = 0;
|
|
const newFindings: { serviceId: string; serviceName: string; apis: apiRepo.DetectedApiEntry[] }[] = [];
|
|
|
|
try {
|
|
for (let i = 0; i < services.length; i++) {
|
|
const service = services[i];
|
|
updateJobProgress("apis", i, service.displayName);
|
|
|
|
const found = await detectApis(service.url);
|
|
if (found.length > 0) {
|
|
const { added } = apiRepo.replaceApisForService(service.id, found);
|
|
servicesWithApi++;
|
|
totalFound += found.length;
|
|
if (added.length > 0) {
|
|
newFindings.push({ serviceId: service.id, serviceName: service.displayName, apis: added });
|
|
}
|
|
} else {
|
|
apiRepo.deleteApisForService(service.id);
|
|
}
|
|
}
|
|
|
|
logRepo.logScan({
|
|
type: "api",
|
|
targetId: null,
|
|
level: "info",
|
|
message: `API-Scan: ${services.length} Dienst(e) geprüft, bei ${servicesWithApi} Dienst(en) ${totalFound} API-Endpunkt(e) gefunden.`,
|
|
});
|
|
|
|
finishJob("apis", { checked: services.length, servicesWithApi, totalFound, newFindings });
|
|
} catch (err) {
|
|
failJob("apis", err instanceof Error ? err.message : "Unbekannter Fehler");
|
|
}
|
|
})();
|
|
|
|
return reply.code(202).send({ started: true });
|
|
});
|
|
|
|
app.post("/api/scan/apis/:serviceId", async (request, reply) => {
|
|
const { serviceId } = request.params as { serviceId: string };
|
|
const service = serviceRepo.getService(serviceId);
|
|
if (!service) {
|
|
return reply.code(404).send({ error: "Dienst nicht gefunden" });
|
|
}
|
|
|
|
const found = await detectApis(service.url);
|
|
const { saved, added } =
|
|
found.length > 0 ? apiRepo.replaceApisForService(service.id, found) : { saved: [], added: [] };
|
|
if (found.length === 0) apiRepo.deleteApisForService(service.id);
|
|
|
|
return { serviceId, apis: saved, added };
|
|
});
|
|
}
|