Moves API handlers from server/lambda to /api

This commit is contained in:
Alicia Sykes
2023-07-22 17:53:33 +01:00
parent b81882e6cc
commit 8e1bc7a97e
24 changed files with 64 additions and 162 deletions

63
api/check-hsts.js Normal file
View File

@@ -0,0 +1,63 @@
const https = require('https');
exports.handler = async function(event, context) {
const siteURL = event.queryStringParameters.url;
const errorResponse = (message, statusCode = 500) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};
const hstsIncompatible = (message, statusCode = 200) => {
return {
statusCode: statusCode,
body: JSON.stringify({ message, compatible: false }),
};
};
if (!siteURL) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'URL parameter is missing!' }),
};
}
return new Promise((resolve, reject) => {
const req = https.request(siteURL, res => {
const headers = res.headers;
const hstsHeader = headers['strict-transport-security'];
if (!hstsHeader) {
resolve(hstsIncompatible(`Site does not serve any HSTS headers.`));
} else {
const maxAgeMatch = hstsHeader.match(/max-age=(\d+)/);
const includesSubDomains = hstsHeader.includes('includeSubDomains');
const preload = hstsHeader.includes('preload');
if (!maxAgeMatch || parseInt(maxAgeMatch[1]) < 10886400) {
resolve(hstsIncompatible(`HSTS max-age is less than 10886400.`));
} else if (!includesSubDomains) {
resolve(hstsIncompatible(`HSTS header does not include all subdomains.`));
} else if (!preload) {
resolve(hstsIncompatible(`HSTS header does not contain the preload directive.`));
} else {
resolve({
statusCode: 200,
body: JSON.stringify({
message: "Site is compatible with the HSTS preload list!",
compatible: true,
hstsHeader: hstsHeader,
}),
});
}
}
});
req.on('error', (error) => {
resolve(errorResponse(`Error making request: ${error.message}`));
});
req.end();
});
};

90
api/check-ports.js Normal file
View File

@@ -0,0 +1,90 @@
const net = require('net');
// A list of commonly used ports.
const PORTS = [
20, 21, 22, 23, 25, 53, 80, 67, 68, 69,
110, 119, 123, 143, 156, 161, 162, 179, 194,
389, 443, 587, 993, 995,
3000, 3306, 3389, 5060, 5900, 8000, 8080, 8888
];
async function checkPort(port, domain) {
return new Promise((resolve, reject) => {
const socket = new net.Socket();
socket.setTimeout(1500); // you may want to adjust the timeout
socket.once('connect', () => {
socket.destroy();
resolve(port);
});
socket.once('timeout', () => {
socket.destroy();
reject(new Error(`Timeout at port: ${port}`));
});
socket.once('error', (e) => {
socket.destroy();
reject(e);
});
socket.connect(port, domain);
});
}
exports.handler = async (event, context) => {
const domain = event.queryStringParameters.url;
if (!domain) {
return errorResponse('Missing domain parameter.');
}
const delay = ms => new Promise(res => setTimeout(res, ms));
const timeout = delay(9000);
const openPorts = [];
const failedPorts = [];
const promises = PORTS.map(port => checkPort(port, domain)
.then(() => {
openPorts.push(port);
return { status: 'fulfilled', port };
})
.catch(() => {
failedPorts.push(port);
return { status: 'rejected', port };
}));
let timeoutReached = false;
for (const promise of promises) {
const result = await Promise.race([promise, timeout.then(() => ({ status: 'timeout', timeout: true }))]);
if (result.status === 'timeout') {
timeoutReached = true;
if (result.timeout) {
// Add the ports not checked yet to the failedPorts array
const checkedPorts = [...openPorts, ...failedPorts];
const portsNotChecked = PORTS.filter(port => !checkedPorts.includes(port));
failedPorts.push(...portsNotChecked);
}
break;
}
}
if(timeoutReached){
return errorResponse('The function timed out before completing.');
}
return {
statusCode: 200,
body: JSON.stringify({ openPorts, failedPorts }),
};
};
const errorResponse = (message, statusCode = 444) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};

69
api/dns-sec.js Normal file
View File

