Files
picpeak/backend/scripts/set-admin-password.js
T
Paul Nothaft f3c2cee362 security: Fix critical vulnerabilities and harden application
## Security Fixes

### CRITICAL: Command Injection (adminBackup.js)
- Replaced exec() with spawn() using argument arrays
- Added input sanitization for host, user, and ssh_key
- Added regex validation for hostname/IP format
- Added username format validation
- Added SSH key file existence check
- Prevents shell metacharacter injection attacks

### HIGH: Hardcoded Password (set-admin-password.js)
- Removed hardcoded 'admin123' password
- Now requires password as CLI argument or env variable
- Added password strength validation (8+ chars, mixed case, numbers, special chars)
- Added --help flag with usage instructions
- Invalidates existing sessions on password change

### MEDIUM: XSS Vulnerability (WelcomeMessageEditor.tsx)
- Added DOMPurify sanitization to getPreviewHtml()
- Strips all HTML tags before rendering preview
- Prevents script injection in admin preview

### LOW: Sample Password Exposure (EmailConfigPage.tsx)
- Replaced plaintext sample password with masked placeholder
- Uses '••••••••' instead of realistic password

## Dependency Updates
- Fixed npm audit vulnerabilities (jws, qs, express)
- Backend: 0 vulnerabilities
- Frontend: 0 vulnerabilities
2026-01-03 10:12:01 +01:00

136 lines
3.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Script to set/reset admin password
*
* Usage:
* node set-admin-password.js <new-password>
* node set-admin-password.js --env (uses ADMIN_PASSWORD environment variable)
*
* Security: Password must be at least 8 characters with mixed case, numbers, and special characters
*/
const bcrypt = require('bcrypt');
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../.env') });
const knex = require('knex');
const db = knex({
client: process.env.DB_CLIENT || 'pg',
connection: {
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
user: process.env.DB_USER || 'picpeak',
password: process.env.DB_PASSWORD || 'picpeak',
database: process.env.DB_NAME || 'picpeak_dev'
}
});
/**
* Validate password strength
*/
function validatePassword(password) {
if (!password || password.length < 8) {
return { valid: false, error: 'Password must be at least 8 characters long' };
}
if (!/[a-z]/.test(password)) {
return { valid: false, error: 'Password must contain at least one lowercase letter' };
}
if (!/[A-Z]/.test(password)) {
return { valid: false, error: 'Password must contain at least one uppercase letter' };
}
if (!/[0-9]/.test(password)) {
return { valid: false, error: 'Password must contain at least one number' };
}
if (!/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
return { valid: false, error: 'Password must contain at least one special character' };
}
return { valid: true };
}
function printUsage() {
console.log(`
Usage:
node set-admin-password.js <new-password>
node set-admin-password.js --env
Options:
<new-password> The new password to set (must meet security requirements)
--env Use ADMIN_PASSWORD environment variable
Security Requirements:
- At least 8 characters
- At least one lowercase letter
- At least one uppercase letter
- At least one number
- At least one special character (!@#$%^&*()_+-=[]{}|;':\",./<>?)
Examples:
node set-admin-password.js "MySecure@Pass123"
ADMIN_PASSWORD="MySecure@Pass123" node set-admin-password.js --env
`);
}
async function setAdminPassword() {
try {
// Get password from argument or environment
const args = process.argv.slice(2);
let password;
if (args.length === 0) {
console.error('❌ Error: No password provided\n');
printUsage();
process.exit(1);
}
if (args[0] === '--env') {
password = process.env.ADMIN_PASSWORD;
if (!password) {
console.error('❌ Error: ADMIN_PASSWORD environment variable not set');
process.exit(1);
}
} else if (args[0] === '--help' || args[0] === '-h') {
printUsage();
process.exit(0);
} else {
password = args[0];
}
// Validate password strength
const validation = validatePassword(password);
if (!validation.valid) {
console.error(`❌ Error: ${validation.error}`);
process.exit(1);
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Update database
const updated = await db('admin_users')
.where('username', 'admin')
.update({
password_hash: hashedPassword,
password_changed_at: new Date(),
updated_at: new Date()
});
if (updated === 0) {
console.error('❌ Error: Admin user not found');
process.exit(1);
}
console.log('✅ Admin password updated successfully');
console.log(' Note: All existing sessions have been invalidated');
await db.destroy();
process.exit(0);
} catch (error) {
console.error('❌ Error setting password:', error.message);
await db.destroy();
process.exit(1);
}
}
setAdminPassword();