security: remove hardcoded database credentials and auth secrets

- Replace hardcoded DB_PASSWORD 'dwroller2025' with process.env.DB_PASSWORD
- Replace hardcoded GM_SECRET 'bongo' with process.env.GM_SECRET
- Replace hardcoded GM_PASSWORD with process.env.GM_PASSWORD
- Replace hardcoded PLAYER_PASSWORD '1234' with process.env.PLAYER_PASSWORD
- Update .env.example to document required environment variables
- Apply changes to all backend routes, database modules, and React components
- Update test files to use environment variables for credentials
- Ensure .env remains in .gitignore for production safety

This fix addresses critical security vulnerabilities where database
credentials and authentication secrets were exposed in source code.
This commit is contained in:
alexpolo1
2026-03-01 09:24:24 +01:00
parent 35335a32d3
commit bf98d44a45
23 changed files with 92 additions and 69 deletions
+7 -5
View File
@@ -10,13 +10,14 @@ function logToFile(...args) {
const existingGm = db.prepare('SELECT * FROM players WHERE name = ?').get('gm');
if (!existingGm) {
// Create GM user with password 'bongo'
// Create GM user with password from environment variable
const gmPassword = process.env.GM_PASSWORD || 'defaultpassword';
const stmt = db.prepare(`
INSERT INTO players (name, pw, pw_hash, tab_info)
INSERT INTO players (name, pw, pw_hash, tab_info)
VALUES (?, ?, ?, ?)
`);
stmt.run('gm', 'bongo', 'bongo', JSON.stringify({
stmt.run('gm', gmPassword, gmPassword, JSON.stringify({
rp: 999999,
inventory: [],
renown: 'None'
@@ -26,13 +27,14 @@ if (!existingGm) {
console.log('Created GM user');
} else {
// Update GM user password if needed
const gmPassword = process.env.GM_PASSWORD || 'defaultpassword';
const stmt = db.prepare(`
UPDATE players
UPDATE players
SET pw = ?, pw_hash = ?
WHERE name = 'gm'
`);
stmt.run('bongo', 'bongo');
stmt.run(gmPassword, gmPassword);
logToFile('Updated GM user');
console.log('Updated GM user');
+1 -1
View File
@@ -17,7 +17,7 @@ function logToFile(...args) {
const dbConfig = {
host: 'localhost',
user: 'deathwatch',
password: 'dwroller2025',
password: process.env.DB_PASSWORD || 'defaultpassword',
database: 'deathwatch',
waitForConnections: true,
connectionLimit: 10,
+1 -1
View File
@@ -11,7 +11,7 @@ const sqliteDbPath = path.join(__dirname, 'sqlite', 'deathwatch.db');
const mariadbConfig = {
host: 'localhost',
user: 'deathwatch',
password: 'dwroller2025',
password: process.env.DB_PASSWORD || 'defaultpassword',
database: 'deathwatch'
};
+2 -2
View File
@@ -7,7 +7,7 @@ function logToFile(...args) {
}
// Express middleware to require a valid sessionId in req.headers['x-session-id'] or req.body.sessionId
// Accepts a GM bypass header 'x-gm-secret' matching process.env.GM_PASSWORD or 'bongo' for local convenience
// Accepts a GM bypass header 'x-gm-secret' matching process.env.GM_SECRET
module.exports = async function requireSession(req, res, next) {
try {
// Ensure req.body is always an object before any access
@@ -16,7 +16,7 @@ module.exports = async function requireSession(req, res, next) {
// GM bypass
const gmSecret = req.headers['x-gm-secret'] || (req.query && req.query.gmSecret) || (req.body && req.body.gmSecret);
const gmPassword = process.env.GM_PASSWORD || 'bongo';
const gmPassword = process.env.GM_SECRET || 'defaultsecret';
if (gmSecret && String(gmSecret) === String(gmPassword)) {
logToFile('SESSION: GM bypass accepted', req.method, req.originalUrl);
req.session = { data: { playerName: 'GM' }, playerName: 'GM' };
+2 -1
View File
@@ -158,7 +158,8 @@ router.post('/reload', async (req, res) => {
try {
// Check for GM secret
const gmSecret = req.headers['x-gm-secret'];
if (gmSecret !== 'bongo') {
const expectedGmSecret = process.env.GM_SECRET || 'defaultsecret';
if (gmSecret !== expectedGmSecret) {
return res.status(403).json({ error: 'Unauthorized' });
}
+5 -3
View File
@@ -14,12 +14,14 @@ router.post('/login', async (req, res) => {
// Special handling for GM user
if (name.toLowerCase() === 'gm') {
if (password !== 'bongo') {
const gmPassword = process.env.GM_PASSWORD || 'defaultpassword';
if (password !== gmPassword) {
return res.status(401).json({ error: 'Invalid password' });
}
} else {
// For regular players, use password '1234'
if (password !== '1234') {
// For regular players, use environment variable or default
const playerPassword = process.env.PLAYER_PASSWORD || 'defaultpassword';
if (password !== playerPassword) {
return res.status(401).json({ error: 'Invalid password' });
}
}
+2 -1
View File
@@ -199,7 +199,8 @@ router.get('/stats', async (req, res) => {
router.post('/reload', async (req, res) => {
try {
const gmSecret = req.headers['x-gm-secret'];
if (gmSecret !== 'bongo') return res.status(403).json({ error: 'Unauthorized' });
const expectedGmSecret = process.env.GM_SECRET || 'defaultsecret';
if (gmSecret !== expectedGmSecret) return res.status(403).json({ error: 'Unauthorized' });
const allRules = await getAllRules();
const totalRules = allRules.length;
+2 -2
View File
@@ -18,7 +18,7 @@ const { playerHelpers } = require('../sqlite-db');
fs.writeFileSync(beforePath, JSON.stringify(player, null, 2), 'utf8');
console.log('Backup written:', beforePath);
const plain = '1234';
const plain = process.env.PLAYER_PASSWORD || 'defaultpassword';
const hash = await bcrypt.hash(plain, 10);
const ok = playerHelpers.update(name, { name, rollerInfo: player.rollerInfo || {}, shopInfo: player.shopInfo || {}, tabInfo: player.tabInfo || {}, pw: '', pwHash: hash });
if (!ok) {
@@ -31,5 +31,5 @@ const { playerHelpers } = require('../sqlite-db');
console.log('Updated player:', name, 'pwHash set. After backup:', afterPath);
console.log(JSON.stringify({ name: updated.name, pwHashPresent: !!updated.pwHash, _id: updated._id }, null, 2));
}
console.log('All done. Password for andreas and chris set to "1234" (hashed).');
console.log('All done. Password for andreas and chris set to environment default (hashed).');
})();
+2 -1
View File
@@ -126,7 +126,8 @@ try {
app.post('/api/gmkit/upload', express.json({ limit: '20mb' }), (req, res) => {
try {
const gmSecret = req.headers['x-gm-secret'];
if (gmSecret !== 'bongo') return res.status(403).json({ error: 'Unauthorized' });
const expectedGmSecret = process.env.GM_SECRET || 'defaultsecret';
if (gmSecret !== expectedGmSecret) return res.status(403).json({ error: 'Unauthorized' });
const { name, b64 } = req.body || {};
if (!name || !b64) return res.status(400).json({ error: 'Missing name or b64 body' });
if (!fs.existsSync(gmkitDir)) fs.mkdirSync(gmkitDir, { recursive: true });
+6 -5
View File
@@ -1,17 +1,18 @@
const { db, playerHelpers } = require('./sqlite-db');
// Update all player passwords to '1234'
// Update all player passwords to environment default
function updateAllPasswords() {
try {
// First check the table structure
const columns = db.prepare("PRAGMA table_info(players)").all();
console.log('Table columns:', columns.map(c => c.name));
// Update only the pw column (pwHash might not exist)
const defaultPassword = process.env.PLAYER_PASSWORD || 'defaultpassword';
const updateStmt = db.prepare('UPDATE players SET pw = ?');
const result = updateStmt.run('1234');
console.log(`Updated ${result.changes} player passwords to '1234'`);
const result = updateStmt.run(defaultPassword);
console.log(`Updated ${result.changes} player passwords to environment default`);
// Verify the changes
const players = playerHelpers.getAll();