@@ -0,0 +1,69 @@
const https = require('https');
exports.handler = async function(event, context) {
let { url } = event.queryStringParameters;
if (!url) {
return errorResponse('URL query parameter is required.');
}
// Extract hostname from URL
const parsedUrl = new URL(url);
const domain = parsedUrl.hostname;
const dnsTypes = ['DNSKEY', 'DS', 'RRSIG'];
const records = {};
for (const type of dnsTypes) {
const options = {
hostname: 'dns.google',
path: `/resolve?name=${encodeURIComponent(domain)}&type=${type}`,
method: 'GET',
headers: {
'Accept': 'application/dns-json'
}
};
try {
const dnsResponse = await new Promise((resolve, reject) => {
const req = https.request(options, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
resolve(JSON.parse(data));
});
res.on('error', error => {
reject(error);
});
});
req.end();
});
if (dnsResponse.Answer) {
records[type] = { isFound: true, answer: dnsResponse.Answer, response: dnsResponse.Answer };
} else {
records[type] = { isFound: false, answer: null, response: dnsResponse};
}
} catch (error) {
return errorResponse(`Error fetching ${type} record: ${error.message}`);
}
}
return {
statusCode: 200,
body: JSON.stringify(records),
};
};
const errorResponse = (message, statusCode = 444) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};

48
api/dns-server.js Normal file
View File

@@ -0,0 +1,48 @@
const dns = require('dns');
const dnsPromises = dns.promises;
// const https = require('https');
const axios = require('axios');
exports.handler = async (event) => {
const domain = event.queryStringParameters.url.replace(/^(?:https?:\/\/)?/i, "");
try {
const addresses = await dnsPromises.resolve4(domain);
const results = await Promise.all(addresses.map(async (address) => {
const hostname = await dnsPromises.reverse(address).catch(() => null);
let dohDirectSupports = false;
try {
await axios.get(`https://${address}/dns-query`);
dohDirectSupports = true;
} catch (error) {
dohDirectSupports = false;
}
return {
address,
hostname,
dohDirectSupports,
};
}));
// let dohMozillaSupport = false;
// try {
// const mozillaList = await axios.get('https://firefox.settings.services.mozilla.com/v1/buckets/security-state/collections/onecrl/records');
// dohMozillaSupport = results.some(({ hostname }) => mozillaList.data.data.some(({ id }) => id.includes(hostname)));
// } catch (error) {
// console.error(error);
// }
return {
statusCode: 200,
body: JSON.stringify({
domain,
dns: results,
// dohMozillaSupport,
}),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({
error: `An error occurred while resolving DNS. ${error.message}`,
}),
};
}
};

33
api/find-url-ip.js Normal file
View File

@@ -0,0 +1,33 @@
const dns = require('dns');
/* Lambda function to fetch the IP address of a given URL */
exports.handler = function (event, context, callback) {
const addressParam = event.queryStringParameters.url;
if (!addressParam) {
callback(null, errorResponse('Address parameter is missing.'));
return;
}
const address = decodeURIComponent(addressParam)
.replaceAll('https://', '')
.replaceAll('http://', '');
dns.lookup(address, (err, ip, family) => {
if (err) {
callback(null, errorResponse(err.message));
} else {
callback(null, {
statusCode: 200,
body: JSON.stringify({ ip, family }),
});
}
});
};
const errorResponse = (message, statusCode = 444) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};

35
api/follow-redirects.js Normal file
View File

@@ -0,0 +1,35 @@
exports.handler = async (event) => {
const { url } = event.queryStringParameters;
const redirects = [url];
try {
const got = await import('got');
await got.default(url, {
followRedirect: true,
maxRedirects: 12,
hooks: {
beforeRedirect: [
(options, response) => {
redirects.push(response.headers.location);
},
],
},
});
return {
statusCode: 200,
body: JSON.stringify({
redirects: redirects,
}),
};
} catch (error) {
return errorResponse(`Error: ${error.message}`);
}
};
const errorResponse = (message, statusCode = 444) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};

55
api/get-carbon.js Normal file
View File

