diff --git a/.env b/.env index 7e40b56..297d271 100644 --- a/.env +++ b/.env @@ -24,3 +24,4 @@ REACT_APP_WHO_API_KEY='' # API_CORS_ORIGIN='*' # Enable CORS, by setting your allowed hostname(s) here # API_ENABLE_RATE_LIMIT='true' # Enable rate limiting for the API # REACT_APP_API_ENDPOINT='/api' # The endpoint for the API (can be local or remote) +# ENABLE_ANALYTICS='false' # Enable Plausible hit counter for the frontend diff --git a/.gitignore b/.gitignore index 9c077d8..d818df7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,12 +9,14 @@ /build/ # ------------------------ -# DEPLOYMENT +# BUILT FILES # ------------------------ +dist/ .vercel/ .netlify/ .webpack/ .serverless/ +.astro/ # ------------------------ # DEPENDENCIES diff --git a/Dockerfile b/Dockerfile index 7a95405..a01ddd8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Specify the Node.js version to use -ARG NODE_VERSION=16 +ARG NODE_VERSION=21 # Specify the Debian version to use, the default is "bullseye" ARG DEBIAN_VERSION=bullseye @@ -30,7 +30,7 @@ COPY package.json yarn.lock ./ # Run yarn install to install dependencies and clear yarn cache RUN apt-get update && \ - yarn install --production --frozen-lockfile --network-timeout 100000 && \ + yarn install --frozen-lockfile --network-timeout 100000 && \ rm -rf /app/node_modules/.cache # Copy all files to working directory @@ -59,4 +59,4 @@ EXPOSE ${PORT:-3000} ENV CHROME_PATH='/usr/bin/chromium' # Define the command executed when the container starts and start the server.js of the Node.js application -CMD ["yarn", "serve"] \ No newline at end of file +CMD ["yarn", "start"] diff --git a/api/_common/middleware.js b/api/_common/middleware.js index 6eb4ca9..b00f3b6 100644 --- a/api/_common/middleware.js +++ b/api/_common/middleware.js @@ -124,4 +124,8 @@ const commonMiddleware = (handler) => { return nativeMode ? vercelHandler : netlifyHandler; }; -module.exports = commonMiddleware; +if (PLATFORM === 'NETLIFY') { + module.exports = commonMiddleware; +} + +export default commonMiddleware; diff --git a/api/archives.js b/api/archives.js index 6c7cb48..7c6ae99 100644 --- a/api/archives.js +++ b/api/archives.js @@ -1,5 +1,5 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; const convertTimestampToDate = (timestamp) => { const [year, month, day, hour, minute, second] = [ @@ -46,7 +46,7 @@ const getScanFrequency = (firstScan, lastScan, totalScans, changeCount) => { }; }; -const getWaybackData = async (url) => { +const wayBackHandler = async (url) => { const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${url}&output=json&fl=timestamp,statuscode,digest,length,offset`; try { @@ -80,5 +80,5 @@ const getWaybackData = async (url) => { } }; -module.exports = middleware(getWaybackData); -module.exports.handler = middleware(getWaybackData); +export const handler = middleware(wayBackHandler); +export default handler; diff --git a/api/block-lists.js b/api/block-lists.js index 82f6b1c..34d46a1 100644 --- a/api/block-lists.js +++ b/api/block-lists.js @@ -1,6 +1,6 @@ -const dns = require('dns'); -const { URL } = require('url'); -const middleware = require('./_common/middleware'); +import dns from 'dns'; +import { URL } from 'url'; +import middleware from './_common/middleware.js'; const DNS_SERVERS = [ { name: 'AdGuard', ip: '176.103.130.130' }, @@ -94,12 +94,12 @@ const checkDomainAgainstDnsServers = async (domain) => { return results; }; -const handler = async (url) => { +export const blockListHandler = async (url) => { const domain = new URL(url).hostname; const results = await checkDomainAgainstDnsServers(domain); return { blocklists: results }; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(blockListHandler); +export default handler; diff --git a/api/carbon.js b/api/carbon.js index a2c5210..7db750c 100644 --- a/api/carbon.js +++ b/api/carbon.js @@ -1,7 +1,7 @@ -const https = require('https'); -const middleware = require('./_common/middleware'); +import https from 'https'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const carbonHandler = async (url) => { // First, get the size of the website's HTML const getHtmlSize = (url) => new Promise((resolve, reject) => { @@ -48,5 +48,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(carbonHandler); +export default handler; diff --git a/api/cookies.js b/api/cookies.js index 1f10470..a86ccb6 100644 --- a/api/cookies.js +++ b/api/cookies.js @@ -1,6 +1,6 @@ -const axios = require('axios'); -const puppeteer = require('puppeteer'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import puppeteer from 'puppeteer'; +import middleware from './_common/middleware.js'; const getPuppeteerCookies = async (url) => { const browser = await puppeteer.launch({ @@ -21,7 +21,7 @@ const getPuppeteerCookies = async (url) => { } }; -const handler = async (url) => { +const cookieHandler = async (url) => { let headerCookies = null; let clientCookies = null; @@ -54,5 +54,5 @@ const handler = async (url) => { return { headerCookies, clientCookies }; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(cookieHandler); +export default handler; diff --git a/api/dns-server.js b/api/dns-server.js index c9af720..29ac34f 100644 --- a/api/dns-server.js +++ b/api/dns-server.js @@ -1,9 +1,8 @@ -const dns = require('dns'); -const dnsPromises = dns.promises; -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import { promises as dnsPromises, lookup } from 'dns'; +import axios from 'axios'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const dnsHandler = async (url) => { try { const domain = url.replace(/^(?:https?:\/\/)?/i, ""); const addresses = await dnsPromises.resolve4(domain); @@ -41,5 +40,7 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); + +export const handler = middleware(dnsHandler); +export default handler; + diff --git a/api/dns.js b/api/dns.js index 34d6c02..e01a8a8 100644 --- a/api/dns.js +++ b/api/dns.js @@ -1,8 +1,8 @@ -const dns = require('dns'); -const util = require('util'); -const middleware = require('./_common/middleware'); +import dns from 'dns'; +import util from 'util'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const dnsHandler = async (url) => { let hostname = url; // Handle URLs by extracting hostname @@ -51,5 +51,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(dnsHandler); +export default handler; diff --git a/api/dnssec.js b/api/dnssec.js index afaa955..21f7510 100644 --- a/api/dnssec.js +++ b/api/dnssec.js @@ -1,7 +1,7 @@ -const https = require('https'); -const middleware = require('./_common/middleware'); // Make sure this path is correct +import https from 'https'; +import middleware from './_common/middleware.js'; -const handler = async (domain) => { +const dnsSecHandler = async (domain) => { const dnsTypes = ['DNSKEY', 'DS', 'RRSIG']; const records = {}; @@ -53,5 +53,5 @@ const handler = async (domain) => { return records; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); \ No newline at end of file +export const handler = middleware(dnsSecHandler); +export default handler; diff --git a/api/features.js b/api/features.js index 67e4c81..148a854 100644 --- a/api/features.js +++ b/api/features.js @@ -1,7 +1,7 @@ -const https = require('https'); -const middleware = require('./_common/middleware'); +import https from 'https'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const featuresHandler = async (url) => { const apiKey = process.env.BUILT_WITH_API_KEY; if (!url) { @@ -45,5 +45,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(featuresHandler); +export default handler; diff --git a/api/firewall.js b/api/firewall.js index a37ed61..bc46ba6 100644 --- a/api/firewall.js +++ b/api/firewall.js @@ -1,5 +1,5 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; const hasWaf = (waf) => { return { @@ -7,7 +7,7 @@ const hasWaf = (waf) => { } }; -const handler = async (url) => { +const firewallHandler = async (url) => { const fullUrl = url.startsWith('http') ? url : `http://${url}`; try { @@ -110,5 +110,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(firewallHandler); +export default handler; diff --git a/api/get-ip.js b/api/get-ip.js index 303a0f1..aa7d058 100644 --- a/api/get-ip.js +++ b/api/get-ip.js @@ -1,5 +1,5 @@ -const dns = require('dns'); -const middleware = require('./_common/middleware'); +import dns from 'dns'; +import middleware from './_common/middleware.js'; const lookupAsync = (address) => { return new Promise((resolve, reject) => { @@ -13,11 +13,11 @@ const lookupAsync = (address) => { }); }; -const handler = async (url) => { +const ipHandler = async (url) => { const address = url.replaceAll('https://', '').replaceAll('http://', ''); return await lookupAsync(address); }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(ipHandler); +export default handler; diff --git a/api/headers.js b/api/headers.js index 84b179f..13c2792 100644 --- a/api/headers.js +++ b/api/headers.js @@ -1,7 +1,7 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; -const handler = async (url, event, context) => { +const headersHandler = async (url, event, context) => { try { const response = await axios.get(url, { validateStatus: function (status) { @@ -15,5 +15,5 @@ const handler = async (url, event, context) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(headersHandler); +export default handler; diff --git a/api/hsts.js b/api/hsts.js index d06bf1c..a98d4c0 100644 --- a/api/hsts.js +++ b/api/hsts.js @@ -1,7 +1,7 @@ -const https = require('https'); -const middleware = require('./_common/middleware'); +import https from 'https'; +import middleware from './_common/middleware.js'; -const handler = async (url, event, context) => { +const hstsHandler = async (url, event, context) => { const errorResponse = (message, statusCode = 500) => { return { statusCode: statusCode, @@ -45,6 +45,5 @@ const handler = async (url, event, context) => { }); }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); - +export const handler = middleware(hstsHandler); +export default handler; diff --git a/api/http-security.js b/api/http-security.js index 2f8370a..764cb93 100644 --- a/api/http-security.js +++ b/api/http-security.js @@ -1,7 +1,7 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const httpsSecHandler = async (url) => { const fullUrl = url.startsWith('http') ? url : `http://${url}`; try { @@ -22,5 +22,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(httpsSecHandler); +export default handler; diff --git a/api/legacy-rank.js b/api/legacy-rank.js index 19dd1bd..301cc2c 100644 --- a/api/legacy-rank.js +++ b/api/legacy-rank.js @@ -1,8 +1,8 @@ -const axios = require('axios'); -const unzipper = require('unzipper'); -const csv = require('csv-parser'); -const fs = require('fs'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import unzipper from 'unzipper'; +import csv from 'csv-parser'; +import fs from 'fs'; +import middleware from './_common/middleware.js'; // Should also work with the following sources: // https://www.domcop.com/files/top/top10milliondomains.csv.zip @@ -14,7 +14,7 @@ const middleware = require('./_common/middleware'); const FILE_URL = 'https://s3-us-west-1.amazonaws.com/umbrella-static/top-1m.csv.zip'; const TEMP_FILE_PATH = '/tmp/top-1m.csv'; -const handler = async (url) => { +const rankHandler = async (url) => { let domain = null; try { @@ -66,6 +66,5 @@ return new Promise((resolve, reject) => { }); }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); - +export const handler = middleware(rankHandler); +export default handler; diff --git a/api/linked-pages.js b/api/linked-pages.js index 60dd88f..e576d3a 100644 --- a/api/linked-pages.js +++ b/api/linked-pages.js @@ -1,9 +1,9 @@ -const axios = require('axios'); -const cheerio = require('cheerio'); -const urlLib = require('url'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import cheerio from 'cheerio'; +import urlLib from 'url'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const linkedPagesHandler = async (url) => { const response = await axios.get(url); const html = response.data; const $ = cheerio.load(html); @@ -33,17 +33,17 @@ const handler = async (url) => { if (internalLinks.length === 0 && externalLinks.length === 0) { return { statusCode: 400, - body: JSON.stringify({ + body: { skipped: 'No internal or external links found. ' + 'This may be due to the website being dynamically rendered, using a client-side framework (like React), and without SSR enabled. ' + 'That would mean that the static HTML returned from the HTTP request doesn\'t contain any meaningful content for Web-Check to analyze. ' + 'You can rectify this by using a headless browser to render the page instead.', - }), + }, }; } return { internal: internalLinks, external: externalLinks }; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(linkedPagesHandler); +export default handler; diff --git a/api/mail-config.js b/api/mail-config.js index 402fd67..1c8aff8 100644 --- a/api/mail-config.js +++ b/api/mail-config.js @@ -1,9 +1,10 @@ -const middleware = require('./_common/middleware'); +import dns from 'dns'; +import URL from 'url-parse'; +import middleware from './_common/middleware.js'; -const dns = require('dns').promises; -const URL = require('url-parse'); +// TODO: Fix. -const handler = async (url, event, context) => { +const mailConfigHandler = async (url, event, context) => { try { const domain = new URL(url).hostname || new URL(url).pathname; @@ -71,11 +72,11 @@ const handler = async (url, event, context) => { } else { return { statusCode: 500, - body: JSON.stringify({ error: error.message }), + body: { error: error.message }, }; } } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(mailConfigHandler); +export default handler; diff --git a/api/ports.js b/api/ports.js index ea188bd..6cef48d 100644 --- a/api/ports.js +++ b/api/ports.js @@ -1,5 +1,5 @@ -const net = require('net'); -const middleware = require('./_common/middleware'); +import net from 'net'; +import middleware from './_common/middleware.js'; // A list of commonly used ports. const PORTS = [ @@ -34,7 +34,7 @@ async function checkPort(port, domain) { }); } -const handler = async (url, event, context) => { +const portsHandler = async (url, event, context) => { const domain = url.replace(/(^\w+:|^)\/\//, ''); const delay = ms => new Promise(res => setTimeout(res, ms)); @@ -84,5 +84,5 @@ const errorResponse = (message, statusCode = 444) => { return { error: message }; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(portsHandler); +export default handler; diff --git a/api/quality.js b/api/quality.js index a49afe1..1d123c1 100644 --- a/api/quality.js +++ b/api/quality.js @@ -1,7 +1,7 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; -const handler = async (url, event, context) => { +const qualityHandler = async (url, event, context) => { const apiKey = process.env.GOOGLE_CLOUD_API_KEY; if (!apiKey) { @@ -18,5 +18,5 @@ const handler = async (url, event, context) => { return (await axios.get(endpoint)).data; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(qualityHandler); +export default handler; diff --git a/api/rank.js b/api/rank.js index 1947185..d2936ee 100644 --- a/api/rank.js +++ b/api/rank.js @@ -1,7 +1,7 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const rankHandler = async (url) => { const domain = url ? new URL(url).hostname : null; if (!domain) throw new Error('Invalid URL'); @@ -21,6 +21,6 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(rankHandler); +export default handler; diff --git a/api/redirects.js b/api/redirects.js index cd815b1..af7016e 100644 --- a/api/redirects.js +++ b/api/redirects.js @@ -1,9 +1,10 @@ -const handler = async (url) => { - const redirects = [url]; - const got = await import('got'); +import got from 'got'; +import middleware from './_common/middleware.js'; +const redirectsHandler = async (url) => { + const redirects = [url]; try { - await got.default(url, { + await got(url, { followRedirect: true, maxRedirects: 12, hooks: { @@ -23,7 +24,5 @@ const handler = async (url) => { } }; -const middleware = require('./_common/middleware'); - -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(redirectsHandler); +export default handler; diff --git a/api/robots-txt.js b/api/robots-txt.js index 9f008e3..afe6f52 100644 --- a/api/robots-txt.js +++ b/api/robots-txt.js @@ -1,5 +1,5 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; const parseRobotsTxt = (content) => { const lines = content.split('\n'); @@ -31,7 +31,7 @@ const parseRobotsTxt = (content) => { return { robots: rules }; } -const handler = async function(url) { +const robotsHandler = async function(url) { let parsedURL; try { parsedURL = new URL(url); @@ -67,5 +67,5 @@ const handler = async function(url) { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(robotsHandler); +export default handler; diff --git a/api/screenshot.js b/api/screenshot.js index 9345f14..f6d33bf 100644 --- a/api/screenshot.js +++ b/api/screenshot.js @@ -1,8 +1,8 @@ -const puppeteer = require('puppeteer-core'); -const chromium = require('chrome-aws-lambda'); -const middleware = require('./_common/middleware'); +import puppeteer from 'puppeteer-core'; +import chromium from 'chrome-aws-lambda'; +import middleware from './_common/middleware.js'; -const handler = async (targetUrl) => { +const screenshotHandler = async (targetUrl) => { if (!targetUrl) { throw new Error('URL is missing from queryStringParameters'); @@ -58,5 +58,5 @@ const handler = async (targetUrl) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(screenshotHandler); +export default handler; diff --git a/api/security-txt.js b/api/security-txt.js index dd08ca0..08cdcff 100644 --- a/api/security-txt.js +++ b/api/security-txt.js @@ -1,6 +1,8 @@ -const { https } = require('follow-redirects'); -const { URL } = require('url'); -const middleware = require('./_common/middleware'); +import { URL } from 'url'; +import followRedirects from 'follow-redirects'; +import middleware from './_common/middleware.js'; + +const { https } = followRedirects; const SECURITY_TXT_PATHS = [ '/security.txt', @@ -38,7 +40,7 @@ const isPgpSigned = (result) => { return false; }; -const handler = async (urlParam) => { +const securityTxtHandler = async (urlParam) => { let url; try { @@ -90,5 +92,5 @@ async function fetchSecurityTxt(baseURL, path) { }); } -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(securityTxtHandler); +export default handler; diff --git a/api/sitemap.js b/api/sitemap.js index 4d6c01d..282bf77 100644 --- a/api/sitemap.js +++ b/api/sitemap.js @@ -1,9 +1,8 @@ -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import xml2js from 'xml2js'; +import middleware from './_common/middleware.js'; -const axios = require('axios'); -const xml2js = require('xml2js'); - -const handler = async (url) => { +const sitemapHandler = async (url) => { let sitemapUrl = `${url}/sitemap.xml`; const hardTimeOut = 5000; @@ -49,6 +48,6 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(sitemapHandler); +export default handler; diff --git a/api/social-tags.js b/api/social-tags.js index f8b38a3..e2c6bb4 100644 --- a/api/social-tags.js +++ b/api/social-tags.js @@ -1,9 +1,8 @@ -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import cheerio from 'cheerio'; +import middleware from './_common/middleware.js'; -const axios = require('axios'); -const cheerio = require('cheerio'); - -const handler = async (url) => { +const socialTagsHandler = async (url) => { // Check if url includes protocol if (!url.startsWith('http://') && !url.startsWith('https://')) { @@ -61,5 +60,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(socialTagsHandler); +export default handler; diff --git a/api/ssl.js b/api/ssl.js index 1eb5c55..6f58be0 100644 --- a/api/ssl.js +++ b/api/ssl.js @@ -1,7 +1,7 @@ -const tls = require('tls'); -const middleware = require('./_common/middleware'); +import tls from 'tls'; +import middleware from './_common/middleware.js'; -const handler = async (urlString) => { +const sslHandler = async (urlString) => { try { const parsedUrl = new URL(urlString); const options = { @@ -40,5 +40,5 @@ const handler = async (urlString) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(sslHandler); +export default handler; diff --git a/api/status.js b/api/status.js index 2a70b66..0fcc578 100644 --- a/api/status.js +++ b/api/status.js @@ -1,8 +1,8 @@ -const https = require('https'); -const { performance, PerformanceObserver } = require('perf_hooks'); -const middleware = require('./_common/middleware'); +import https from 'https'; +import { performance, PerformanceObserver } from 'perf_hooks'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const statusHandler = async (url) => { if (!url) { throw new Error('You must provide a URL query parameter!'); } @@ -55,5 +55,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(statusHandler); +export default handler; diff --git a/api/tech-stack.js b/api/tech-stack.js index d649d2b..dcf0b8a 100644 --- a/api/tech-stack.js +++ b/api/tech-stack.js @@ -1,7 +1,7 @@ -const Wappalyzer = require('wappalyzer'); -const middleware = require('./_common/middleware'); +import Wappalyzer from 'wappalyzer'; +import middleware from './_common/middleware.js'; -const handler = async (url) => { +const techStackHandler = async (url) => { const options = {}; const wappalyzer = new Wappalyzer(options); @@ -27,5 +27,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(techStackHandler); +export default handler; diff --git a/api/threats.js b/api/threats.js index afff98c..de95932 100644 --- a/api/threats.js +++ b/api/threats.js @@ -1,6 +1,6 @@ -const axios = require('axios'); -const xml2js = require('xml2js'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import xml2js from 'xml2js'; +import middleware from './_common/middleware.js'; const getGoogleSafeBrowsingResult = async (url) => { try { @@ -84,7 +84,7 @@ const getCloudmersiveResult = async (url) => { } }; -const handler = async (url) => { +const threatsHandler = async (url) => { try { const urlHaus = await getUrlHausResult(url); const phishTank = await getPhishTankResult(url); @@ -99,5 +99,5 @@ const handler = async (url) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(threatsHandler); +export default handler; diff --git a/api/tls.js b/api/tls.js index 09aeaeb..5249f6f 100644 --- a/api/tls.js +++ b/api/tls.js @@ -1,9 +1,9 @@ -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import axios from 'axios'; +import middleware from './_common/middleware.js'; const MOZILLA_TLS_OBSERVATORY_API = 'https://tls-observatory.services.mozilla.com/api/v1'; -const handler = async (url) => { +const tlsHandler = async (url) => { try { const domain = new URL(url).hostname; const scanResponse = await axios.post(`${MOZILLA_TLS_OBSERVATORY_API}/scan?target=${domain}`); @@ -12,18 +12,18 @@ const handler = async (url) => { if (typeof scanId !== 'number') { return { statusCode: 500, - body: JSON.stringify({ error: 'Failed to get scan_id from TLS Observatory' }), + body: { error: 'Failed to get scan_id from TLS Observatory' }, }; } const resultResponse = await axios.get(`${MOZILLA_TLS_OBSERVATORY_API}/results?id=${scanId}`); return { statusCode: 200, - body: JSON.stringify(resultResponse.data), + body: resultResponse.data, }; } catch (error) { return { error: error.message }; } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(tlsHandler); +export default handler; diff --git a/api/trace-route.js b/api/trace-route.js index 2b71e48..50cd3b1 100644 --- a/api/trace-route.js +++ b/api/trace-route.js @@ -1,8 +1,8 @@ -const traceroute = require('traceroute'); -const url = require('url'); -const middleware = require('./_common/middleware'); +import url from 'url'; +import traceroute from 'traceroute'; +import middleware from './_common/middleware.js'; -const handler = async (urlString, context) => { +const traceRouteHandler = async (urlString, context) => { // Parse the URL and get the hostname const urlObject = url.parse(urlString); const host = urlObject.hostname; @@ -28,5 +28,5 @@ const handler = async (urlString, context) => { }; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(traceRouteHandler); +export default handler; diff --git a/api/txt-records.js b/api/txt-records.js index ed88dd7..73104f1 100644 --- a/api/txt-records.js +++ b/api/txt-records.js @@ -1,7 +1,7 @@ -const dns = require('dns').promises; -const middleware = require('./_common/middleware'); +import dns from 'dns/promises'; +import middleware from './_common/middleware.js'; -const handler = async (url, event, context) => { +const txtRecordHandler = async (url, event, context) => { try { const parsedUrl = new URL(url); @@ -29,5 +29,5 @@ const handler = async (url, event, context) => { } }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(txtRecordHandler); +export default handler; diff --git a/api/whois.js b/api/whois.js index 7224b22..8daba8a 100644 --- a/api/whois.js +++ b/api/whois.js @@ -1,7 +1,7 @@ -const net = require('net'); -const psl = require('psl'); -const axios = require('axios'); -const middleware = require('./_common/middleware'); +import net from 'net'; +import psl from 'psl'; +import axios from 'axios'; +import middleware from './_common/middleware.js'; const getBaseDomain = (url) => { let protocol = ''; @@ -83,7 +83,7 @@ const fetchFromMyAPI = async (hostname) => { } }; -const handler = async (url) => { +const whoisHandler = async (url) => { if (!url.startsWith('http://') && !url.startsWith('https://')) { url = 'http://' + url; } @@ -106,6 +106,6 @@ const handler = async (url) => { }; }; -module.exports = middleware(handler); -module.exports.handler = middleware(handler); +export const handler = middleware(whoisHandler); +export default handler; diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..1790e63 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,79 @@ +import { defineConfig } from 'astro/config'; + +// Integrations +import svelte from '@astrojs/svelte'; +import react from "@astrojs/react"; +import partytown from '@astrojs/partytown'; +import sitemap from '@astrojs/sitemap'; + +// Adapters +import vercelAdapter from '@astrojs/vercel/serverless'; +import netlifyAdapter from '@astrojs/netlify'; +import nodeAdapter from '@astrojs/node'; +import cloudflareAdapter from '@astrojs/cloudflare'; + +// Helper function to unwrap both Vite and Node environment variables +const unwrapEnvVar = (varName, fallbackValue) => { + const classicEnvVar = process?.env && process.env[varName]; + const viteEnvVar = import.meta.env[varName]; + return classicEnvVar || viteEnvVar || fallbackValue; +} + +// Determine the deploy target (vercel, netlify, cloudflare, node) +const deployTarget = unwrapEnvVar('PLATFORM', 'node').toLowerCase(); + +// Determine the output mode (server, hybrid or static) +const output = unwrapEnvVar('OUTPUT', 'hybrid'); + +// The FQDN of where the site is hosted (used for sitemaps & canonical URLs) +const site = unwrapEnvVar('SITE_URL', 'https://web-check.xyz'); + +// The base URL of the site (if serving from a subdirectory) +const base = unwrapEnvVar('BASE_URL', '/'); + +// Should run the app in boss-mode (requires extra configuration) +const isBossServer = unwrapEnvVar('BOSS_SERVER', false); + +// Initialize Astro integrations +const integrations = [svelte(), react(), partytown(), sitemap()]; + +// Set the appropriate adapter, based on the deploy target +function getAdapter(target) { + switch(target) { + case 'vercel': + return vercelAdapter(); + case 'netlify': + return netlifyAdapter(); + case 'cloudflare': + return cloudflareAdapter(); + case 'node': + return nodeAdapter({ mode: 'middleware' }); + default: + throw new Error(`Unsupported deploy target: ${target}`); + } +} +const adapter = getAdapter(deployTarget); + +// Print build information to console +console.log( + `\n\x1b[1m\x1b[35m Preparing to start build of Web Check.... \x1b[0m\n`, + `\x1b[35m\x1b[2mCompiling for "${deployTarget}" using "${output}" mode, ` + + `to deploy to "${site}" at "${base}"\x1b[0m\n`, + `\x1b[2m\x1b[36m🛟 For documentation and support, visit the GitHub repo: ` + + `https://github.com/lissy93/web-check \n`, + `💖 Found Web-Check useful? Consider sponsoring us on GitHub ` + + `to help fund maintenance & development.\x1b[0m\n`, +); + +const redirects = { + '/about': '/check/about', +}; + +// Skip the marketing homepage for self-hosted users +if (!isBossServer && isBossServer !== true) { + redirects['/'] = '/check'; +} + +// Export Astro configuration +export default defineConfig({ output, base, integrations, site, adapter, redirects }); + diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..a529b54 --- /dev/null +++ b/fly.toml @@ -0,0 +1,17 @@ +app = 'web-check' +primary_region = 'lhr' + +[build] + +[http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = true + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + +[[vm]] + memory = '1gb' + cpu_kind = 'shared' + cpus = 1 diff --git a/netlify.toml b/netlify.toml index 3c9dab1..136485e 100644 --- a/netlify.toml +++ b/netlify.toml @@ -2,11 +2,13 @@ [build] base = "/" command = "yarn build" - publish = "build" + publish = "dist" functions = "api" # Environmental variables and optional secrets [build.environment] + PLATFORM = "netlify" + PUBLIC_API_ENDPOINT = "/.netlify/functions" # Build configuration env vars (uncomment if you want to conigure these) # CI="false" # Set CI to false, to prevent warnings from exiting the build # CHROME_PATH='/usr/bin/chromium' # Path to Chromium binary @@ -25,12 +27,6 @@ status = 301 force = true -# For router history mode, ensure pages land on index -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 - # Plugins [[plugins]] package = "netlify-plugin-chromium" diff --git a/package.json b/package.json index 2efe12c..4a39aca 100644 --- a/package.json +++ b/package.json @@ -1,103 +1,73 @@ { "name": "web-check", - "version": "1.1.2", - "private": false, - "description": "All-in-one OSINT tool for analyzing any website", - "repository": "github:lissy93/web-check", + "type": "module", + "version": "0.0.1", "homepage": "https://web-check.xyz", - "license": "MIT", - "author": { - "name": "Alicia Sykes", - "email": "alicia@omg.lol" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Lissy93" - }, "scripts": { - "dev": "netlify dev", - "serve": "node server", - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", + "start": "node server", + "start-pm": "pm2 start server.js -i max", + "build": "astro check && astro build", "dev:vercel": "PLATFORM='vercel' npx vercel dev", "dev:netlify": "PLATFORM='netlify' npx netlify dev", - "dev:node": "npx concurrently --names \"frontend,backend\" \"REACT_APP_API_ENDPOINT='http://localhost:3001/api' npx react-scripts start\" \"PLATFORM='node' DISABLE_GUI='true' PORT='3001' API_CORS_ORIGIN='*' npx nodemon server\"" + "dev:api": "DISABLE_GUI='true' PORT='3001' nodemon server", + "dev:astro": "PUBLIC_API_ENDPOINT=http://localhost:3001/api astro dev", + "dev": "concurrently -c magenta,cyan -n backend,frontend 'yarn dev:api' 'yarn dev:astro'" }, "dependencies": { - "@netlify/functions": "^1.6.0", - "@sentry/react": "^7.60.0", - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.4.3", - "@types/jest": "^29.5.3", - "@types/node": "^20.4.4", - "@types/react": "^18.2.15", - "@types/react-dom": "^18.2.7", - "@types/react-router-dom": "^5.3.3", - "@types/react-simple-maps": "^3.0.0", - "@types/styled-components": "^5.1.26", - "axios": "^1.4.0", + "@astrojs/check": "^0.5.10", + "@astrojs/react": "^3.3.2", + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-brands-svg-icons": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/svelte-fontawesome": "^0.2.2", + "@types/react": "^18.3.1", + "@types/react-dom": "^18.3.0", + "astro": "^4.7.1", + "axios": "^1.4.8", "cheerio": "^1.0.0-rc.12", "chrome-aws-lambda": "^10.1.0", "chromium": "^3.0.3", "connect-history-api-fallback": "^2.0.0", "cors": "^2.8.5", "csv-parser": "^3.0.0", - "dotenv": "^16.3.1", + "dotenv": "^16.4.5", + "express": "^4.19.2", "express-rate-limit": "^7.2.0", - "flatted": "^3.2.7", - "follow-redirects": "^1.15.2", - "got": "^13.0.0", - "jest-styled-components": "^7.1.1", - "netlify-cli": "^15.9.1", - "perf_hooks": "^0.0.1", + "framer-motion": "^11.2.6", + "got": "^14.2.1", + "pm2": "^5.3.1", "psl": "^1.9.0", - "puppeteer": "^20.9.0", - "puppeteer-core": "^21.0.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "puppeteer": "^22.8.0", + "puppeteer-core": "^22.8.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-masonry-css": "^1.0.16", - "react-router-dom": "^6.14.2", - "react-scripts": "5.0.1", + "react-router-dom": "^6.23.0", "react-simple-maps": "^3.0.0", - "react-toastify": "^9.1.3", - "recharts": "^2.7.3", - "styled-components": "^6.0.5", + "react-toastify": "^10.0.5", + "recharts": "^2.12.6", + "svelte": "^4.2.17", "traceroute": "^1.0.0", - "typescript": "^5.1.6", - "unzipper": "^0.10.14", + "typescript": "^5.4.5", + "unzipper": "^0.11.5", + "url-parse": "^1.5.10", "wappalyzer": "^6.10.65", - "web-vitals": "^3.4.0", "xml2js": "^0.6.2" }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "compilerOptions": { - "allowJs": true, - "outDir": "./dist" - }, "devDependencies": { - "serverless-domain-manager": "^7.1.1", - "serverless-offline": "^12.0.4", - "serverless-webpack": "^5.13.0", - "webpack": "^5.88.2", - "webpack-node-externals": "^3.0.0" + "@astrojs/cloudflare": "^10.2.5", + "@astrojs/netlify": "^5.2.0", + "@astrojs/node": "^8.2.5", + "@astrojs/partytown": "^2.1.0", + "@astrojs/sitemap": "^3.1.4", + "@astrojs/svelte": "^5.4.0", + "@astrojs/ts-plugin": "^1.6.1", + "@astrojs/vercel": "^7.5.4", + "concurrently": "^8.2.2", + "nodemon": "^3.1.0", + "sass": "^1.77.1" } } diff --git a/public/assets/badges/dockerhub.svg b/public/assets/badges/dockerhub.svg new file mode 100644 index 0000000..8463f32 --- /dev/null +++ b/public/assets/badges/dockerhub.svg @@ -0,0 +1 @@ +DockerHub: Lissy93/Web CheckDockerHubLissy93/Web Check \ No newline at end of file diff --git a/public/assets/badges/github.svg b/public/assets/badges/github.svg new file mode 100644 index 0000000..a6bf455 --- /dev/null +++ b/public/assets/badges/github.svg @@ -0,0 +1 @@ +GitHub: Lissy93/Web CheckGitHubLissy93/Web Check \ No newline at end of file diff --git a/public/assets/badges/sponsor.svg b/public/assets/badges/sponsor.svg new file mode 100644 index 0000000..790c486 --- /dev/null +++ b/public/assets/badges/sponsor.svg @@ -0,0 +1 @@ +Sponsor: Alicia SykesSponsorAlicia Sykes \ No newline at end of file diff --git a/public/assets/badges/webcheck.svg b/public/assets/badges/webcheck.svg new file mode 100644 index 0000000..a5f7287 --- /dev/null +++ b/public/assets/badges/webcheck.svg @@ -0,0 +1 @@ +Website: web-check.zyzWebsitewebcheck.zyz diff --git a/public/assets/images/background-dots.svg b/public/assets/images/background-dots.svg new file mode 100644 index 0000000..1ec8456 --- /dev/null +++ b/public/assets/images/background-dots.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/images/docker.svg b/public/assets/images/docker.svg new file mode 100644 index 0000000..b8a5f2c --- /dev/null +++ b/public/assets/images/docker.svg @@ -0,0 +1 @@ +Docker diff --git a/public/assets/images/fly.svg b/public/assets/images/fly.svg new file mode 100644 index 0000000..7800b6b --- /dev/null +++ b/public/assets/images/fly.svg @@ -0,0 +1 @@ + diff --git a/public/assets/images/github.svg b/public/assets/images/github.svg new file mode 100644 index 0000000..2334976 --- /dev/null +++ b/public/assets/images/github.svg @@ -0,0 +1 @@ +GitHub diff --git a/public/assets/images/netlify.svg b/public/assets/images/netlify.svg new file mode 100644 index 0000000..fd6acf8 --- /dev/null +++ b/public/assets/images/netlify.svg @@ -0,0 +1 @@ +Netlify diff --git a/public/assets/images/swagger.svg b/public/assets/images/swagger.svg new file mode 100644 index 0000000..38af79d --- /dev/null +++ b/public/assets/images/swagger.svg @@ -0,0 +1 @@ +Swagger diff --git a/public/assets/images/vercel.svg b/public/assets/images/vercel.svg new file mode 100644 index 0000000..7e9d62e --- /dev/null +++ b/public/assets/images/vercel.svg @@ -0,0 +1 @@ +Vercel diff --git a/public/assets/images/webauthn.svg b/public/assets/images/webauthn.svg new file mode 100644 index 0000000..209807b --- /dev/null +++ b/public/assets/images/webauthn.svg @@ -0,0 +1 @@ +WebAuthn diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..ed4ef50 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/public/fonts/Hubot-Sans/Hubot-Sans.ttf b/public/fonts/Hubot-Sans/Hubot-Sans.ttf new file mode 100644 index 0000000..2b74a40 Binary files /dev/null and b/public/fonts/Hubot-Sans/Hubot-Sans.ttf differ diff --git a/public/fonts/Hubot-Sans/Hubot-Sans.woff2 b/public/fonts/Hubot-Sans/Hubot-Sans.woff2 new file mode 100644 index 0000000..c828121 Binary files /dev/null and b/public/fonts/Hubot-Sans/Hubot-Sans.woff2 differ diff --git a/public/fonts/Hubot-Sans/LICENSE b/public/fonts/Hubot-Sans/LICENSE new file mode 100644 index 0000000..49e454d --- /dev/null +++ b/public/fonts/Hubot-Sans/LICENSE @@ -0,0 +1,93 @@ +Copyright (c) 2022, GitHub https://github.com/github/hubot-sans +with Reserved Font Name "Hubot Sans" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-Black.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-Black.otf new file mode 100644 index 0000000..1931b37 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-Black.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-BlackItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-BlackItalic.otf new file mode 100644 index 0000000..7d765d1 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-BlackItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-Bold.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-Bold.otf new file mode 100644 index 0000000..518391b Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-Bold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-BoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-BoldItalic.otf new file mode 100644 index 0000000..5504dab Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-BoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraBold.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraBold.otf new file mode 100644 index 0000000..1204363 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraBold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraBoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraBoldItalic.otf new file mode 100644 index 0000000..0a2fb07 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraBoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraLight.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraLight.otf new file mode 100644 index 0000000..c945af2 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraLight.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraLightItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraLightItalic.otf new file mode 100644 index 0000000..d73534f Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-ExtraLightItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-Italic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-Italic.otf new file mode 100644 index 0000000..03868be Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-Italic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-Light.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-Light.otf new file mode 100644 index 0000000..f142b34 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-Light.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-LightItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-LightItalic.otf new file mode 100644 index 0000000..b8c9b03 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-LightItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-Medium.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-Medium.otf new file mode 100644 index 0000000..9ba599b Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-Medium.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-MediumItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-MediumItalic.otf new file mode 100644 index 0000000..5bce903 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-MediumItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-Regular.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-Regular.otf new file mode 100644 index 0000000..8d2e29e Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-Regular.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-SemiBold.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-SemiBold.otf new file mode 100644 index 0000000..c17a1a7 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-SemiBold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSans-SemiBoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSans-SemiBoldItalic.otf new file mode 100644 index 0000000..29fed55 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSans-SemiBoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Black.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Black.otf new file mode 100644 index 0000000..9384628 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Black.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-BlackItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-BlackItalic.otf new file mode 100644 index 0000000..2093a14 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-BlackItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Bold.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Bold.otf new file mode 100644 index 0000000..9648ee7 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Bold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-BoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-BoldItalic.otf new file mode 100644 index 0000000..8be495b Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-BoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraBold.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraBold.otf new file mode 100644 index 0000000..735e71f Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraBold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraBoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraBoldItalic.otf new file mode 100644 index 0000000..b765fd8 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraBoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraLight.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraLight.otf new file mode 100644 index 0000000..f1f0bbc Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraLight.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraLightItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraLightItalic.otf new file mode 100644 index 0000000..aa1c2dc Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-ExtraLightItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Italic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Italic.otf new file mode 100644 index 0000000..c2c00d9 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Italic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Light.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Light.otf new file mode 100644 index 0000000..4ab8ac0 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Light.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-LightItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-LightItalic.otf new file mode 100644 index 0000000..d581fc8 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-LightItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Medium.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Medium.otf new file mode 100644 index 0000000..b708673 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Medium.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-MediumItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-MediumItalic.otf new file mode 100644 index 0000000..0a54597 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-MediumItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Regular.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Regular.otf new file mode 100644 index 0000000..f1c26f9 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-Regular.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-SemiBold.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-SemiBold.otf new file mode 100644 index 0000000..59681e9 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-SemiBold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-SemiBoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-SemiBoldItalic.otf new file mode 100644 index 0000000..24323d5 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansCondensed-SemiBoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Black.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Black.otf new file mode 100644 index 0000000..7788a78 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Black.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-BlackItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-BlackItalic.otf new file mode 100644 index 0000000..d4ed13e Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-BlackItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Bold.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Bold.otf new file mode 100644 index 0000000..b9eeb7d Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Bold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-BoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-BoldItalic.otf new file mode 100644 index 0000000..adfdd78 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-BoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraBold.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraBold.otf new file mode 100644 index 0000000..f34ca1e Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraBold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraBoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraBoldItalic.otf new file mode 100644 index 0000000..67d951c Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraBoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraLight.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraLight.otf new file mode 100644 index 0000000..260be7d Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraLight.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraLightItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraLightItalic.otf new file mode 100644 index 0000000..20e5ef3 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-ExtraLightItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Italic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Italic.otf new file mode 100644 index 0000000..23acbd3 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Italic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Light.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Light.otf new file mode 100644 index 0000000..e6457e1 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Light.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-LightItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-LightItalic.otf new file mode 100644 index 0000000..ef082e7 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-LightItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Medium.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Medium.otf new file mode 100644 index 0000000..3fd3fb3 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Medium.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-MediumItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-MediumItalic.otf new file mode 100644 index 0000000..c14d0e0 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-MediumItalic.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Regular.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Regular.otf new file mode 100644 index 0000000..097c410 Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-Regular.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-SemiBold.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-SemiBold.otf new file mode 100644 index 0000000..0fd65cc Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-SemiBold.otf differ diff --git a/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-SemiBoldItalic.otf b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-SemiBoldItalic.otf new file mode 100644 index 0000000..39c2e1f Binary files /dev/null and b/public/fonts/Hubot-Sans/OTF/HubotSansExpanded-SemiBoldItalic.otf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-Black.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-Black.ttf new file mode 100644 index 0000000..48efb8b Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-Black.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-BlackItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-BlackItalic.ttf new file mode 100644 index 0000000..b79c3bc Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-BlackItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-Bold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-Bold.ttf new file mode 100644 index 0000000..f60d805 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-Bold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-BoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-BoldItalic.ttf new file mode 100644 index 0000000..7aac29a Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-BoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraBold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraBold.ttf new file mode 100644 index 0000000..0e48f55 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraBold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraBoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraBoldItalic.ttf new file mode 100644 index 0000000..229a706 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraBoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraLight.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraLight.ttf new file mode 100644 index 0000000..54710c1 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraLight.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraLightItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraLightItalic.ttf new file mode 100644 index 0000000..a2fa306 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-ExtraLightItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-Italic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-Italic.ttf new file mode 100644 index 0000000..4e73f01 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-Italic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-Light.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-Light.ttf new file mode 100644 index 0000000..cdc4c58 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-Light.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-LightItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-LightItalic.ttf new file mode 100644 index 0000000..b12418c Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-LightItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-Medium.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-Medium.ttf new file mode 100644 index 0000000..4a0d740 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-Medium.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-MediumItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-MediumItalic.ttf new file mode 100644 index 0000000..56bfc46 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-MediumItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-Regular.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-Regular.ttf new file mode 100644 index 0000000..293fb69 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-Regular.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-SemiBold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-SemiBold.ttf new file mode 100644 index 0000000..a57a347 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-SemiBold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSans-SemiBoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSans-SemiBoldItalic.ttf new file mode 100644 index 0000000..f965b2e Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSans-SemiBoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Black.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Black.ttf new file mode 100644 index 0000000..ff33433 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Black.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-BlackItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-BlackItalic.ttf new file mode 100644 index 0000000..74c0cb1 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-BlackItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Bold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Bold.ttf new file mode 100644 index 0000000..937775a Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Bold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-BoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-BoldItalic.ttf new file mode 100644 index 0000000..5bbbf99 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-BoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraBold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraBold.ttf new file mode 100644 index 0000000..e8edf2e Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraBold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraBoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..ebcf745 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraBoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraLight.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraLight.ttf new file mode 100644 index 0000000..2b926b5 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraLight.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraLightItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..59d8318 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-ExtraLightItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Italic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Italic.ttf new file mode 100644 index 0000000..310c6cd Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Italic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Light.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Light.ttf new file mode 100644 index 0000000..0389708 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Light.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-LightItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-LightItalic.ttf new file mode 100644 index 0000000..b270fa1 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-LightItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Medium.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Medium.ttf new file mode 100644 index 0000000..ad02d63 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Medium.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-MediumItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-MediumItalic.ttf new file mode 100644 index 0000000..ef837a2 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-MediumItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Regular.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Regular.ttf new file mode 100644 index 0000000..7004de7 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-Regular.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-SemiBold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-SemiBold.ttf new file mode 100644 index 0000000..9c01e86 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-SemiBold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-SemiBoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..7aef86e Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansCondensed-SemiBoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Black.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Black.ttf new file mode 100644 index 0000000..7fd97d9 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Black.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-BlackItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-BlackItalic.ttf new file mode 100644 index 0000000..764c2fb Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-BlackItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Bold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Bold.ttf new file mode 100644 index 0000000..eae654f Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Bold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-BoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-BoldItalic.ttf new file mode 100644 index 0000000..31038be Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-BoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraBold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraBold.ttf new file mode 100644 index 0000000..d87dc12 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraBold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraBoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraBoldItalic.ttf new file mode 100644 index 0000000..dff888c Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraBoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraLight.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraLight.ttf new file mode 100644 index 0000000..eb308ba Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraLight.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraLightItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraLightItalic.ttf new file mode 100644 index 0000000..1ca3833 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-ExtraLightItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Italic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Italic.ttf new file mode 100644 index 0000000..e53a1bf Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Italic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Light.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Light.ttf new file mode 100644 index 0000000..04f47da Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Light.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-LightItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-LightItalic.ttf new file mode 100644 index 0000000..bd2a555 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-LightItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Medium.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Medium.ttf new file mode 100644 index 0000000..62b8387 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Medium.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-MediumItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-MediumItalic.ttf new file mode 100644 index 0000000..e5b39b6 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-MediumItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Regular.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Regular.ttf new file mode 100644 index 0000000..a446d1e Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-Regular.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-SemiBold.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-SemiBold.ttf new file mode 100644 index 0000000..7974442 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-SemiBold.ttf differ diff --git a/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-SemiBoldItalic.ttf b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-SemiBoldItalic.ttf new file mode 100644 index 0000000..fba5ac9 Binary files /dev/null and b/public/fonts/Hubot-Sans/TTF/HubotSansExpanded-SemiBoldItalic.ttf differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-Black.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Black.woff2 new file mode 100644 index 0000000..5b68c1d Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Black.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-BlackItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-BlackItalic.woff2 new file mode 100644 index 0000000..ec0d285 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-BlackItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-Bold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Bold.woff2 new file mode 100644 index 0000000..bda3db2 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Bold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-BoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-BoldItalic.woff2 new file mode 100644 index 0000000..c53405d Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-BoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBold.woff2 new file mode 100644 index 0000000..6e91ab5 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..4e5a956 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraBoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraLight.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraLight.woff2 new file mode 100644 index 0000000..1172db7 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraLight.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraLightItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraLightItalic.woff2 new file mode 100644 index 0000000..f340380 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-ExtraLightItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-Italic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Italic.woff2 new file mode 100644 index 0000000..93f2f55 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Italic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-Light.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Light.woff2 new file mode 100644 index 0000000..28495e5 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Light.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-LightItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-LightItalic.woff2 new file mode 100644 index 0000000..bc56160 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-LightItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-Medium.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Medium.woff2 new file mode 100644 index 0000000..cfcd325 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Medium.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-MediumItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-MediumItalic.woff2 new file mode 100644 index 0000000..dffe146 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-MediumItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-Regular.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Regular.woff2 new file mode 100644 index 0000000..5c05680 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-Regular.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-SemiBold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-SemiBold.woff2 new file mode 100644 index 0000000..1714325 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-SemiBold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans-SemiBoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans-SemiBoldItalic.woff2 new file mode 100644 index 0000000..b0be7d0 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans-SemiBoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Black.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Black.woff2 new file mode 100644 index 0000000..9bd3191 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Black.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-BlackItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-BlackItalic.woff2 new file mode 100644 index 0000000..2ed4c8b Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-BlackItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Bold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Bold.woff2 new file mode 100644 index 0000000..873382d Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Bold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-BoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-BoldItalic.woff2 new file mode 100644 index 0000000..11ecf04 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-BoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraBold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraBold.woff2 new file mode 100644 index 0000000..81e9cf2 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraBold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraBoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..d94bf8f Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraBoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraLight.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraLight.woff2 new file mode 100644 index 0000000..93793d5 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraLight.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraLightItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraLightItalic.woff2 new file mode 100644 index 0000000..5565655 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-ExtraLightItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Italic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Italic.woff2 new file mode 100644 index 0000000..b416d82 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Italic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Light.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Light.woff2 new file mode 100644 index 0000000..2a6b79a Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Light.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-LightItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-LightItalic.woff2 new file mode 100644 index 0000000..dd9c9cf Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-LightItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Medium.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Medium.woff2 new file mode 100644 index 0000000..3d58451 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Medium.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-MediumItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-MediumItalic.woff2 new file mode 100644 index 0000000..50341f5 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-MediumItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Regular.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Regular.woff2 new file mode 100644 index 0000000..1580ee6 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-Regular.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-SemiBold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-SemiBold.woff2 new file mode 100644 index 0000000..fa70199 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-SemiBold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-SemiBoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-SemiBoldItalic.woff2 new file mode 100644 index 0000000..7560dea Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansCondensed-SemiBoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Black.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Black.woff2 new file mode 100644 index 0000000..986ef50 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Black.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-BlackItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-BlackItalic.woff2 new file mode 100644 index 0000000..a72a530 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-BlackItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Bold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Bold.woff2 new file mode 100644 index 0000000..409c9a0 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Bold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-BoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-BoldItalic.woff2 new file mode 100644 index 0000000..9c82783 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-BoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraBold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraBold.woff2 new file mode 100644 index 0000000..59979d2 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraBold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraBoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..9a8346e Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraBoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraLight.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraLight.woff2 new file mode 100644 index 0000000..b7209ab Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraLight.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraLightItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraLightItalic.woff2 new file mode 100644 index 0000000..0af2bd2 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-ExtraLightItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Italic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Italic.woff2 new file mode 100644 index 0000000..26d3753 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Italic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Light.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Light.woff2 new file mode 100644 index 0000000..a8304f4 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Light.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-LightItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-LightItalic.woff2 new file mode 100644 index 0000000..b45656a Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-LightItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Medium.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Medium.woff2 new file mode 100644 index 0000000..0f90b1a Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Medium.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-MediumItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-MediumItalic.woff2 new file mode 100644 index 0000000..0b5c93a Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-MediumItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Regular.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Regular.woff2 new file mode 100644 index 0000000..e391548 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-Regular.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-SemiBold.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-SemiBold.woff2 new file mode 100644 index 0000000..4de6cf7 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-SemiBold.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-SemiBoldItalic.woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-SemiBoldItalic.woff2 new file mode 100644 index 0000000..f845542 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSansExpanded-SemiBoldItalic.woff2 differ diff --git a/public/fonts/Hubot-Sans/WOFF2/HubotSans[slnt,wdth,wght].woff2 b/public/fonts/Hubot-Sans/WOFF2/HubotSans[slnt,wdth,wght].woff2 new file mode 100644 index 0000000..c828121 Binary files /dev/null and b/public/fonts/Hubot-Sans/WOFF2/HubotSans[slnt,wdth,wght].woff2 differ diff --git a/public/fonts/Inter-Black.ttf b/public/fonts/Inter-Black.ttf new file mode 100644 index 0000000..b27822b Binary files /dev/null and b/public/fonts/Inter-Black.ttf differ diff --git a/public/fonts/Inter-Bold.ttf b/public/fonts/Inter-Bold.ttf new file mode 100644 index 0000000..fe23eeb Binary files /dev/null and b/public/fonts/Inter-Bold.ttf differ diff --git a/public/fonts/Inter-ExtraBold.ttf b/public/fonts/Inter-ExtraBold.ttf new file mode 100644 index 0000000..874b1b0 Binary files /dev/null and b/public/fonts/Inter-ExtraBold.ttf differ diff --git a/public/fonts/Inter-ExtraLight.ttf b/public/fonts/Inter-ExtraLight.ttf new file mode 100644 index 0000000..c993e82 Binary files /dev/null and b/public/fonts/Inter-ExtraLight.ttf differ diff --git a/public/fonts/Inter-Light.ttf b/public/fonts/Inter-Light.ttf new file mode 100644 index 0000000..71188f5 Binary files /dev/null and b/public/fonts/Inter-Light.ttf differ diff --git a/public/fonts/Inter-Medium.ttf b/public/fonts/Inter-Medium.ttf new file mode 100644 index 0000000..a01f377 Binary files /dev/null and b/public/fonts/Inter-Medium.ttf differ diff --git a/public/fonts/Inter-Regular.ttf b/public/fonts/Inter-Regular.ttf new file mode 100644 index 0000000..5e4851f Binary files /dev/null and b/public/fonts/Inter-Regular.ttf differ diff --git a/public/fonts/Inter-SemiBold.ttf b/public/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000..ecc7041 Binary files /dev/null and b/public/fonts/Inter-SemiBold.ttf differ diff --git a/public/fonts/Inter-Thin.ttf b/public/fonts/Inter-Thin.ttf new file mode 100644 index 0000000..fe77243 Binary files /dev/null and b/public/fonts/Inter-Thin.ttf differ diff --git a/public/fonts/Inter-VariableFont_slnt,wght.ttf b/public/fonts/Inter-VariableFont_slnt,wght.ttf new file mode 100644 index 0000000..e724708 Binary files /dev/null and b/public/fonts/Inter-VariableFont_slnt,wght.ttf differ diff --git a/src/assets/fonts/PTMono-Regular.ttf b/public/fonts/PTMono-Regular.ttf similarity index 100% rename from src/assets/fonts/PTMono-Regular.ttf rename to public/fonts/PTMono-Regular.ttf diff --git a/public/placeholder.html b/public/placeholder.html index 9b2038c..b59b553 100644 --- a/public/placeholder.html +++ b/public/placeholder.html @@ -32,6 +32,7 @@ } p, span, a, section, div { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } code { color: #9fef00cc;} + .logs { background: #141d2b; padding: 0.5rem;} diff --git a/public/resources/openapi-spec.yml b/public/resources/openapi-spec.yml new file mode 100644 index 0000000..e549be5 --- /dev/null +++ b/public/resources/openapi-spec.yml @@ -0,0 +1,2856 @@ +openapi: 3.0.0 +info: + title: Web Check 🕵 + description: > + **API documentation for the [Web Check](https://github.com/lissy93/web-check) backend endpoints.**
+ _Web Check gives you x-ray vision, revealing the configration and inner workings of any website._ +

