From 2566dcc98dcf21ef443319960e57470585ce2215 Mon Sep 17 00:00:00 2001 From: Thomas Hackner Date: Tue, 14 Jul 2026 12:55:20 +0200 Subject: [PATCH] Einrichtung: Wizard um E-Mail, Kalender und REST-API erweitert Der Assistent deckt nun alle Bereiche aus /einstellungen ab. Neue Schritte fuer E-Mail (SMTP/IMAP) & CalDAV sowie REST-API; Indikator auf 7 Schritte. Secrets (Passwort, Token) werden nie vorausgefuellt und beim Speeren leerer Felder nicht ueberschrieben. Neuer Endpunkt /api/einrichtung/config mit SSRF-Guard (Mail-Host, CalDAV-URL, Ollama-Host) und API-Token-Kollisionspruefung. Abschluss-Checkliste trennt Pflicht- von optionalen Verbindungen. Co-Authored-By: Claude --- server.js | 60 ++++- views/partials/einrichtung.ejs | 466 +++++++++++++++++++++------------ 2 files changed, 355 insertions(+), 171 deletions(-) diff --git a/server.js b/server.js index 02fe5a7..b81eb21 100644 --- a/server.js +++ b/server.js @@ -1709,8 +1709,9 @@ initializeDatabase().then(async () => { // Einrichtungsassistent: eingeblendet, wenn der Nutzer noch keine // Bewerbung angelegt hat und den Assistenten nicht dauerhaft ausgeblendet - // hat. Der Wizard selbst führt dann durch die fehlenden Einstellungen - // (Persönliche Angaben, Basis-Unterlagen, Ollama-KI). + // hat. Der Wizard führt durch alle Bereiche, die sonst unter /einstellungen + // liegen: Persönliche Angaben, Basis-Unterlagen, Ollama-KI, E-Mail, + // Bewerbungskalender (CalDAV) und REST-API. const keinDashboard = !totalCount || totalCount.count === 0; const verstecktRow = keinDashboard ? await dbGet("SELECT value FROM app_state WHERE user_id = ? AND key = 'state:einrichtung_versteckt'", [U]) @@ -1720,6 +1721,10 @@ initializeDatabase().then(async () => { const basisRows = await dbAll('SELECT id, typ, inhalt FROM basis_dokumente WHERE user_id = ? ORDER BY id ASC', [U]); const anschreiben = basisRows.find((d) => d.typ === 'Anschreiben'); const lebenslauf = basisRows.find((d) => d.typ === 'Lebenslauf'); + // Aktuelle Konfiguration für die Vorbelegung — Secrets (Passwörter, Token, + // API-Schlüssel) werden bewusst nicht ans Frontend gegeben, sondern nur + // als „vorhanden"-Flag. + const cfg = config.getAll(); setupWizard = { aktiv: true, persoenlich: await loadSettings(), @@ -1728,6 +1733,14 @@ initializeDatabase().then(async () => { hasApiKey: Boolean(config.get('OLLAMA_API_KEY')), ollamaModel: config.get('OLLAMA_MODEL'), ollamaHost: config.get('OLLAMA_HOST'), + cfg: { + MAIL_HOST: cfg.MAIL_HOST, MAIL_SMTP_PORT: cfg.MAIL_SMTP_PORT, MAIL_IMAP_PORT: cfg.MAIL_IMAP_PORT, + MAIL_USER: cfg.MAIL_USER, MAIL_FROM_NAME: cfg.MAIL_FROM_NAME, MAIL_FROM: cfg.MAIL_FROM, + MAIL_IMAP_MAILBOX: cfg.MAIL_IMAP_MAILBOX, MAIL_POLL_MS: cfg.MAIL_POLL_MS, + hasMailPassword: Boolean(cfg.MAIL_PASSWORD), + CALDAV_URL: cfg.CALDAV_URL, CALDAV_ALARM_MIN: cfg.CALDAV_ALARM_MIN, CALDAV_POLL_MS: cfg.CALDAV_POLL_MS, + hasApiToken: Boolean(cfg.API_TOKEN), + }, }; } @@ -2063,6 +2076,49 @@ initializeDatabase().then(async () => { } }); + // Generisches Speichern für die weiteren Wizard-Schritte (E-Mail, CalDAV, + // REST-API). Nimmt nur Schlüssel aus config.DEFAULTS entgegen und schreibt + // sie gezielt via setForUser — ungenannte Keys bleiben unangetastet. Hosts + // werden gegen SSRF geprüft, der API-Token auf Kollision mit anderem Benutzer. + app.post('/api/einrichtung/config', async (req, res) => { + try { + const u = uid(); + const erlaubt = new Set(Object.keys(config.DEFAULTS)); + const werte = {}; + Object.keys(req.body || {}).forEach(function (k) { + if (erlaubt.has(k)) werte[k] = String(req.body[k] == null ? '' : req.body[k]).trim(); + }); + // SSRF-Guard für externe Hosts, die der Server anruft. + if (werte.OLLAMA_HOST) { + try { await assertSafeUrl(werte.OLLAMA_HOST); } + catch (vErr) { return res.status(400).json({ error: 'Ollama-Host abgelehnt: ' + (vErr.message || 'ungültig') }); } + } + if (werte.CALDAV_URL) { + try { await assertSafeUrl(werte.CALDAV_URL); } + catch (vErr) { return res.status(400).json({ error: 'Kalender-URL abgelehnt: ' + (vErr.message || 'ungültig') }); } + } + if (werte.MAIL_HOST) { + try { await assertSafeHost(werte.MAIL_HOST); } + catch (vErr) { return res.status(400).json({ error: 'Mail-Host abgelehnt: ' + (vErr.message || 'ungültig') }); } + } + // API-Token identifiziert den Benutzer gegenüber /api/v1 — Kollision würde + // beide Nutzer aussperren, daher schon hier ablehnen. + if (werte.API_TOKEN) { + const fremd = await dbGet( + `SELECT u.username FROM app_state a JOIN users u ON u.id = a.user_id + WHERE a.key = ? AND a.value = ? AND a.user_id != ?`, + [config.PREFIX + 'API_TOKEN', werte.API_TOKEN, u] + ); + if (fremd) return res.status(409).json({ error: 'API-Token bereits von anderem Benutzer belegt. Bitte „Neu generieren".' }); + } + for (const k of Object.keys(werte)) await config.setForUser(u, k, werte[k]); + res.json({ success: true }); + } catch (error) { + console.error('Einrichtung/config:', error); + res.status(500).json({ error: 'Serverfehler' }); + } + }); + // Wizard dauerhaft ausblenden (app_state-Flag), damit er nicht bei jedem // Besuch der leeren Startseite wieder erscheint. app.post('/api/einrichtung/verstecken', async (req, res) => { diff --git a/views/partials/einrichtung.ejs b/views/partials/einrichtung.ejs index 476e9a3..287eeae 100644 --- a/views/partials/einrichtung.ejs +++ b/views/partials/einrichtung.ejs @@ -1,11 +1,14 @@ <% // Einrichtungsassistent für Nutzer, die noch keine Bewerbung angelegt haben. - // Server liefert `setupWizard` mit: aktiv, persoenlich (row), anschreiben/lebenslauf - // ({id,inhalt}), hasApiKey, ollamaModel, ollamaHost. Das Partial rendert nur den - // Wizard; das umgebende index.ejs blendet gleichzeitig das Dashboard aus. + // Server liefert `setupWizard` mit: aktiv, persoenlich (row), + // anschreiben/lebenslauf ({id,inhalt}), hasApiKey, ollamaModel, ollamaHost, + // cfg (nicht-geheime Mail-/CalDAV-Werte + hasMailPassword + hasApiToken). + // Das Partial rendert nur den Wizard; das umgebende index.ejs blendet + // gleichzeitig das Dashboard aus (Markup bleibt im DOM für main.js). var p = setupWizard.persoenlich || {}; var anschreibenInhalt = setupWizard.anschreiben ? setupWizard.anschreiben.inhalt : ''; var lebenslaufInhalt = setupWizard.lebenslauf ? setupWizard.lebenslauf.inhalt : ''; + var c = setupWizard.cfg || {}; %>
-
+
- - +

NextJobs einrichten

In wenigen Schritten bist du startklar für deine erste Bewerbung. - Du kannst jeden Schritt überspringen und später nachholen. + Jeder Schritt lässt sich überspringen und später nachholen.

@@ -66,56 +63,36 @@
- -
    -
  1. - 1 - Willkommen -
  2. -
  3. - 2 - Profil -
  4. -
  5. - 3 - Vorlagen -
  6. -
  7. - 4 - KI -
  8. -
  9. - 5 - Abschluss + +
      + <% var stepLabels = ['Willkommen','Profil','Vorlagen','KI','E-Mail','API','Abschluss']; %> + <% for (var si = 1; si <= 7; si++) { %> +
    1. + <%= si %> +
    2. + <% } %>
-
+

Herzlich willkommen!

NextJobs begleitet dich vom Stellenimport bis zur fertigen Bewerbung — mit - automatisch zugeschnittenen Anschreiben und Lebenslauf. Damit das klappt, - hinterlegen wir zuerst deine Kontaktdaten, deine Basis-Unterlagen und den - KI-Zugang. Das dauert etwa drei Minuten. + automatisch zugeschnittenen Anschreiben und Lebenslauf, E-Mail-Posteingang + und Bewerbungskalender. Damit das klappt, hinterlegen wir zuerst die nötigen + Daten. Das dauert etwa fünf Minuten.

    -
  • - - Persönliche Angaben — fließen in jede Bewerbung ein. -
  • -
  • - - Basis-Unterlagen — dein Standard-Anschreiben & Lebenslauf als Text. -
  • -
  • - - Ollama-KI — erzeugt daraus später die passenden Unterlagen. -
  • +
  • Persönliche Angaben — fließen in jede Bewerbung ein.
  • +
  • Basis-Unterlagen — dein Standard-Anschreiben & Lebenslauf als Text.
  • +
  • Ollama-KI — erzeugt daraus die passenden Unterlagen.
  • +
  • E-Mail & Kalender — Posteingang versenden und Termine synchronisieren.
  • +
  • REST-API — optional, für Drittanbietersoftware.
@@ -184,8 +161,7 @@

Basis-Unterlagen

Füge dein Standard-Anschreiben und deinen Lebenslauf als Text ein. Die KI - nutzt sie als Faktengrundlage und erfindet nichts hinzu. Du kannst auch - schon vorhandene Texte einfach hereinkopieren. + nutzt sie als Faktengrundlage und erfindet nichts hinzu.

@@ -201,8 +177,7 @@ placeholder="Persönliche Daten, Berufserfahrung, Ausbildung, Kenntnisse …"><%= lebenslaufInhalt %>

- Tipp: Ausführliche Vorlagen lassen sich später unter „Vorlagen“ verfeinern — - inkl. Design, Foto und weiteren Doktypen. + Tipp: Ausführliche Vorlagen lassen sich später unter „Vorlagen“ verfeinern — inkl. Design und Foto.

@@ -212,8 +187,8 @@

Ollama-KI einrichten

Über die Ollama Cloud erzeugt NextJobs automatisch zugeschnittene - Bewerbungsunterlagen. Ohne API-Schlüssel bleibt dieser Schritt deaktiviert — - du kannst ihn auch später unter „Einstellungen“ nachholen. + Bewerbungsunterlagen. Ohne API-Schlüssel bleibt die KI deaktiviert — + später unter „Einstellungen“ nachholbar.

@@ -222,21 +197,14 @@ -

Schlüssel holen unter ollama.com/settings/keys. - <% if (setupWizard.hasApiKey) { %> - · bereits hinterlegt - <% } %> + <% if (setupWizard.hasApiKey) { %>· bereits hinterlegt<% } %>

@@ -256,30 +224,159 @@
- + + + + + + +