193 lines
6.1 KiB
JavaScript
193 lines
6.1 KiB
JavaScript
|
const http = require('http');
|
||
|
const querystring = require('querystring');
|
||
|
const secret_password = process.env.SECRET_PASSWORD;
|
||
|
|
||
|
const username = process.env.MAILMAN_USERNAME;
|
||
|
const password = process.env.MAILMAN_PASSWORD;
|
||
|
const hostname = process.env.HOSTNAME;
|
||
|
const port = process.env.PORT
|
||
|
const buf = Buffer.from(username + ":" + password);
|
||
|
const auth = "Basic " + buf.toString("base64");
|
||
|
const mailman = require('http');
|
||
|
const requestor = process.env.REQUESTOR_IP
|
||
|
|
||
|
// Server loop
|
||
|
const server = http.createServer(function (request, response) {
|
||
|
|
||
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||
|
|
||
|
let body = '';
|
||
|
let receivedBytes = 0;
|
||
|
const MAX_BODY_SIZE = 1e6; // 1MB limit to prevent DoS attacks
|
||
|
|
||
|
request.on('data', function (chunk) {
|
||
|
receivedBytes += chunk.length;
|
||
|
// console.log("Received chunk:", chunk.toString()); // Log each received chunk
|
||
|
|
||
|
if (receivedBytes > MAX_BODY_SIZE) {
|
||
|
console.warn("Request body too large, potential DoS attack detected.");
|
||
|
response.writeHead(413, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Request entity too large" }));
|
||
|
request.connection.destroy();
|
||
|
return;
|
||
|
}
|
||
|
body += chunk;
|
||
|
});
|
||
|
|
||
|
request.on('end', function () {
|
||
|
// console.log("Final accumulated body before parsing:", body);
|
||
|
|
||
|
try {
|
||
|
let data = JSON.parse(body);
|
||
|
let realIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||
|
|
||
|
if ((requestor && realIp.includes(requestor)) || !requestor) {
|
||
|
if (data.subscribe === 'subscribe') {
|
||
|
console.log("From IP:", realIp);
|
||
|
console.log("Data", data);
|
||
|
handleSubscribe(data, response);
|
||
|
} else {
|
||
|
console.log("From IP:", realIp);
|
||
|
console.log("Data", data);
|
||
|
handleUnsubscribe(data, response);
|
||
|
}
|
||
|
} else {
|
||
|
// Handle the case where the `requestor` does not match or is empty
|
||
|
console.log("Requestor does not match the real IP or is empty.");
|
||
|
response.writeHead(400, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Requestor does not match IP" }));
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error("Invalid JSON received:", body);
|
||
|
let realIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
|
||
|
console.warn("Potential malicious request detected from IP:", realIp);
|
||
|
response.writeHead(400, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Invalid JSON format" }));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
server.on('error', function (e) {
|
||
|
console.error(e);
|
||
|
});
|
||
|
|
||
|
server.listen(Number(process.argv[2]));
|
||
|
|
||
|
// Handle subscription requests
|
||
|
function handleSubscribe(data, response) {
|
||
|
if (secret_password !== data.password) {
|
||
|
response.writeHead(403, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Unauthorized" }));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let url_object = {
|
||
|
list_id: process.env.MAILMAN_LIST_ID,
|
||
|
subscriber: data.email,
|
||
|
display_name: data.first_name + ' ' + data.last_name,
|
||
|
pre_verified: 'True',
|
||
|
pre_confirmed: 'True',
|
||
|
pre_approved: 'True'
|
||
|
};
|
||
|
|
||
|
let mailman_options = {
|
||
|
hostname: hostname,
|
||
|
port: port,
|
||
|
path: '/3.1/members?' + querystring.stringify(url_object),
|
||
|
method: 'POST',
|
||
|
headers: { "Authorization": auth }
|
||
|
};
|
||
|
|
||
|
let req = mailman.request(mailman_options, function (res) {
|
||
|
let body = '';
|
||
|
res.on('data', (chunk) => { body += chunk; });
|
||
|
res.on('end', () => {
|
||
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||
|
response.end(body);
|
||
|
});
|
||
|
});
|
||
|
req.on('error', (err) => {
|
||
|
console.error(err);
|
||
|
response.writeHead(500, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Internal server error" }));
|
||
|
});
|
||
|
req.end();
|
||
|
}
|
||
|
|
||
|
// Handle unsubscription requests
|
||
|
async function handleUnsubscribe(data, response) {
|
||
|
if (secret_password !== data.password) {
|
||
|
response.writeHead(403, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Unauthorized" }));
|
||
|
return;
|
||
|
}
|
||
|
try {
|
||
|
let member_id = await find_member_id(data.email);
|
||
|
if (member_id) {
|
||
|
await unsubscribe_member_id(member_id, response);
|
||
|
} else {
|
||
|
response.writeHead(404, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Member not found" }));
|
||
|
}
|
||
|
} catch (error) {
|
||
|
console.error("Error handling unsubscribe:", error);
|
||
|
response.writeHead(500, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Internal server error" }));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Find member in mailing list
|
||
|
function find_member_id(email) {
|
||
|
return new Promise((resolve, reject) => {
|
||
|
let url_object = { subscriber: email, list_id: process.env.MAILMAN_LIST_ID, role: 'member' };
|
||
|
|
||
|
let mailman_options = {
|
||
|
hostname: hostname,
|
||
|
port: port,
|
||
|
path: '/3.1/members/find?' + querystring.stringify(url_object),
|
||
|
method: 'GET',
|
||
|
headers: { "Authorization": auth }
|
||
|
};
|
||
|
|
||
|
let req = mailman.request(mailman_options, function (res) {
|
||
|
let body = '';
|
||
|
res.on('data', (chunk) => { body += chunk; });
|
||
|
res.on('end', () => {
|
||
|
try {
|
||
|
let parsed = JSON.parse(body);
|
||
|
resolve(parsed.entries && parsed.entries.length > 0 ? parsed.entries[0].member_id : null);
|
||
|
} catch (err) {
|
||
|
reject(new Error("Error parsing JSON response"));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
req.on('error', reject);
|
||
|
req.end();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Unsubscribe member
|
||
|
function unsubscribe_member_id(member_id, response) {
|
||
|
let mailman_options = {
|
||
|
hostname: hostname,
|
||
|
port: port,
|
||
|
path: '/3.1/members/' + member_id,
|
||
|
method: 'DELETE',
|
||
|
headers: { "Authorization": auth }
|
||
|
};
|
||
|
|
||
|
let req = mailman.request(mailman_options, function (res) {
|
||
|
let body = '';
|
||
|
res.on('data', (chunk) => { body += chunk; });
|
||
|
res.on('end', () => {
|
||
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||
|
response.end(body);
|
||
|
});
|
||
|
});
|
||
|
req.on('error', (err) => {
|
||
|
console.error(err);
|
||
|
response.writeHead(500, { 'Content-Type': 'application/json' });
|
||
|
response.end(JSON.stringify({ error: "Internal server error" }));
|
||
|
});
|
||
|
req.end();
|
||
|
}
|