Spaces:
Runtime error
Runtime error
File size: 8,773 Bytes
0501295 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 | // admin-tasks.js
const http = require('http');
const { exec, spawn } = require('child_process');
const { URL } = require('url');
// --- Configuration ---
const ADMIN_PORT = process.env.ADMIN_PORT || 9001;
const ADMIN_SECRET_KEY = process.env.ADMIN_SECRET_KEY; // MUST be set via environment variable
const API_DIR = '/app/api'; // Directory where 'npm run ...' commands should execute
// --- Basic Authentication Middleware ---
function authenticate(req, res, callback) {
const providedSecret = req.headers['x-admin-secret'];
if (!ADMIN_SECRET_KEY) {
console.error('CRITICAL: ADMIN_SECRET_KEY is not set!');
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Server configuration error: Admin secret not set.' }));
return;
}
if (providedSecret !== ADMIN_SECRET_KEY) {
console.warn('Authentication failed: Incorrect or missing X-Admin-Secret header.');
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Unauthorized: Missing or incorrect X-Admin-Secret header.' }));
return;
}
callback(); // Proceed if authenticated
}
// --- Request Body Parser (Simple) ---
function parseRequestBody(req, callback) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
try {
if (!body) {
return callback(null, {}); // Handle empty body
}
const data = JSON.parse(body);
callback(null, data);
} catch (error) {
callback(new Error('Invalid JSON body'));
}
});
req.on('error', (err) => {
callback(err);
});
}
// --- Request Handler ---
const server = http.createServer((req, res) => {
const reqUrl = new URL(req.url, `http://${req.headers.host}`);
const path = reqUrl.pathname;
const method = req.method;
console.log(`Admin task request: ${method} ${path}`);
authenticate(req, res, () => {
// Authenticated requests proceed here
if (path === '/create-user' && method === 'POST') {
handleCreateUser(req, res);
} else if (path === '/delete-user' && method === 'POST') { // Using POST for simplicity, could be DELETE
handleDeleteUser(req, res);
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not Found' }));
}
});
});
// --- Handler for Create User ---
function handleCreateUser(req, res) {
parseRequestBody(req, (err, data) => {
if (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: err.message }));
}
const { email, password } = data;
if (!email || !password) {
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'Missing email or password in request body.' }));
}
console.log(`Attempting to create user: ${email}`);
// Use spawn for interactive script
const createUserProcess = spawn('npm', ['run', 'create-user'], {
cwd: API_DIR,
stdio: ['pipe', 'pipe', 'pipe'] // stdin, stdout, stderr
});
let output = '';
let errorOutput = '';
createUserProcess.stdout.on('data', (data) => {
output += data.toString();
console.log(`create-user stdout: ${data}`);
// Respond to prompts based on output (this is fragile)
if (output.includes('Enter email')) {
console.log(`Sending email: ${email}`);
createUserProcess.stdin.write(email + '\n');
} else if (output.includes('Enter password')) {
console.log(`Sending password...`);
createUserProcess.stdin.write(password + '\n');
} else if (output.includes('Confirm password')) {
console.log(`Sending password confirmation...`);
createUserProcess.stdin.write(password + '\n');
createUserProcess.stdin.end(); // Signal end of input
}
});
createUserProcess.stderr.on('data', (data) => {
errorOutput += data.toString();
console.error(`create-user stderr: ${data}`);
});
createUserProcess.on('close', (code) => {
console.log(`create-user process exited with code ${code}`);
if (code === 0 && !errorOutput.toLowerCase().includes('error')) { // Basic success check
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: `User creation process initiated for ${email}. Check container logs for success/failure.`, output: output }));
} else {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: `User creation process failed with code ${code}.`, stderr: errorOutput, stdout: output }));
}
});
createUserProcess.on('error', (err) => {
console.error('Failed to start create-user process:', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Failed to start user creation process.', details: err.message }));
});
});
}
// --- Handler for Delete User ---
function handleDeleteUser(req, res) {
parseRequestBody(req, (err, data) => {
if (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: err.message }));
}
const { email } = data;
if (!email) {
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'Missing email in request body.' }));
}
// Basic email format validation (optional but recommended)
if (!/\S+@\S+\.\S+/.test(email)) {
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'Invalid email format provided.' }));
}
// Sanitize email to prevent command injection (basic example)
const sanitizedEmail = email.replace(/[^a-zA-Z0-9@._-]/g, '');
if(sanitizedEmail !== email) {
console.warn(`Potential command injection attempt detected and sanitized: ${email} -> ${sanitizedEmail}`);
res.writeHead(400, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'Invalid characters in email.' }));
}
console.log(`Attempting to delete user: ${sanitizedEmail}`);
const command = `npm run delete-user ${sanitizedEmail}`;
exec(command, { cwd: API_DIR }, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
res.writeHead(500, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ error: 'Failed to execute delete-user script.', details: stderr || error.message }));
}
if (stderr) {
console.warn(`delete-user stderr: ${stderr}`);
// Decide if stderr indicates a true error or just warnings
// For now, we'll return success but include stderr
}
console.log(`delete-user stdout: ${stdout}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: `Delete command executed for user ${sanitizedEmail}.`, output: stdout, warnings: stderr }));
});
});
}
// --- Start Server ---
server.listen(ADMIN_PORT, '0.0.0.0', () => {
if (!ADMIN_SECRET_KEY) {
console.error(`
##################################################################
# WARNING: ADMIN_SECRET_KEY environment variable is not set! #
# The admin tasks endpoint is INSECURE and will not function. #
# Please set this variable before running in production. #
##################################################################
`);
} else {
console.log(`Admin tasks server listening on port ${ADMIN_PORT}`);
console.log('Ensure this port is not exposed publicly unless secured (e.g., via VPN or firewall).');
console.log(`Access requires the X-Admin-Secret header.`);
}
});
process.on('SIGTERM', () => {
console.log('Admin tasks server received SIGTERM. Shutting down.');
server.close(() => {
process.exit(0);
});
}); |