From 7d33c38897d284aaa4a88f557a9baaa7dc62748d Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 7 Jul 2023 20:56:58 +0100 Subject: [PATCH] Writes lambdas for DNSSEC, site features, carbon footprint and more --- netlify.toml | 15 ++++++++ server/lambda/check-ports.js | 7 +++- server/lambda/dns-sec.js | 69 ++++++++++++++++++++++++++++++++++ server/lambda/get-carbon.js | 55 +++++++++++++++++++++++++++ server/lambda/site-features.js | 44 ++++++++++++++++++++++ server/lambda/ssl-check.js | 35 ++++++++++++----- 6 files changed, 215 insertions(+), 10 deletions(-) create mode 100644 server/lambda/dns-sec.js create mode 100644 server/lambda/get-carbon.js create mode 100644 server/lambda/site-features.js diff --git a/netlify.toml b/netlify.toml index 92dbae4..7207102 100644 --- a/netlify.toml +++ b/netlify.toml @@ -81,6 +81,21 @@ to = "/.netlify/functions/trace-route" status = 301 force = true +[[redirects]] + from = "/get-carbon" + to = "/.netlify/functions/get-carbon" + status = 301 + force = true +[[redirects]] + from = "/site-features" + to = "/.netlify/functions/site-features" + status = 301 + force = true +[[redirects]] + from = "/dns-sec" + to = "/.netlify/functions/dns-sec" + status = 301 + force = true # For router history mode, ensure pages land on index [[redirects]] diff --git a/server/lambda/check-ports.js b/server/lambda/check-ports.js index 63df738..ae698bb 100644 --- a/server/lambda/check-ports.js +++ b/server/lambda/check-ports.js @@ -1,7 +1,12 @@ const net = require('net'); // A list of commonly used ports. -const PORTS = [21, 22, 25, 80, 110, 143, 443, 587, 993, 995, 3306, 3389, 5900, 8080]; +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 => { diff --git a/server/lambda/dns-sec.js b/server/lambda/dns-sec.js new file mode 100644 index 0000000..0a5b54d --- /dev/null +++ b/server/lambda/dns-sec.js @@ -0,0 +1,69 @@ +const https = require('https'); +const urlModule = require('url'); + +exports.handler = async function(event, context) { + let { url } = event.queryStringParameters; + + if (!url) { + return { + statusCode: 400, + body: JSON.stringify({ error: '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)); + }); + }); + + req.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 { + statusCode: 500, + body: JSON.stringify({ error: `Error fetching ${type} record: ${error.message}` }), + }; + } + } + + return { + statusCode: 200, + body: JSON.stringify(records), + }; +}; diff --git a/server/lambda/get-carbon.js b/server/lambda/get-carbon.js new file mode 100644 index 0000000..16c001c --- /dev/null +++ b/server/lambda/get-carbon.js @@ -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({ message: '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({ message: `Error: ${error.message}` }), + }; + } +}; diff --git a/server/lambda/site-features.js b/server/lambda/site-features.js new file mode 100644 index 0000000..12196b6 --- /dev/null +++ b/server/lambda/site-features.js @@ -0,0 +1,44 @@ +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 = 444) => { + return { + statusCode: statusCode, + body: JSON.stringify({ error: message }), + }; + }; + + if (!url) { + return errorResponse('url query parameter is required', 400); + } + + const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`; + + return new Promise((resolve, reject) => { + https.get(apiUrl, (res) => { + let data = ''; + + // A chunk of data has been received. + res.on('data', (chunk) => { + data += chunk; + }); + + // The whole response has been received. + res.on('end', () => { + if(res.statusCode !== 200){ + resolve(errorResponse(`Request failed with status code: ${res.statusCode}`)); + } else { + resolve({ + statusCode: 200, + body: data, + }); + } + }); + }).on('error', (err) => { + resolve(errorResponse(`Error making request: ${err.message}`, 500)); + }); + }); +}; diff --git a/server/lambda/ssl-check.js b/server/lambda/ssl-check.js index 8c95b02..0855e7d 100644 --- a/server/lambda/ssl-check.js +++ b/server/lambda/ssl-check.js @@ -1,28 +1,45 @@ const https = require('https'); +const { stringify } = require('flatted'); exports.handler = async function (event, context) { const { url } = event.queryStringParameters; + const errorResponse = (message, statusCode = 444) => { + return { + statusCode: statusCode, + body: JSON.stringify({ error: message }), + }; + }; + if (!url) { return { statusCode: 400, - body: 'url query parameter is required', + body: errorResponse('url query parameter is required'), }; } return new Promise((resolve, reject) => { const req = https.request(url, res => { - resolve({ - statusCode: 200, - body: JSON.stringify(res.socket.getPeerCertificate()), - }); + + // Check if the SSL handshake was authorized + if (!res.socket.authorized) { + resolve(errorResponse(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`)); + } else { + const cert = res.socket.getPeerCertificate(true); + if (!cert || Object.keys(cert).length === 0) { + resolve(errorResponse("No certificate presented by the server.")); + } else { + resolve({ + statusCode: 200, + body: stringify(cert), + }); + } + } }); req.on('error', (error) => { - resolve({ - statusCode: 500, - body: `Error fetching site certificate: ${error.message}`, - }); + resolve( + errorResponse(`Error fetching site certificate: ${error.message}`, 500)); }); req.end();