Security & UX Release v7

Security:
- H1: Stored-XSS-Fix — Upload-Pfad-Whitelist (Server + Frontend-Guard safeFileUrl)
- H2: Transaktionen repariert — txDb-Contract in db.js (PG + SQLite), Rollback funktioniert
- H3-Vorbereitung: SESSION_SECRET wird in Compose durchgereicht (Fix M3)
- registerLimiter exportiert (Crash-Bug: Route.post ohne Callback)
- LDAP-Sync: PG-Transaktionsabbruch bei UNIQUE-Verstoß behoben (Precheck-Selects)
- LDAP-Filter: nur echte Benutzerkonten (keine Computer/Service-Accounts, Bit 512)
- Rollen app-seitig: Sync ändert nie role/status, neue User immer user+inaktiv
- DB-Cleanup: 82 Computer-/Service-Accounts aus lokaler User-Tabelle entfernt

UX:
- Dashboard: Vorlagen als Table-Liste + Column-Chart (Top 5 in %), 2 gleich große Spalten
- Table-Listen (Dashboard/Vorlageneditor/Aufgaben) scrollbar bis Seitenende
- Pagination 10/Seite im Dashboard, Sidebar-Label Dashboard
This commit is contained in:
Kühn
2026-09-10 16:57:20 +02:00
parent ec2ed91621
commit 1aec65dc95
86 changed files with 636 additions and 208 deletions

View File

@@ -68,12 +68,20 @@ if (usePostgres) {
},
transaction(fn) {
// H2: Transaktions-Contract — fn erhält eine txDb-Instanz, deren
// prepare()-Statements auf DERSELBEN Connection laufen wie die Transaktion.
// WICHTIG: Alle Statements innerhalb von fn MÜSSEN über txDb.prepare()
// erzeugt werden. Statements, die außerhalb (über das globale db-Objekt)
// vorbereitet wurden, laufen außerhalb der Transaktion und machen sie
// wirkungslos (kein Rollback bei Fehlern).
return async (...args) => {
const client = await pool.connect();
try {
await client.query('BEGIN');
const txDb = {
_type: 'postgres',
_inTransaction: true,
prepare(sql) {
const pgSql = convertPlaceholders(sql);
return {
@@ -117,6 +125,12 @@ if (usePostgres) {
console.log('[DB] SQLite-Datenbank verbunden (better-sqlite3, WAL-Modus, async-Wrapper).');
// H2: GLOBALER Transaktions-Mutex — SQLite hat nur EINE Connection, daher
// dürfen sich nie zwei Transaktionen überlappen (auch nicht verschiedene
// transaction()-Wrapper wie createTask + updateTemplate). Ein pro-Wrapper
// Mutex würde "cannot start a transaction within a transaction" ermöglichen.
let sqliteTxQueue = Promise.resolve();
db = {
_type: 'sqlite',
@@ -140,8 +154,52 @@ if (usePostgres) {
},
transaction(fn) {
const tx = sqliteDb.transaction(fn);
return (...args) => Promise.resolve(tx(...args));
// H2: Transaktions-Contract (siehe PostgreSQL-Modus) — fn erhält eine
// txDb-Instanz, deren Statements innerhalb der Transaktion laufen.
// better-sqlite3's transaction() ist synchron; die async fn wird über
// den GLOBALEN Warteschlangen-Mutex serialisiert (siehe oben).
return (...args) => {
const run = async () => {
const txDb = {
_type: 'sqlite',
_inTransaction: true,
prepare(sql) {
const stmt = sqliteDb.prepare(sql);
return {
run: (...params) => Promise.resolve(stmt.run(...params)),
get: (...params) => Promise.resolve(stmt.get(...params)),
all: (...params) => Promise.resolve(stmt.all(...params)),
};
},
exec(sql) {
sqliteDb.exec(sql);
return Promise.resolve();
},
pragma(str) {
sqliteDb.pragma(str);
return Promise.resolve({});
},
};
// BEGIN IMMEDIATE sichert den Schreib-Lock für die gesamte Transaktion.
// busy_timeout verhindert SQLITE_BUSY bei konkurrierenden Lesern (WAL).
sqliteDb.pragma('busy_timeout = 5000');
sqliteDb.exec('BEGIN IMMEDIATE');
try {
const result = await fn.call(txDb, ...args);
sqliteDb.exec('COMMIT');
return result;
} catch (err) {
try { sqliteDb.exec('ROLLBACK'); } catch (rollbackErr) {
console.error('[DB] Rollback-Fehler:', rollbackErr.message);
}
throw err;
}
};
const result = sqliteTxQueue.then(run, run);
// Queue darf nie in einem Fehlerzustand hängen bleiben
sqliteTxQueue = result.catch(() => {});
return result;
};
},
close() {