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);
    });
});