@@ -0,0 +1,55 @@
const https = require('https');
exports.handler = async (event, context) => {
const { url } = event.queryStringParameters;
if (!url) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'url query parameter is required' }),
};
}
// First, get the size of the website's HTML
const getHtmlSize = (url) => new Promise((resolve, reject) => {
https.get(url, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
const sizeInBytes = Buffer.byteLength(data, 'utf8');
resolve(sizeInBytes);
});
}).on('error', reject);
});
try {
const sizeInBytes = await getHtmlSize(url);
const apiUrl = `https://api.websitecarbon.com/data?bytes=${sizeInBytes}&green=0`;
// Then use that size to get the carbon data
const carbonData = await new Promise((resolve, reject) => {
https.get(apiUrl, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
resolve(JSON.parse(data));
});
}).on('error', reject);
});
carbonData.scanUrl = url;
return {
statusCode: 200,
body: JSON.stringify(carbonData),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: `Error: ${error.message}` }),
};
}
};

26
api/get-cookies.js Normal file
View File

@@ -0,0 +1,26 @@
const axios = require('axios');
exports.handler = async function(event, context) {
const { url } = event.queryStringParameters;
if (!url) {
return {
statusCode: 400,
body: JSON.stringify({ message: 'url query string parameter is required' }),
};
}
try {
const response = await axios.get(url, {withCredentials: true});
const cookies = response.headers['set-cookie'];
return {
statusCode: 200,
body: JSON.stringify({ cookies }),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};

59
api/get-dns.js Normal file
View File

@@ -0,0 +1,59 @@
const dns = require('dns');
const util = require('util');
exports.handler = async function(event, context) {
let hostname = event.queryStringParameters.url;
// Handle URLs by extracting hostname
if (hostname.startsWith('http://') || hostname.startsWith('https://')) {
hostname = new URL(hostname).hostname;
}
try {
const lookupPromise = util.promisify(dns.lookup);
const resolve4Promise = util.promisify(dns.resolve4);
const resolve6Promise = util.promisify(dns.resolve6);
const resolveMxPromise = util.promisify(dns.resolveMx);
const resolveTxtPromise = util.promisify(dns.resolveTxt);
const resolveNsPromise = util.promisify(dns.resolveNs);
const resolveCnamePromise = util.promisify(dns.resolveCname);
const resolveSoaPromise = util.promisify(dns.resolveSoa);
const resolveSrvPromise = util.promisify(dns.resolveSrv);
const resolvePtrPromise = util.promisify(dns.resolvePtr);
const [a, aaaa, mx, txt, ns, cname, soa, srv, ptr] = await Promise.all([
lookupPromise(hostname),
resolve4Promise(hostname).catch(() => []), // A record
resolve6Promise(hostname).catch(() => []), // AAAA record
resolveMxPromise(hostname).catch(() => []), // MX record
resolveTxtPromise(hostname).catch(() => []), // TXT record
resolveNsPromise(hostname).catch(() => []), // NS record
resolveCnamePromise(hostname).catch(() => []), // CNAME record
resolveSoaPromise(hostname).catch(() => []), // SOA record
resolveSrvPromise(hostname).catch(() => []), // SRV record
resolvePtrPromise(hostname).catch(() => []) // PTR record
]);
return {
statusCode: 200,
body: JSON.stringify({
A: a,
AAAA: aaaa,
MX: mx,
TXT: txt,
NS: ns,
CNAME: cname,
SOA: soa,
SRV: srv,
PTR: ptr
})
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({
error: error.message
})
};
}
};

31
api/get-headers.js Normal file
View File

@@ -0,0 +1,31 @@
const axios = require('axios');
exports.handler = async function(event, context) {
const { url } = event.queryStringParameters;
if (!url) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'url query string parameter is required' }),
};
}
try {
const response = await axios.get(url, {
validateStatus: function (status) {
return status >= 200 && status < 600; // Resolve only if the status code is less than 600
},
});
return {
statusCode: 200,
body: JSON.stringify(response.headers),
};
} catch (error) {
console.log(error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};

30
api/get-txt.js Normal file
View File

@@ -0,0 +1,30 @@
const dns = require('dns').promises;
exports.handler = async (event) => {
const url = new URL(event.queryStringParameters.url);
try {
const txtRecords = await dns.resolveTxt(url.hostname);
// Parsing and formatting TXT records into a single object
const readableTxtRecords = txtRecords.reduce((acc, recordArray) => {
const recordObject = recordArray.reduce((recordAcc, recordString) => {
const splitRecord = recordString.split('=');
const key = splitRecord[0];
const value = splitRecord.slice(1).join('=');
return { ...recordAcc, [key]: value };
}, {});
return { ...acc, ...recordObject };
}, {});
return {
statusCode: 200,
body: JSON.stringify(readableTxtRecords),
};
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};

32
api/lighthouse-report.js Normal file
View File

@@ -0,0 +1,32 @@
const axios = require('axios');
exports.handler = function(event, context, callback) {
const { url } = event.queryStringParameters;
if (!url) {
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: 'URL param is required'}),
});
}
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&category=PERFORMANCE&category=ACCESSIBILITY&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile&key=${apiKey}`;
axios.get(endpoint)
.then(
(response) => {
callback(null, {
statusCode: 200,
body: JSON.stringify(response.data),
});
}
).catch(
() => {
callback(null, {
statusCode: 500,
body: JSON.stringify({ error: 'Error running Lighthouse'}),
});
}
);
};

45
api/read-robots-txt.js Normal file
View File

@@ -0,0 +1,45 @@
const axios = require('axios');
exports.handler = async function(event, context) {
const siteURL = event.queryStringParameters.url;
if (!siteURL) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Missing url query parameter' }),
};
}
let parsedURL;
try {
parsedURL = new URL(siteURL);
} catch (error) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Invalid url query parameter' }),
};
}
const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`;
try {
const response = await axios.get(robotsURL);
if (response.status === 200) {
return {
statusCode: 200,
body: response.data,
};
} else {
return {
statusCode: response.status,
body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }),
};
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }),
};
}
};

