DEV1.0: Initial commit - Workflow Portal with security fixes
- Backend: Express.js + PostgreSQL/SQLite with LDAP/AD integration - Frontend: React 18 + Vite + TailwindCSS/DaisyUI - Security fixes applied (2026-07 + 2026-08): - LDAP injection prevention, CSRF protection, HttpOnly cookies - Session hashing (SHA-256), account lockout, rate limiting - Input validation (zod), file upload security, CSP/HSTS headers - V3: express-rate-limit updated (ip-address SSRF fix) - V4: postcss updated (nanoid DoS fix) - V5: Rate-limit on /health endpoint - V6: Session rotation on login (session fixation prevention) - V9: Task values array limit (DoS prevention) - V10: Frontend XSS audit completed - Docker: Multi-stage build, non-root user, PostgreSQL + backup service
This commit is contained in:
154
backend/db.js
Normal file
154
backend/db.js
Normal file
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Database abstraction layer (async).
|
||||
*
|
||||
* Punkt 4: PostgreSQL for production, SQLite fallback for development.
|
||||
* Both modes expose the SAME async API: db.prepare(sql).run/get/all() return Promises.
|
||||
*
|
||||
* - If DATABASE_URL starts with 'postgresql://' → PostgreSQL (pg)
|
||||
* - Otherwise → SQLite (better-sqlite3, wrapped in Promises for unified async API)
|
||||
*/
|
||||
const path = require('path');
|
||||
|
||||
const DATABASE_URL = process.env.DATABASE_URL || '';
|
||||
const usePostgres = DATABASE_URL.startsWith('postgresql://') || DATABASE_URL.startsWith('postgres://');
|
||||
|
||||
// Helper: convert SQLite ? placeholders to PostgreSQL $1, $2, etc.
|
||||
function convertPlaceholders(sql) {
|
||||
let idx = 0;
|
||||
return sql.replace(/\?/g, () => { idx++; return '$' + idx; });
|
||||
}
|
||||
|
||||
let db;
|
||||
|
||||
if (usePostgres) {
|
||||
// ============ PostgreSQL mode (production) ============
|
||||
const { Pool } = require('pg');
|
||||
const pool = new Pool({
|
||||
connectionString: DATABASE_URL,
|
||||
max: 10,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 10000,
|
||||
});
|
||||
|
||||
pool.on('error', (err) => {
|
||||
console.error('[DB] PostgreSQL Pool-Fehler:', err.message);
|
||||
});
|
||||
|
||||
console.log('[DB] PostgreSQL-Verbindung hergestellt (Production-Modus).');
|
||||
|
||||
db = {
|
||||
_pool: pool,
|
||||
_type: 'postgres',
|
||||
|
||||
prepare(sql) {
|
||||
const pgSql = convertPlaceholders(sql);
|
||||
return {
|
||||
run: (...params) => {
|
||||
// For INSERT statements, append RETURNING id to get the generated ID
|
||||
const isInsert = pgSql.trim().toUpperCase().startsWith('INSERT');
|
||||
const finalSql = isInsert && !pgSql.toUpperCase().includes('RETURNING')
|
||||
? pgSql.replace(/;?\s*$/, ' RETURNING id')
|
||||
: pgSql;
|
||||
return pool.query(finalSql, params).then(result => ({
|
||||
changes: result.rowCount,
|
||||
lastInsertRowid: result.rows[0]?.id || null,
|
||||
}));
|
||||
},
|
||||
get: (...params) => pool.query(pgSql, params).then(result => result.rows[0] || null),
|
||||
all: (...params) => pool.query(pgSql, params).then(result => result.rows),
|
||||
};
|
||||
},
|
||||
|
||||
exec(sql) {
|
||||
return pool.query(sql);
|
||||
},
|
||||
|
||||
pragma(_str) {
|
||||
return Promise.resolve({});
|
||||
},
|
||||
|
||||
transaction(fn) {
|
||||
return async (...args) => {
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
const txDb = {
|
||||
prepare(sql) {
|
||||
const pgSql = convertPlaceholders(sql);
|
||||
return {
|
||||
run: (...params) => client.query(pgSql, params).then(result => ({
|
||||
changes: result.rowCount,
|
||||
lastInsertRowid: result.rows[0]?.id || null,
|
||||
})),
|
||||
get: (...params) => client.query(pgSql, params).then(result => result.rows[0] || null),
|
||||
all: (...params) => client.query(pgSql, params).then(result => result.rows),
|
||||
};
|
||||
},
|
||||
exec: (sql) => client.query(sql),
|
||||
pragma: () => Promise.resolve({}),
|
||||
};
|
||||
|
||||
const result = await fn.call(txDb, ...args);
|
||||
await client.query('COMMIT');
|
||||
return result;
|
||||
} catch (err) {
|
||||
await client.query('ROLLBACK');
|
||||
throw err;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
close() {
|
||||
return pool.end();
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// ============ SQLite mode (development) ============
|
||||
// Wrapped in Promises so the API is identical to PostgreSQL (async)
|
||||
const Database = require('better-sqlite3');
|
||||
const dbPath = path.join(__dirname, 'data', 'workflow.db');
|
||||
const sqliteDb = new Database(dbPath);
|
||||
|
||||
sqliteDb.pragma('journal_mode = WAL');
|
||||
sqliteDb.pragma('foreign_keys = ON');
|
||||
|
||||
console.log('[DB] SQLite-Datenbank verbunden (better-sqlite3, WAL-Modus, async-Wrapper).');
|
||||
|
||||
db = {
|
||||
_type: 'sqlite',
|
||||
|
||||
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({});
|
||||
},
|
||||
|
||||
transaction(fn) {
|
||||
const tx = sqliteDb.transaction(fn);
|
||||
return (...args) => Promise.resolve(tx(...args));
|
||||
},
|
||||
|
||||
close() {
|
||||
sqliteDb.close();
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = db;
|
||||
Reference in New Issue
Block a user