Shuffle/e2e/support/auth-config.ts
cschlaefke aa421b12b0
Some checks failed
CI / flutter (pull_request) Successful in 1m15s
CI / web-build (pull_request) Successful in 53s
CI / supabase (pull_request) Successful in 3s
CI / supabase-tests (pull_request) Successful in 30s
CI / e2e (pull_request) Failing after 3m56s
test(auth): address review findings for e2e
Refs #38

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 10:55:27 +01:00

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';