80
api/screenshot.js Normal file
View File

@@ -0,0 +1,80 @@
const puppeteer = require('puppeteer-core');
const chromium = require('chrome-aws-lambda');
exports.handler = async (event, context, callback) => {
let browser = null;
let targetUrl = event.queryStringParameters.url;
if (!targetUrl) {
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: 'URL is missing from queryStringParameters' }),
});
return;
}
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
targetUrl = 'http://' + targetUrl;
}
try {
new URL(targetUrl);
} catch (error) {
callback(null, {
statusCode: 400,
body: JSON.stringify({ error: 'URL provided is invalid' }),
});
return;
}
try {
browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: { width: 800, height: 600 },
executablePath: process.env.CHROME_PATH || await chromium.executablePath,
headless: chromium.headless,
ignoreHTTPSErrors: true,
ignoreDefaultArgs: ['--disable-extensions'],
});
let page = await browser.newPage();
await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
page.setDefaultNavigationTimeout(8000);
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
await page.evaluate(() => {
const selector = 'body';
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (!element) {
reject(new Error(`Error: No element found with selector: ${selector}`));
}
resolve();
});
});
const screenshotBuffer = await page.screenshot();
const base64Screenshot = screenshotBuffer.toString('base64');
const response = {
statusCode: 200,
body: JSON.stringify({ image: base64Screenshot }),
};
callback(null, response);
} catch (error) {
console.log(error);
callback(null, {
statusCode: 500,
body: JSON.stringify({ error: `An error occurred: ${error.message}` }),
});
} finally {
if (browser !== null) {
await browser.close();
}
}
};

69
api/server-status.js Normal file
View File

@@ -0,0 +1,69 @@
const https = require('https');
const { performance, PerformanceObserver } = require('perf_hooks');
exports.handler = async function(event, context) {
const { url } = event.queryStringParameters;
if (!url) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'You must provide a URL query parameter!' }),
};
}
let dnsLookupTime;
let responseCode;
let startTime;
const obs = new PerformanceObserver((items) => {
dnsLookupTime = items.getEntries()[0].duration;
performance.clearMarks();
});
obs.observe({ entryTypes: ['measure'] });
performance.mark('A');
try {
startTime = performance.now();
const response = await new Promise((resolve, reject) => {
const req = https.get(url, res => {
let data = '';
responseCode = res.statusCode;
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
resolve(res);
});
});
req.on('error', reject);
req.end();
});
if (responseCode < 200 || responseCode >= 400) {
return {
statusCode: 200,
body: JSON.stringify({ error: `Received non-success response code: ${responseCode}` }),
};
}
performance.mark('B');
performance.measure('A to B', 'A', 'B');
let responseTime = performance.now() - startTime;
obs.disconnect();
return {
statusCode: 200,
body: JSON.stringify({ isUp: true, dnsLookupTime, responseTime, responseCode }),
};
} catch (error) {
obs.disconnect();
return {
statusCode: 200,
body: JSON.stringify({ error: `Error during operation: ${error.message}` }),
};
}
};

