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:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user