+ [![Website - Web-Check.xyz](https://img.shields.io/badge/Website-webcheck.zyz-9fef00?style=flat&logo=googlecloudstorage&logoColor=white&labelColor=1c1d28)](https://web-check.xyz/) + [![DockerHub - Lissy93/Web-Check](https://img.shields.io/badge/DockerHub-Lissy93/Web_Check-1fb1f4?style=flat&logo=docker&logoColor=white&labelColor=1c1d28)](https://hub.docker.com/r/lissy93/web-check) + [![GitHub - Lissy93/Web-Check](https://img.shields.io/badge/GitHub-Lissy93/Web_Check-a832fc?style=flat&logo=github&logoColor=white&labelColor=1c1d28)](https://github.com/lissy93/web-check) + [![Sponsor - Alicia Sykes](https://img.shields.io/badge/Sponsor-Alicia_Sykes-f2159a?style=flat&logo=githubsponsors&logoColor=white&labelColor=1c1d28)](https://github.com/sponsors/Lissy93) + + version: 1.0.0 + license: + name: 'License: MIT' + url: https://github.com/Lissy93/web-check/blob/master/LICENSE + termsOfService: https://web-check.xyz/about#terms-info +externalDocs: + description: 'Source: GitHub' + url: https://github.com/Lissy93/web-check +servers: + - url: http://localhost:3001/api + description: Local (Development) + - url: http://localhost:3000/api + description: Local (Production) + - url: https://web-check.xyz/api + description: Public Demo (Vercel) + - url: https://web-check.as93.net/api + description: Public Demo (Netlify) +tags: + - name: Quality & Info + description: Endpoints providing quality metrics, and general website information. + - name: Security + description: Endpoints related to website and server security configurations. + - name: Server Info + description: Endpoints providing information about the server hosting the website. + - name: Client-Side Information + description: Endpoints providing metrics about the website's client-side content. +components: + responses: + Error: + description: Internal Server Error - An error occurred while processing the request. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Skipped: + description: No Content - The request was successful, but no content is returned. + content: + application/json: + schema: + $ref: '#/components/schemas/SkippedResponse' + MissingParam: + description: Bad Request - Missing or incorrect input parameters. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Unauthorized: + description: Unauthorized - Authentication credentials were missing or incorrect. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + Forbidden: + description: Forbidden - The credentials provided do not grant the necessary permissions. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + TooManyRequests: + description: Too Many Requests - Rate limit exceeded. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + schemas: + ErrorResponse: + type: object + properties: + error: + type: string + description: A description of the error + SkippedResponse: + type: object + properties: + skipped: + type: string + description: A description of why the check was skipped +paths: + /archives: + get: + summary: Retrieve archive data + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + firstScan: + type: string + format: date-time + description: The timestamp of the first scan + example: "1996-12-21T17:11:14.000Z" + lastScan: + type: string + format: date-time + description: The timestamp of the last scan + example: "2024-05-14T13:45:47.000Z" + totalScans: + type: integer + description: The total number of scans + example: 3393 + changeCount: + type: integer + description: The total number of changes + example: 1946 + averagePageSize: + type: integer + description: The average page size in KB + example: 527 + scanFrequency: + type: object + properties: + daysBetweenScans: + type: number + format: float + description: Average days between scans + example: 2.95 + daysBetweenChanges: + type: number + format: float + description: Average days between changes + example: 5.14 + scansPerDay: + type: number + format: float + description: Number of scans per day + example: 0.34 + changesPerDay: + type: number + format: float + description: Number of changes per day + example: 0.19 + scans: + type: array + items: + type: array + items: + type: string + description: List of scan details + example: + - ["19961221171114", "200", "RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ", "971", null] + - ["19970209085620", "200", "6PUZMQAGXXV3RCEQ65WWUCMIJVSG4OQI", "1025", null] + - ["19970209085620", "200", "RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ", "975", null] + - ["19970412142046", "200", "RX44GNWXPO4HX6ERA2LHBORWD4BJ2HUJ", "1022", null] + - ["19980206180754", "200", "B6ACYSFKSPWKXRPASZYOK3IBIBY7HB3I", "1102", null] + scanUrl: + type: string + format: uri + description: The URL to the scan + example: "https://duck.com" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /block-lists: + get: + summary: Retrieve block lists data + tags: + - Security + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + blocklists: + type: array + items: + type: object + properties: + server: + type: string + description: The name of the blocklist server + example: "AdGuard" + serverIp: + type: string + description: The IP address of the blocklist server + example: "176.103.130.130" + isBlocked: + type: boolean + description: Whether the URL is blocked by the server + example: false + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /carbon: + get: + summary: Retrieve carbon data + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + statistics: + type: object + properties: + adjustedBytes: + type: number + format: float + description: Adjusted bytes transferred + example: 104.19 + energy: + type: number + format: float + description: Energy consumption in kWh + example: 7.859794422984123e-8 + co2: + type: object + properties: + grid: + type: object + properties: + grams: + type: number + format: float + description: CO2 emissions in grams from grid energy + example: 0.00003474029134958983 + litres: + type: number + format: float + description: CO2 emissions in litres from grid energy + example: 0.00001932255004864186 + renewable: + type: object + properties: + grams: + type: number + format: float + description: CO2 emissions in grams from renewable energy + example: 0.000030118732228875164 + litres: + type: number + format: float + description: CO2 emissions in litres from renewable energy + example: 0.000016752038865700363 + cleanerThan: + type: integer + description: Percentage of websites that are less clean than the queried site + example: 1 + rating: + type: string + description: Environmental rating + example: "A+" + green: + type: boolean + description: Whether the site is green + example: false + scanUrl: + type: string + format: uri + description: The URL to the scan + example: "https://duck.com" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /cookies: + get: + summary: Retrieve cookies data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + headerCookies: + type: array + items: + type: string + description: List of cookies from the HTTP headers + example: + - "SOCS=CAAaBgiAmZWyBg; expires=Sun, 15-Jun-2025 12:33:07 GMT; path=/; domain=.google.com; Secure; SameSite=lax" + - "AEC=AQTF6HyLiMgX13QRJxvRyBXFos8vw-et4igVlyhgeZaeLlfDgvkgkhQmSg; expires=Tue, 12-Nov-2024 12:33:07 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax" + - "__Secure-ENID=19.SE=YMEDMLYol9GgV7AzLehnn5lFsrHNqD3emYqfTaVxHQPMmAHiIqeKLyqdjfwED8gPGFN5Gb7OqkGiGTki34F94cir_SXxYvVJXTFuUU9hPAMaxoluY4JeeWKP3ma4RlsBZDKDUrBBVIA-PoPwMMBRyy8Fu0m_nOPTGjlz8WMhm_jMuHdWEF8; expires=Mon, 16-Jun-2025 04:51:25 GMT; path=/; domain=.google.com; Secure; HttpOnly; SameSite=lax" + clientCookies: + type: array + items: + type: object + properties: + name: + type: string + description: The name of the cookie + example: "__Secure-ENID" + value: + type: string + description: The value of the cookie + example: "19.SE=LZ6Zj5jPrWer7Kk1PqTOWpwREJDJlMaxrCes3es4eXfLZ3-9e-HQ0tnRU-RNA_ezSw_CSyFbUWAd4A3FnTJGGYRsjK2FcqJfIum0UEcSy-oFXM49VreKYjxFyUNEshqsY_VQnZw1lFuRqRyH2JA2V90uHmaL_AVOlL_Myv1_PXwcPYgOCqsBQTkxYPeDVpS6QyHO9g" + domain: + type: string + description: The domain of the cookie + example: ".google.com" + path: + type: string + description: The path of the cookie + example: "/" + expires: + type: number + format: float + description: The expiration time of the cookie in Unix time + example: 1750049486.285637 + size: + type: integer + description: The size of the cookie + example: 217 + httpOnly: + type: boolean + description: Whether the cookie is HttpOnly + example: true + secure: + type: boolean + description: Whether the cookie is Secure + example: true + session: + type: boolean + description: Whether the cookie is a session cookie + example: false + sameSite: + type: string + description: The SameSite attribute of the cookie + example: "Lax" + priority: + type: string + description: The priority of the cookie + example: "Medium" + sameParty: + type: boolean + description: Whether the cookie is SameParty + example: false + sourceScheme: + type: string + description: The source scheme of the cookie + example: "Secure" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /dns-server: + get: + summary: Retrieve DNS server data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + domain: + type: string + description: The domain name queried + example: "duck.com" + dns: + type: array + items: + type: object + properties: + address: + type: string + description: The IP address of the DNS server + example: "52.142.124.215" + hostname: + type: array + items: + type: string + description: Hostnames associated with the DNS server + nullable: true + example: + - "lhr48s30-in-f14.1e100.net" + dohDirectSupports: + type: boolean + description: Whether the server supports DoH (DNS over HTTPS) directly + example: false + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /dns: + get: + summary: Retrieve DNS data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + A: + type: object + properties: + address: + type: string + description: IPv4 address + example: "52.142.124.215" + family: + type: integer + description: IP family + example: 4 + AAAA: + type: array + items: + type: string + description: List of IPv6 addresses + example: ["52.142.124.215"] + MX: + type: array + items: + type: string + description: List of mail exchange servers + example: [] + TXT: + type: array + items: + type: object + properties: + exchange: + type: string + description: Exchange server + example: "smtp-inbound1.duck.com" + priority: + type: integer + description: Priority of the exchange server + example: 5 + description: List of TXT records + NS: + type: array + items: + type: array + items: + type: string + description: List of name servers + example: + - ["v=spf1 ip4:20.13.235.192/26 ip4:20.67.221.0/24 ip4:20.67.222.0/24 ip4:20.67.223.0/24 -all"] + - ["google-site-verification=xWLxaaNt2iGObwJ_jeX1E3Wn-xro--W75DBKWs5uufc"] + CNAME: + type: array + items: + type: string + description: List of canonical names + example: + - "dns1.p03.nsone.net" + - "dns2.p03.nsone.net" + - "dns3.p03.nsone.net" + - "dns4.p03.nsone.net" + - "ns01.quack-dns.com" + - "ns02.quack-dns.com" + - "ns03.quack-dns.com" + - "ns04.quack-dns.com" + SOA: + type: array + items: + type: string + description: Start of Authority records + example: [] + SRV: + type: object + properties: + nsname: + type: string + description: Name server + example: "dns1.p03.nsone.net" + hostmaster: + type: string + description: Hostmaster email + example: "hostmaster.nsone.net" + serial: + type: integer + description: Serial number + example: 1654974460 + refresh: + type: integer + description: Refresh interval + example: 7200 + retry: + type: integer + description: Retry interval + example: 7200 + expire: + type: integer + description: Expiration time + example: 1209600 + minttl: + type: integer + description: Minimum TTL + example: 14400 + description: Service records + PTR: + type: array + items: + type: string + description: Pointer records + example: [] + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /dnssec: + get: + summary: Retrieve DNSSEC data + tags: + - Security + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + DNSKEY: + type: object + properties: + isFound: + type: boolean + description: Whether the DNSKEY record is found + example: false + answer: + type: string + nullable: true + description: The DNSKEY answer (if any) + example: null + response: + type: object + properties: + Status: + type: integer + description: The status code of the response + example: 3 + TC: + type: boolean + description: Truncated response flag + example: false + RD: + type: boolean + description: Recursion desired flag + example: true + RA: + type: boolean + description: Recursion available flag + example: true + AD: + type: boolean + description: Authentic data flag + example: false + CD: + type: boolean + description: Checking disabled flag + example: false + Question: + type: array + items: + type: object + properties: + name: + type: string + description: Question name + example: "https://duck.com." + type: + type: integer + description: Question type + example: 48 + Authority: + type: array + items: + type: object + properties: + name: + type: string + description: Authority name + example: "com." + type: + type: integer + description: Authority type + example: 6 + TTL: + type: integer + description: Time to live + example: 900 + data: + type: string + description: Authority data + example: "a.gtld-servers.net. nstld.verisign-grs.com. 1715938809 1800 900 604800 86400" + Comment: + type: string + description: Additional comments + example: "Response from 192.41.162.30." + DS: + type: object + properties: + isFound: + type: boolean + description: Whether the DS record is found + example: false + answer: + type: string + nullable: true + description: The DS answer (if any) + example: null + response: + type: object + properties: + Status: + type: integer + description: The status code of the response + example: 3 + TC: + type: boolean + description: Truncated response flag + example: false + RD: + type: boolean + description: Recursion desired flag + example: true + RA: + type: boolean + description: Recursion available flag + example: true + AD: + type: boolean + description: Authentic data flag + example: false + CD: + type: boolean + description: Checking disabled flag + example: false + Question: + type: array + items: + type: object + properties: + name: + type: string + description: Question name + example: "https://duck.com." + type: + type: integer + description: Question type + example: 43 + Authority: + type: array + items: + type: object + properties: + name: + type: string + description: Authority name + example: "com." + type: + type: integer + description: Authority type + example: 6 + TTL: + type: integer + description: Time to live + example: 900 + data: + type: string + description: Authority data + example: "a.gtld-servers.net. nstld.verisign-grs.com. 1715938824 1800 900 604800 86400" + Comment: + type: string + description: Additional comments + example: "Response from 192.52.178.30." + RRSIG: + type: object + properties: + isFound: + type: boolean + description: Whether the RRSIG record is found + example: false + answer: + type: string + nullable: true + description: The RRSIG answer (if any) + example: null + response: + type: object + properties: + Status: + type: integer + description: The status code of the response + example: 3 + TC: + type: boolean + description: Truncated response flag + example: false + RD: + type: boolean + description: Recursion desired flag + example: true + RA: + type: boolean + description: Recursion available flag + example: true + AD: + type: boolean + description: Authentic data flag + example: false + CD: + type: boolean + description: Checking disabled flag + example: false + Question: + type: array + items: + type: object + properties: + name: + type: string + description: Question name + example: "https://duck.com." + type: + type: integer + description: Question type + example: 46 + Authority: + type: array + items: + type: object + properties: + name: + type: string + description: Authority name + example: "com." + type: + type: integer + description: Authority type + example: 6 + TTL: + type: integer + description: Time to live + example: 900 + data: + type: string + description: Authority data + example: "a.gtld-servers.net. nstld.verisign-grs.com. 1715938809 1800 900 604800 86400" + Comment: + type: string + description: Additional comments + example: "Response from 2001:502:7094::30." + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /firewall: + get: + summary: Retrieve firewall data + tags: + - Security + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + hasWaf: + type: boolean + description: Whether a Web Application Firewall (WAF) is present + example: false + waf: + type: string + nullable: true + description: The name of the WAF, if present + example: "Cloudflare" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /get-ip: + get: + summary: Retrieve IP data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + ip: + type: string + description: The IP address + example: "52.142.124.215" + family: + type: integer + description: The IP family (4 for IPv4, 6 for IPv6) + example: 4 + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /headers: + get: + summary: Retrieve headers data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + additionalProperties: + oneOf: + - type: string + - type: array + items: + type: string + example: + date: "Fri, 17 May 2024 11:51:38 GMT" + content-type: "text/html; charset=utf-8" + transfer-encoding: "chunked" + connection: "keep-alive" + cache-control: "public, max-age=0, must-revalidate" + strict-transport-security: "max-age=31536000; includeSubDomains" + permissions-policy: "geolocation=(), camera=(), microphone=()" + referrer-policy: "strict-origin-when-cross-origin" + x-content-type-options: "nosniff" + x-frame-options: "SAMEORIGIN" + x-gww-loc: "EN-US" + x-pgs-loc: "EN-US" + x-rm: "GW" + x-xss-protection: "1; mode=block" + vary: "Accept-Encoding" + server: "cloudflare" + cf-ray: "8853658f9da5940b-LHR" + alt-svc: "h3=\":443\"; ma=86400" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /hsts: + get: + summary: Retrieve HSTS data + tags: + - Server Info + - Security + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + message: + type: string + description: A message regarding the HSTS status + example: "HSTS header does not include all subdomains." + compatible: + type: boolean + description: Whether the site is compatible with HSTS + example: false + hstsHeader: + type: string + nullable: true + description: The HSTS header if present + example: null + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /http-security: + get: + summary: Retrieve HTTP security data + tags: + - Server Info + - Security + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + strictTransportPolicy: + type: boolean + description: Whether Strict Transport Security is enabled + example: false + xFrameOptions: + type: boolean + description: Whether X-Frame-Options header is set + example: true + xContentTypeOptions: + type: boolean + description: Whether X-Content-Type-Options header is set + example: false + xXSSProtection: + type: boolean + description: Whether X-XSS-Protection header is set + example: true + contentSecurityPolicy: + type: boolean + description: Whether Content Security Policy is enabled + example: false + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /linked-pages: + get: + summary: Retrieve linked pages data + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + internal: + type: array + items: + type: string + description: List of internal links + example: + - "https://bbc.com/news/business" + - "https://bbc.com/news/entertainment_and_arts" + - "https://bbc.com/news/uk" + - "https://bbc.com/news/world" + - "https://bbc.com/#chameleon-global-navigation-more-menu" + - "https://bbc.com/sport/golf" + - "https://bbc.com/sport" + external: + type: array + items: + type: string + description: List of external links + example: + - "https://www.bbc.co.uk/" + - "https://www.bbc.co.uk/news" + - "https://www.bbc.co.uk/sport" + - "https://www.bbc.co.uk/weather" + - "https://www.bbc.co.uk/iplayer" + - "https://www.bbc.co.uk/sounds" + - "https://www.bbc.co.uk/bitesize" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /mail-config: + get: + summary: Retrieve mail configuration data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + mxRecords: + type: array + items: + type: object + properties: + exchange: + type: string + description: The mail exchange server + example: "smtp.google.com" + priority: + type: integer + description: The priority of the mail exchange server + example: 10 + description: List of MX (Mail Exchange) records + txtRecords: + type: array + items: + type: array + items: + type: string + description: List of TXT records + example: + - ["v=spf1 include:_spf.google.com ~all"] + - ["google-site-verification=wD8N7i1JTNTkezJ49swvWW48f8_9xveREV4oB-0Hf5o"] + mailServices: + type: array + items: + type: object + properties: + provider: + type: string + description: The mail service provider + example: "Google Workspace" + value: + type: string + description: The verification value for the mail service + example: "wD8N7i1JTNTkezJ49swvWW48f8_9xveREV4oB-0Hf5o" + description: List of mail services and their verification values + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /ports: + get: + summary: Retrieve open and failed ports data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + openPorts: + type: array + items: + type: integer + description: List of open ports + example: + - 80 + - 443 + - 8080 + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /quality: + get: + summary: Retrieve website quality metrics + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL of the website to analyze + schema: + type: string + - name: apiKey + in: query + required: true + description: The API key for accessing Google PageSpeed Insights + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + performance: + type: object + description: Performance category data + properties: + score: + type: number + format: float + description: Performance score + example: 0.85 + accessibility: + type: object + description: Accessibility category data + properties: + score: + type: number + format: float + description: Accessibility score + example: 0.92 + best_practices: + type: object + description: Best Practices category data + properties: + score: + type: number + format: float + description: Best Practices score + example: 0.88 + seo: + type: object + description: SEO category data + properties: + score: + type: number + format: float + description: SEO score + example: 0.95 + pwa: + type: object + description: Progressive Web App category data + properties: + score: + type: number + format: float + description: PWA score + example: 0.75 + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /rank: + get: + summary: Retrieve rank data + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch rank data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + ranks: + type: array + items: + type: object + properties: + date: + type: string + format: date + description: The date of the rank + example: "2024-05-16" + rank: + type: integer + description: The rank value + example: 145896 + description: List of rank entries + domain: + type: string + description: The domain name for which rank data is provided + example: "duck.com" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /redirects: + get: + summary: Retrieve redirects data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch redirect data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + redirects: + type: array + items: + type: string + description: List of URLs the given URL redirects to + example: + - "https://duck.com" + - "https://duckduckgo.com/" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /robots-txt: + get: + summary: Retrieve robots.txt data + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch robots.txt data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + robots: + type: array + items: + type: object + properties: + lbl: + type: string + description: The label of the robots.txt entry (e.g., User-agent, Disallow, Allow) + example: "User-agent" + val: + type: string + description: The value of the robots.txt entry + example: "*" + description: List of robots.txt entries + example: + - lbl: "User-agent" + val: "*" + - lbl: "Disallow" + val: "/lite" + - lbl: "Disallow" + val: "/html" + - lbl: "Disallow" + val: "/*?" + - lbl: "Disallow" + val: "/chrome_newtab" + - lbl: "Disallow" + val: "/email/" + - lbl: "Allow" + val: "/email/$" + - lbl: "Allow" + val: "/email/privacy-guarantees" + - lbl: "Allow" + val: "/email/privacy-terms" + - lbl: "Disallow" + val: "/2012-privacy-policy" + - lbl: "User-agent" + val: "ia_archiver" + - lbl: "Disallow" + val: "/" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /screenshot: + get: + summary: Retrieve screenshot data + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: {} + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /security-txt: + get: + summary: Retrieve security.txt data + tags: + - Security + parameters: + - name: url + in: query + required: true + description: The URL to fetch security.txt data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + isPresent: + type: boolean + description: Whether the security.txt file is present + example: true + foundIn: + type: string + nullable: true + description: The location where the security.txt file was found + example: "/.well-known/security.txt" + content: + type: string + nullable: true + description: The content of the security.txt file + example: "Contact: mailto:security@proton.me\nExpires: 2024-12-31T23:59:59.999Z\nEncryption: https://api.protonmail.ch/pks/lookup?op=get&search=security@proton.me\nPreferred-Languages: en\nCanonical: https://proton.me/.well-known/security.txt\nPolicy: https://proton.me/blog/protonmail-bug-bounty-program\nHiring: https://proton.me/careers\n" + isPgpSigned: + type: boolean + description: Whether the security.txt file is PGP signed + example: false + fields: + type: object + nullable: true + description: Key-value pairs of the security.txt fields + properties: + Contact: + type: string + description: Contact information + example: "mailto:security@proton.me" + Expires: + type: string + format: date-time + description: Expiry date of the security.txt information + example: "2024-12-31T23:59:59.999Z" + Encryption: + type: string + description: Encryption key location + example: "https://api.protonmail.ch/pks/lookup?op=get&search=security@proton.me" + Preferred-Languages: + type: string + description: Preferred languages for contact + example: "en" + Canonical: + type: string + description: Canonical URL for the security.txt file + example: "https://proton.me/.well-known/security.txt" + Policy: + type: string + description: Policy URL + example: "https://proton.me/blog/protonmail-bug-bounty-program" + Hiring: + type: string + description: Hiring information URL + example: "https://proton.me/careers" + Acknowledgments: + type: string + nullable: true + description: Acknowledgments information + example: "https://hackerone.com/github/hacktivity" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /sitemap: + get: + summary: Retrieve sitemap data + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch sitemap data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + urlset: + type: object + properties: + $: + type: object + properties: + xmlns: + type: string + description: XML namespace + example: "http://www.sitemaps.org/schemas/sitemap/0.9" + xmlns:xsi: + type: string + description: XML Schema instance namespace + example: "http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation: + type: string + description: Schema location + example: "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" + url: + type: array + items: + type: object + properties: + loc: + type: array + items: + type: string + description: The URL of the page + example: "https://duckduckgo.com/" + lastmod: + type: array + items: + type: string + format: date + description: The last modification date of the page + example: "2023-08-22" + changefreq: + type: array + items: + type: string + description: The frequency of changes to the page + example: "monthly" + priority: + type: array + items: + type: number + format: float + description: The priority of the page + example: 1.00 + description: | + Note: The structure might need changing to show pages below each user-agent. + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /social-tags: + get: + summary: Retrieve social media tags data + tags: + - Quality & Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch social tags data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + title: + type: string + description: The title of the page + example: "DuckDuckGo — Privacy, simplified." + description: + type: string + description: The description of the page + example: "The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs." + canonicalUrl: + type: string + description: The canonical URL of the page + example: "https://duckduckgo.com" + ogTitle: + type: string + description: The Open Graph title of the page + example: "DuckDuckGo — Privacy, simplified." + ogType: + type: string + description: The Open Graph type of the page + example: "website" + ogImage: + type: string + description: The Open Graph image URL of the page + example: "https://duckduckgo.com/assets/logo_social-media.png" + ogUrl: + type: string + description: The Open Graph URL of the page + example: "https://duckduckgo.com" + ogDescription: + type: string + description: The Open Graph description of the page + example: "The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs." + ogSiteName: + type: string + description: The Open Graph site name of the page + example: "DuckDuckGo" + twitterCard: + type: string + description: The Twitter card type of the page + example: "summary_large_image" + twitterSite: + type: string + description: The Twitter handle of the site + example: "@duckduckgo" + twitterTitle: + type: string + description: The Twitter title of the page + example: "DuckDuckGo — Privacy, simplified." + twitterDescription: + type: string + description: The Twitter description of the page + example: "The Internet privacy company that empowers you to seamlessly take control of your personal information online, without any tradeoffs." + twitterImage: + type: string + description: The Twitter image URL of the page + example: "https://duckduckgo.com/assets/logo_social-media.png" + viewport: + type: string + description: The viewport settings of the page + example: "width=device-width, initial-scale=1, user-scalable=1 , viewport-fit=auto" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /ssl: + get: + summary: Retrieve SSL certificate data + tags: + - Security + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to fetch SSL certificate data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + subject: + type: object + description: The subject of the SSL certificate + properties: + C: + type: string + description: Country + example: "US" + ST: + type: string + description: State or province + example: "Pennsylvania" + L: + type: string + description: Locality or city + example: "Paoli" + O: + type: string + description: Organization + example: "Duck Duck Go, Inc." + CN: + type: string + description: Common Name + example: "*.duck.com" + issuer: + type: object + description: The issuer of the SSL certificate + properties: + C: + type: string + description: Country + example: "US" + O: + type: string + description: Organization + example: "DigiCert Inc" + CN: + type: string + description: Common Name + example: "DigiCert Global G2 TLS RSA SHA256 2020 CA1" + subjectaltname: + type: string + description: Subject alternative name + example: "DNS:*.duck.com, DNS:duck.com" + infoAccess: + type: object + description: Information Access details + properties: + "OCSP - URI": + type: array + items: + type: string + description: OCSP URI + example: + - "http://ocsp.digicert.com" + "CA Issuers - URI": + type: array + items: + type: string + description: CA Issuers URI + example: + - "http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt" + ca: + type: boolean + description: Whether it is a CA certificate + example: false + modulus: + type: string + description: The modulus of the public key + example: "A4B4F5B418A538F2570010FEDA319C3F73400EF8C20BB36668B34AE776233C5F06BE0572E36180FD8064D8AC41BD54AD2C6BAAEDEB31308F37B11EED1CD5D2F046C8B6B16381C4E80E817352C16B6F2A8687589BB564BF4B3C87A45284DB9A8240805002846A067C8CCEB50290D2A799907DFBF6EB0432528F413C5CC913F0230374250062C027664240E5A3A8574B8914F26216CEF076CECE04427A489137EA5AACFCD446827432C18CFF5E31AF0F3999B60A303925082FDDB1535513D6B131497F003397FD1EA4D58C24640261E1C205F33A83FDEE9D8C48A638F98927CEFE42C7DD3852C62AD62ECA74A417DE79290755689B3A6D19E44152695A5872491F" + bits: + type: integer + description: The number of bits in the key + example: 2048 + exponent: + type: string + description: The public exponent + example: "0x10001" + pubkey: + type: object + description: The public key + properties: + type: + type: string + description: Type of the public key data + example: "Buffer" + data: + type: array + items: + type: integer + description: The public key data + example: [48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 164, 180, 245, 180, 24, 165, 56, 242, 87, 0, 16, 254, 218, 49, 156, 63, 115, 64, 14, 248, 194, 11, 179, 102, 104, 179, 74, 231, 118, 35, 60, 95, 6, 190, 5, 114, 227, 97, 128, 253, 128, 100, 216, 172, 65, 189, 84, 173, 44, 107, 170, 237, 235, 49, 48, 143, 55, 177, 30, 237, 28, 213, 210, 240, 70, 200, 182, 177, 99, 129, 196, 232, 14, 129, 115, 82, 193, 107, 111, 42, 134, 135, 88, 155, 181, 100, 191, 75, 60, 135, 164, 82, 132, 219, 154, 130, 64, 128, 80, 2, 132, 106, 6, 124, 140, 206, 181, 2, 144, 210, 167, 153, 144, 125, 251, 246, 235, 4, 50, 82, 143, 65, 60, 92, 201, 19, 240, 35, 3, 116, 37, 0, 98, 192, 39, 102, 66, 64, 229, 163, 168, 87, 75, 137, 20, 242, 98, 22, 206, 240, 118, 206, 206, 4, 66, 122, 72, 145, 55, 234, 90, 172, 252, 212, 70, 130, 116, 50, 193, 140, 255, 94, 49, 175, 15, 57, 153, 182, 10, 48, 57, 37, 8, 47, 221, 177, 83, 85, 19, 214, 177, 49, 73, 127, 0, 51, 151, 253, 30, 164, 213, 140, 36, 100, 2, 97, 225, 194, 5, 243, 58, 131, 253, 238, 157, 140, 72, 166, 56, 249, 137, 39, 206, 254, 66, 199, 221, 56, 82, 198, 42, 214, 46, 202, 116, 164, 23, 222, 121, 41, 7, 85, 104, 155, 58, 109, 25, 228, 65, 82, 105, 90, 88, 114, 73, 31, 2, 3, 1, 0, 1] + valid_from: + type: string + description: The start date of the certificate's validity period + example: "Oct 3 00:00:00 2023 GMT" + valid_to: + type: string + description: The end date of the certificate's validity period + example: "Nov 2 23:59:59 2024 GMT" + fingerprint: + type: string + description: The SHA-1 fingerprint of the certificate + example: "54:3C:CB:82:A2:33:D0:CB:81:4D:7D:2C:AA:E6:84:CE:37:9A:0B:7A" + fingerprint256: + type: string + description: The SHA-256 fingerprint of the certificate + example: "F3:64:C5:26:C1:BD:31:DE:67:BB:98:57:96:7B:B6:9B:C7:1A:CD:44:21:29:62:4C:CE:E2:C9:DF:49:73:DE:05" + fingerprint512: + type: string + description: The SHA-512 fingerprint of the certificate + example: "FC:5A:8F:4D:27:DC:A1:4B:2B:31:8A:DD:37:DF:7E:7B:64:18:49:C9:B8:AB:29:9E:B2:5E:F6:43:36:D0:8B:9D:D0:3E:10:83:4D:B2:78:DF:E2:44:B3:7F:70:22:C7:1F:29:81:72:9E:D5:F2:7C:25:2A:02:FE:90:75:D6:35:CB" + ext_key_usage: + type: array + items: + type: string + description: Extended key usage + example: + - "1.3.6.1.5.5.7.3.1" + - "1.3.6.1.5.5.7.3.2" + serialNumber: + type: string + description: The serial number of the certificate + example: "0ABA674EF6840EFD177509B0BF82C4A5" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /status: + get: + summary: Retrieve website status + tags: + - Server Info + parameters: + - name: url + in: query + required: true + description: The URL to check the status of + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + isUp: + type: boolean + description: Whether the website is up + example: true + responseTime: + type: number + format: float + description: The response time in milliseconds + example: 105.34512500000005 + responseCode: + type: integer + description: The HTTP response code + example: 302 + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /tech-stack: + get: + summary: Retrieve technology stack data + tags: + - Quality & Info + - Client-Side Information + parameters: + - name: url + in: query + required: true + description: The URL to fetch technology stack data about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + urls: + type: object + description: Status of different URLs + additionalProperties: + type: object + properties: + status: + type: integer + description: HTTP status code + example: 302 + example: + "https://duck.com/": + status: 302 + "https://duckduckgo.com/": + status: 200 + technologies: + type: array + description: List of detected technologies + items: + type: object + properties: + slug: + type: string + description: Slug identifier for the technology + example: "node-js" + name: + type: string + description: Name of the technology + example: "Node.js" + description: + type: string + nullable: true + description: Description of the technology + example: "Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside a web browser." + confidence: + type: integer + description: Confidence level of the detection + example: 100 + version: + type: string + nullable: true + description: Detected version of the technology + example: "12.3.4" + icon: + type: string + description: Icon file name for the technology + example: "Node.js.svg" + website: + type: string + description: Official website for the technology + example: "https://nodejs.org" + cpe: + type: string + nullable: true + description: Common Platform Enumeration (CPE) identifier + example: "cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*" + categories: + type: array + description: List of categories the technology belongs to + items: + type: object + properties: + id: + type: integer + description: Category ID + example: 27 + slug: + type: string + description: Category slug + example: "programming-languages" + name: + type: string + description: Category name + example: "Programming languages" + rootPath: + type: boolean + description: Whether the technology is detected at the root path + example: true + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /threats: + get: + summary: Retrieve threats data + tags: + - Security + parameters: + - name: url + in: query + required: true + description: The URL to fetch results about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: {} + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /tls: + get: + summary: Retrieve TLS information for a target + tags: + - Security + - Server Info + parameters: + - name: target + in: query + required: true + description: The target domain to fetch TLS information about + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + id: + type: integer + description: The ID of the TLS scan + example: 57369181 + timestamp: + type: string + format: date-time + description: Timestamp of the scan + example: "2024-05-18T13:52:49.230114Z" + target: + type: string + description: Target domain of the TLS scan + example: "duck.com" + replay: + type: integer + description: Replay value + example: -1 + has_tls: + type: boolean + description: Indicates if TLS is present + example: true + cert_id: + type: integer + description: Certificate ID + example: 189148642 + trust_id: + type: integer + description: Trust ID + example: 343378161 + is_valid: + type: boolean + description: Indicates if the TLS certificate is valid + example: true + completion_perc: + type: integer + description: Completion percentage of the scan + example: 100 + connection_info: + type: object + description: Connection information of the TLS scan + properties: + scanIP: + type: string + description: Scan IP address + example: "52.250.42.157" + serverside: + type: boolean + description: Indicates if the scan is server-side + example: true + ciphersuite: + type: array + description: List of cipher suites used + items: + type: object + properties: + cipher: + type: string + description: Name of the cipher suite + example: "ECDHE-RSA-AES128-GCM-SHA256" + code: + type: integer + description: Cipher code + example: 49199 + protocols: + type: array + description: Supported protocols + items: + type: string + example: "TLSv1.2" + pubkey: + type: integer + description: Public key length + example: 2048 + sigalg: + type: string + description: Signature algorithm + example: "sha256WithRSAEncryption" + ticket_hint: + type: string + description: Ticket hint + example: "1200" + ocsp_stapling: + type: boolean + description: Indicates if OCSP stapling is enabled + example: false + pfs: + type: string + description: Perfect forward secrecy information + example: "ECDH,P-256,256bits" + curves: + type: array + description: Supported curves + items: + type: string + example: "prime256v1" + curvesFallback: + type: boolean + description: Indicates if curve fallback is used + example: false + analysis: + type: array + description: Analysis results + items: + type: object + properties: + id: + type: integer + description: Analyzer ID + example: 157573176 + analyzer: + type: string + description: Analyzer name + example: "awsCertlint" + result: + type: object + description: Analyzer result + properties: + bugs: + type: array + nullable: true + description: List of bugs found + items: + type: string + errors: + type: array + nullable: true + description: List of errors found + items: + type: string + notices: + type: array + nullable: true + description: List of notices found + items: + type: string + warnings: + type: array + nullable: true + description: List of warnings found + items: + type: string + fatalErrors: + type: array + nullable: true + description: List of fatal errors found + items: + type: string + informational: + type: array + nullable: true + description: List of informational messages + items: + type: string + host: + type: string + description: Host information + example: "" + issue: + type: array + nullable: true + description: List of issues found + items: + type: string + has_caa: + type: boolean + description: Indicates if CAA is present + example: false + issuewild: + type: array + nullable: true + description: List of wildcard issues found + items: + type: string + revoked: + type: boolean + description: Indicates if the certificate is revoked + example: false + RevocationTime: + type: string + format: date-time + description: Time of revocation + example: "0001-01-01T00:00:00Z" + level: + type: string + description: Mozilla evaluation level + example: "intermediate" + failures: + type: object + description: List of failures + properties: + bad: + type: array + nullable: true + description: List of bad failures + items: + type: string + old: + type: array + nullable: true + description: List of old failures + items: + type: string + example: [ + "sha256WithRSAEncryption is not an old certificate signature, use sha1WithRSAEncryption", + "consider adding ciphers ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, DHE-DSS-AES128-GCM-SHA256, DHE-DSS-AES256-GCM-SHA384, DHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-AES256-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA, DHE-DSS-AES128-SHA256, DHE-RSA-AES256-SHA256, DHE-DSS-AES256-SHA, DHE-RSA-AES256-SHA, ECDHE-RSA-DES-CBC3-SHA, ECDHE-ECDSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, DHE-DSS-AES256-SHA256, DHE-DSS-AES128-SHA, DES-CBC3-SHA, DHE-RSA-CHACHA20-POLY1305, ECDHE-RSA-CAMELLIA256-SHA384, ECDHE-ECDSA-CAMELLIA256-SHA384, DHE-RSA-CAMELLIA256-SHA256, DHE-DSS-CAMELLIA256-SHA256, DHE-RSA-CAMELLIA256-SHA, DHE-DSS-CAMELLIA256-SHA, CAMELLIA256-SHA256, CAMELLIA256-SHA, ECDHE-RSA-CAMELLIA128-SHA256, ECDHE-ECDSA-CAMELLIA128-SHA256, DHE-RSA-CAMELLIA128-SHA256, DHE-DSS-CAMELLIA128-SHA256, DHE-RSA-CAMELLIA128-SHA, DHE-DSS-CAMELLIA128-SHA, CAMELLIA128-SHA256, CAMELLIA128-SHA, DHE-RSA-SEED-SHA, DHE-DSS-SEED-SHA, SEED-SHA", + "add protocols TLSv1.1, TLSv1, SSLv3", + "consider enabling OCSP stapling", + "add cipher DES-CBC3-SHA for backward compatibility" + ] + modern: + type: array + nullable: true + description: List of modern failures + items: + type: string + example: [ + "remove ciphersuites ECDHE-RSA-AES128-SHA, AES128-GCM-SHA256, AES128-SHA256, AES128-SHA, ECDHE-RSA-AES256-SHA, AES256-GCM-SHA384, AES256-SHA256, AES256-SHA", + "consider adding ciphers ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-AES128-SHA256", + "consider enabling OCSP stapling", + "use a certificate of type ecdsa, not RSA" + ] + intermediate: + type: array + nullable: true + description: List of intermediate failures + items: + type: string + example: [ + "consider adding ciphers ECDHE-ECDSA-CHACHA20-POLY1305, ECDHE-RSA-CHACHA20-POLY1305, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, DHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES128-SHA256, ECDHE-ECDSA-AES128-SHA, ECDHE-ECDSA-AES256-SHA384, ECDHE-ECDSA-AES256-SHA, DHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA, DHE-RSA-AES256-SHA256, DHE-RSA-AES256-SHA, ECDHE-ECDSA-DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, DES-CBC3-SHA", + "add protocols TLSv1.1, TLSv1", + "consider enabling OCSP stapling", + "increase priority of ECDHE-RSA-AES256-GCM-SHA384 over AES128-SHA", + "fix ciphersuite ordering, use recommended intermediate ciphersuite" + ] + grade: + type: integer + description: Mozilla grading worker grade + example: 93 + lettergrade: + type: string + description: Mozilla grading worker letter grade + example: "A" + rank: + type: integer + description: Rank of the target domain + example: 2147483647 + domain: + type: string + description: Domain name + example: "duck.com" + alexa_rank: + type: integer + description: Alexa rank of the domain + example: 2147483647 + cisco_rank: + type: integer + description: Cisco rank of the domain + example: 2147483647 + alexa_domain: + type: string + description: Alexa domain name + example: "duck.com" + cisco_domain: + type: string + description: Cisco domain name + example: "duck.com" + reasons: + type: array + description: List of reasons for distrust + items: + type: string + example: [ + "path uses a blacklisted cert: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5 (id=123)", + "whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=187368699) override blacklisting of 123", + "path uses a blacklisted cert: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5 (id=188669208)", + "whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=187368699) override blacklisting of 188669208", + "path uses a root not trusted by Mozilla: C=IE, O=Baltimore, OU=CyberTrust, CN=Baltimore CyberTrust Root (id=16)", + "whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=189284449) override blacklisting of 34174538", + "whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=189094508) override blacklisting of 34174538", + "path uses a blacklisted cert: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2008 VeriSign, Inc. - For authorized use only, CN=VeriSign Universal Root Certification Authority (id=124)", + "whitelisted intermediate C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root G2 (id=188535492) override blacklisting of 124" + ] + isDistrusted: + type: boolean + description: Indicates if the certificate is distrusted + example: false + ack: + type: boolean + description: Acknowledgment flag + example: true + attempts: + type: integer + description: Number of attempts + example: 1 + analysis_params: + type: object + description: Parameters used for the analysis + example: {} + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /trace-route: + get: + summary: Perform a traceroute to the specified URL + tags: + - Server Info + parameters: + - name: urlString + in: query + required: true + description: The URL to trace the route to + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + message: + type: string + description: The status message of the traceroute + example: "Traceroute completed!" + result: + type: array + description: The result of the traceroute + items: + oneOf: + - type: object + additionalProperties: + type: array + items: + type: number + description: The response time in milliseconds + example: 2.015 + - type: boolean + description: Indicates if a hop was not found + example: false + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /txt-records: + get: + summary: Retrieve the TXT records for a specified domain + tags: + - Server Info + parameters: + - name: domain + in: query + required: true + description: The domain to retrieve TXT records for + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + additionalProperties: + type: string + description: The value of the TXT record + example: + brave-ledger-verification: "b9067df1ce0bb02c06924b88b62143d0c3c0b9c03a8f5c4aba3c0d0ec99f0a98" + google-site-verification: "qFBMUPljNGHE2S-NwmQmYvxpa58UfmmIidAbJG9BiuM" + pinterest-site-verification: "c71cdf6ff37b315830c4af9d98cf2add" + protonmail-verification: "2493afe89892e5b986b59415cdb104152c24f29e" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' + /whois: + get: + summary: Retrieve WHOIS information for a specified domain + tags: + - Server Info + parameters: + - name: domain + in: query + required: true + description: The domain to retrieve WHOIS information for + schema: + type: string + responses: + '200': + description: Successful response + content: + application/json: + schema: + type: object + properties: + internicData: + type: object + properties: + Domain_Name: + type: string + Registry_Domain_ID: + type: string + Registrar_WHOIS_Server: + type: string + Registrar_URL: + type: string + Updated_Date: + type: string + format: date-time + Creation_Date: + type: string + format: date-time + Registry_Expiry_Date: + type: string + format: date-time + Registrar: + type: string + Registrar_IANA_ID: + type: string + Domain_Status: + type: string + Name_Server: + type: string + DNSSEC: + type: string + URL_of_the_ICANN_Whois_Inaccuracy_Complaint_Form: + type: string + _Last_update_of_whois_database: + type: string + format: date-time + For_more_information_on_Whois_status_codes_please_visit_https: + type: string + NOTICE: + type: string + TERMS_OF_USE: + type: string + by_the_following_terms_of_use: + type: string + to: + type: string + whoisData: + type: object + properties: + domain: + type: object + properties: + id: + type: string + domain: + type: string + name: + type: string + extension: + type: string + whois_server: + type: string + status: + type: array + items: + type: string + name_servers: + type: array + items: + type: string + created_date: + type: string + format: date-time + updated_date: + type: string + format: date-time + expiration_date: + type: string + format: date-time + registrar: + type: object + properties: + id: + type: string + name: + type: string + phone: + type: string + email: + type: string + referral_url: + type: string + registrant: + type: object + properties: + name: + type: string + organization: + type: string + street: + type: string + city: + type: string + province: + type: string + postal_code: + type: string + country: + type: string + phone: + type: string + phone_ext: + type: string + fax: + type: string + fax_ext: + type: string + email: + type: string + administrative: + type: object + properties: + name: + type: string + organization: + type: string + street: + type: string + city: + type: string + province: + type: string + postal_code: + type: string + country: + type: string + phone: + type: string + phone_ext: + type: string + fax: + type: string + fax_ext: + type: string + email: + type: string + technical: + type: object + properties: + name: + type: string + organization: + type: string + street: + type: string + city: + type: string + province: + type: string + postal_code: + type: string + country: + type: string + phone: + type: string + phone_ext: + type: string + fax: + type: string + fax_ext: + type: string + email: + type: string + billing: + type: object + properties: + name: + type: string + organization: + type: string + street: + type: string + city: + type: string + province: + type: string + postal_code: + type: string + country: + type: string + phone: + type: string + phone_ext: + type: string + fax: + type: string + fax_ext: + type: string + email: + type: string + example: + internicData: + Domain_Name: "AS93.NET" + Registry_Domain_ID: "1722029712_DOMAIN_NET-VRSN" + Registrar_WHOIS_Server: "whois.cloudflare.com" + Registrar_URL: "http://www.cloudflare.com" + Updated_Date: "2019-05-07T04:20:11Z" + Creation_Date: "2012-05-22T16:36:05Z" + Registry_Expiry_Date: "2025-05-22T16:36:05Z" + Registrar: "CloudFlare, Inc." + Registrar_IANA_ID: "1910" + Domain_Status: "clientTransferProhibited https://icann.org/epp#clientTransferProhibited" + Name_Server: "ALEX.NS.CLOUDFLARE.COM" + DNSSEC: "unsigned" + URL_of_the_ICANN_Whois_Inaccuracy_Complaint_Form: "https://www.icann.org/wicf/" + _Last_update_of_whois_database: "2024-05-18T13:59:10Z <<< " + For_more_information_on_Whois_status_codes_please_visit_https: "//icann.org/epp " + NOTICE: "The expiration date displayed in this record is the date the registrar's sponsorship of the domain name registration in the registry is currently set to expire. This date does not necessarily reflect the expiration date of the domain name registrant's agreement with the sponsoring registrar. Users may consult the sponsoring registrar's Whois database to view the registrar's reported date of expiration for this registration. " + TERMS_OF_USE: "You are not authorized to access or query our Whois database through the use of electronic processes that are high-volume and automated except as reasonably necessary to register domain names or modify existing registrations; the Data in VeriSign Global Registry Services' (\"VeriSign\") Whois database is provided by VeriSign for information purposes only, and to assist persons in obtaining information about or related to a domain name registration record. VeriSign does not guarantee its accuracy. By submitting a Whois query, you agree to abide" + by_the_following_terms_of_use: "You agree that you may use this Data only for lawful purposes and that under no circumstances will you use this Data" + to: "(1) allow, enable, or otherwise support the transmission of mass unsolicited, commercial advertising or solicitations via e-mail, telephone, or facsimile; or (2) enable high volume, automated, electronic processes that apply to VeriSign (or its computer systems). The compilation, repackaging, dissemination or other use of this Data is expressly prohibited without the prior written consent of VeriSign. You agree not to use electronic processes that are automated and high-volume to access or query the Whois database except as reasonably necessary to register domain names or modify existing registrations. VeriSign reserves the right to restrict your access to the Whois database in its sole discretion to ensure operational stability. VeriSign may restrict or terminate your access to the Whois database for failure to abide by these terms of use. VeriSign reserves the right to modify these terms at any time. The Registry database contains ONLY .COM, .NET, .EDU domains and Registrars. " + whoisData: + domain: + id: "1722029712_DOMAIN_NET-VRSN" + domain: "as93.net" + name: "as93" + extension: "net" + whois_server: "whois.cloudflare.com" + status: + - "clienttransferprohibited" + name_servers: + - "adrian.ns.cloudflare.com" + - "alex.ns.cloudflare.com" + created_date: "2012-05-22T16:36:05Z" + updated_date: "2019-05-07T04:20:11Z" + expiration_date: "2025-05-22T16:36:05Z" + registrar: + id: "1910" + name: "Cloudflare, Inc." + phone: "+1.4153197517" + email: "registrar-abuse@cloudflare.com" + referral_url: "https://www.cloudflare.com" + registrant: + name: "DATA REDACTED" + organization: "DATA REDACTED" + street: "DATA REDACTED" + city: "DATA REDACTED" + province: "London" + postal_code: "DATA REDACTED" + country: "GB" + phone: "DATA REDACTED" + phone_ext: "DATA REDACTED" + fax: "DATA REDACTED" + fax_ext: "DATA REDACTED" + email: "https://domaincontact.cloudflareregistrar.com/as93.net" + administrative: + name: "DATA REDACTED" + organization: "DATA REDACTED" + street: "DATA REDACTED" + city: "DATA REDACTED" + province: "DATA REDACTED" + postal_code: "DATA REDACTED" + country: "DATA REDACTED" + phone: "DATA REDACTED" + phone_ext: "DATA REDACTED" + fax: "DATA REDACTED" + fax_ext: "DATA REDACTED" + email: "https://domaincontact.cloudflareregistrar.com/as93.net" + technical: + name: "DATA REDACTED" + organization: "DATA REDACTED" + street: "DATA REDACTED" + city: "DATA REDACTED" + province: "DATA REDACTED" + postal_code: "DATA REDACTED" + country: "DATA REDACTED" + phone: "DATA REDACTED" + phone_ext: "DATA REDACTED" + fax: "DATA REDACTED" + fax_ext: "DATA REDACTED" + email: "https://domaincontact.cloudflareregistrar.com/as93.net" + billing: + name: "DATA REDACTED" + organization: "DATA REDACTED" + street: "DATA REDACTED" + city: "DATA REDACTED" + province: "DATA REDACTED" + postal_code: "DATA REDACTED" + country: "DATA REDACTED" + phone: "DATA REDACTED" + phone_ext: "DATA REDACTED" + fax: "DATA REDACTED" + fax_ext: "DATA REDACTED" + email: "https://domaincontact.cloudflareregistrar.com/as93.net" + '204': + $ref: '#/components/responses/Skipped' + '500': + $ref: '#/components/responses/Error' + '400': + $ref: '#/components/responses/MissingParam' + '401': + $ref: '#/components/responses/Unauthorized' + '429': + $ref: '#/components/responses/TooManyRequests' diff --git a/server.js b/server.js index cc25ecf..03bdf2b 100644 --- a/server.js +++ b/server.js @@ -1,17 +1,25 @@ -const express = require('express'); -const fs = require('fs'); -const path = require('path'); -const cors = require('cors'); -const rateLimit = require('express-rate-limit'); -const historyApiFallback = require('connect-history-api-fallback'); -require('dotenv').config(); +import fs from 'fs'; +import path from 'path'; +import cors from 'cors'; +import dotenv from 'dotenv'; +import express from 'express'; +import rateLimit from 'express-rate-limit'; +import historyApiFallback from 'connect-history-api-fallback'; + +// Load environment variables from .env file +dotenv.config(); + +// Create the Express app const app = express(); +const __filename = new URL(import.meta.url).pathname; +const __dirname = path.dirname(__filename); + const port = process.env.PORT || 3000; // The port to run the server on const API_DIR = '/api'; // Name of the dir containing the lambda functions const dirPath = path.join(__dirname, API_DIR); // Path to the lambda functions dir -const guiPath = path.join(__dirname, 'build'); +const guiPath = path.join(__dirname, 'dist', 'client'); const placeholderFilePath = path.join(__dirname, 'public', 'placeholder.html'); const handlers = {}; // Will store list of API endpoints process.env.WC_SERVER = 'true'; // Tells middleware to return in non-lambda mode @@ -46,16 +54,19 @@ const limiters = limits.map(limit => rateLimit({ // If rate-limiting enabled, then apply the limiters to the /api endpoint if (process.env.API_ENABLE_RATE_LIMIT === 'true') { - app.use('/api', limiters); + app.use(API_DIR, limiters); } // Read and register each API function as an Express routes fs.readdirSync(dirPath, { withFileTypes: true }) .filter(dirent => dirent.isFile() && dirent.name.endsWith('.js')) - .forEach(dirent => { + .forEach(async dirent => { const routeName = dirent.name.split('.')[0]; const route = `${API_DIR}/${routeName}`; - const handler = require(path.join(dirPath, dirent.name)); + // const handler = require(path.join(dirPath, dirent.name)); + + const handlerModule = await import(path.join(dirPath, dirent.name)); + const handler = handlerModule.default || handlerModule; handlers[route] = handler; app.get(route, async (req, res) => { @@ -67,89 +78,114 @@ fs.readdirSync(dirPath, { withFileTypes: true }) }); }); - // Create a single API endpoint to execute all lambda functions - app.get('/api', async (req, res) => { - const results = {}; - const { url } = req.query; - const maxExecutionTime = process.env.API_TIMEOUT_LIMIT || 20000; - - const executeHandler = async (handler, req, res) => { - return new Promise(async (resolve, reject) => { - try { - const mockRes = { - status: (statusCode) => mockRes, - json: (body) => resolve({ body }), - }; - await handler({ ...req, query: { url } }, mockRes); - } catch (err) { - reject(err); - } - }); - }; - - const timeout = (ms, jobName = null) => { - return new Promise((_, reject) => { - setTimeout(() => { - reject(new Error( - `Timed out after ${ms/1000} seconds${jobName ? `, when executing ${jobName}` : ''}` - )); - }, ms); - }); - }; - - const handlerPromises = Object.entries(handlers).map(async ([route, handler]) => { - const routeName = route.replace(`${API_DIR}/`, ''); - +const renderPlaceholderPage = async (res, msgId, logs) => { + const errorMessages = { + notCompiled: 'Looks like the GUI app has not yet been compiled.
' + + 'Run yarn build to continue, then restart the server.', + notCompiledSsrHandler: 'Server-side rendering failed to initiate, as SSR handler not found.
' + + 'This can be fixed by running yarn build, then restarting the server.
', + disabledGui: 'Web-Check API is up and running!
Access the endpoints at ' + + `${API_DIR}`, + }; + const logOutput = logs ? `
${logs}
` : ''; + const errorMessage = (errorMessages[msgId] || 'An mystery error occurred.') + logOutput; + const placeholderContent = await fs.promises.readFile(placeholderFilePath, 'utf-8'); + const htmlContent = placeholderContent.replace('', errorMessage ); + res.status(500).send(htmlContent); +}; + +// Create a single API endpoint to execute all lambda functions +app.get(API_DIR, async (req, res) => { + const results = {}; + const { url } = req.query; + const maxExecutionTime = process.env.API_TIMEOUT_LIMIT || 20000; + + const executeHandler = async (handler, req) => { + return new Promise(async (resolve, reject) => { try { - const result = await Promise.race([ - executeHandler(handler, req, res), - timeout(maxExecutionTime, routeName) - ]); - results[routeName] = result.body; + const mockRes = { + status: () => mockRes, + json: (body) => resolve({ body }), + }; + await handler({ ...req, query: { url } }, mockRes); } catch (err) { - results[routeName] = { error: err.message }; + reject(err); } }); - - await Promise.all(handlerPromises); - res.json(results); + }; + + const timeout = (ms, jobName = null) => { + return new Promise((_, reject) => { + setTimeout(() => { + reject(new Error( + `Timed out after ${ms/1000} seconds${jobName ? `, when executing ${jobName}` : ''}` + )); + }, ms); + }); + }; + + const handlerPromises = Object.entries(handlers).map(async ([route, handler]) => { + const routeName = route.replace(`${API_DIR}/`, ''); + + try { + const result = await Promise.race([ + executeHandler(handler, req, res), + timeout(maxExecutionTime, routeName) + ]); + results[routeName] = result.body; + } catch (err) { + results[routeName] = { error: err.message }; + } }); + await Promise.all(handlerPromises); + res.json(results); +}); + +// Skip the marketing homepage, for self-hosted users +app.use((req, res, next) => { + if (req.path === '/' && process.env.BOSS_SERVER !== 'true' && !process.env.DISABLE_GUI) { + req.url = '/check'; + } + next(); +}); + +// Serve up the GUI - if build dir exists, and GUI feature enabled +if (process.env.DISABLE_GUI && process.env.DISABLE_GUI !== 'false') { + app.get('/', async (req, res) => { + renderPlaceholderPage(res, 'disabledGui'); + }); +} else if (!fs.existsSync(guiPath)) { + app.get('/', async (req, res) => { + renderPlaceholderPage(res, 'notCompiled'); + }); +} else { // GUI enabled, and build files present, let's go!! + app.use(express.static('dist/client/')); + app.use(async (req, res, next) => { + const ssrHandlerPath = path.join(__dirname, 'dist', 'server', 'entry.mjs'); + import(ssrHandlerPath).then(({ handler: ssrHandler }) => { + ssrHandler(req, res, next); + }).catch(async err => { + renderPlaceholderPage(res, 'notCompiledSsrHandler', err.message); + }); + }); +} + // Handle SPA routing app.use(historyApiFallback({ rewrites: [ - { from: /^\/api\/.*$/, to: (context) => context.parsedUrl.path }, + { from: new RegExp(`^${API_DIR}/.*$`), to: (context) => context.parsedUrl.path }, + { from: /^.*$/, to: '/index.html' } ] })); -// Serve up the GUI - if build dir exists, and GUI feature enabled -if (process.env.DISABLE_GUI && process.env.DISABLE_GUI !== 'false') { - app.get('*', async (req, res) => { - const placeholderContent = await fs.promises.readFile(placeholderFilePath, 'utf-8'); - const htmlContent = placeholderContent.replace( - '', - 'Web-Check API is up and running!
Access the endpoints at ' - +'/api' - ); - - res.status(500).send(htmlContent); - }); -} else if (!fs.existsSync(guiPath)) { - app.get('*', async (req, res) => { - const placeholderContent = await fs.promises.readFile(placeholderFilePath, 'utf-8'); - const htmlContent = placeholderContent.replace( - '', - 'Looks like the GUI app has not yet been compiled.
' + - 'Run yarn build to continue, then restart the server.' - ); - res.status(500).send(htmlContent); -}); -} else { // GUI enabled, and build files present, let's go!! - app.use(express.static(guiPath)); -} - +// Anything left unhandled (which isn't an API endpoint), return a 404 app.use((req, res, next) => { - res.status(404).sendFile(path.join(__dirname, 'public', 'error.html')); + if (!req.path.startsWith(`${API_DIR}/`)) { + res.status(404).sendFile(path.join(__dirname, 'public', 'error.html')); + } else { + next(); + } }); // Print nice welcome message to user diff --git a/serverless.yml b/serverless.yml deleted file mode 100644 index b81b754..0000000 --- a/serverless.yml +++ /dev/null @@ -1,256 +0,0 @@ -service: web-check-api - -provider: - name: aws - runtime: nodejs14.x - region: us-east-1 - # environment: - # GOOGLE_CLOUD_API_KEY: ${ssm:GOOGLE_CLOUD_API_KEY~true, ''} - # TORRENT_IP_API_KEY: ${ssm:TORRENT_IP_API_KEY~true, ''} - # SECURITY_TRAILS_API_KEY: ${ssm:SECURITY_TRAILS_API_KEY~true, ''} - # BUILT_WITH_API_KEY: ${ssm:BUILT_WITH_API_KEY~true, ''} - # URL_SCAN_API_KEY: ${ssm:URL_SCAN_API_KEY~true, ''} - # TRANCO_USERNAME: ${ssm:TRANCO_USERNAME~true, ''} - # TRANCO_API_KEY: ${ssm:TRANCO_API_KEY~true, ''} - # CLOUDMERSIVE_API_KEY: ${ssm:CLOUDMERSIVE_API_KEY~true, ''} - # CHROME_PATH: ${ssm:CHROME_PATH~true, ''} - # API_TIMEOUT_LIMIT: ${ssm:API_TIMEOUT_LIMIT~true, ''} - # API_CORS_ORIGIN: ${ssm:API_CORS_ORIGIN~true, ''} - iamRoleStatements: - - Effect: Allow - Action: - - ssm:GetParameter - Resource: - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/GOOGLE_CLOUD_API_KEY - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/TORRENT_IP_API_KEY - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/SECURITY_TRAILS_API_KEY - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/BUILT_WITH_API_KEY - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/URL_SCAN_API_KEY - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/TRANCO_USERNAME - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/TRANCO_API_KEY - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/CLOUDMERSIVE_API_KEY - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/CHROME_PATH - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/API_TIMEOUT_LIMIT - - arn:aws:ssm:us-east-1:${env:AWS_ACCOUNT_ID}:parameter/API_CORS_ORIGIN -functions: - archives: - handler: api/archives.handler - events: - - http: - path: api/archives - method: get - blockLists: - handler: api/block-lists.handler - events: - - http: - path: api/block-lists - method: get - carbon: - handler: api/carbon.handler - events: - - http: - path: api/carbon - method: get - cookies: - handler: api/cookies.handler - events: - - http: - path: api/cookies - method: get - dnsServer: - handler: api/dns-server.handler - events: - - http: - path: api/dns-server - method: get - dns: - handler: api/dns.handler - events: - - http: - path: api/dns - method: get - dnssec: - handler: api/dnssec.handler - events: - - http: - path: api/dnssec - method: get - features: - handler: api/features.handler - events: - - http: - path: api/features - method: get - firewall: - handler: api/firewall.handler - events: - - http: - path: api/firewall - method: get - getIp: - handler: api/get-ip.handler - events: - - http: - path: api/get-ip - method: get - headers: - handler: api/headers.handler - events: - - http: - path: api/headers - method: get - hsts: - handler: api/hsts.handler - events: - - http: - path: api/hsts - method: get - httpSecurity: - handler: api/http-security.handler - events: - - http: - path: api/http-security - method: get - legacyRank: - handler: api/legacy-rank.handler - events: - - http: - path: api/legacy-rank - method: get - linkedPages: - handler: api/linked-pages.handler - events: - - http: - path: api/linked-pages - method: get - mailConfig: - handler: api/mail-config.handler - events: - - http: - path: api/mail-config - method: get - ports: - handler: api/ports.handler - events: - - http: - path: api/ports - method: get - quality: - handler: api/quality.handler - events: - - http: - path: api/quality - method: get - rank: - handler: api/rank.handler - events: - - http: - path: api/rank - method: get - redirects: - handler: api/redirects.handler - events: - - http: - path: api/redirects - method: get - robotsTxt: - handler: api/robots-txt.handler - events: - - http: - path: api/robots-txt - method: get - screenshot: - handler: api/screenshot.handler - events: - - http: - path: api/screenshot - method: get - securityTxt: - handler: api/security-txt.handler - events: - - http: - path: api/security-txt - method: get - sitemap: - handler: api/sitemap.handler - events: - - http: - path: api/sitemap - method: get - socialTags: - handler: api/social-tags.handler - events: - - http: - path: api/social-tags - method: get - ssl: - handler: api/ssl.handler - events: - - http: - path: api/ssl - method: get - status: - handler: api/status.handler - events: - - http: - path: api/status - method: get - techStack: - handler: api/tech-stack.handler - events: - - http: - path: api/tech-stack - method: get - threats: - handler: api/threats.handler - events: - - http: - path: api/threats - method: get - tls: - handler: api/tls.handler - events: - - http: - path: api/tls - method: get - traceRoute: - handler: api/trace-route.handler - events: - - http: - path: api/trace-route - method: get - txtRecords: - handler: api/txt-records.handler - events: - - http: - path: api/txt-records - method: get - whois: - handler: api/whois.handler - events: - - http: - path: api/whois - method: get - - -plugins: - - serverless-webpack - # - serverless-domain-manager - # - serverless-offline - -custom: - webpack: - webpackConfig: 'api/_common/aws-webpack.config.js' - includeModules: false - packagerOptions: - noInstall: true - - # customDomain: - # domainName: example.com - # basePath: 'api' - # stage: ${self:provider.stage} - # createRoute53Record: true - - # serverless-offline: - # prefix: '' - # httpPort: 3000 diff --git a/src/App.test.tsx b/src/App.test.tsx deleted file mode 100644 index 2a68616..0000000 --- a/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index 7be0166..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Route, Routes } from 'react-router-dom'; -import Styled from 'styled-components'; -import * as Sentry from '@sentry/react'; - -import Home from 'pages/Home'; -import Results from 'pages/Results'; -import About from 'pages/About'; -import NotFound from 'pages/NotFound'; -import colors from 'styles/colors'; - -const Container = Styled.main` - background: ${colors.background}; - color: ${colors.textColor}; - width: 100vw; - margin: 0; -`; - -Sentry.init({ - dsn: 'https://30eb6135d37643fb95c7da4e77a46142@glitch.as93.net/1', - beforeSend(event) { // Check if error logging is disabled - const ignoredHosts = ['localhost', '127.0.0.1']; - const disableErrors = process.env.REACT_APP_DISABLE_ERROR_LOGGING; - if (disableErrors || ignoredHosts.includes(window.location.hostname)) { - return null; - } - return event; - } -}); - -function App() { - return ( - - - } /> - } /> - } /> - } /> - - - ); -} - -export default App; diff --git a/src/components/Demo.tsx b/src/components/Demo.tsx deleted file mode 100644 index 9ffa9f6..0000000 --- a/src/components/Demo.tsx +++ /dev/null @@ -1,7 +0,0 @@ -interface Props { - message: string, -}; - -const Demo = ({ message }: Props): JSX.Element =>
{message}
; - -export default Demo; diff --git a/src/components/homepage/AboutSection.astro b/src/components/homepage/AboutSection.astro new file mode 100644 index 0000000..a80a357 --- /dev/null +++ b/src/components/homepage/AboutSection.astro @@ -0,0 +1,113 @@ +--- + +import ButtonGroup from '@components/homepage/ButtonGroup.astro'; +import Features from '@components/homepage/Features.astro'; +import SponsorSegment from '@components/homepage/SponsorSegment.astro'; + +const supportedChecks = [ + 'Archive History', + 'Block List Check', + 'Carbon Footprint', + 'Cookies', + 'DNS Server', + 'DNS Records', + 'DNSSEC', + 'Site Features', + 'Firewall Types', + 'Get IP Address', + 'Headers', + 'HSTS', + 'HTTP Security', + 'Linked Pages', + 'Mail Config', + 'Open Ports', + 'Quality Check', + 'Global Rank', + 'Redirects', + 'Robots.txt', + 'Screenshot', + 'Security.txt', + 'Sitemap', + 'Social Tags', + 'SSL Certificate', + 'Uptime Status', + 'Tech Stack', + 'Known Threats', + 'TLS Version', + 'Trace Route', + 'TXT Records', + 'Whois Lookup' +]; + +const links = [ + { + title: 'View on GitHub', + url: 'https://github.com/lissy93/web-check', + icon: 'github', + isCta: true, + }, + { + title: 'Deploy your Own', + url: '/self-hosted-setup', + icon: 'rocket', + isCta: false, + }, + { + title: 'Use the API', + url: '/web-check-api', + icon: 'code', + isCta: false, + }, +]; + +--- + +
+
+

Ready to get started?

+

+ With over 30 supported checks + you can view and analyse key website information in an instant +

+
+
+ + +
+ +
+ + diff --git a/src/components/homepage/AnimatedButton.astro b/src/components/homepage/AnimatedButton.astro new file mode 100644 index 0000000..6b2a5c0 --- /dev/null +++ b/src/components/homepage/AnimatedButton.astro @@ -0,0 +1,93 @@ +--- +const buttonText = 'Analyze URL'; +const buttonType = 'submit'; +--- + + + + + + diff --git a/src/components/homepage/AnimatedInput.astro b/src/components/homepage/AnimatedInput.astro new file mode 100644 index 0000000..d74202f --- /dev/null +++ b/src/components/homepage/AnimatedInput.astro @@ -0,0 +1,186 @@ +--- +const placeholders = [ + 'duck.com', + 'github.com', + 'google.com', + 'x.com', + 'bbc.co.uk', + 'wikipedia.org', + 'openai.com', +]; +--- + + +
+ +
+ + {placeholders.map((placeholder, index) => ( + + ))} +
+
+ + + + + diff --git a/src/components/homepage/ButtonGroup.astro b/src/components/homepage/ButtonGroup.astro new file mode 100644 index 0000000..a99aa61 --- /dev/null +++ b/src/components/homepage/ButtonGroup.astro @@ -0,0 +1,66 @@ +--- +import Icon from '@components/molecules/Icon.svelte'; + +interface Props { + links: { + title: string; + url: string; + icon: string; + isCta: boolean; + }[]; +} + +const { links } = Astro.props; + +--- + +
+ {links.map(link => ( + {link.title} + ))} +
+ + diff --git a/src/components/homepage/Features.astro b/src/components/homepage/Features.astro new file mode 100644 index 0000000..5e7037a --- /dev/null +++ b/src/components/homepage/Features.astro @@ -0,0 +1,40 @@ +--- + +import Icon from '@components/molecules/Icon.svelte'; + +interface Props { + supportedChecks: string[]; +} + +const { supportedChecks } = Astro.props; + +--- + + + + diff --git a/src/components/homepage/HeroForm.astro b/src/components/homepage/HeroForm.astro new file mode 100644 index 0000000..d0b2671 --- /dev/null +++ b/src/components/homepage/HeroForm.astro @@ -0,0 +1,141 @@ +--- +import AnimatedButton from "./AnimatedButton.astro" +import AnimatedInput from "./AnimatedInput.astro" +import Screenshots from "./Screenshots.astro" +--- + +
+
+

+ Check Web + Web + Check +

+
+

We give you X-Ray
Vision for your Website

+

+ In just 20 seconds, you can see + what attackers already know +

+
+ + + + +
+
+ +
+ + + + diff --git a/src/components/homepage/HomeBackground.tsx b/src/components/homepage/HomeBackground.tsx new file mode 100644 index 0000000..73c61f4 --- /dev/null +++ b/src/components/homepage/HomeBackground.tsx @@ -0,0 +1,172 @@ +import { useState, useEffect } from 'react'; +import { motion } from 'framer-motion'; +import styled from '@emotion/styled'; + +// Global Animation Configuration Constants +const dotSpacing = 32; // Number of px between each dot +const meteorCount = 4; // Number of meteors to display at any given time +const tailLength = 80; // Length of the meteor tail in px +const distanceBase = 5; // Base distance for meteor to travel in grid units +const distanceVariance = 5; // Variance for randomization to append to travel in grid units +const durationBase = 1.5; // Base duration for meteor to travel in seconds +const durationVariance = 1; // Variance for randomization to append to travel in seconds +const delayBase = 500; // Base delay for meteor to respawn in milliseconds +const delayVariance = 1500; // Variance for randomization to append to respawn in milliseconds +const tailDuration = 0.25; // Duration for meteor tail to retract in seconds +const headEasing = [0.8, 0.6, 1, 1]; // Easing for meteor head +const tailEasing = [0.5, 0.6, 0.6, 1]; // Easing for meteor tail + +const MeteorContainer = styled(motion.div)` + position: absolute; + width: 4px; + height: 4px; + border-radius: 50%; + background-color: #9fef00; + top: 1px; +`; + +const Tail = styled(motion.div)` + position: absolute; + top: -80px; + left: 1px; + width: 2px; + height: 80px; + background: linear-gradient(to bottom, transparent, #9fef00); +`; + +const StyledSvg = styled.svg` + pointer-events: none; + position: absolute; + inset: 0; + height: 100%; + width: 100%; + fill: rgba(100, 100, 100, 0.5); + height: 100vh; +`; + +const StyledRect = styled.rect` + width: 100%; + height: 100%; + stroke-width: 0; +`; + +const Container = styled.div` + pointer-events: none; + position: absolute; + height: 100vh; + width: 100vw; + z-index: 1; + top: 0; + left: 0; + background: radial-gradient(circle at center top, transparent, transparent 60%, var(--background) 100%); +`; + +const generateMeteor = (id: number, gridSizeX: number, gridSizeY: number) => { + const column = Math.floor(Math.random() * gridSizeX); + const startRow = Math.floor(Math.random() * (gridSizeY - 12)); + const travelDistance = distanceBase + Math.floor(Math.random() * distanceVariance); + const duration = durationBase + Math.floor(Math.random() * durationVariance); + + return { + id, + column, + startRow, + endRow: startRow + travelDistance, + duration, + tailVisible: true, + animationStage: 'traveling', + opacity: 1, + }; +}; + +const generateInitialMeteors = (gridSizeX: number, gridSizeY: number) => { + const seen = new Set(); + return Array.from({ length: meteorCount }, (_, index) => generateMeteor(index, gridSizeX, gridSizeY)) + .filter(item => !seen.has(item.column) && seen.add(item.column)); +}; + +const WebCheckHomeBackground = () => { + const [gridSizeX, setGridSizeX] = useState(Math.floor(window.innerWidth / dotSpacing)); + const [gridSizeY, setGridSizeY] = useState(Math.floor(window.innerHeight / dotSpacing)); + const [meteors, setMeteors] = useState(() => generateInitialMeteors(gridSizeX, gridSizeY)); + + const handleAnimationComplete = (id: number) => { + setMeteors(current => + current.map(meteor => { + if (meteor.id === id) { + if (meteor.animationStage === 'traveling') { + // Transition to retracting tail + return { ...meteor, tailVisible: false, animationStage: 'retractingTail' }; + } else if (meteor.animationStage === 'retractingTail') { + // Set to resetting and make invisible + return { ...meteor, animationStage: 'resetting', opacity: 0 }; + } else if (meteor.animationStage === 'resetting') { + // Respawn the meteor after a delay + setTimeout(() => { + setMeteors(current => + current.map(m => m.id === id ? generateMeteor(id, gridSizeX, gridSizeY) : m) + ); + }, delayBase + Math.random() * delayVariance); + } + } + return meteor; + }) + ); + }; + + useEffect(() => { + const handleResize = () => { + setGridSizeX(Math.floor(window.innerWidth / dotSpacing)); + setGridSizeY(Math.floor(window.innerHeight / dotSpacing)); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return ( + <> + + + + + + + + + + + {meteors.map(({ id, column, startRow, endRow, duration, tailVisible, animationStage, opacity }) => { + return ( + handleAnimationComplete(id)} + > + + + ); + })} + + ); +}; + +export default WebCheckHomeBackground; diff --git a/src/components/homepage/Screenshots.astro b/src/components/homepage/Screenshots.astro new file mode 100644 index 0000000..336df27 --- /dev/null +++ b/src/components/homepage/Screenshots.astro @@ -0,0 +1,107 @@ +--- +const screenshots = [ + "https://i.ibb.co/kB7LsV1/wc-ssl.png", + "https://i.ibb.co/7Q1kMwM/wc-dns.png", + "https://i.ibb.co/TTQ6DtP/wc-cookies.png", + "https://i.ibb.co/KwQCjPf/wc-robots.png", + "https://i.ibb.co/t3xcwP1/wc-headers.png", + "https://i.ibb.co/Kqg8rx7/wc-quality.png", + "https://i.ibb.co/cXH2hfR/wc-location.png", + "https://i.ibb.co/25j1sT7/wc-hosts.png", + "https://i.ibb.co/hVVrmwh/wc-redirects.png", + "https://i.ibb.co/wyt21QN/wc-txt-records.png", + "https://i.ibb.co/V9CNLBK/wc-status.png", + "https://i.ibb.co/F8D1hmf/wc-ports.png", + "https://i.ibb.co/M59qgxP/wc-trace-route.png", + "https://i.ibb.co/5v6fSyw/Screenshot-from-2023-07-29-19-07-50.png", + "https://i.ibb.co/Mk1jx32/wc-server.png", + "https://i.ibb.co/89WLp14/wc-domain.png", + "https://i.ibb.co/89WLp14/wc-domain.png", + "https://i.ibb.co/J54zVmQ/wc-dnssec.png", + "https://i.ibb.co/gP4P6kp/wc-features.png", + "https://i.ibb.co/k253fq4/Screenshot-from-2023-07-17-20-10-52.png", + "https://i.ibb.co/tKpL8F9/Screenshot-from-2023-08-12-15-43-12.png", + "https://i.ibb.co/bBQSQNz/Screenshot-from-2023-08-12-15-43-46.png", + "https://i.ibb.co/GtrCQYq/Screenshot-from-2023-07-21-12-28-38.png", + "https://i.ibb.co/tq1FT5r/Screenshot-from-2023-07-24-20-31-21.png", + "https://i.ibb.co/LtK14XR/Screenshot-from-2023-07-29-11-16-44.png", + "https://i.ibb.co/4srTT1w/Screenshot-from-2023-07-29-11-15-27.png", + "https://i.ibb.co/yqhwx5G/Screenshot-from-2023-07-29-18-22-20.png", + "https://i.ibb.co/MfcxQt2/Screenshot-from-2023-08-12-15-40-52.png", + "https://i.ibb.co/LP05HMV/Screenshot-from-2023-08-12-15-40-28.png", + "https://i.ibb.co/nB9szT1/Screenshot-from-2023-08-14-22-31-16.png", + "https://i.ibb.co/nkbczgb/Screenshot-from-2023-08-14-22-02-40.png", + "https://i.ibb.co/M5JSXbW/Screenshot-from-2023-08-26-12-12-43.png", + "https://i.ibb.co/hYgy621/Screenshot-from-2023-08-26-12-07-47.png", + "https://i.ibb.co/6ydtH5R/Screenshot-from-2023-08-26-12-09-58.png", + "https://i.ibb.co/FmksZJt/Screenshot-from-2023-08-26-12-12-09.png", + "https://i.ibb.co/F7qRZkh/Screenshot-from-2023-08-26-12-11-28.png", + "https://i.ibb.co/2F0x8kP/Screenshot-from-2023-07-29-18-34-48.png" +]; +--- + +
+
+ {screenshots.slice(0, 20).map((src, idx) => ( + {`Screenshot + ))} +
+
+ {screenshots.slice(20).map((src, idx) => ( + {`Screenshot + ))} +
+
+ + + + diff --git a/src/components/homepage/SponsorSegment.astro b/src/components/homepage/SponsorSegment.astro new file mode 100644 index 0000000..7d22f97 --- /dev/null +++ b/src/components/homepage/SponsorSegment.astro @@ -0,0 +1,62 @@ +--- +const sponsorName = 'Terminal Trove'; +const sponsorTagline = 'The $HOME of all things terminal.'; +const sponsorLink = 'https://terminaltrove.com/?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh'; + +const ctaPreText = 'Get updates on the latest CLI/TUI tools via the'; +const ctaLinkHref = 'https://terminaltrove.com/newsletter?utm_campaign=github&utm_medium=referral&utm_content=web-check&utm_source=wcgh'; +const ctaLinkText = 'Terminal Trove Newsletter'; +const ctaImageSrc = 'https://i.ibb.co/5jJ4bzZ/terminal-trove-cta.png'; +--- + + + + diff --git a/src/components/molecules/Icon.svelte b/src/components/molecules/Icon.svelte new file mode 100644 index 0000000..daa8069 --- /dev/null +++ b/src/components/molecules/Icon.svelte @@ -0,0 +1,36 @@ + + +{#if iconMap[name]} + +{/if} + + diff --git a/src/components/scafold/Footer.astro b/src/components/scafold/Footer.astro new file mode 100644 index 0000000..1b81201 --- /dev/null +++ b/src/components/scafold/Footer.astro @@ -0,0 +1,53 @@ +--- +const repo = 'github.com/lissy93/web-check'; +const github = `https://github.com/${repo}`; + +const licenseText = 'MIT'; +const licenseLink = `${github}/blob/master/LICENSE`; + +const aboutLink = '/about'; +const projectName = 'Web Check'; + +const authorName = 'Alicia Sykes'; +const authorLink = 'https://aliciasykes.com'; +const currentYear = new Date().getFullYear(); +--- + + + + diff --git a/src/components/scafold/Nav.astro b/src/components/scafold/Nav.astro new file mode 100644 index 0000000..291957f --- /dev/null +++ b/src/components/scafold/Nav.astro @@ -0,0 +1,108 @@ +--- + +--- + + + + + diff --git a/src/env.d.ts b/src/env.d.ts new file mode 100644 index 0000000..acef35f --- /dev/null +++ b/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/src/index.tsx b/src/index.tsx deleted file mode 100644 index db18799..0000000 --- a/src/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { BrowserRouter } from 'react-router-dom'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; - -const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement -); -root.render( - - - - - -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro new file mode 100644 index 0000000..2f178ae --- /dev/null +++ b/src/layouts/Base.astro @@ -0,0 +1,39 @@ +--- +import { ViewTransitions } from 'astro:transitions' +import MetaTags from '@layouts/MetaTags.astro'; + +import '@styles/typography.scss'; +import '@styles/global.scss'; +import '@styles/colors.scss'; +import '@styles/media-queries.scss'; + +interface Props { + title?: string; + description?: string; + keywords?: string; + customSchemaJson?: any; + breadcrumbs?: Array<{ + name: string; + item: string; + }> +} + +--- + + + + + + + + + + + + + diff --git a/src/layouts/MetaTags.astro b/src/layouts/MetaTags.astro new file mode 100644 index 0000000..71bff58 --- /dev/null +++ b/src/layouts/MetaTags.astro @@ -0,0 +1,105 @@ +--- + +interface Props { + title?: string; + description?: string; + keywords?: string; + customSchemaJson?: any; + breadcrumbs?: Array<{ + name: string; + item: string; + }> +} + +// Default meta tag values +const siteInfo = { + title: 'Web Check', + titleLong: 'Web Check - X-Ray Vision for any Website', + description: 'Web Check is the all-in-one OSINT and security tool, for revealing the inner workings of any website', + keywords: '', + author: 'Alicia Sykes', + twitter: '@Lissy_Sykes', + site: import.meta.env.SITE_URL || 'https://web-check.xyz', + analytics: { + enable: import.meta.env.ENABLE_ANALYTICS, + domain: 'web-check.xyz', + script: 'https://no-track.as93.net/js/script.js', + }, +}; + +// Set values for the meta tags, from props or defaults +const { + title = siteInfo.title, + description = siteInfo.description, + keywords = siteInfo.keywords, + breadcrumbs, + customSchemaJson, +} = Astro.props; + +// Set non-customizable values for meta tags, from the siteInfo +const { site, author, twitter, analytics, titleLong } = siteInfo; + +// Given a map of breadcrumbs, return the JSON-LD for the BreadcrumbList schema +const makeBreadcrumbs = () => { + if (!breadcrumbs) return null; + return { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + "itemListElement": breadcrumbs.map((breadcrumb, index) => ({ + "@type": "ListItem", + "position": index + 1, + "name": breadcrumb.name, + "item": `${site}/${breadcrumb.item}` + })) + } +} + +--- + + +{title} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{analytics.enable && ( + +)} + + +{breadcrumbs && ( + diff --git a/src/pages/index.astro b/src/pages/index.astro new file mode 100644 index 0000000..60e4120 --- /dev/null +++ b/src/pages/index.astro @@ -0,0 +1,63 @@ +--- +import BaseLayout from '@layouts/Base.astro'; +import HeroForm from '@components/homepage/HeroForm.astro'; +import HomeBackground from '@/components/homepage/HomeBackground'; +import AboutSection from '@/components/homepage/AboutSection.astro'; +import Footer from '@components/scafold/Footer.astro'; + +const isBossServer = import.meta.env.BOSS_SERVER === true; + +// Redirect strait to /check or /check/:url if running as self-hosted instance +if (!isBossServer) { + const searchUrl = new URLSearchParams(new URL(Astro.request.url).search).get('url'); + const redirectUrl = searchUrl ? `/check/${encodeURIComponent(searchUrl)}` : '/check'; + Astro.redirect(redirectUrl); +} + +--- + + + + {!isBossServer && ()} + +
+ + +
+