56
api/site-features.js Normal file
View File

@@ -0,0 +1,56 @@
const https = require('https');
exports.handler = async function (event, context) {
const { url } = event.queryStringParameters;
const apiKey = process.env.BUILT_WITH_API_KEY;
const errorResponse = (message, statusCode = 500) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};
if (!url) {
return errorResponse('URL query parameter is required', 400);
}
if (!apiKey) {
return errorResponse('Missing BuiltWith API key in environment variables', 500);
}
const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`;
try {
const response = await new Promise((resolve, reject) => {
const req = https.get(apiUrl, res => {
let data = '';
res.on('data', chunk => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode <= 299) {
resolve(data);
} else {
reject(new Error(`Request failed with status code: ${res.statusCode}`));
}
});
});
req.on('error', error => {
reject(error);
});
req.end();
});
return {
statusCode: 200,
body: response,
};
} catch (error) {
return errorResponse(`Error making request: ${error.message}`);
}
};

41
api/sitemap.js Normal file
View File

@@ -0,0 +1,41 @@
const axios = require('axios');
const xml2js = require('xml2js');
exports.handler = async (event) => {
const baseUrl = event.queryStringParameters.url.replace(/^(?:https?:\/\/)?/i, "");
const url = baseUrl.startsWith('http') ? baseUrl : `http://${baseUrl}`;
let sitemapUrl;
try {
// Fetch robots.txt
const robotsRes = await axios.get(`${url}/robots.txt`);
const robotsTxt = robotsRes.data.split('\n');
for (let line of robotsTxt) {
if (line.startsWith('Sitemap:')) {
sitemapUrl = line.split(' ')[1];
}
}
if (!sitemapUrl) {
return {
statusCode: 404,
body: JSON.stringify({ error: 'Sitemap not found in robots.txt' }),
};
}
// Fetch sitemap
const sitemapRes = await axios.get(sitemapUrl);
const sitemap = await xml2js.parseStringPromise(sitemapRes.data);
return {
statusCode: 200,
body: JSON.stringify(sitemap),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};

50
api/ssl-check.js Normal file
View File

@@ -0,0 +1,50 @@
const https = require('https');
exports.handler = async function (event, context) {
const { url } = event.queryStringParameters;
const errorResponse = (message, statusCode = 500) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};
if (!url) {
return errorResponse('URL query parameter is required', 400);
}
try {
const response = await new Promise((resolve, reject) => {
const req = https.request(url, res => {
// Check if the SSL handshake was authorized
if (!res.socket.authorized) {
resolve(errorResponse(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`));
} else {
let cert = res.socket.getPeerCertificate(true);
if (!cert || Object.keys(cert).length === 0) {
resolve(errorResponse("No certificate presented by the server."));
} else {
// omit the raw and issuerCertificate fields
const { raw, issuerCertificate, ...certWithoutRaw } = cert;
resolve({
statusCode: 200,
body: JSON.stringify(certWithoutRaw),
});
}
}
});
req.on('error', error => {
resolve(errorResponse(`Error fetching site certificate: ${error.message}`));
});
req.end();
});
return response;
} catch (error) {
return errorResponse(`Unexpected error occurred: ${error.message}`);
}
};

69
api/tech-stack.js Normal file
View File

@@ -0,0 +1,69 @@
const Wappalyzer = require('wappalyzer');
const analyze = async (url) => {
const options = {};
const wappalyzer = new Wappalyzer(options);
return (async function() {
try {
await wappalyzer.init()
const headers = {}
const storage = {
local: {},
session: {},
}
const site = await wappalyzer.open(url, headers, storage)
const results = await site.analyze()
return results;
} catch (error) {
return error;
} finally {
await wappalyzer.destroy()
}
})();
}
exports.handler = async (event, context, callback) => {
// Validate URL parameter
if (!event.queryStringParameters || !event.queryStringParameters.url) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Missing url parameter' }),
};
}
// Get URL from param
let url = event.queryStringParameters.url;
if (!/^https?:\/\//i.test(url)) {
url = 'http://' + url;
}
try {
return analyze(url).then(
(results) => {
if (!results.technologies || results.technologies.length === 0) {
return {
statusCode: 200,
body: JSON.stringify({ error: 'Unable to find any technologies for site' }),
};
}
return {
statusCode: 200,
body: JSON.stringify(results),
}
}
)
.catch((error) => {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
});
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};

55
api/trace-route.js Normal file
View File

@@ -0,0 +1,55 @@
const traceroute = require('traceroute');
const url = require('url');
exports.handler = async function(event, context) {
const urlString = event.queryStringParameters.url;
try {
if (!urlString) {
throw new Error('URL parameter is missing!');
}
// Parse the URL and get the hostname
const urlObject = url.parse(urlString);
const host = urlObject.hostname;
if (!host) {
throw new Error('Invalid URL provided');
}
// Traceroute with callback
const result = await new Promise((resolve, reject) => {
traceroute.trace(host, (err, hops) => {
if (err || !hops) {
reject(err || new Error('No hops found'));
} else {
resolve(hops);
}
});
// Check if remaining time is less than 8.8 seconds, then reject promise
if (context.getRemainingTimeInMillis() < 8800) {
reject(new Error('Lambda is about to timeout'));
}
});
return {
statusCode: 200,
body: JSON.stringify({
message: "Traceroute completed!",
result,
}),
};
} catch (err) {
const message = err.code === 'ENOENT'
? 'Traceroute command is not installed on the host.'
: err.message;
return {
statusCode: 500,
body: JSON.stringify({
error: message,
}),
};
}
};

102
api/whois-lookup.js Normal file
View File

@@ -0,0 +1,102 @@
const net = require('net');
const psl = require('psl');
// const { URL } = require('url');
const errorResponse = (message, statusCode = 444) => {
return {
statusCode: statusCode,
body: JSON.stringify({ error: message }),
};
};
const getBaseDomain = (url) => {
let protocol = '';
if (url.startsWith('http://')) {
protocol = 'http://';
} else if (url.startsWith('https://')) {
protocol = 'https://';
}
let noProtocolUrl = url.replace(protocol, '');
const parsed = psl.parse(noProtocolUrl);
return protocol + parsed.domain;
};
exports.handler = async function(event, context) {
let url = event.queryStringParameters.url;
if (!url) {
return errorResponse('URL query parameter is required.', 400);
}
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = 'http://' + url;
}
let hostname;
try {
hostname = getBaseDomain(new URL(url).hostname);
} catch (error) {
return errorResponse(`Unable to parse URL: ${error}`, 400);
}
return new Promise((resolve, reject) => {
const client = net.createConnection({ port: 43, host: 'whois.internic.net' }, () => {
client.write(hostname + '\r\n');
});
let data = '';
client.on('data', (chunk) => {
data += chunk;
});
client.on('end', () => {
try {
const parsedData = parseWhoisData(data);
resolve({
statusCode: 200,
body: JSON.stringify(parsedData),
});
} catch (error) {
resolve(errorResponse(error.message));
}
});
client.on('error', (err) => {
resolve(errorResponse(err.message, 500));
});
});
};
const parseWhoisData = (data) => {
if (data.includes('No match for')) {
return { error: 'No matches found for domain in internic database'};
}
const lines = data.split('\r\n');
const parsedData = {};
let lastKey = '';
for (const line of lines) {
const index = line.indexOf(':');
if (index === -1) {
if (lastKey !== '') {
parsedData[lastKey] += ' ' + line.trim();
}
continue;
}
let key = line.slice(0, index).trim();
const value = line.slice(index + 1).trim();
if (value.length === 0) continue;
key = key.replace(/\W+/g, '_');
lastKey = key;
parsedData[key] = value;
}
return parsedData;
};