Files
ProxLedger/src/lib/adresse.ts
T
florian.wildeandClaude Opus 5 3657b60d16 feat(hosts): Doppelte Hosts erkennen und stilllegen (#041)
Zwei Eintraege desselben Namens beim selben Kunden entstehen, wenn sich die
Kennung aus /etc/machine-id aendert - Neuinstallation, geklonter Container,
zurueckgespielte Sicherung. Dieselbe Maschine meldet sich dann als neuer Host,
der alte Eintrag bleibt stehen. Zwei getrennte Maschinen mit gleichem Namen
sind aber genauso moeglich.

Neue dreizehnte Warnregel meldet den Fall und nennt die IP-Adressen zum
Unterscheiden; gleiche Namen bei verschiedenen Kunden bleiben still.

Dazu die bis dahin fehlende Moeglichkeit, einen Host loszuwerden: Stilllegen
blendet ihn aus Listen, Zahlen, Suche und Warnungen aus und schliesst seine
offenen Warnungen, laesst Laufzeiten und Rechnungen aber unberuehrt.
Endgueltig loeschen geht nur ohne Laufzeiten - Buchhaltung wirft man nicht weg.

Kein automatisches Zusammenfuehren: Es muesste raten, welcher Eintrag welche
Rechnung erbt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 17:57:18 +02:00

71 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Ermittelt die Adresse, unter der das Dashboard **von außen** erreichbar ist.
*
* Im Container ist `request.url` die interne Adresse (`http://<container-id>:3000`). Eine Weiterleitung
* dorthin führt im Browser ins Leere – genau das passierte nach der Anmeldung im Docker-Stapel.
*
* Reihenfolge: eingetragene Adresse → `APP_URL` → Kopfzeilen des Reverse Proxy → Anfrage selbst.
* Die Kopfzeilen stehen bewusst hinter den konfigurierten Werten: Sie kommen vom Client und lassen
* sich fälschen; für eine Weiterleitung auf die eigene Seite ist das unkritisch, als erste Wahl aber
* unnötig.
*/
/**
* Lausch-Adressen taugen nicht als Ziel für einen Browser: `0.0.0.0` heißt „alle Schnittstellen“.
* Steht so etwas in der Konfiguration, wird es übergangen statt eine unerreichbare Seite zu bauen.
*/
const UNBRAUCHBAR = new Set(["0.0.0.0", "::", "[::]", "", "0"]);
function brauchbar(adresse: string | undefined): URL | null {
if (!adresse) return null;
try {
const ergebnis = new URL(adresse);
return UNBRAUCHBAR.has(ergebnis.hostname) ? null : ergebnis;
} catch {
return null;
}
}
export function oeffentlicheBasis(request: Request, eingetragen?: string): URL {
const ausEinstellung = brauchbar(eingetragen);
if (ausEinstellung) return ausEinstellung;
const ausUmgebung = brauchbar(process.env.APP_URL);
if (ausUmgebung) return ausUmgebung;
const gastgeber = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
if (gastgeber) {
const schema = request.headers.get("x-forwarded-proto") ?? new URL(request.url).protocol.replace(":", "");
const ausKopfzeile = brauchbar(`${schema}://${gastgeber}`);
if (ausKopfzeile) return ausKopfzeile;
}
return new URL(request.url);
}
/** Baut eine Adresse auf derselben Installation – für Weiterleitungen nach der Anmeldung. */
export function eigeneAdresse(request: Request, pfad: string, eingetragen?: string): URL {
return new URL(pfad, oeffentlicheBasis(request, eingetragen));
}
/**
* Dasselbe für Server Actions und Seiten, die keine `Request` zur Hand haben.
*
* Nötig, weil Next.js eine Weiterleitung aus einer Server Action serverseitig absolut macht – und
* dafür die Adresse nimmt, auf die der Server lauscht. Im Container ist das `0.0.0.0` (oder der
* Containername); im Browser landet man damit auf einer Seite, die es nicht gibt.
*/
export async function eigenerPfad(pfad: string, eingetragen?: string): Promise<string> {
let kopfzeilen: Headers;
try {
const { headers } = await import("next/headers");
kopfzeilen = await headers();
} catch {
// Außerhalb einer Anfrage (Tests, Skripte) gibt es keine Kopfzeilen – dann bleibt der Pfad relativ.
return pfad;
}
// Die Anfrage selbst gibt es hier nicht; eine leere mit den echten Kopfzeilen genügt.
const basis = oeffentlicheBasis(new Request("http://platzhalter.invalid", { headers: kopfzeilen }), eingetragen);
return new URL(pfad, basis).toString();
}