Einstellungen strikt pro Benutzer: kein process.env-Fallback mehr

config.get() ist bei einem nicht gesetzten Schluessel auf process.env
zurueckgefallen. Da die Env-Variablen die Konfiguration des Admins
enthalten (Docker-Env: MAIL_*, CALDAV_URL, OLLAMA_API_KEY, API_TOKEN),
hat damit JEDER neu angelegte Benutzer ohne eigene Einstellungen
stillschweigend die Zugangsdaten des Admins geerbt:

- /einstellungen zeigte ihm die Zugangsdaten des Admins an.
- mailer/caldav isConfigured() war true -> der IMAP-Poller hat fuer den
  neuen Benutzer das Postfach des Admins abgerufen und dessen E-Mails in
  sein Konto einsortiert; CalDAV synchronisierte den Kalender des Admins.
- Der bezahlte Ollama-Key des Admins wurde mitbenutzt.

Jetzt:
- config.get() loest ausschliesslich die Zeilen des aktuellen Benutzers auf,
  sonst den eingebauten Standard (nicht-geheime Werte wie Modell, Host,
  Ports, Intervalle). Alle Credentials sind bei neuen Benutzern leer,
  d. h. Ollama/E-Mail/CalDAV/API sind fuer sie aus, bis sie sich selbst
  etwas eintragen.
- Noch per Env gesetzte Konfiguration wird einmalig in die Zeilen des
  ADMIN uebernommen (importEnvIntoAdmin, Aufruf beim Boot nachdem
  app_state existiert - in runMigration war das bei Neuinstallationen ein
  No-op, weil die Tabelle dort noch nicht angelegt ist).
- config.ensureLoaded(user.id) beim Aufloesen der Session bzw. des
  X-API-Key. config.get() ist synchron und liest den Per-User-Cache; ohne
  Warmladen las ein Web-Request die Werte als "nicht konfiguriert". Das
  hat bisher der env-Fallback verdeckt (er hielt zufaellig die Werte des
  Admins) - ohne ihn muss die Config pro Request wirklich geladen werden.

Verifiziert gegen eine Kopie der Produktions-DB mit Sentinel-Env-Werten:
Admin behaelt seine kompletten Einstellungen, der zweite Benutzer sieht
ueberall leere Credentials, Mail/CalDAV sind fuer ihn inaktiv, und der
Env-API-Token wird nicht mehr als gueltiger X-API-Key akzeptiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 22:31:08 +02:00
co-authored by Claude Opus 4.8
parent b2e884d9b3
commit b6cb94fdb5
4 changed files with 71 additions and 18 deletions
+29 -12
View File
@@ -8,10 +8,15 @@
// context (lib/context.js). An edit therefore takes effect immediately, scoped
// to the user who made it — no restart, no .env file.
//
// On first start of an install that previously used .env, init() migrates any
// still-present env value into the *admin* user's config once. After that the
// database is the single source of truth; process.env is only a fallback for keys
// that were never saved.
// On first start of an install that previously used .env (or that still passes
// the values as container env vars), the still-present env values are imported
// into the *admin* user's rows once — see importEnvIntoAdmin(), called from
// server.js at boot. After that the database is the single source of truth.
//
// process.env is deliberately NOT a read fallback: env config belongs to the
// admin, so falling back to it would hand every freshly created user the admin's
// mailbox, calendar, Ollama key and API token. Unset keys resolve to the
// built-in defaults instead, which leaves a new user's settings empty.
const { currentUser } = require('./context');
@@ -95,9 +100,16 @@ const loaded = new Set(); // userIds whose cfg rows have been read from the DB
let dbAllFn = null;
let dbRunFn = null;
function envOrDefault(key) {
const e = process.env[key];
return e && e.length ? e : DEFAULTS[key];
// The built-in default for a key. Deliberately NOT a process.env lookup: env
// values belong to the admin (they are imported into the admin's rows once at
// boot, see importEnvIntoAdmin in lib/migrate-multiuser.js). If get() fell back
// to process.env, every user without their own row would silently inherit the
// admin's credentials — their own mailbox, calendar, Ollama key and API token.
// The defaults below are non-secret standards only (model, host, ports, poll
// intervals); every credential defaults to empty, so a new user starts with
// Ollama/E-Mail/CalDAV/API switched off until they configure their own.
function builtinDefault(key) {
return DEFAULTS[key];
}
// Load one user's cfg rows from the DB into the cache. No-op if already loaded.
@@ -117,15 +129,20 @@ function invalidate(userId) {
cache.delete(userId);
}
// Synchronous read for the current user. Falls back to process.env (pre-migration
// / never saved) then to the built-in default. Outside a request context (boot)
// only the env/default fallback applies — callers that need a specific user must
// run inside the user context (see lib/context.js).
// Synchronous read for the *current user only*. A key the user has not stored
// falls back to the built-in default — never to another user's value and never
// to process.env. A user who has configured nothing therefore reads as "empty"
// (no mail host, no keys), which is exactly what isolates them: mailer/caldav
// isConfigured() turns false and their background jobs stay idle.
//
// Outside a request context (boot) there is no user, so only the defaults apply.
// Callers that need a specific user's values must run inside that user's context
// (see lib/context.js) and must have called ensureLoaded(userId) first.
function get(key) {
const u = currentUser();
const userObj = u ? cache.get(u.id) : null;
if (userObj && userObj[key] !== undefined) return userObj[key];
return envOrDefault(key);
return builtinDefault(key);
}
// All keys with their effective values for the current user — used by the