Files
nebula/src/lib/config.ts
T
Florian WildeandClaude Opus 5 37573962a8 feat(auth): Anmeldung mit Passwort und Passkey
Kern der Benutzerverwaltung.

Passwoerter:
- scrypt aus der Node-Standardbibliothek (N=32768, r=8, p=1), speicherhart
  und ohne zusaetzliches natives Modul
- Format scrypt$N$r$p$salt$hash, damit sich die Parameter spaeter anheben
  lassen
- fakeVerifyDelay verbrennt bei unbekanntem Benutzernamen dieselbe
  Rechenzeit, sonst liesse sich an der Antwortzeit ablesen, welche Konten
  existieren

Passkeys ueber SimpleWebAuthn 13:
- Anmeldung ohne Benutzernamen ueber auffindbare Credentials
- Challenges liegen in der Datenbank und werden beim Einloesen geloescht,
  koennen also nicht erneut eingespielt werden
- rpID und Origin kommen aus APP_URL

Sitzungen:
- Zufallstoken im HttpOnly-Cookie, in der Datenbank nur dessen Hash
- Abmelden und Sperren wirken sofort, weil bei jeder Anfrage nachgeschlagen
  wird - anders als bei einem selbsttragenden Token

Middleware: CSRF-Pruefung ueber Sec-Fetch-Site bzw. Origin,
Sicherheitskopfzeilen inklusive CSP mit Per-Request-Nonce und
strict-dynamic, dazu eine grobe Zugangsschranke. Letztere ist ausdruecklich
keine Sicherheitsgrenze - im Edge-Runtime gibt es keine Datenbank.
Verbindlich entscheiden requireUserPage und requireUserApi, serverseitig,
in jeder Seite und jedem Endpunkt.

config.ts liest APP_NAME, APP_TAGLINE und APP_URL zur Laufzeit statt ueber
NEXT_PUBLIC_-Variablen. Die waeren beim Build fest ins Bundle geschrieben;
so laesst sich dasselbe Docker-Image ohne Neubau umbenennen und unter einer
anderen Adresse betreiben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 21:30:12 +02:00

81 lines
2.7 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.
/**
* Laufzeitkonfiguration.
*
* Bewusst ohne NEXT_PUBLIC_-Variablen: die wuerden beim Build fest in das
* JavaScript-Bundle geschrieben. So laesst sich dasselbe Docker-Image ohne
* Neubau anders benennen und unter einer anderen URL betreiben. Werte, die
* der Browser braucht, reicht das Layout als Props weiter.
*/
function trimmed(value: string | undefined): string | null {
const result = value?.trim();
return result ? result : null;
}
/** Anzeigename der Instanz – Seitentitel, Kopfzeile, Passkey-Dialog. */
export const APP_NAME = trimmed(process.env.APP_NAME) ?? "Nebula";
/** Kleine Zeile unter dem Namen in der Kopfzeile. */
export const APP_TAGLINE = trimmed(process.env.APP_TAGLINE) ?? "Vibe Coding";
const RAW_APP_URL = trimmed(process.env.APP_URL) ?? "http://localhost:3000";
function parseAppUrl(raw: string): URL {
try {
const url = new URL(raw);
if (url.protocol !== "http:" && url.protocol !== "https:") {
throw new Error(`APP_URL muss http oder https sein, ist aber "${url.protocol}"`);
}
return url;
} catch (error) {
throw new Error(
`APP_URL ist keine gueltige URL: "${raw}". Beispiel: https://nebula.example.com` +
` (${error instanceof Error ? error.message : String(error)})`,
);
}
}
const APP_URL_PARSED = parseAppUrl(RAW_APP_URL);
/** Vollstaendige Basis-URL ohne abschliessenden Slash. */
export const APP_URL = APP_URL_PARSED.origin;
/** Origin, den WebAuthn in der Assertion erwartet. */
export const WEBAUTHN_ORIGIN = APP_URL_PARSED.origin;
/**
* Relying-Party-ID fuer WebAuthn: der reine Hostname ohne Port.
* Passkeys sind an diesen Wert gebunden – aendert er sich, sind bestehende
* Passkeys nicht mehr verwendbar.
*/
export const WEBAUTHN_RP_ID = APP_URL_PARSED.hostname;
/**
* WebAuthn verlangt einen sicheren Kontext. Ausnahme: localhost gilt in
* Browsern als sicher, auch ohne TLS.
*/
export const WEBAUTHN_AVAILABLE =
APP_URL_PARSED.protocol === "https:" ||
APP_URL_PARSED.hostname === "localhost" ||
APP_URL_PARSED.hostname === "127.0.0.1";
/** Cookies nur ueber TLS ausliefern, wenn die App per https laeuft. */
export const COOKIES_SECURE = APP_URL_PARSED.protocol === "https:";
/** Lebensdauer einer Sitzung. */
export const SESSION_TTL_DAYS = Number(process.env.SESSION_TTL_DAYS ?? 30);
export const SESSION_TTL_MS = SESSION_TTL_DAYS * 24 * 60 * 60 * 1000;
export const SESSION_COOKIE = "nebula_session";
/** Wird beim Start einmal geloggt, damit Fehlkonfiguration frueh auffaellt. */
export function describeConfig(): string {
return [
`name=${APP_NAME}`,
`url=${APP_URL}`,
`rpId=${WEBAUTHN_RP_ID}`,
`passkeys=${WEBAUTHN_AVAILABLE ? "moeglich" : "deaktiviert (kein sicherer Kontext)"}`,
`secureCookies=${COOKIES_SECURE}`,
].join(" ");
}