#!/usr/bin/env node /** * Warum klappt die Anmeldung nicht? * * node --env-file=.env scripts/anmeldung-pruefen.mjs * docker compose exec app node scripts/anmeldung-pruefen.mjs * * Die Anwendung sagt am Anmeldeformular bewusst nicht, woran es lag - sonst * liesse sich daran ablesen, welche Adressen es gibt. Auf dem Server darf * man es wissen: Jeder Versuch wird mit Grund protokolliert, und dieses * Skript liest ihn vor. * * Es aendert nichts. Es liest nur. */ import { PrismaClient } from '@prisma/client' const email = (process.argv[2] ?? '').trim().toLowerCase() if (!email) { console.error('Aufruf: node scripts/anmeldung-pruefen.mjs ') process.exit(1) } const db = new PrismaClient() function zeit(d) { return d ? new Date(d).toLocaleString('de-DE') : '—' } /** Klartext zu den Gruenden, die src/auth.ts protokolliert. */ const GRUND = { unbekannt: 'Diese E-Mail-Adresse gibt es nicht (Tippfehler? andere Adresse?)', 'passwort-falsch': 'Das Passwort stimmt nicht', 'code-falsch': 'Der Code aus der Authenticator-App stimmt nicht', gesperrt: 'Abgewiesen, weil zu diesem Zeitpunkt bereits gesperrt war', 'konto-inaktiv': 'Der Zugang ist deaktiviert oder im Papierkorb', } try { const benutzer = await db.user.findUnique({ where: { email } }) console.log('\n══ Zugang ' + '═'.repeat(50)) if (!benutzer) { console.log(` Es gibt KEINEN Zugang mit der Adresse ${email}.`) const alle = await db.user.findMany({ select: { email: true, aktiv: true, geloeschtAm: true }, orderBy: { erstelltAm: 'asc' }, }) console.log( alle.length ? '\n Vorhandene Adressen:\n' + alle.map((u) => ` ${u.email}${u.geloeschtAm ? ' (im Papierkorb)' : u.aktiv ? '' : ' (deaktiviert)'}`).join('\n') : '\n Es gibt ueberhaupt keinen Zugang. Leg ihn im Browser unter /ersteinrichtung an.', ) } else { const zweiterFaktor = benutzer.totpAktiviertAm ? `eingerichtet am ${zeit(benutzer.totpAktiviertAm)}` : benutzer.totpSecretVerschluesselt ? 'vorbereitet, aber NICHT bestaetigt - beim Anmelden wird noch kein Code verlangt' : 'noch nicht eingerichtet - beim Anmelden wird noch kein Code verlangt' console.log(` Name: ${benutzer.name}`) console.log(` Rolle: ${benutzer.rolle}`) console.log(` Aktiv: ${benutzer.aktiv ? 'ja' : 'NEIN - Anmeldung nicht moeglich'}`) console.log(` Papierkorb: ${benutzer.geloeschtAm ? 'JA seit ' + zeit(benutzer.geloeschtAm) : 'nein'}`) console.log(` Zweiter Faktor: ${zweiterFaktor}`) console.log(` Wiederherstellung: ${benutzer.wiederherstellungscodes.length} Codes uebrig`) console.log(` Passwort gesetzt: ${zeit(benutzer.passwortGeaendertAm)}`) console.log(` Letzte Anmeldung: ${zeit(benutzer.letzteAnmeldung)}`) const zugaenge = await db.betreuungsZugang.count({ where: { userId: benutzer.id } }) const betreute = await db.betreute.count({ where: { geloeschtAm: null } }) console.log(` Betreute Personen: ${zugaenge} zugeordnet, ${betreute} insgesamt angelegt`) if (zugaenge === 0 && betreute === 0) { console.log( '\n Hinweis: Nach dem Anmelden landest du zuerst bei der\n' + ' Ersteinrichtung - erst zweiter Faktor, dann betreute Person.', ) } } // --- Versuche der letzten Tage ------------------------------------------- const versuche = await db.loginVersuch.findMany({ where: { email }, orderBy: { zeitpunkt: 'desc' }, take: 15, }) console.log('\n══ Letzte Anmeldeversuche ' + '═'.repeat(35)) if (versuche.length === 0) { console.log( ' Keine. Es ist also NIE ein Versuch beim Server angekommen.\n\n' + ' Das heisst: Das Formular hat gar nicht abgeschickt, oder es lief\n' + ' auf eine andere Adresse. Pruef AUTH_URL - siehe unten.', ) } else { for (const v of versuche) { const kennzeichen = v.erfolg ? 'OK ' : 'FEHLER' const grund = v.erfolg ? 'angemeldet' : (GRUND[v.grund] ?? v.grund ?? 'unbekannter Grund') console.log(` ${zeit(v.zeitpunkt).padEnd(20)} ${kennzeichen} ${grund}`) } } // --- Sperre --------------------------------------------------------------- const fenster = Number.parseInt(process.env.LOGIN_FENSTER_SEKUNDEN ?? '900', 10) const grenze = Number.parseInt(process.env.LOGIN_MAX_VERSUCHE ?? '5', 10) const seit = new Date(Date.now() - fenster * 1000) const offen = await db.loginVersuch.count({ where: { email, erfolg: false, zeitpunkt: { gte: seit } }, }) console.log('\n══ Sperre ' + '═'.repeat(51)) console.log(` ${offen} von ${grenze} Fehlversuchen im laufenden Fenster (${fenster / 60} Minuten)`) if (offen >= grenze) { console.log( '\n GESPERRT. Warte das Fenster ab, oder setz die Zaehler zurueck:\n' + ' docker compose exec db psql -U cockpit betreuungs_cockpit \\\n' + ' -c "DELETE FROM login_versuch WHERE erfolg = false;"', ) } // --- Umgebung ------------------------------------------------------------- console.log('\n══ Umgebung ' + '═'.repeat(49)) console.log(` AUTH_URL: ${process.env.AUTH_URL ?? 'NICHT GESETZT'}`) console.log( '\n Diese Adresse muss EXAKT die sein, die du im Browser aufrufst -\n' + ' gleiches Schema (https), gleicher Hostname, kein Schraegstrich am\n' + ' Ende. Weicht sie ab, wird das Sitzungs-Cookie fuer die falsche\n' + ' Herkunft gesetzt: Die Anmeldung landet ohne Fehlermeldung wieder\n' + ' auf dem Formular, und oben steht kein einziger Fehlversuch.\n', ) } catch (fehler) { // Ein Prisma-Stacktrace hilft hier niemandem weiter. const meldung = fehler instanceof Error ? fehler.message : String(fehler) if (/Can't reach database server|ECONNREFUSED/i.test(meldung)) { // Das Passwort in der Verbindungszeichenkette gehoert nicht auf den Schirm. const url = (process.env.DATABASE_URL ?? 'NICHT GESETZT').replace(/:[^:@/]+@/, ':***@') console.error( [ '', 'Die Datenbank ist nicht erreichbar.', '', ` DATABASE_URL: ${url}`, '', ' Im Docker-Betrieb gehoert dieses Skript IN den Behaelter:', ' docker compose exec app node scripts/anmeldung-pruefen.mjs ', '', ' Laeuft die Datenbank ueberhaupt?', ' docker compose ps', '', ].join('\n'), ) } else { console.error(`\nFehler: ${meldung}\n`) } process.exitCode = 1 } finally { await db.$disconnect() }