Spaces:
Runtime error
Runtime error
| // 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); | |
| }); | |
| }); |