Vier Aenderungen seit 1.10.1, alle bereits auf main. Minor statt Patch,
weil eine neue Funktion dabei ist und zwei Schemaaenderungen mitkommen.
Der Schwerpunkt liegt beim Abgleich, und die drei Punkte dort gehoeren
zusammen: Ein verschluckter Fehler sah aus wie ein leeres Ergebnis (#19),
ueberschrieb dabei den letzten guten Stand (#20), und weil niemand die
Ursache sehen konnte, blieb sie tagelang unentdeckt. Erst als die Fehler
sichtbar waren, liess sich der Rest ueberhaupt benennen: eine Seiten-
groesse, die angefragt statt gemessen wurde (#22), und Zeitueber-
schreitungen, die einen zweiten Versuch verdienen (#23).
Dazu die Karten je Spalte (#16) und das Issue-Kennzeichen (#12) aus den
Fassungen davor.
Verlauf und Versionsnummer wandern zusammen; changelog.test.ts haelt das
fest.
Beim Ausrollen laufen zwei Migrationen mit: taskColumnLimit an
AppSettings und missing/completeAt an RepoSnapshot.
Refs #16
Refs #19
Refs #20
Refs #22
Refs #23
Zwei Aenderungen seit 1.10.0, beide bereits auf main: das Kennzeichen fuer
ein verknuepftes Git-Issue auf der Aufgabenkarte (#12) und die Token-Rechte,
Aufruf fuer Aufruf belegt (#10). Dazu die Korrektur am Hinweis unter dem
Token-Feld, der die Rechte fuer Actions nicht nannte - ohne die sieht bei
GitHub niemand einen CI-Zustand.
Verlauf und Versionsnummer wandern zusammen, sonst laufen sie auseinander;
changelog.test.ts haelt das fest. Die package-lock.json stand noch auf 1.2.0
und beschrieb damit ein Paket, das es so nie gab - mitgezogen.
Refs #10
Refs #12
Seit v1.9.0 sind drei Features nach main gegangen, ohne dass die
Versionsnummer mitgezogen waere: Wochenrueckblick (#2), globale Zeitleiste
(#3) und die Suche ueber Aufgaben (#4).
Genau dieselbe Luecke wie vor v1.6.0. Der Test in changelog.test.ts prueft,
dass die oberste Fassung im Verlauf zur package.json passt - er greift aber
nicht, wenn beide gemeinsam zurueckhaengen. Ein Test, der das faende,
muesste den Verlauf gegen die Git-Historie halten; das waere mehr Maschinerie
als der Fehler wert, solange die Freigabe ohnehin von Hand laeuft.
Der Verlauf wird in der Anwendung angezeigt (Knopf an der Versionsnummer in
der Fusszeile), deshalb steht dort jetzt, was dazugekommen ist. Nachgesehen
auf 375px und 1280px: Eintraege vollstaendig, keine Beanstandung von
axe-core.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Behebt florian.wilde/nebula#1.
Der Rueckweg "Issue geschlossen -> Aufgabe erledigt" gab es seit v1.7.0
schon, aber nur ueber den Webhook. Ist keiner eingerichtet - und am
Repository war keiner eingetragen -, blieb er ganz aus. Damit hing die
Funktion an einer Voraussetzung, die man ihr von aussen nicht ansieht.
Der Abgleich fragt jetzt den Zustand verknuepfter Issues nach und hakt ab,
was in Git geschlossen ist. Beides ergaenzt sich: der Webhook wirkt sofort,
der Abgleich verlaesslich.
Gespart wird, wo es geht: Nummern, die der gerade geholte Snapshot als offen
fuehrt, werden nicht einzeln nachgefragt. Umgekehrt gilt das nicht - die
Liste ist gedeckelt, aus dem Fehlen einer Nummer folgt nichts. Deshalb ein
Deckel von 25 Nachfragen je Durchgang.
**Nur abhaken, nie zuruecknehmen.** Der Abgleich sieht Zustaende, keine
Ereignisse. Ein offenes Issue kann auch bedeuten, dass das Schliessen von
Nebula aus fehlgeschlagen ist; ein symmetrischer Abgleich wuerde dann eine
bewusst erledigte Aufgabe wieder aufmachen. Das Zuruecknehmen bleibt dem
Webhook vorbehalten, der ein echtes "reopened" sieht. Ein geloeschtes Issue
ist ebenfalls kein erledigtes.
Nebenbei eine Unstimmigkeit aus v1.7.0 geradegezogen: der Webhook setzte nur
den Status und liess aus, was die Oberflaeche tut - naechste Fassung einer
wiederkehrenden Aufgabe, Eintrag im Verlauf. Beide Wege gehen jetzt durch
dieselbe Funktion.
Geprueft: 232 Unit-Tests, 31 E2E-Tests. Der neue E2E-Test laeuft als
einziger wirklich nach draussen - er haengt eine Aufgabe an ein seit Jahren
geschlossenes Issue und prueft, dass der Abgleich sie abhakt. Ist GitHub
nicht erreichbar, ueberspringt er sich statt rot zu werden.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Geprueft gegen ASVS und die Cheat Sheets zu Authentication, Session
Management und Password Storage. Das meiste stand schon gut da – Token mit
256 Bit und nur als Hash in der Datenbank, serverseitige Pruefung bei jeder
Anfrage, Sitzungen weg beim Passwortwechsel, TOTP mit Replay-Schutz. Fuenf
Punkte blieben.
**Aufwand beim Hashen.** N war 2^15 bei p=1. Das Password Storage Cheat
Sheet nennt 2^17/p=1 als Minimum und listet als gleichwertig unter anderem
2^16/p=2 und 2^15/p=3 – mit p=1 lag 2^15 unter *allen* zulaessigen
Kombinationen und war rund dreimal billiger zu knacken als vorgesehen. Jetzt
2^17/p=1, gemessen 0,5 s und 128 MiB je Pruefung. Der Speicher ist gedeckelt:
Node rechnet scrypt im libuv-Threadpool, also hoechstens vier parallel.
**Neuhashen bei der Anmeldung.** Ohne das blieben bestehende Passwoerter fuer
immer auf den alten Parametern – anheben laesst er sich nur dort, wo das
Klartextpasswort vorliegt. needsRehash vergleicht den Gesamtaufwand N*r*p,
nicht Feld fuer Feld, damit gleichwertige Kombinationen nicht grundlos neu
gehasht werden.
**Aufzaehlbarkeit.** Drei Zweige beantworteten sich *vor* der Passwort-
pruefung und verrieten damit die Existenz eines Kontos: gesperrt (423),
deaktiviert (403) und "kein Passwort hinterlegt, nimm einen Passkey" (401).
Der einheitliche Text GENERIC war damit wirkungslos. Jetzt wird zuerst das
Passwort geprueft; Klartext gibt es erst danach. Reine Passkey-Konten
verbrennen dieselbe Rechenzeit, damit sich der Fall auch nicht an der Dauer
zeigt. Der Preis ist der verlorene Hinweis "nimm den Passkey" – bewusst
hingenommen.
**Leerlauffrist.** lastSeenAt wurde gefuehrt, aber nie ausgewertet: ein
abgegriffener Cookie galt bis zu 30 Tage, egal ob das Konto benutzt wurde.
ASVS 3.3.2 will beide Grenzen. Acht Stunden, ueber SESSION_IDLE_HOURS
einstellbar. Das Schreibintervall fuer lastSeenAt haengt jetzt an der Frist –
bei fester Stunde haette eine kuerzere Frist sich selbst ausgehebelt.
**Cookies.** SameSite Strict statt Lax, und bei https der Praefix "__Host-".
Der laesst den Browser erzwingen, dass kein Domain-Attribut gesetzt ist –
eine andere Subdomain kann den Cookie dann nicht ueberschreiben. Beides
kostet einmalig eine neue Anmeldung.
Dazu die Passwortpruefung: Wiederholungen, durchlaufende Zeichenfolgen und
Passwoerter, die den Benutzernamen enthalten, werden abgelehnt.
Zusammensetzungsregeln bleiben bewusst aus – ASVS 2.1.9 raet davon ab.
Geprueft: 232 Unit-Tests (davon 12 neue zu Aufwand, Neuhashen und Mustern)
und 30 E2E-Tests, darunter einer, der die Antwort auf ein stillgelegtes
Konto gegen die auf ein erfundenes haelt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ein Push aktualisiert den Stand sofort, statt bis zum naechsten Durchlauf
des Zeitgebers zu warten; ein geschlossenes Issue hakt seine Aufgabe ab.
Der Zeitplan bleibt als Rueckfalllosung.
Der Endpunkt ist der einzige ohne Anmeldung, der schreibt – entsprechend
eng gefasst:
- Ausgewiesen wird sich mit einer Signatur ueber den Rumpf. GitHub und
Gitea rechnen HMAC-SHA256, GitLab schickt nur den Token im Klartext;
dessen Verfahren ist schwaecher, mehr gibt die Plattform nicht her.
Verglichen wird durchweg zeitkonstant.
- Geprueft wird gegen den **rohen** Rumpf. Durch JSON.parse und
JSON.stringify gedreht kaeme eine andere Zeichenfolge heraus und die
Signatur passte nie.
- Der Rumpf ist nie Quelle fuer Inhalte. Wer das Geheimnis kennt, kann eine
Aufgabe auf- oder zumachen und einen Abgleich ausloesen – keine
beliebigen Daten einschleusen.
- Ratenbegrenzung je Repository, Deckel auf die Rumpfgroesse, und ein
unbekanntes Repository antwortet wie ein nicht eingerichtetes: 404,
damit die ID nicht bestaetigt wird.
- Unbekannte Ereignisse werden angenommen und liegen gelassen. Ein Fehler
brachte das Git-System zum Wiederholen und irgendwann zum Abschalten.
Das Geheimnis liegt verschluesselt am Repository, wie die Zugangstoken, und
ist genau einmal zu sehen. Damit es nicht doch nach draussen wandert, laedt
repositoryInclude die Repositories nicht mehr per include, sondern ueber
eine ausdrueckliche Feldliste: ein include haette das neue Feld still in
jede Projektantwort und jede Seite mitgenommen. Ob ein Haken eingerichtet
ist, holt die Projektseite getrennt.
Die Migration legt zwei nullbare Spalten an, mehr nicht; der Gegenlauf von
migrate diff kommt leer heraus.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der Server hat den neuen Stand ausgeliefert und in der Fusszeile weiter
"v1.5.0" angezeigt: Listenansicht, Schnellerfassung und die Aufgabenseite
waren drin, die Versionsnummer nicht.
Der Test in changelog.test.ts prueft, dass die oberste Fassung im Verlauf
zur package.json passt - er greift aber nicht, wenn beide gemeinsam
zurueckhaengen. Genau das war hier der Fall.
Der Verlauf wird in der Anwendung angezeigt (Knopf an der Versionsnummer in
der Fusszeile), deshalb steht dort jetzt, was sich geaendert hat: die
mobile Ansicht, die vierte Ansicht im Board, die Schnellerfassung, die
Aufgaben ueber alle Projekte, 16px in Eingabefeldern gegen den iOS-Zoom,
die fehlenden Beschriftungen der Symbolknoepfe und das beschnittene
Tag-Formular.
In der Roadmap ist v1.6.0 als erledigt eingetragen. Offen bleibt daraus
nur der dritte Teil, die persoenlichen API-Token - die sind
sicherheitsrelevant und bekommen einen eigenen Durchgang.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der letzte Anlauf hat das Problem nicht geloest. Die Freigabe stand weiter in
der Basisdatei, nur mit anderem Vorgabewert -- und Vorgabewerte greifen nicht,
wo die Variable schon gesetzt ist: in jeder .env, die aus der bisherigen
.env.example entstanden ist, steht POSTGRES_PORT="5432". Postgres band also
unveraendert auf 0.0.0.0 und kollidierte mit anderen Stacks auf demselben
Host; der Container kam gar nicht erst hoch. Das Overlay half nicht, weil es
erst greift, wenn man es dazunimmt.
Jetzt hat der db-Dienst keinen ports-Block mehr. Gebraucht wird er nur, wenn
die Anwendung ausserhalb des Containers laeuft, und dafuer gibt es
docker-compose.dev.yml (npm run docker:db). Damit ist der sichere Fall die
Vorgabe und der Sonderfall die Ausnahme, nicht umgekehrt.
Der App-Port bindet aus demselben Grund auf 127.0.0.1: Docker traegt seine
Freigaben direkt in iptables ein und umgeht dabei ufw.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DEPLOYMENT.md fuehrt Schritt fuer Schritt vom leeren Server zur Instanz
hinter TLS: Docker, Konfiguration, Reverse Proxy mit Caddy oder nginx,
Firewall, Sicherungen per Cron. Dazu, was an Haertung schon eingebaut ist -
und was der Betreiber selbst beisteuern muss.
scripts/update.sh macht die Schritte in der richtigen Reihenfolge: pruefen,
holen, sichern, bauen, starten, Gesundheit pruefen. Erst bauen und dann
umschalten, damit ein gescheiterter Bau die laufende Fassung nicht
mitnimmt; die Sicherung liegt vor jeder Schemaaenderung. Antwortet
/api/health nach einer Minute nicht, zeigt es die letzten Logzeilen und
nennt den Weg zurueck.
Es weigert sich zu laufen, solange APP_SECRET leer oder noch der
Entwicklungswert ist - mit diesem Wert liegen die Git-Tokens faktisch offen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Elf Tests: welche Dateinamen als README gelten, dass Markdown Vorrang hat,
dass an einer Zeilengrenze gekuerzt wird und dass base64 mit Umlauten und
Zeilenumbruechen richtig ankommt.
Im Browser gegen vercel/swr geprueft, dazu ein erfundenes README fuer die
relativen Links, die swr nicht hergibt: aufgeloeste Pfade, unveraenderte
absolute Links, Anker bleiben Anker, kein img im Dokument und kein a im a.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sieben Tests fuer die Terminlogik: Rechnen vom Faelligkeitsdatum,
Vorruecken bei langem Liegenbleiben, der Monatsuebergang ueber kurze
Monate, und dass die Schleife auch bei einem Datum von 1970 endet.
Dazu ein Ende-zu-Ende-Test ueber beide Wege nach ERLEDIGT und den Nachweis,
dass ein erneutes Speichern keine zweite Fassung erzeugt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Elf Tests fuer die Seitenlogik des Kalenders: Deckel, Abbruch bei
unvollstaendiger Seite, Commits mit Datum in der Zukunft, und dass der
Zeitraum beim aeltesten bekannten Commit beginnt statt vor einem Jahr.
Dazu ein Ende-zu-Ende-Test fuer Vorlagen: sichern, anwenden, Datentrennung
gegen ein zweites Konto, und dass die Vorlage das Loeschen ihres
Ursprungsprojekts ueberlebt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prisma liest seine Einstellungen aus prisma.config.ts statt aus der
package.json; die Warnung vor Prisma 7 ist damit weg. Der dotenv-Aufruf
darin ist nicht optional: sobald die Datei existiert, laedt Prisma die .env
nicht mehr von selbst, und migrate deploy staende ohne DATABASE_URL da.
Vier neue Ende-zu-Ende-Tests: Reihenfolge und Mehrfachauswahl ueber die API,
Mehrfachauswahl in der Oberflaeche, der Aenderungsverlauf und das Manifest
samt Symbolen ohne Anmeldung. Damit sind es 21.
Der Test zum Ziehen von Aufgaben war gelegentlich rot, ohne dass etwas kaputt
war: das Neuladen brach den noch laufenden Speicheraufruf ab. Er wartet jetzt
auf die Antwort, statt auf eine feste Pause zu hoffen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drei neue Ende-zu-Ende-Tests: das Verwalten mehrerer Repositories samt
Datentrennung, das Lesen von Exportdateien der Version 1, und die
Uebernahme ohne verknuepftes Repository.
Dabei fiel auf, dass der Uebernahme-Endpunkt ohne Repository mit 404
antwortete, obwohl gar keine Repository-ID angefragt war. Wer nichts angibt,
hat schlicht noch keines verknuepft - das ist kein "nicht gefunden", sondern
409 mit dem Hinweis, eines zu hinterlegen.
Version 1.0.0: der Funktionsumfang ist beisammen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ein Einmalkennwort zusaetzlich zum Passwort, mit QR-Code zum Einrichten und
zehn Wiederherstellungscodes fuer den Fall, dass das Geraet verloren geht.
Die Rechnung nach RFC 6238 steht in lib/auth/totp.ts statt in einer
Abhaengigkeit. Es sind wenige Zeilen, das Verfahren liegt seit 2011 fest, und
der RFC bringt Testvektoren mit - die Richtigkeit ist damit belegt und nicht
nur behauptet. Fuer den QR-Code kommt qrcode dazu; ihn selbst zu zeichnen
waere unverhaeltnismaessig.
Einrichtung in zwei Schritten: der Schluessel wird hinterlegt, aber erst ein
bestaetigter Code schaltet ihn scharf. Wer den QR-Code nicht scannen konnte
und abbricht, sperrt sich sonst beim naechsten Anmelden selbst aus.
Zwischen Passwort und Code steht ein eigenes Modell statt einer halbfertigen
Sitzung. Eine Sitzungszeile mit einem "noch nicht gueltig"-Merker waere eine
Zeile, die bei einem vergessenen Filter irgendwo doch als Anmeldung
durchgeht; dieses Versehen kann hier nicht passieren. Nach fuenf
Fehlversuchen ist der Vorgang verbrannt, damit sechs Stellen nicht in Ruhe
durchprobiert werden.
Die Wiederherstellungscodes liegen als SHA-256 statt als scrypt: sie sind
zufaellig erzeugt und lang genug, dass Raten aussichtslos ist - ein
langsames Verfahren brauchte bei jeder Anmeldung zehn teure Vergleiche und
kaufte nichts dafuer. Das Alphabet laesst O, I und B weg, weil die Codes von
Hand abgeschrieben werden.
Abschalten und Neuerzeugen verlangen das Passwort. Ein unbeaufsichtigter
Rechner mit offener Sitzung soll den zweiten Faktor nicht im Vorbeigehen
entfernen koennen.
Nachgewiesen im Browser: Einrichtung, Anmeldung mit Code, Anmeldung mit
Wiederherstellungscode, derselbe Code ein zweites Mal abgelehnt, Abschalten
mit falschem und richtigem Passwort.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zwei neue Ende-zu-Ende-Tests: die Palette findet Projekt und Notiz und
oeffnet den Treffer mit Enter; die Uebernahme meldet ohne Repo-Stand einen
Hinweis statt still zu scheitern. Damit sind es 15.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FEATURES erklaert, warum der Massstab der letzte Commit ist und nicht das
Aenderungsdatum, wen die Pruefung auslaesst und wann erneut gemeldet wird.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drei Anlaesse: fehlgeschlagener Abgleich, rote CI, neues Release. Die Daten
dafuer liegen seit dem CI-Status vor, es fehlte nur der Weg nach draussen.
Der Kern ist die Entprellung. Gemeldet wird ausschliesslich bei einer
Aenderung gegenueber dem zuletzt gemeldeten Stand, der am Projekt vermerkt
ist. Ein Repo mit abgelaufenem Token meldet sich damit einmal statt bei
jedem stuendlichen Durchlauf. Zwei Feinheiten:
- Beim allerersten Abgleich wird kein Release gemeldet, sonst waere jede
bereits vorhandene Version eine "Neuigkeit".
- Der Merkstand wird auch ohne eingerichtetes Ziel fortgeschrieben, sonst
kaeme beim ersten Ziel die ganze Vergangenheit hinterher.
Zustellwege:
- ntfy mit Titel, Prioritaet und Symbol als Kopfzeilen, optionalem
Bearer-Token. Emojis werden aus den Kopfzeilen entfernt statt durch
Fragezeichen ersetzt - Kopfzeilen duerfen nur Latin-1 tragen, und das
Sinnbild liefert ohnehin der Tags-Header.
- Webhook mit JSON, Projektbezug und Tieflink. Mit Geheimnis wird der Rumpf
per HMAC-SHA256 signiert, damit der Empfaenger die Herkunft pruefen kann.
- E-Mail ueber SMTP, verfuegbar sobald SMTP_HOST gesetzt ist.
Zieladressen sind Benutzereingaben, die der Server abruft - sie laufen
deshalb durch denselben SSRF-Schutz wie die Git-Verbindungen. Redirects
werden nicht verfolgt, weil ein Umleitungsziel nicht geprueft waere.
Fehler beim Zustellen werden geschluckt: eine kaputte Benachrichtigung darf
den Abgleich nicht scheitern lassen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
13 Tests gegen die gebaute Anwendung mit eigener Datenbank auf Port 3111 -
also durch Middleware, Authentifizierung, Validierung und Datenbank hindurch.
Genau die Schicht, die Unit-Tests nicht erreichen. Laufzeit rund 20 Sekunden.
api.spec.ts prueft, was bisher nur von Hand geprueft war:
- Einrichtung sperrt sich nach dem ersten Konto
- ohne Anmeldung liefert jeder Endpunkt 401
- fremdes Origin und falscher Content-Type werden abgewiesen
- SSRF-Sperren greifen
- Datentrennung: ein zweites Konto bekommt auf fremde Projekte, Aufgaben und
Adminwege durchgaengig 404 beziehungsweise 403, das Original bleibt
unveraendert
- Aufgaben: Reihenfolge, abgeleiteter Fortschritt, untergeschobene IDs
- Export ohne Geheimnisse, Import je Strategie
ui.spec.ts geht den Weg eines Menschen: anmelden, Projekt anlegen, Aufgaben
anlegen und ziehen, suchen, abmelden. Das Ziehen wird nach einem Neuladen
erneut geprueft - sonst waere nur die Anzeige getestet, nicht das Speichern.
Zwei Vorkehrungen:
- global-setup.ts bricht ab, wenn der Datenbankname nicht "test" enthaelt,
und zwar vor dem TRUNCATE. Der teuerste denkbare Fehler waere eine
Testreihe, die die Produktivdaten leert.
- Playwright startet den Server selbst mit eigener DATABASE_URL und eigenem
Port; eine laufende Instanz auf 3000 stoert nicht.
Beim Schreiben zeigte sich, dass synthetische Zieh-Gesten eine Pause vor dem
Loslassen brauchen - dnd-kit wertet die Bewegung in einem eigenen Frame aus
und faende sonst kein Ziel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der benannte Import aus der package.json loeste in Webpack die Warnung
"Should not import the named export 'version'" aus. Jetzt als Standardimport.
Version auf 0.5.0 gehoben.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bisher wurde alles als reiner Text ausgegeben. Jetzt werden Ueberschriften,
Listen, Tabellen, Codebloecke, Zitate und Links dargestellt, und aus
"- [ ] Aufgabe" werden echte Checklisten.
Sicherheitsrelevant: der Inhalt stammt vom Benutzer. Ohne rehype-sanitize
waere das ein XSS-Loch, sobald jemand rohes HTML in eine Notiz schreibt. Das
Standardschema erlaubt nur harmlose Elemente; ergaenzt wird nur, was fuer
Aufgabenlisten und Sprachklassen an Codebloecken noetig ist. Links bekommen
zusaetzlich target=_blank mit noopener und nofollow.
Die Formatierung steht in components.css: Tailwinds Preflight setzt alle
Abstaende und Schriftgroessen zurueck, Ueberschriften und Listen brauchen
also wieder Form.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Grundlage fuer Nebula, das Dashboard fuer Vibe-Coding Projekte.
- Next.js 15 (App Router) mit React 19 und TypeScript im strict-Modus
- Tailwind CSS 4 ueber @tailwindcss/postcss, ohne separate Config-Datei
- output: "standalone", damit dasselbe Build sowohl nativ als auch im
Docker-Image laeuft
- Pfad-Alias @/* auf src/
- .gitattributes erzwingt LF fuer Shell-Skripte (wichtig fuers Image)
- MIT-Lizenz
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>