# INFO1 Ein selbst gehostetes Dashboard für eine Klasse, Lerngruppe oder kleine Kohorte. Ein Ort für Stundenplan, Hausaufgaben, Noten, Fehlzeiten, gemeinsamen Kalender, Klassenchat und Ende zu Ende verschlüsselte Direktnachrichten. Bewusst unspektakulär aufgebaut: Node.js, Express, SQLite, vanilla JavaScript. Kein Buildschritt, kein Framework Lock in, läuft auf einem 1 GB VPS. ![License](https://img.shields.io/badge/license-MIT-blue.svg) ![Node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen) ![No Build Step](https://img.shields.io/badge/build-none-lightgrey) ![Self Hosted](https://img.shields.io/badge/self--hosted-yes-informational) ## Worum geht es Die meisten Schul- und Lernplattformen sind schwergewichtig, kommerziell oder an eine bestimmte Institution gebunden. INFO1 ist das Gegenteil: eine einzige App, die jede Gruppe für sich selbst hosten kann, mit voller Kontrolle über die eigenen Daten und ohne Anbieter dazwischen. Das Projekt nimmt Datenschutz ernst (Direktnachrichten werden im Browser verschlüsselt, der Server sieht nie Klartext), bleibt im Umfang ehrlich (für ein paar hundert aktive Nutzer, nicht für tausende) und ist so geschrieben, dass eine Person den Code an einem Nachmittag vollständig lesen kann. ## Funktionen **Persönliches Dashboard** - Stundenplan, Hausaufgabenverwaltung, Notenbuch mit automatischen Durchschnitten - Fehlzeitenprotokoll, persönliche Todos, Countdowns, anpassbare Schnelllinks - Dateiablage pro Nutzer mit Speicherkontingent **Geteilte Klassenebene** - Klassenkalender für Prüfungen, Ferien, Ereignisse (ohne Login einsehbar) - Klassenchat mit Rate Limiting und sanfter Moderation - Ende zu Ende verschlüsselte Gruppennachrichten (ECDH P-256 und AES-GCM, Schlüssel verlassen nie den Browser) **Lehrerwerkzeuge** - Fachbezogene Ankündigungen, Materialupload, Klausurplanung - Direktes Setzen von Noten für einzelne Schüler - Freigabeprozess: neue Lehrerkonten landen in einer Warteschlange für einen Admin **Adminkonsole** - Nutzerverwaltung (sperren, entsperren, befördern, zurückstufen, löschen) - Manuelles Freischalten der E-Mail-Verifizierung, Rollenänderung, Auditlog - Triage von Supporttickets mit Thread-Antworten - Speicher- und Nutzungsstatistiken pro Nutzer **Auth und Kontosicherheit** - E-Mail-Verifizierung bei der Registrierung über [Resend](https://resend.com) - Passwort-Reset per Mail mit einmalig verwendbaren Tokens (1 Stunde gültig) - TOTP-Zweifaktor-Authentifizierung mit QR-Code - Passwort-Hashing mit bcrypt (Kosten 12), JWT in HttpOnly-Cookie - Rate Limiting auf allen sensiblen Endpoints ## Technologie | Schicht | Wahl | |---------|------| | Laufzeit | Node.js 20, Express 5 | | Datenbank | SQLite über `better-sqlite3` (synchron, in-process) | | Auth | JWT im HttpOnly-Cookie, bcrypt, otplib für TOTP | | Mail | Resend (austauschbar gegen jeden SMTP-Anbieter) | | Frontend | Vanilla JavaScript, kein Bundler, kein Framework | | Security | Helmet, CSP, express-rate-limit | Gesamtgröße: rund 3 KLOC Servercode und eine Handvoll eigenständige HTML-Dateien. ## Schnellstart ```bash git clone https://github.com/lulinretrograde/ifb-schulapp.git info1 cd info1 npm install cp .env.example .env # dann bearbeiten node index.js ``` Die App bindet an `127.0.0.1:3010`. Für öffentliche Deployments einen Reverse Proxy davorschalten (Caddy, nginx, Cloudflare Tunnel). ## Konfiguration Alle Einstellungen laufen über Umgebungsvariablen. Vollständige Liste in `.env.example`. ```dotenv JWT_SECRET= # Pflicht, 32+ zufällige Bytes RESEND_API_KEY= # Pflicht für Verifizierungs- und Reset-Mails MAIL_FROM=noreply@deinedomain.example MAIL_FROM_NAME=Klassenportal APP_URL=https://deinedomain.example NODE_ENV=production ``` ### E-Mail-Domain-Beschränkung Die Registrierung ist standardmäßig auf eine einzige E-Mail-Domain beschränkt, via Regex in `src/routes.js`. `IFB_EMAIL_RE` anpassen oder entfernen, um die Beschränkung zu lockern, oder durch eine eigene Allowlist ersetzen. ### Datenbank SQLite-Datei liegt unter `./data.db` und wird beim ersten Start erzeugt. Migrationen sind idempotent und stehen am Ende von `src/db.js`. Für ein Backup die Datei zur Laufzeit kopieren (SQLite erlaubt parallele Lesezugriffe während des Kopierens). ## Deployment Minimale systemd-Unit: ```ini [Unit] Description=INFO1 After=network.target [Service] Type=simple User=www WorkingDirectory=/opt/info1 ExecStart=/usr/bin/node index.js Restart=always Environment=NODE_ENV=production EnvironmentFile=/opt/info1/.env [Install] WantedBy=multi-user.target ``` TLS am Reverse Proxy terminieren und `NODE_ENV=production` setzen, damit das Auth-Cookie das `Secure`-Flag bekommt. ## Sicherheit - Die App startet nicht, wenn `JWT_SECRET` nicht gesetzt ist. - Direktnachrichten werden im Browser verschlüsselt, bevor sie hochgeladen werden. Der Server speichert ausschließlich opaken Chiffretext und kann auch mit Datenbankzugriff den Inhalt nicht lesen. - Alle schreibenden Endpoints sind Rate limitiert. Die Limits sind für kleine Gruppen abgestimmt und sollten für größere Deployments nachgezogen werden. - Adminaktionen landen in `admin_logs` mit Akteur, Ziel und einem JSON-Detail-Blob. Verantwortungsvolle Offenlegung von Schwachstellen: Issue mit Kurzbeschreibung öffnen, Details per Mail nachreichen. ## Projektstruktur ``` index.js Express-Bootstrap, Static Serving, Routenmontage src/ db.js SQLite-Singleton, Schema, Migrationen auth.js JWT-Hilfsfunktionen, requireAuth-Middleware routes.js Auth-Endpoints, Admin, Tickets, Chat, Klassenkalender teacher.js Lehrer-Endpoints und Material-Upload files.js Dateiablage pro Nutzer mit Quota mailer.js Resend-Wrapper und Mail-Templates public/ index.html Öffentliche Startseite login.html Login, Registrierung, Passwort-Reset app.html Hauptdashboard nach Login admin.html Adminkonsole reset-password.html Zielseite für Passwort-Reset-Links datenschutz.html Datenschutzerklärung e2ee.js Kryptoprimitiven für Ende zu Ende Verschlüsselung ``` ## Status Aktiv entwickelt für eine einzelne Kohorte. Im produktiven Einsatz bei einer kleinen Gruppe von Lernenden und Lehrenden. Die APIs sind nicht versioniert und können sich zwischen Commits ändern. ## Lizenz MIT. Siehe [LICENSE](./LICENSE).