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
+1 -1
View File
@@ -335,7 +335,7 @@ function App() {
{players && players.length > 0 ? players.map(player => (
<button
key={player.name}
onClick={() => {setLoginName(player.name); setLoginPw('1234');}}
onClick={() => {setLoginName(player.name); setLoginPw(process.env.REACT_APP_PLAYER_PASSWORD || 'defaultpassword');}}
className="px-2 py-1 rounded bg-blue-700/30 text-blue-200 hover:bg-blue-600/40 transition-colors"
>
{player.name}
+3 -3
View File
@@ -115,11 +115,11 @@ export default function BestiaryTab(){
setIsRefreshing(true)
try {
// First try to reload the database
const reloadResponse = await fetch('/api/bestiary/reload', {
const reloadResponse = await fetch('/api/bestiary/reload', {
method: 'POST',
headers: {
headers: {
'Content-Type': 'application/json',
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.REACT_APP_GM_SECRET || 'defaultsecret'
}
})
+20 -17
View File
@@ -12,7 +12,7 @@ export default function PlayerManagement({ authedPlayer, sessionId }) {
function buildHeaders(extra = {}) {
const headers = { 'Content-Type': 'application/json' };
if (sessionId) headers['x-session-id'] = sessionId;
if (authedPlayer === 'gm') headers['x-gm-secret'] = 'bongo';
if (authedPlayer === 'gm') headers['x-gm-secret'] = process.env.REACT_APP_GM_SECRET || 'defaultsecret';
return { ...headers, ...extra };
}
@@ -84,8 +84,9 @@ export default function PlayerManagement({ authedPlayer, sessionId }) {
async function gmAddOrUpdatePlayer(name, rp, pw) {
console.log('PlayerManagement: gmAddOrUpdatePlayer', { name, rp, pwProvided: !!pw });
try {
const resPost = await axios.post('/api/players/gm/add-or-update',
{ name, requisitionPoints: parseInt(rp), password: pw || '1234' },
const defaultPassword = process.env.REACT_APP_PLAYER_PASSWORD || 'defaultpassword';
const resPost = await axios.post('/api/players/gm/add-or-update',
{ name, requisitionPoints: parseInt(rp), password: pw || defaultPassword },
{ headers: buildHeaders() }
);
console.log('PlayerManagement: gmAddOrUpdatePlayer POST response', resPost.status, resPost.data);
@@ -142,8 +143,9 @@ export default function PlayerManagement({ authedPlayer, sessionId }) {
async function gmResetPlayerPw(name, pw) {
console.log('PlayerManagement: gmResetPlayerPw', { name, pwProvided: !!pw });
try {
const res = await axios.post('/api/players/gm/reset-password',
{ playerName: name, newPassword: pw || '1234' },
const defaultPassword = process.env.REACT_APP_PLAYER_PASSWORD || 'defaultpassword';
const res = await axios.post('/api/players/gm/reset-password',
{ playerName: name, newPassword: pw || defaultPassword },
{ headers: buildHeaders() }
);
console.log('PlayerManagement: gmResetPlayerPw response', res.status, res.data);
@@ -201,15 +203,16 @@ export default function PlayerManagement({ authedPlayer, sessionId }) {
<input
className="rounded border border-slate-600 bg-slate-800 px-3 py-2 text-white placeholder-slate-400 text-sm"
type="password"
placeholder="Password (default: 1234)"
value={pw}
onChange={e => setPw(e.target.value)}
placeholder="Password (use environment default)"
value={pw}
onChange={e => setPw(e.target.value)}
/>
<button
<button
className="rounded bg-green-600 hover:bg-green-500 px-4 py-2 text-white text-sm font-medium transition-colors"
onClick={() => {
if (name.trim()) {
onAdd(name.trim(), rp, pw || '1234');
const defaultPassword = process.env.REACT_APP_PLAYER_PASSWORD || 'defaultpassword';
onAdd(name.trim(), rp, pw || defaultPassword);
setName('');
setRp('50');
setPw('');
@@ -340,17 +343,17 @@ export default function PlayerManagement({ authedPlayer, sessionId }) {
return (
<div className="flex items-center gap-2 w-full">
<input
<input
className="rounded border border-slate-600 bg-slate-800 px-2 py-1 text-white placeholder-slate-400 text-sm flex-1 min-w-0"
type="password"
placeholder="New password (default: 1234)"
value={pw}
onChange={e => setPw(e.target.value)}
type="password"
placeholder="New password (use environment default)"
value={pw}
onChange={e => setPw(e.target.value)}
/>
<button
<button
className="rounded bg-orange-600 hover:bg-orange-500 px-3 py-1 text-white text-xs font-medium transition-colors whitespace-nowrap"
onClick={() => {
onReset(name, pw || '1234');
onReset(name, pw || process.env.REACT_APP_PLAYER_PASSWORD || 'defaultpassword');
setPw('');
}}
>
+1 -1
View File
@@ -9,7 +9,7 @@ const STORAGE_SHOP_PLAYERS = 'dw:shop:players:v1';
const RANK_ORDER = ['None','Respected','Distinguished','Famed','Hero'];
const GM_PASSWORD = 'bongo';
const GM_PASSWORD = process.env.REACT_APP_GM_SECRET || 'defaultsecret';
function safeGet(key) {
try {
+1 -1
View File
@@ -22,7 +22,7 @@ function RulesTab({ authedPlayer, sessionId }) {
const buildHeaders = () => {
const headers = { 'x-session-id': sessionId || '' };
if (authedPlayer === 'gm') headers['x-gm-secret'] = 'bongo';
if (authedPlayer === 'gm') headers['x-gm-secret'] = process.env.REACT_APP_GM_SECRET || 'defaultsecret';
return headers;
};
+6 -4
View File
@@ -86,9 +86,10 @@ describe('Login Functionality', () => {
const passwordInput = screen.getByPlaceholderText(/password/i);
const loginButton = screen.getByRole('button', { name: /login|enter/i });
// Fill in login form with GM credentials
// Fill in login form with GM credentials (test credentials)
const testGmPassword = process.env.GM_PASSWORD || 'defaultpassword';
fireEvent.change(nameInput, { target: { value: 'gm' } });
fireEvent.change(passwordInput, { target: { value: 'bongo' } });
fireEvent.change(passwordInput, { target: { value: testGmPassword } });
// Click login button
fireEvent.click(loginButton);
@@ -99,7 +100,7 @@ describe('Login Functionality', () => {
'/api/players/login',
{
name: 'gm',
password: 'bongo'
password: testGmPassword
}
);
});
@@ -157,8 +158,9 @@ describe('Login Functionality', () => {
const passwordInput = screen.getByPlaceholderText(/password/i);
const loginButton = screen.getByRole('button', { name: /login|enter/i });
const testPlayerPassword = process.env.PLAYER_PASSWORD || 'defaultpassword';
fireEvent.change(nameInput, { target: { value: 'nonexistent' } });
fireEvent.change(passwordInput, { target: { value: '1234' } });
fireEvent.change(passwordInput, { target: { value: testPlayerPassword } });
fireEvent.click(loginButton);
await waitFor(() => {
+10 -10
View File
@@ -72,7 +72,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
});
});
@@ -126,7 +126,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -159,7 +159,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -192,7 +192,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -225,7 +225,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -257,7 +257,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -289,7 +289,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -318,7 +318,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -356,7 +356,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);
@@ -393,7 +393,7 @@ describe('PlayerManagement Component', () => {
headers: {
'Content-Type': 'application/json',
'x-session-id': mockSessionId,
'x-gm-secret': 'bongo'
'x-gm-secret': process.env.GM_SECRET || 'defaultsecret'
}
}
);