119 lines
5.5 KiB
TypeScript
119 lines
5.5 KiB
TypeScript
/**
|
|
* Konstanten der Auth-Konfiguration (Spec #1 § 4.1, `docs/auth-konfiguration.md`).
|
|
*
|
|
* Diese Datei muss mit `supabase/config.toml` und `docs/auth-konfiguration.md` uebereinstimmen;
|
|
* `tests/api/config.spec.ts` prueft das. Tests lesen Zahlen nur von hier und verweisen im Namen
|
|
* auf die Zeilen-ID (z. B. `AUTH-CFG-07`).
|
|
*
|
|
* Werte, die lokal (Supabase CLI) und in Staging/Prod unterschiedlich sind, tragen hier den
|
|
* LOKALEN Wert (die E2E-Tests laufen nur lokal bzw. in der CI gegen die CLI).
|
|
*/
|
|
|
|
/** Hosts, die als „lokal“ gelten (URL-Hostnamen; IPv6 steht in URL.hostname mit Klammern). */
|
|
const LOCAL_HOSTS = ['127.0.0.1', 'localhost', '[::1]'];
|
|
|
|
/**
|
|
* Zusaetzlich erlaubte Hosts fuer die CI (z. B. der Docker-Host `docker`), kommagetrennt in
|
|
* E2E_ALLOWED_HOSTS. Nur setzen, wenn der Dienst ein Wegwerf-Stack ist.
|
|
*/
|
|
function allowedHosts(): string[] {
|
|
const extra = (process.env.E2E_ALLOWED_HOSTS ?? '').split(',').map((h) => h.trim()).filter(Boolean);
|
|
return [...LOCAL_HOSTS, ...extra];
|
|
}
|
|
|
|
export function isLocalHost(hostname: string): boolean {
|
|
return allowedHosts().includes(hostname.toLowerCase());
|
|
}
|
|
|
|
/** Bricht ab, wenn die URL nicht auf einen lokalen (oder explizit erlaubten) Host zeigt. */
|
|
export function assertLocalUrl(name: string, value: string): string {
|
|
const host = new URL(value).hostname;
|
|
if (!isLocalHost(host)) {
|
|
throw new Error(`${name}: Host "${host}" ist nicht lokal. Die E2E-Tests legen Testkonten an; ggf. E2E_ALLOWED_HOSTS setzen.`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
const DEMO_ANON_KEY =
|
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0';
|
|
const DEMO_SERVICE_KEY =
|
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU';
|
|
|
|
const supabaseUrl = assertLocalUrl('SUPABASE_URL', process.env.SUPABASE_URL ?? 'http://127.0.0.1:54321');
|
|
const isDefaultLocal = ['127.0.0.1', 'localhost', '[::1]'].includes(new URL(supabaseUrl).hostname);
|
|
|
|
function key(envName: string, demo: string): string {
|
|
const v = process.env[envName];
|
|
if (v) return v;
|
|
// Demo-Schluessel nur bei lokaler URL; sonst muss der Aufrufer den Schluessel setzen.
|
|
if (!isDefaultLocal) throw new Error(`${envName} muss gesetzt sein, wenn SUPABASE_URL nicht lokal ist.`);
|
|
return demo;
|
|
}
|
|
|
|
/** Umgebung (Standardwerte = lokale Demo-Werte der Supabase CLI, keine Secrets). */
|
|
export const env = {
|
|
/** Kong, wie die App. */
|
|
supabaseUrl,
|
|
/** Legacy-JWT-Anon-Key der CLI (oeffentlicher Demo-Wert, nur bei lokaler URL als Fallback). */
|
|
anonKey: key('SUPABASE_ANON_KEY', DEMO_ANON_KEY),
|
|
/** Legacy-Service-Role-Key der CLI (oeffentlicher Demo-Wert, nur lokal). */
|
|
serviceRoleKey: key('SUPABASE_SERVICE_ROLE_KEY', DEMO_SERVICE_KEY),
|
|
mailpitUrl: assertLocalUrl('MAILPIT_URL', process.env.MAILPIT_URL ?? 'http://127.0.0.1:54324'),
|
|
dbUrl: process.env.E2E_DB_URL ?? 'postgresql://postgres:postgres@127.0.0.1:54322/postgres',
|
|
/** Web-Build. Immer `localhost` (nicht 127.0.0.1): Die Mail-Links nutzen AUTH-CFG-01. */
|
|
webUrl: process.env.WEB_URL ?? 'http://localhost:3000',
|
|
/** Zusaetzlicher GoTrue-Container mit AUTH-CFG-13 (Projekt `throttle`, Skript start-throttle-gotrue.sh). */
|
|
throttleGotrueUrl: process.env.THROTTLE_GOTRUE_URL,
|
|
/** GoTrue-Container mit unerreichbarem SMTP (Projekt `spike`, Issue #37). */
|
|
spikeGotrueUrl: process.env.SPIKE_GOTRUE_URL,
|
|
} as const;
|
|
|
|
export const authConfig = {
|
|
/** AUTH-CFG-01: Site-URL lokal. */
|
|
siteUrl: 'http://localhost:3000',
|
|
/** AUTH-CFG-05: Passwort-Mindestlaenge. */
|
|
passwordMinLength: 10,
|
|
/** AUTH-CFG-07: Gueltigkeit Bestaetigungs- und Reset-Link in Sekunden. */
|
|
otpExpirySeconds: 3600,
|
|
/** AUTH-CFG-08: Mindestabstand zwischen Mails je Nutzer, lokal 1 s. */
|
|
maxFrequencySeconds: 1,
|
|
/** AUTH-CFG-10: Burst des Login-Limiters je IP (Login #31 -> 429). */
|
|
loginBurst: 30,
|
|
/** AUTH-CFG-11: Burst des OTP-Limiters (Signup, Recover, Resend) je IP. */
|
|
otpBurst: 30,
|
|
/** AUTH-CFG-14: JWT-Laufzeit in Sekunden. */
|
|
jwtExpirySeconds: 3600,
|
|
/** AUTH-CFG-15: Wiederverwendungsfenster der Refresh-Token-Rotation in Sekunden. */
|
|
refreshTokenReuseIntervalSeconds: 10,
|
|
/** Anzeigename: Grenzen in Codepoints (Spec 3.2). */
|
|
displayNameMaxLength: 30,
|
|
} as const;
|
|
|
|
/** Mailbetreffe (AUTH-CFG-17/-18, `supabase/config.toml`). */
|
|
export const subjects = {
|
|
confirmation: { de: 'Bestätige deine E-Mail-Adresse für Shuffle', en: 'Confirm your email address for Shuffle' },
|
|
recovery: { de: 'Passwort für Shuffle zurücksetzen', en: 'Reset your Shuffle password' },
|
|
} as const;
|
|
|
|
/** Fehlercodes von GoTrue, auf die sich die Spec stuetzt (§ 4.3/4.4). */
|
|
export const errorCodes = {
|
|
invalidCredentials: 'invalid_credentials',
|
|
emailNotConfirmed: 'email_not_confirmed',
|
|
weakPassword: 'weak_password',
|
|
samePassword: 'same_password',
|
|
otpExpired: 'otp_expired',
|
|
overRequestRateLimit: 'over_request_rate_limit',
|
|
overEmailSendRateLimit: 'over_email_send_rate_limit',
|
|
userAlreadyExists: 'user_already_exists',
|
|
} as const;
|
|
|
|
/** Trigger-Fehlertexte bei signUp (HTTP 500, Body `{"code":"P0001","message":...}`, B0 (f)). */
|
|
export const triggerErrors = {
|
|
displayNameInvalid: 'display_name_invalid',
|
|
ageConfirmationRequired: 'age_confirmation_required',
|
|
termsAcceptanceRequired: 'terms_acceptance_required',
|
|
termsVersionRequired: 'terms_version_required',
|
|
} as const;
|
|
|
|
/** Version der Rechtstexte, die der Web-Build sendet (`app/env/ci.example.json`). */
|
|
export const TERMS_VERSION = process.env.TERMS_VERSION ?? '2026-11-draft';
|