Compare commits
105 Commits
WIP/extra-
...
FEAT/impro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc45b91b51 | ||
|
|
6d8b2368e7 | ||
|
|
fbe774829d | ||
|
|
08eadbe02c | ||
|
|
273e2539cc | ||
|
|
6ba75cebf2 | ||
|
|
98a3243846 | ||
|
|
f9530f8b03 | ||
|
|
5145b661f4 | ||
|
|
54dc70e6b2 | ||
|
|
b68447ed60 | ||
|
|
94281bef08 | ||
|
|
56aac52f6b | ||
|
|
e4c99c755e | ||
|
|
7559716c5d | ||
|
|
63d09738d2 | ||
|
|
5496fb6e5c | ||
|
|
95a63825f0 | ||
|
|
fe6931efc6 | ||
|
|
db64e5a3d4 | ||
|
|
a779057762 | ||
|
|
52cb0fd618 | ||
|
|
0194ada819 | ||
|
|
d805848dd7 | ||
|
|
749a61358c | ||
|
|
b6b0c25966 | ||
|
|
09f5af26df | ||
|
|
805cc41bce | ||
|
|
4bd3085fe9 | ||
|
|
823873bce2 | ||
|
|
49cfad2dbe | ||
|
|
b6dfd3321a | ||
|
|
57481c8757 | ||
|
|
d13db8d438 | ||
|
|
5e755c1dc2 | ||
|
|
93aa496a30 | ||
|
|
5dc9a82718 | ||
|
|
86bb64a4d0 | ||
|
|
f573faf304 | ||
|
|
737639ae84 | ||
|
|
8ca747c02f | ||
|
|
759bb603df | ||
|
|
83c8d311b3 | ||
|
|
93ed8d6c44 | ||
|
|
981f79f676 | ||
|
|
55d59c2d07 | ||
|
|
502b9fd7f4 | ||
|
|
dbcbd36874 | ||
|
|
519d2f0f79 | ||
|
|
6b8c50a9aa | ||
|
|
e24934b7dd | ||
|
|
645dcf229f | ||
|
|
8ce46fbf89 | ||
|
|
fac47e27c6 | ||
|
|
2bf7454950 | ||
|
|
8d4c09ffd9 | ||
|
|
c55b23086d | ||
|
|
e2ec9b2f62 | ||
|
|
07656c6fea | ||
|
|
fe1e74a22f | ||
|
|
cb0143de40 | ||
|
|
9430fc7913 | ||
|
|
d3fa33b104 | ||
|
|
a8eadf40b0 | ||
|
|
9b9c31674d | ||
|
|
d38f2ae384 | ||
|
|
56ee47fef4 | ||
|
|
d63f891667 | ||
|
|
8263b9b7fd | ||
|
|
b0008823da | ||
|
|
1e8d6e868c | ||
|
|
efba42d59d | ||
|
|
15e5ba3cfc | ||
|
|
e47b39041b | ||
|
|
6a31927562 | ||
|
|
18faeb631b | ||
|
|
e1d9b13045 | ||
|
|
c5d8cd1641 | ||
|
|
5f3a99f2b9 | ||
|
|
0fd0e537f7 | ||
|
|
20cc52a304 | ||
|
|
361c65348d | ||
|
|
6cb133a46a | ||
|
|
ca5d43cea1 | ||
|
|
9e426ed55e | ||
|
|
f552e5cb69 | ||
|
|
95b13240c7 | ||
|
|
f96c7ba25f | ||
|
|
976ca7d47a | ||
|
|
af1689bd85 | ||
|
|
d2a56eb526 | ||
|
|
85af5f9327 | ||
|
|
8624237760 | ||
|
|
f0ff33e081 | ||
|
|
65ff004b63 | ||
|
|
127db45247 | ||
|
|
30c5dbb898 | ||
|
|
42eea33809 | ||
|
|
c46fed5ebb | ||
|
|
57fadde151 | ||
|
|
af409245fb | ||
|
|
77d4ca26a4 | ||
|
|
22995995d0 | ||
|
|
4d69848350 | ||
|
|
d03acb8a3c |
815
.github/README.md
vendored
1
.github/screenshots/README.md
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|

|
||||||
BIN
.github/screenshots/tiles/archives.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
.github/screenshots/tiles/block-lists.png
vendored
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
.github/screenshots/tiles/carbon.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
BIN
.github/screenshots/tiles/dns-server.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 165 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
BIN
.github/screenshots/tiles/email-config.png
vendored
Normal file
|
After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
BIN
.github/screenshots/tiles/firewall.png
vendored
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
BIN
.github/screenshots/tiles/hsts.png
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
.github/screenshots/tiles/http-security.png
vendored
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
.github/screenshots/tiles/linked-pages.png
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
BIN
.github/screenshots/tiles/ranking.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB |
BIN
.github/screenshots/tiles/screenshot.png
vendored
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
.github/screenshots/tiles/security-txt.png
vendored
Normal file
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
.github/screenshots/tiles/sitemap.png
vendored
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
.github/screenshots/tiles/social-tags.png
vendored
Normal file
|
After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
BIN
.github/screenshots/tiles/tech-stack.png
vendored
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
.github/screenshots/tiles/threats.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
.github/screenshots/tiles/tls-cipher-suites.png
vendored
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
.github/screenshots/tiles/tls-handshake-simulation.png
vendored
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
.github/screenshots/tiles/tls-security-config.png
vendored
Normal file
|
After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
BIN
.github/screenshots/wc_carbon.png
vendored
|
Before Width: | Height: | Size: 31 KiB |
BIN
.github/screenshots/wc_dnssec-2.png
vendored
|
Before Width: | Height: | Size: 46 KiB |
BIN
.github/screenshots/wc_features-2.png
vendored
|
Before Width: | Height: | Size: 132 KiB |
BIN
.github/screenshots/web-check-screenshot1.png
vendored
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
.github/screenshots/web-check-screenshot10.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
.github/screenshots/web-check-screenshot2.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
.github/screenshots/web-check-screenshot3.png
vendored
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
.github/screenshots/web-check-screenshot4.png
vendored
Normal file
|
After Width: | Height: | Size: 810 KiB |
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
37
.github/workflows/credits.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Inserts list of community members into ./README.md
|
||||||
|
name: 💓 Inserts Contributors & Sponsors
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # Manual dispatch
|
||||||
|
schedule:
|
||||||
|
- cron: '45 1 * * 0' # At 01:45 on Sunday.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Job #1 - Fetches sponsors and inserts table into readme
|
||||||
|
insert-sponsors:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Inserts Sponsors 💓
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Updates readme with sponsors
|
||||||
|
uses: JamesIves/github-sponsors-readme-action@1.0.5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
file: .github/README.md
|
||||||
|
|
||||||
|
# Job #2 - Fetches contributors and inserts table into readme
|
||||||
|
insert-contributors:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Inserts Contributors 💓
|
||||||
|
steps:
|
||||||
|
- name: Updates readme with contributors
|
||||||
|
uses: akhilmhdh/contributors-readme-action@v2.3.4
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
image_size: 80
|
||||||
|
readme_path: .github/README.md
|
||||||
|
columns_per_row: 6
|
||||||
|
commit_message: 'docs: Updates contributors list'
|
||||||
|
committer_username: liss-bot
|
||||||
|
committer_email: liss-bot@d0h.co
|
||||||
25
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
# Keys
|
# keys
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
@@ -7,22 +7,21 @@
|
|||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
|
||||||
# misc
|
# Random AWS and Netlify crap
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Local Netlify folder
|
|
||||||
.netlify
|
.netlify
|
||||||
|
.serverless
|
||||||
|
.webpack
|
||||||
|
|
||||||
|
# OS generated files
|
||||||
|
.DS_Store
|
||||||
|
|||||||
24
Dockerfile
@@ -1,12 +1,22 @@
|
|||||||
FROM node:16-buster-slim AS base
|
FROM node:16-buster-slim
|
||||||
WORKDIR /app
|
|
||||||
FROM base AS builder
|
|
||||||
COPY . .
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y chromium traceroute && \
|
apt-get install -y chromium traceroute && \
|
||||||
chmod 755 /usr/bin/chromium && \
|
chmod 755 /usr/bin/chromium && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
RUN npm install --force
|
|
||||||
EXPOSE 8888
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json yarn.lock ./
|
||||||
|
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
EXPOSE ${PORT:-3000}
|
||||||
|
|
||||||
ENV CHROME_PATH='/usr/bin/chromium'
|
ENV CHROME_PATH='/usr/bin/chromium'
|
||||||
CMD ["npm", "run", "serve"]
|
|
||||||
|
CMD ["yarn", "serve"]
|
||||||
|
|||||||
51
api/_common/aws-webpack.config.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
target: 'node',
|
||||||
|
mode: 'production',
|
||||||
|
entry: {
|
||||||
|
'carbon': './api/carbon.js',
|
||||||
|
'cookies': './api/cookies.js',
|
||||||
|
'dns-server': './api/dns-server.js',
|
||||||
|
'dns': './api/dns.js',
|
||||||
|
'dnssec': './api/dnssec.js',
|
||||||
|
'features': './api/features.js',
|
||||||
|
'get-ip': './api/get-ip.js',
|
||||||
|
'headers': './api/headers.js',
|
||||||
|
'hsts': './api/hsts.js',
|
||||||
|
'linked-pages': './api/linked-pages.js',
|
||||||
|
'mail-config': './api/mail-config.js',
|
||||||
|
'ports': './api/ports.js',
|
||||||
|
'quality': './api/quality.js',
|
||||||
|
'redirects': './api/redirects.js',
|
||||||
|
'robots-txt': './api/robots-txt.js',
|
||||||
|
'screenshot': './api/screenshot.js',
|
||||||
|
'security-txt': './api/security-txt.js',
|
||||||
|
'sitemap': './api/sitemap.js',
|
||||||
|
'social-tags': './api/social-tags.js',
|
||||||
|
'ssl': './api/ssl.js',
|
||||||
|
'status': './api/status.js',
|
||||||
|
'tech-stack': './api/tech-stack.js',
|
||||||
|
'trace-route': './api/trace-route.js',
|
||||||
|
'txt-records': './api/txt-records.js',
|
||||||
|
'whois': './api/whois.js',
|
||||||
|
},
|
||||||
|
externals: [nodeExternals()],
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: path.resolve(__dirname, '.webpack'),
|
||||||
|
libraryTarget: 'commonjs2'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader'
|
||||||
|
},
|
||||||
|
exclude: /node_modules/,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
38
api/_common/middleware.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const normalizeUrl = (url) => {
|
||||||
|
return url.startsWith('http') ? url : `https://${url}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const commonMiddleware = (handler) => {
|
||||||
|
return async (event, context, callback) => {
|
||||||
|
const queryParams = event.queryStringParameters || event.query || {};
|
||||||
|
const rawUrl = queryParams.url;
|
||||||
|
|
||||||
|
if (!rawUrl) {
|
||||||
|
callback(null, {
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: 'No URL specified' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = normalizeUrl(rawUrl);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await handler(url, event, context);
|
||||||
|
if (response.body && response.statusCode) {
|
||||||
|
callback(null, response);
|
||||||
|
} else {
|
||||||
|
callback(null, {
|
||||||
|
statusCode: 200,
|
||||||
|
body: typeof response === 'object' ? JSON.stringify(response) : response,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
callback(null, {
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: error.message }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = commonMiddleware;
|
||||||
83
api/archives.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const convertTimestampToDate = (timestamp) => {
|
||||||
|
const [year, month, day, hour, minute, second] = [
|
||||||
|
timestamp.slice(0, 4),
|
||||||
|
timestamp.slice(4, 6) - 1,
|
||||||
|
timestamp.slice(6, 8),
|
||||||
|
timestamp.slice(8, 10),
|
||||||
|
timestamp.slice(10, 12),
|
||||||
|
timestamp.slice(12, 14),
|
||||||
|
].map(num => parseInt(num, 10));
|
||||||
|
|
||||||
|
return new Date(year, month, day, hour, minute, second);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countPageChanges = (results) => {
|
||||||
|
let prevDigest = null;
|
||||||
|
return results.reduce((acc, curr) => {
|
||||||
|
if (curr[2] !== prevDigest) {
|
||||||
|
prevDigest = curr[2];
|
||||||
|
return acc + 1;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getAveragePageSize = (scans) => {
|
||||||
|
const totalSize = scans.map(scan => parseInt(scan[3], 10)).reduce((sum, size) => sum + size, 0);
|
||||||
|
return Math.round(totalSize / scans.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScanFrequency = (firstScan, lastScan, totalScans, changeCount) => {
|
||||||
|
const formatToTwoDecimal = num => parseFloat(num.toFixed(2));
|
||||||
|
|
||||||
|
const dayFactor = (lastScan - firstScan) / (1000 * 60 * 60 * 24);
|
||||||
|
const daysBetweenScans = formatToTwoDecimal(dayFactor / totalScans);
|
||||||
|
const daysBetweenChanges = formatToTwoDecimal(dayFactor / changeCount);
|
||||||
|
const scansPerDay = formatToTwoDecimal((totalScans - 1) / dayFactor);
|
||||||
|
const changesPerDay = formatToTwoDecimal(changeCount / dayFactor);
|
||||||
|
return {
|
||||||
|
daysBetweenScans,
|
||||||
|
daysBetweenChanges,
|
||||||
|
scansPerDay,
|
||||||
|
changesPerDay,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getWaybackData = async (url) => {
|
||||||
|
const cdxUrl = `https://web.archive.org/cdx/search/cdx?url=${url}&output=json&fl=timestamp,statuscode,digest,length,offset`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(cdxUrl);
|
||||||
|
|
||||||
|
// Check there's data
|
||||||
|
if (!data || !Array.isArray(data) || data.length <= 1) {
|
||||||
|
return { skipped: 'Site has never before been archived via the Wayback Machine' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the header row
|
||||||
|
data.shift();
|
||||||
|
|
||||||
|
// Process and return the results
|
||||||
|
const firstScan = convertTimestampToDate(data[0][0]);
|
||||||
|
const lastScan = convertTimestampToDate(data[data.length - 1][0]);
|
||||||
|
const totalScans = data.length;
|
||||||
|
const changeCount = countPageChanges(data);
|
||||||
|
return {
|
||||||
|
firstScan,
|
||||||
|
lastScan,
|
||||||
|
totalScans,
|
||||||
|
changeCount,
|
||||||
|
averagePageSize: getAveragePageSize(data),
|
||||||
|
scanFrequency: getScanFrequency(firstScan, lastScan, totalScans, changeCount),
|
||||||
|
scans: data,
|
||||||
|
scanUrl: url,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return { error: `Error fetching Wayback data: ${err.message}` };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(getWaybackData);
|
||||||
105
api/block-lists.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
const dns = require('dns');
|
||||||
|
const { URL } = require('url');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const DNS_SERVERS = [
|
||||||
|
{ name: 'AdGuard', ip: '176.103.130.130' },
|
||||||
|
{ name: 'AdGuard Family', ip: '176.103.130.132' },
|
||||||
|
{ name: 'CleanBrowsing Adult', ip: '185.228.168.10' },
|
||||||
|
{ name: 'CleanBrowsing Family', ip: '185.228.168.168' },
|
||||||
|
{ name: 'CleanBrowsing Security', ip: '185.228.168.9' },
|
||||||
|
{ name: 'CloudFlare', ip: '1.1.1.1' },
|
||||||
|
{ name: 'CloudFlare Family', ip: '1.1.1.3' },
|
||||||
|
{ name: 'Comodo Secure', ip: '8.26.56.26' },
|
||||||
|
{ name: 'Google DNS', ip: '8.8.8.8' },
|
||||||
|
{ name: 'Neustar Family', ip: '156.154.70.3' },
|
||||||
|
{ name: 'Neustar Protection', ip: '156.154.70.2' },
|
||||||
|
{ name: 'Norton Family', ip: '199.85.126.20' },
|
||||||
|
{ name: 'OpenDNS', ip: '208.67.222.222' },
|
||||||
|
{ name: 'OpenDNS Family', ip: '208.67.222.123' },
|
||||||
|
{ name: 'Quad9', ip: '9.9.9.9' },
|
||||||
|
{ name: 'Yandex Family', ip: '77.88.8.7' },
|
||||||
|
{ name: 'Yandex Safe', ip: '77.88.8.88' },
|
||||||
|
];
|
||||||
|
const knownBlockIPs = [
|
||||||
|
'146.112.61.106', // OpenDNS
|
||||||
|
'185.228.168.10', // CleanBrowsing
|
||||||
|
'8.26.56.26', // Comodo
|
||||||
|
'9.9.9.9', // Quad9
|
||||||
|
'208.69.38.170', // Some OpenDNS IPs
|
||||||
|
'208.69.39.170', // Some OpenDNS IPs
|
||||||
|
'208.67.222.222', // OpenDNS
|
||||||
|
'208.67.222.123', // OpenDNS FamilyShield
|
||||||
|
'199.85.126.10', // Norton
|
||||||
|
'199.85.126.20', // Norton Family
|
||||||
|
'156.154.70.22', // Neustar
|
||||||
|
'77.88.8.7', // Yandex
|
||||||
|
'77.88.8.8', // Yandex
|
||||||
|
'::1', // Localhost IPv6
|
||||||
|
'2a02:6b8::feed:0ff', // Yandex DNS
|
||||||
|
'2a02:6b8::feed:bad', // Yandex Safe
|
||||||
|
'2a02:6b8::feed:a11', // Yandex Family
|
||||||
|
'2620:119:35::35', // OpenDNS
|
||||||
|
'2620:119:53::53', // OpenDNS FamilyShield
|
||||||
|
'2606:4700:4700::1111', // Cloudflare
|
||||||
|
'2606:4700:4700::1001', // Cloudflare
|
||||||
|
'2001:4860:4860::8888', // Google DNS
|
||||||
|
'2a0d:2a00:1::', // AdGuard
|
||||||
|
'2a0d:2a00:2::' // AdGuard Family
|
||||||
|
];
|
||||||
|
|
||||||
|
const isDomainBlocked = async (domain, serverIP) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
dns.resolve4(domain, { server: serverIP }, (err, addresses) => {
|
||||||
|
if (!err) {
|
||||||
|
if (addresses.some(addr => knownBlockIPs.includes(addr))) {
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dns.resolve6(domain, { server: serverIP }, (err6, addresses6) => {
|
||||||
|
if (!err6) {
|
||||||
|
if (addresses6.some(addr => knownBlockIPs.includes(addr))) {
|
||||||
|
resolve(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err6.code === 'ENOTFOUND' || err6.code === 'SERVFAIL') {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkDomainAgainstDnsServers = async (domain) => {
|
||||||
|
let results = [];
|
||||||
|
|
||||||
|
for (let server of DNS_SERVERS) {
|
||||||
|
const isBlocked = await isDomainBlocked(domain, server.ip);
|
||||||
|
results.push({
|
||||||
|
server: server.name,
|
||||||
|
serverIp: server.ip,
|
||||||
|
isBlocked,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(async (url) => {
|
||||||
|
const domain = new URL(url).hostname;
|
||||||
|
const results = await checkDomainAgainstDnsServers(domain);
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
body: JSON.stringify({ blocklists: results })
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async (event, context) => {
|
const handler = async (url) => {
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'url query parameter is required' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, get the size of the website's HTML
|
// First, get the size of the website's HTML
|
||||||
const getHtmlSize = (url) => new Promise((resolve, reject) => {
|
const getHtmlSize = (url) => new Promise((resolve, reject) => {
|
||||||
@@ -49,14 +42,10 @@ exports.handler = async (event, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
carbonData.scanUrl = url;
|
carbonData.scanUrl = url;
|
||||||
return {
|
return carbonData;
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(carbonData),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
throw new Error(`Error: ${error.message}`);
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: `Error: ${error.message}` }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
const axios = require('axios');
|
|
||||||
const cheerio = require('cheerio');
|
|
||||||
const urlLib = require('url');
|
|
||||||
|
|
||||||
exports.handler = async (event, context) => {
|
|
||||||
let url = event.queryStringParameters.url;
|
|
||||||
|
|
||||||
// Check if url includes protocol
|
|
||||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
||||||
url = 'http://' + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(url);
|
|
||||||
const html = response.data;
|
|
||||||
const $ = cheerio.load(html);
|
|
||||||
const internalLinksMap = new Map();
|
|
||||||
const externalLinksMap = new Map();
|
|
||||||
|
|
||||||
$('a[href]').each((i, link) => {
|
|
||||||
const href = $(link).attr('href');
|
|
||||||
const absoluteUrl = urlLib.resolve(url, href);
|
|
||||||
|
|
||||||
if (absoluteUrl.startsWith(url)) {
|
|
||||||
const count = internalLinksMap.get(absoluteUrl) || 0;
|
|
||||||
internalLinksMap.set(absoluteUrl, count + 1);
|
|
||||||
} else if (href.startsWith('http://') || href.startsWith('https://')) {
|
|
||||||
const count = externalLinksMap.get(absoluteUrl) || 0;
|
|
||||||
externalLinksMap.set(absoluteUrl, count + 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert maps to sorted arrays
|
|
||||||
const internalLinks = [...internalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);
|
|
||||||
const externalLinks = [...externalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);
|
|
||||||
|
|
||||||
if (internalLinks.length === 0 && externalLinks.length === 0) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({
|
|
||||||
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 {
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ internal: internalLinks, external: externalLinks }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: 'Failed fetching data' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
57
api/cookies.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const getPuppeteerCookies = async (url) => {
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: 'new',
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const navigationPromise = page.goto(url, { waitUntil: 'networkidle2' });
|
||||||
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('Puppeteer took too long!')), 3000)
|
||||||
|
);
|
||||||
|
await Promise.race([navigationPromise, timeoutPromise]);
|
||||||
|
return await page.cookies();
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
let headerCookies = null;
|
||||||
|
let clientCookies = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
withCredentials: true,
|
||||||
|
maxRedirects: 5,
|
||||||
|
});
|
||||||
|
headerCookies = response.headers['set-cookie'];
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response) {
|
||||||
|
return { error: `Request failed with status ${error.response.status}: ${error.message}` };
|
||||||
|
} else if (error.request) {
|
||||||
|
return { error: `No response received: ${error.message}` };
|
||||||
|
} else {
|
||||||
|
return { error: `Error setting up request: ${error.message}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
clientCookies = await getPuppeteerCookies(url);
|
||||||
|
} catch (_) {
|
||||||
|
clientCookies = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!headerCookies && (!clientCookies || clientCookies.length === 0)) {
|
||||||
|
return { skipped: 'No cookies' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { headerCookies, clientCookies };
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
const dns = require('dns');
|
const dns = require('dns');
|
||||||
const dnsPromises = dns.promises;
|
const dnsPromises = dns.promises;
|
||||||
// const https = require('https');
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const commonMiddleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async (event) => {
|
const handler = async (url) => {
|
||||||
const domain = event.queryStringParameters.url.replace(/^(?:https?:\/\/)?/i, "");
|
|
||||||
try {
|
try {
|
||||||
|
const domain = url.replace(/^(?:https?:\/\/)?/i, "");
|
||||||
const addresses = await dnsPromises.resolve4(domain);
|
const addresses = await dnsPromises.resolve4(domain);
|
||||||
const results = await Promise.all(addresses.map(async (address) => {
|
const results = await Promise.all(addresses.map(async (address) => {
|
||||||
const hostname = await dnsPromises.reverse(address).catch(() => null);
|
const hostname = await dnsPromises.reverse(address).catch(() => null);
|
||||||
@@ -22,6 +22,7 @@ exports.handler = async (event) => {
|
|||||||
dohDirectSupports,
|
dohDirectSupports,
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// let dohMozillaSupport = false;
|
// let dohMozillaSupport = false;
|
||||||
// try {
|
// try {
|
||||||
// const mozillaList = await axios.get('https://firefox.settings.services.mozilla.com/v1/buckets/security-state/collections/onecrl/records');
|
// const mozillaList = await axios.get('https://firefox.settings.services.mozilla.com/v1/buckets/security-state/collections/onecrl/records');
|
||||||
@@ -29,20 +30,15 @@ exports.handler = async (event) => {
|
|||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error(error);
|
// console.error(error);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
domain,
|
||||||
body: JSON.stringify({
|
dns: results,
|
||||||
domain,
|
// dohMozillaSupport,
|
||||||
dns: results,
|
|
||||||
// dohMozillaSupport,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
throw new Error(`An error occurred while resolving DNS. ${error.message}`); // This will be caught and handled by the commonMiddleware
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: `An error occurred while resolving DNS. ${error.message}`,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = commonMiddleware(handler);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
const dns = require('dns');
|
const dns = require('dns');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
const handler = async (url) => {
|
||||||
let hostname = event.queryStringParameters.url;
|
let hostname = url;
|
||||||
|
|
||||||
// Handle URLs by extracting hostname
|
// Handle URLs by extracting hostname
|
||||||
if (hostname.startsWith('http://') || hostname.startsWith('https://')) {
|
if (hostname.startsWith('http://') || hostname.startsWith('https://')) {
|
||||||
@@ -35,25 +36,19 @@ exports.handler = async function(event, context) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
A: a,
|
||||||
body: JSON.stringify({
|
AAAA: aaaa,
|
||||||
A: a,
|
MX: mx,
|
||||||
AAAA: aaaa,
|
TXT: txt,
|
||||||
MX: mx,
|
NS: ns,
|
||||||
TXT: txt,
|
CNAME: cname,
|
||||||
NS: ns,
|
SOA: soa,
|
||||||
CNAME: cname,
|
SRV: srv,
|
||||||
SOA: soa,
|
PTR: ptr
|
||||||
SRV: srv,
|
|
||||||
PTR: ptr
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
throw new Error(error.message);
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: error.message
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
const commonMiddleware = require('./_common/middleware'); // Make sure this path is correct
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
const fetchDNSRecords = async (domain, event, context) => {
|
||||||
let { url } = event.queryStringParameters;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return errorResponse('URL query parameter is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract hostname from URL
|
|
||||||
const parsedUrl = new URL(url);
|
|
||||||
const domain = parsedUrl.hostname;
|
|
||||||
|
|
||||||
const dnsTypes = ['DNSKEY', 'DS', 'RRSIG'];
|
const dnsTypes = ['DNSKEY', 'DS', 'RRSIG'];
|
||||||
const records = {};
|
const records = {};
|
||||||
|
|
||||||
@@ -48,22 +39,14 @@ exports.handler = async function(event, context) {
|
|||||||
if (dnsResponse.Answer) {
|
if (dnsResponse.Answer) {
|
||||||
records[type] = { isFound: true, answer: dnsResponse.Answer, response: dnsResponse.Answer };
|
records[type] = { isFound: true, answer: dnsResponse.Answer, response: dnsResponse.Answer };
|
||||||
} else {
|
} else {
|
||||||
records[type] = { isFound: false, answer: null, response: dnsResponse};
|
records[type] = { isFound: false, answer: null, response: dnsResponse };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(`Error fetching ${type} record: ${error.message}`);
|
throw new Error(`Error fetching ${type} record: ${error.message}`); // This will be caught and handled by the commonMiddleware
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return records;
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(records),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const errorResponse = (message, statusCode = 444) => {
|
exports.handler = commonMiddleware(fetchDNSRecords);
|
||||||
return {
|
|
||||||
statusCode: statusCode,
|
|
||||||
body: JSON.stringify({ error: message }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,22 +1,15 @@
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async function (event, context) {
|
const builtWithHandler = async (url) => {
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
const apiKey = process.env.BUILT_WITH_API_KEY;
|
const apiKey = process.env.BUILT_WITH_API_KEY;
|
||||||
|
|
||||||
const errorResponse = (message, statusCode = 500) => {
|
|
||||||
return {
|
|
||||||
statusCode: statusCode,
|
|
||||||
body: JSON.stringify({ error: message }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return errorResponse('URL query parameter is required', 400);
|
throw new Error('URL query parameter is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return errorResponse('Missing BuiltWith API key in environment variables', 500);
|
throw new Error('Missing BuiltWith API key in environment variables');
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`;
|
const apiUrl = `https://api.builtwith.com/free1/api.json?KEY=${apiKey}&LOOKUP=${encodeURIComponent(url)}`;
|
||||||
@@ -46,11 +39,10 @@ exports.handler = async function (event, context) {
|
|||||||
req.end();
|
req.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return response;
|
||||||
statusCode: 200,
|
|
||||||
body: response,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(`Error making request: ${error.message}`);
|
throw new Error(`Error making request: ${error.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(builtWithHandler);
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
const dns = require('dns');
|
|
||||||
|
|
||||||
/* Lambda function to fetch the IP address of a given URL */
|
|
||||||
exports.handler = function (event, context, callback) {
|
|
||||||
const addressParam = event.queryStringParameters.url;
|
|
||||||
|
|
||||||
if (!addressParam) {
|
|
||||||
callback(null, errorResponse('Address parameter is missing.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const address = decodeURIComponent(addressParam)
|
|
||||||
.replaceAll('https://', '')
|
|
||||||
.replaceAll('http://', '');
|
|
||||||
|
|
||||||
dns.lookup(address, (err, ip, family) => {
|
|
||||||
if (err) {
|
|
||||||
callback(null, errorResponse(err.message));
|
|
||||||
} else {
|
|
||||||
callback(null, {
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ ip, family }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorResponse = (message, statusCode = 444) => {
|
|
||||||
return {
|
|
||||||
statusCode: statusCode,
|
|
||||||
body: JSON.stringify({ error: message }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
105
api/firewall.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const hasWaf = (waf) => {
|
||||||
|
return {
|
||||||
|
hasWaf: true, waf,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
const fullUrl = url.startsWith('http') ? url : `http://${url}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(fullUrl);
|
||||||
|
|
||||||
|
const headers = response.headers;
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('cloudflare')) {
|
||||||
|
return hasWaf('Cloudflare');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-powered-by'] && headers['x-powered-by'].includes('AWS Lambda')) {
|
||||||
|
return hasWaf('AWS WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('AkamaiGHost')) {
|
||||||
|
return hasWaf('Akamai');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('Sucuri')) {
|
||||||
|
return hasWaf('Sucuri');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('BarracudaWAF')) {
|
||||||
|
return hasWaf('Barracuda WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && (headers['server'].includes('F5 BIG-IP') || headers['server'].includes('BIG-IP'))) {
|
||||||
|
return hasWaf('F5 BIG-IP');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-sucuri-id'] || headers['x-sucuri-cache']) {
|
||||||
|
return hasWaf('Sucuri CloudProxy WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('FortiWeb')) {
|
||||||
|
return hasWaf('Fortinet FortiWeb WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('Imperva')) {
|
||||||
|
return hasWaf('Imperva SecureSphere WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-protected-by'] && headers['x-protected-by'].includes('Sqreen')) {
|
||||||
|
return hasWaf('Sqreen');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-waf-event-info']) {
|
||||||
|
return hasWaf('Reblaze WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['set-cookie'] && headers['set-cookie'].includes('_citrix_ns_id')) {
|
||||||
|
return hasWaf('Citrix NetScaler');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-denied-reason'] || headers['x-wzws-requested-method']) {
|
||||||
|
return hasWaf('WangZhanBao WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-webcoment']) {
|
||||||
|
return hasWaf('Webcoment Firewall');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('Yundun')) {
|
||||||
|
return hasWaf('Yundun WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-yd-waf-info'] || headers['x-yd-info']) {
|
||||||
|
return hasWaf('Yundun WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('Safe3WAF')) {
|
||||||
|
return hasWaf('Safe3 Web Application Firewall');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['server'] && headers['server'].includes('NAXSI')) {
|
||||||
|
return hasWaf('NAXSI WAF');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers['x-datapower-transactionid']) {
|
||||||
|
return hasWaf('IBM WebSphere DataPower');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
hasWaf: false,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: error.message }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
exports.handler = async (event) => {
|
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
const redirects = [url];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const got = await import('got');
|
|
||||||
await got.default(url, {
|
|
||||||
followRedirect: true,
|
|
||||||
maxRedirects: 12,
|
|
||||||
hooks: {
|
|
||||||
beforeRedirect: [
|
|
||||||
(options, response) => {
|
|
||||||
redirects.push(response.headers.location);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({
|
|
||||||
redirects: redirects,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(`Error: ${error.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorResponse = (message, statusCode = 444) => {
|
|
||||||
return {
|
|
||||||
statusCode: statusCode,
|
|
||||||
body: JSON.stringify({ error: message }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ message: 'url query string parameter is required' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(url, {withCredentials: true});
|
|
||||||
const cookies = response.headers['set-cookie'];
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ cookies }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: error.message }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'url query string parameter is required' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(url, {
|
|
||||||
validateStatus: function (status) {
|
|
||||||
return status >= 200 && status < 600; // Resolve only if the status code is less than 600
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(response.headers),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: error.message }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
21
api/get-ip.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const dns = require('dns');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const lookupAsync = (address) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
dns.lookup(address, (err, ip, family) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve({ ip, family });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
const address = url.replaceAll('https://', '').replaceAll('http://', '');
|
||||||
|
return await lookupAsync(address);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
18
api/headers.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const handler = async (url, event, context) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
validateStatus: function (status) {
|
||||||
|
return status >= 200 && status < 600; // Resolve only if the status code is less than 600
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.headers;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
exports.handler = middleware(async (url, event, context) => {
|
||||||
const siteURL = event.queryStringParameters.url;
|
|
||||||
|
|
||||||
const errorResponse = (message, statusCode = 500) => {
|
const errorResponse = (message, statusCode = 500) => {
|
||||||
return {
|
return {
|
||||||
statusCode: statusCode,
|
statusCode: statusCode,
|
||||||
@@ -16,15 +15,9 @@ exports.handler = async function(event, context) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!siteURL) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'URL parameter is missing!' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const req = https.request(siteURL, res => {
|
const req = https.request(url, res => {
|
||||||
const headers = res.headers;
|
const headers = res.headers;
|
||||||
const hstsHeader = headers['strict-transport-security'];
|
const hstsHeader = headers['strict-transport-security'];
|
||||||
|
|
||||||
@@ -60,4 +53,5 @@ exports.handler = async function(event, context) {
|
|||||||
|
|
||||||
req.end();
|
req.end();
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|
||||||
25
api/http-security.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
const fullUrl = url.startsWith('http') ? url : `http://${url}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(fullUrl);
|
||||||
|
const headers = response.headers;
|
||||||
|
return {
|
||||||
|
strictTransportPolicy: headers['strict-transport-policy'] ? true : false,
|
||||||
|
xFrameOptions: headers['x-frame-options'] ? true : false,
|
||||||
|
xContentTypeOptions: headers['x-content-type-options'] ? true : false,
|
||||||
|
xXSSProtection: headers['x-xss-protection'] ? true : false,
|
||||||
|
contentSecurityPolicy: headers['content-security-policy'] ? true : false,
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: error.message }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
70
api/legacy-rank.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const unzipper = require('unzipper');
|
||||||
|
const csv = require('csv-parser');
|
||||||
|
const fs = require('fs');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
// Should also work with the following sources:
|
||||||
|
// https://www.domcop.com/files/top/top10milliondomains.csv.zip
|
||||||
|
// https://tranco-list.eu/top-1m.csv.zip
|
||||||
|
// https://www.domcop.com/files/top/top10milliondomains.csv.zip
|
||||||
|
// https://radar.cloudflare.com/charts/LargerTopDomainsTable/attachment?id=525&top=1000000
|
||||||
|
// https://statvoo.com/dl/top-1million-sites.csv.zip
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
let domain = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
domain = new URL(url).hostname;
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Invalid URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download and unzip the file if not in cache
|
||||||
|
if (!fs.existsSync(TEMP_FILE_PATH)) {
|
||||||
|
const response = await axios({
|
||||||
|
method: 'GET',
|
||||||
|
url: FILE_URL,
|
||||||
|
responseType: 'stream'
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
response.data
|
||||||
|
.pipe(unzipper.Extract({ path: '/tmp' }))
|
||||||
|
.on('close', resolve)
|
||||||
|
.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the CSV and find the rank
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const csvStream = fs.createReadStream(TEMP_FILE_PATH)
|
||||||
|
.pipe(csv({
|
||||||
|
headers: ['rank', 'domain'],
|
||||||
|
}))
|
||||||
|
.on('data', (row) => {
|
||||||
|
if (row.domain === domain) {
|
||||||
|
csvStream.destroy();
|
||||||
|
resolve({
|
||||||
|
domain: domain,
|
||||||
|
rank: row.rank,
|
||||||
|
isFound: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
resolve({
|
||||||
|
skipped: `Skipping, as ${domain} is not present in the Umbrella top 1M list.`,
|
||||||
|
domain: domain,
|
||||||
|
isFound: false,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on('error', reject);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
exports.handler = function(event, context, callback) {
|
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
callback(null, {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'URL param is required'}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
|
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
callback(null, {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: 'API key (GOOGLE_CLOUD_API_KEY) not set'}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&category=PERFORMANCE&category=ACCESSIBILITY&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile&key=${apiKey}`;
|
|
||||||
|
|
||||||
axios.get(endpoint)
|
|
||||||
.then(
|
|
||||||
(response) => {
|
|
||||||
callback(null, {
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(response.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
).catch(
|
|
||||||
() => {
|
|
||||||
callback(null, {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: 'Error running Lighthouse'}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
48
api/linked-pages.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const cheerio = require('cheerio');
|
||||||
|
const urlLib = require('url');
|
||||||
|
const commonMiddleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
const response = await axios.get(url);
|
||||||
|
const html = response.data;
|
||||||
|
const $ = cheerio.load(html);
|
||||||
|
const internalLinksMap = new Map();
|
||||||
|
const externalLinksMap = new Map();
|
||||||
|
|
||||||
|
// Get all links on the page
|
||||||
|
$('a[href]').each((i, link) => {
|
||||||
|
const href = $(link).attr('href');
|
||||||
|
const absoluteUrl = urlLib.resolve(url, href);
|
||||||
|
|
||||||
|
// Check if absolute / relative, append to appropriate map or increment occurrence count
|
||||||
|
if (absoluteUrl.startsWith(url)) {
|
||||||
|
const count = internalLinksMap.get(absoluteUrl) || 0;
|
||||||
|
internalLinksMap.set(absoluteUrl, count + 1);
|
||||||
|
} else if (href.startsWith('http://') || href.startsWith('https://')) {
|
||||||
|
const count = externalLinksMap.get(absoluteUrl) || 0;
|
||||||
|
externalLinksMap.set(absoluteUrl, count + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by most occurrences, remove supplicates, and convert to array
|
||||||
|
const internalLinks = [...internalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);
|
||||||
|
const externalLinks = [...externalLinksMap.entries()].sort((a, b) => b[1] - a[1]).map(entry => entry[0]);
|
||||||
|
|
||||||
|
// If there were no links, then mark as skipped and show reasons
|
||||||
|
if (internalLinks.length === 0 && externalLinks.length === 0) {
|
||||||
|
return {
|
||||||
|
statusCode: 400,
|
||||||
|
body: JSON.stringify({
|
||||||
|
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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = commonMiddleware(handler);
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
|
const commonMiddleware = require('./_common/middleware');
|
||||||
|
|
||||||
const dns = require('dns').promises;
|
const dns = require('dns').promises;
|
||||||
const URL = require('url-parse');
|
const URL = require('url-parse');
|
||||||
|
|
||||||
exports.handler = async (event, context) => {
|
const handler = async (url, event, context) => {
|
||||||
try {
|
try {
|
||||||
let domain = event.queryStringParameters.url;
|
const domain = new URL(url).hostname || new URL(url).pathname;
|
||||||
const parsedUrl = new URL(domain);
|
|
||||||
domain = parsedUrl.hostname || parsedUrl.pathname;
|
|
||||||
|
|
||||||
// Get MX records
|
// Get MX records
|
||||||
const mxRecords = await dns.resolveMx(domain);
|
const mxRecords = await dns.resolveMx(domain);
|
||||||
@@ -56,19 +56,13 @@ exports.handler = async (event, context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({
|
|
||||||
mxRecords,
|
mxRecords,
|
||||||
txtRecords: emailTxtRecords,
|
txtRecords: emailTxtRecords,
|
||||||
mailServices,
|
mailServices,
|
||||||
}),
|
};
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'ENOTFOUND' || error.code === 'ENODATA') {
|
if (error.code === 'ENOTFOUND' || error.code === 'ENODATA') {
|
||||||
return {
|
return { skipped: 'No mail server in use on this domain' };
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ skipped: 'No mail server in use on this domain' }),
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
@@ -77,3 +71,5 @@ exports.handler = async (event, context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports.handler = commonMiddleware(handler);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const net = require('net');
|
const net = require('net');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
// A list of commonly used ports.
|
// A list of commonly used ports.
|
||||||
const PORTS = [
|
const PORTS = [
|
||||||
@@ -12,7 +13,7 @@ async function checkPort(port, domain) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const socket = new net.Socket();
|
const socket = new net.Socket();
|
||||||
|
|
||||||
socket.setTimeout(1500); // you may want to adjust the timeout
|
socket.setTimeout(1500);
|
||||||
|
|
||||||
socket.once('connect', () => {
|
socket.once('connect', () => {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
@@ -33,13 +34,9 @@ async function checkPort(port, domain) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handler = async (event, context) => {
|
const handler = async (url, event, context) => {
|
||||||
const domain = event.queryStringParameters.url;
|
const domain = url.replace(/(^\w+:|^)\/\//, '');
|
||||||
|
|
||||||
if (!domain) {
|
|
||||||
return errorResponse('Missing domain parameter.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const delay = ms => new Promise(res => setTimeout(res, ms));
|
const delay = ms => new Promise(res => setTimeout(res, ms));
|
||||||
const timeout = delay(9000);
|
const timeout = delay(9000);
|
||||||
|
|
||||||
@@ -88,3 +85,5 @@ const errorResponse = (message, statusCode = 444) => {
|
|||||||
body: JSON.stringify({ error: message }),
|
body: JSON.stringify({ error: message }),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
22
api/quality.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const handler = async (url, event, context) => {
|
||||||
|
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
throw new Error('URL param is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('API key (GOOGLE_CLOUD_API_KEY) not set');
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(url)}&category=PERFORMANCE&category=ACCESSIBILITY&category=BEST_PRACTICES&category=SEO&category=PWA&strategy=mobile&key=${apiKey}`;
|
||||||
|
|
||||||
|
const response = await axios.get(endpoint);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
24
api/rank.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
const domain = url ? new URL(url).hostname : null;
|
||||||
|
if (!domain) throw new Error('Invalid URL');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auth = process.env.TRANCO_API_KEY ? // Auth is optional.
|
||||||
|
{ auth: { username: process.env.TRANCO_USERNAME, password: process.env.TRANCO_API_KEY } }
|
||||||
|
: {};
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://tranco-list.eu/api/ranks/domain/${domain}`, { timeout: 2000 }, auth,
|
||||||
|
);
|
||||||
|
if (!response.data || !response.data.ranks || response.data.ranks.length === 0) {
|
||||||
|
return { skipped: `Skipping, as ${domain} isn't ranked in the top 100 million sites yet.`};
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return { error: `Unable to fetch rank, ${error.message}` };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
|
||||||
const siteURL = event.queryStringParameters.url;
|
|
||||||
|
|
||||||
if (!siteURL) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'Missing url query parameter' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let parsedURL;
|
|
||||||
try {
|
|
||||||
parsedURL = new URL(siteURL);
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'Invalid url query parameter' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get(robotsURL);
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
body: response.data,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
27
api/redirects.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
const handler = async (url) => {
|
||||||
|
const redirects = [url];
|
||||||
|
const got = await import('got');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await got.default(url, {
|
||||||
|
followRedirect: true,
|
||||||
|
maxRedirects: 12,
|
||||||
|
hooks: {
|
||||||
|
beforeRedirect: [
|
||||||
|
(options, response) => {
|
||||||
|
redirects.push(response.headers.location);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirects: redirects,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Error: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
exports.handler = middleware(handler);
|
||||||
70
api/robots-txt.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const parseRobotsTxt = (content) => {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
const rules = [];
|
||||||
|
|
||||||
|
lines.forEach(line => {
|
||||||
|
line = line.trim(); // This removes trailing and leading whitespaces
|
||||||
|
|
||||||
|
let match = line.match(/^(Allow|Disallow):\s*(\S*)$/i);
|
||||||
|
if (match) {
|
||||||
|
const rule = {
|
||||||
|
lbl: match[1],
|
||||||
|
val: match[2],
|
||||||
|
};
|
||||||
|
|
||||||
|
rules.push(rule);
|
||||||
|
} else {
|
||||||
|
match = line.match(/^(User-agent):\s*(\S*)$/i);
|
||||||
|
if (match) {
|
||||||
|
const rule = {
|
||||||
|
lbl: match[1],
|
||||||
|
val: match[2],
|
||||||
|
};
|
||||||
|
|
||||||
|
rules.push(rule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { robots: rules };
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = async function(url) {
|
||||||
|
let parsedURL;
|
||||||
|
try {
|
||||||
|
parsedURL = new URL(url);
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
statusCode: 400,
|
||||||
|
body: JSON.stringify({ error: 'Invalid url query parameter' }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const robotsURL = `${parsedURL.protocol}//${parsedURL.hostname}/robots.txt`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(robotsURL);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const parsedData = parseRobotsTxt(response.data);
|
||||||
|
if (!parsedData.robots || parsedData.robots.length === 0) {
|
||||||
|
return { skipped: 'No robots.txt file present, unable to continue' };
|
||||||
|
}
|
||||||
|
return parsedData;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
statusCode: response.status,
|
||||||
|
body: JSON.stringify({ error: 'Failed to fetch robots.txt', statusCode: response.status }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ error: `Error fetching robots.txt: ${error.message}` }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
const puppeteer = require('puppeteer-core');
|
const puppeteer = require('puppeteer-core');
|
||||||
const chromium = require('chrome-aws-lambda');
|
const chromium = require('chrome-aws-lambda');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async (event, context, callback) => {
|
const screenshotHandler = async (targetUrl) => {
|
||||||
let browser = null;
|
|
||||||
let targetUrl = event.queryStringParameters.url;
|
|
||||||
|
|
||||||
if (!targetUrl) {
|
if (!targetUrl) {
|
||||||
callback(null, {
|
throw new Error('URL is missing from queryStringParameters');
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'URL is missing from queryStringParameters' }),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
|
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
|
||||||
@@ -20,13 +15,10 @@ exports.handler = async (event, context, callback) => {
|
|||||||
try {
|
try {
|
||||||
new URL(targetUrl);
|
new URL(targetUrl);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
callback(null, {
|
throw new Error('URL provided is invalid');
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'URL provided is invalid' }),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let browser = null;
|
||||||
try {
|
try {
|
||||||
browser = await puppeteer.launch({
|
browser = await puppeteer.launch({
|
||||||
args: chromium.args,
|
args: chromium.args,
|
||||||
@@ -40,9 +32,7 @@ exports.handler = async (event, context, callback) => {
|
|||||||
let page = await browser.newPage();
|
let page = await browser.newPage();
|
||||||
|
|
||||||
await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
|
await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'dark' }]);
|
||||||
|
|
||||||
page.setDefaultNavigationTimeout(8000);
|
page.setDefaultNavigationTimeout(8000);
|
||||||
|
|
||||||
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
||||||
|
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
@@ -57,24 +47,15 @@ exports.handler = async (event, context, callback) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const screenshotBuffer = await page.screenshot();
|
const screenshotBuffer = await page.screenshot();
|
||||||
|
|
||||||
const base64Screenshot = screenshotBuffer.toString('base64');
|
const base64Screenshot = screenshotBuffer.toString('base64');
|
||||||
|
|
||||||
const response = {
|
return { image: base64Screenshot };
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ image: base64Screenshot }),
|
|
||||||
};
|
|
||||||
|
|
||||||
callback(null, response);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
callback(null, {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: `An error occurred: ${error.message}` }),
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
if (browser !== null) {
|
if (browser !== null) {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(screenshotHandler);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { https } = require('follow-redirects');
|
const { https } = require('follow-redirects');
|
||||||
const { URL } = require('url');
|
const { URL } = require('url');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
const SECURITY_TXT_PATHS = [
|
const SECURITY_TXT_PATHS = [
|
||||||
'/security.txt',
|
'/security.txt',
|
||||||
@@ -37,59 +38,39 @@ const isPgpSigned = (result) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.handler = async (event, context) => {
|
const securityTxtHandler = async (urlParam) => {
|
||||||
const urlParam = event.queryStringParameters.url;
|
|
||||||
if (!urlParam) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'Missing url parameter' })
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let url;
|
let url;
|
||||||
try {
|
try {
|
||||||
url = new URL(urlParam.includes('://') ? urlParam : 'https://' + urlParam);
|
url = new URL(urlParam.includes('://') ? urlParam : 'https://' + urlParam);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
throw new Error('Invalid URL format');
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: 'Invalid URL format' }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
url.pathname = '';
|
url.pathname = '';
|
||||||
|
|
||||||
for (let path of SECURITY_TXT_PATHS) {
|
for (let path of SECURITY_TXT_PATHS) {
|
||||||
try {
|
try {
|
||||||
const result = await fetchSecurityTxt(url, path);
|
const result = await fetchSecurityTxt(url, path);
|
||||||
if (result && result.includes('<html')) return {
|
if (result && result.includes('<html')) return { isPresent: false };
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ isPresent: false }),
|
|
||||||
};
|
|
||||||
if (result) {
|
if (result) {
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
isPresent: true,
|
||||||
body: JSON.stringify({
|
foundIn: path,
|
||||||
isPresent: true,
|
content: result,
|
||||||
foundIn: path,
|
isPgpSigned: isPgpSigned(result),
|
||||||
content: result,
|
fields: parseResult(result),
|
||||||
isPgpSigned: isPgpSigned(result),
|
|
||||||
fields: parseResult(result),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
throw new Error(error.message);
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: error.message }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { isPresent: false };
|
||||||
statusCode: 404,
|
|
||||||
body: JSON.stringify({ isPresent: false }),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(securityTxtHandler);
|
||||||
|
|
||||||
async function fetchSecurityTxt(baseURL, path) {
|
async function fetchSecurityTxt(baseURL, path) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const url = new URL(path, baseURL);
|
const url = new URL(path, baseURL);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
const commonMiddleware = require('./_common/middleware');
|
||||||
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const xml2js = require('xml2js');
|
const xml2js = require('xml2js');
|
||||||
|
|
||||||
exports.handler = async (event) => {
|
const handler = async (url) => {
|
||||||
const url = event.queryStringParameters.url;
|
|
||||||
let sitemapUrl = `${url}/sitemap.xml`;
|
let sitemapUrl = `${url}/sitemap.xml`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -59,3 +60,5 @@ exports.handler = async (event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = commonMiddleware(handler);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
const commonMiddleware = require('./_common/middleware');
|
||||||
|
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const cheerio = require('cheerio');
|
const cheerio = require('cheerio');
|
||||||
|
|
||||||
exports.handler = async (event, context) => {
|
const handler = async (url) => {
|
||||||
let url = event.queryStringParameters.url;
|
|
||||||
|
|
||||||
// Check if url includes protocol
|
// Check if url includes protocol
|
||||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||||
@@ -66,3 +67,5 @@ exports.handler = async (event, context) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = commonMiddleware(handler);
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
const https = require('https');
|
|
||||||
|
|
||||||
exports.handler = async function (event, context) {
|
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
|
|
||||||
const errorResponse = (message, statusCode = 500) => {
|
|
||||||
return {
|
|
||||||
statusCode: statusCode,
|
|
||||||
body: JSON.stringify({ error: message }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return errorResponse('URL query parameter is required', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await new Promise((resolve, reject) => {
|
|
||||||
const req = https.request(url, res => {
|
|
||||||
|
|
||||||
// Check if the SSL handshake was authorized
|
|
||||||
if (!res.socket.authorized) {
|
|
||||||
resolve(errorResponse(`SSL handshake not authorized. Reason: ${res.socket.authorizationError}`));
|
|
||||||
} else {
|
|
||||||
let cert = res.socket.getPeerCertificate(true);
|
|
||||||
if (!cert || Object.keys(cert).length === 0) {
|
|
||||||
resolve(errorResponse("No certificate presented by the server."));
|
|
||||||
} else {
|
|
||||||
// omit the raw and issuerCertificate fields
|
|
||||||
const { raw, issuerCertificate, ...certWithoutRaw } = cert;
|
|
||||||
resolve({
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(certWithoutRaw),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('error', error => {
|
|
||||||
resolve(errorResponse(`Error fetching site certificate: ${error.message}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(`Unexpected error occurred: ${error.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
43
api/ssl.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const tls = require('tls');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const fetchSiteCertificateHandler = async (urlString) => {
|
||||||
|
try {
|
||||||
|
const parsedUrl = new URL(urlString);
|
||||||
|
const options = {
|
||||||
|
host: parsedUrl.hostname,
|
||||||
|
port: parsedUrl.port || 443,
|
||||||
|
servername: parsedUrl.hostname,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const socket = tls.connect(options, () => {
|
||||||
|
if (!socket.authorized) {
|
||||||
|
return reject(new Error(`SSL handshake not authorized. Reason: ${socket.authorizationError}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cert = socket.getPeerCertificate();
|
||||||
|
if (!cert || Object.keys(cert).length === 0) {
|
||||||
|
return reject(new Error(`
|
||||||
|
No certificate presented by the server.\n
|
||||||
|
The server is possibly not using SNI (Server Name Indication) to identify itself, and you are connecting to a hostname-aliased IP address.
|
||||||
|
Or it may be due to an invalid SSL certificate, or an incomplete SSL handshake at the time the cert is being read.`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { raw, issuerCertificate, ...certWithoutRaw } = cert;
|
||||||
|
resolve(certWithoutRaw);
|
||||||
|
socket.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', (error) => {
|
||||||
|
reject(new Error(`Error fetching site certificate: ${error.message}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(fetchSiteCertificateHandler);
|
||||||
@@ -1,14 +1,10 @@
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const { performance, PerformanceObserver } = require('perf_hooks');
|
const { performance, PerformanceObserver } = require('perf_hooks');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
const checkURLHandler = async (url) => {
|
||||||
const { url } = event.queryStringParameters;
|
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return {
|
throw new Error('You must provide a URL query parameter!');
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'You must provide a URL query parameter!' }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dnsLookupTime;
|
let dnsLookupTime;
|
||||||
@@ -43,10 +39,7 @@ exports.handler = async function(event, context) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (responseCode < 200 || responseCode >= 400) {
|
if (responseCode < 200 || responseCode >= 400) {
|
||||||
return {
|
throw new Error(`Received non-success response code: ${responseCode}`);
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ error: `Received non-success response code: ${responseCode}` }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
performance.mark('B');
|
performance.mark('B');
|
||||||
@@ -54,16 +47,12 @@ exports.handler = async function(event, context) {
|
|||||||
let responseTime = performance.now() - startTime;
|
let responseTime = performance.now() - startTime;
|
||||||
obs.disconnect();
|
obs.disconnect();
|
||||||
|
|
||||||
return {
|
return { isUp: true, dnsLookupTime, responseTime, responseCode };
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ isUp: true, dnsLookupTime, responseTime, responseCode }),
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
obs.disconnect();
|
obs.disconnect();
|
||||||
return {
|
throw error;
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify({ error: `Error during operation: ${error.message}` }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(checkURLHandler);
|
||||||
@@ -1,69 +1,30 @@
|
|||||||
const Wappalyzer = require('wappalyzer');
|
const Wappalyzer = require('wappalyzer');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
const analyze = async (url) => {
|
const analyzeSiteTechnologies = async (url) => {
|
||||||
|
|
||||||
const options = {};
|
const options = {};
|
||||||
|
|
||||||
const wappalyzer = new Wappalyzer(options);
|
const wappalyzer = new Wappalyzer(options);
|
||||||
return (async function() {
|
|
||||||
try {
|
|
||||||
await wappalyzer.init()
|
|
||||||
const headers = {}
|
|
||||||
const storage = {
|
|
||||||
local: {},
|
|
||||||
session: {},
|
|
||||||
}
|
|
||||||
const site = await wappalyzer.open(url, headers, storage)
|
|
||||||
const results = await site.analyze()
|
|
||||||
return results;
|
|
||||||
} catch (error) {
|
|
||||||
return error;
|
|
||||||
} finally {
|
|
||||||
await wappalyzer.destroy()
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.handler = async (event, context, callback) => {
|
|
||||||
// Validate URL parameter
|
|
||||||
if (!event.queryStringParameters || !event.queryStringParameters.url) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
body: JSON.stringify({ error: 'Missing url parameter' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get URL from param
|
|
||||||
let url = event.queryStringParameters.url;
|
|
||||||
if (!/^https?:\/\//i.test(url)) {
|
|
||||||
url = 'http://' + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return analyze(url).then(
|
await wappalyzer.init();
|
||||||
(results) => {
|
const headers = {};
|
||||||
if (!results.technologies || results.technologies.length === 0) {
|
const storage = {
|
||||||
return {
|
local: {},
|
||||||
statusCode: 200,
|
session: {},
|
||||||
body: JSON.stringify({ error: 'Unable to find any technologies for site' }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(results),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: error.message }),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({ error: error.message }),
|
|
||||||
};
|
};
|
||||||
|
const site = await wappalyzer.open(url, headers, storage);
|
||||||
|
const results = await site.analyze();
|
||||||
|
|
||||||
|
if (!results.technologies || results.technologies.length === 0) {
|
||||||
|
throw new Error('Unable to find any technologies for site');
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.message);
|
||||||
|
} finally {
|
||||||
|
await wappalyzer.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(analyzeSiteTechnologies);
|
||||||
|
|||||||
95
api/threats.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const xml2js = require('xml2js');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const getGoogleSafeBrowsingResult = async (url) => {
|
||||||
|
try {
|
||||||
|
const apiKey = process.env.GOOGLE_CLOUD_API_KEY;
|
||||||
|
const apiEndpoint = `https://safebrowsing.googleapis.com/v4/threatMatches:find?key=${apiKey}`;
|
||||||
|
|
||||||
|
const requestBody = {
|
||||||
|
threatInfo: {
|
||||||
|
threatTypes: [
|
||||||
|
'MALWARE', 'SOCIAL_ENGINEERING', 'UNWANTED_SOFTWARE', 'POTENTIALLY_HARMFUL_APPLICATION', 'API_ABUSE'
|
||||||
|
],
|
||||||
|
platformTypes: ["ANY_PLATFORM"],
|
||||||
|
threatEntryTypes: ["URL"],
|
||||||
|
threatEntries: [{ url }]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await axios.post(apiEndpoint, requestBody);
|
||||||
|
if (response.data && response.data.matches) {
|
||||||
|
return {
|
||||||
|
unsafe: true,
|
||||||
|
details: response.data.matches
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { unsafe: false };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { error: `Request failed: ${error.message}` };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUrlHausResult = async (url) => {
|
||||||
|
let domain = new URL(url).hostname;
|
||||||
|
return await axios({
|
||||||
|
method: 'post',
|
||||||
|
url: 'https://urlhaus-api.abuse.ch/v1/host/',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
data: `host=${domain}`
|
||||||
|
})
|
||||||
|
.then((x) => x.data)
|
||||||
|
.catch((e) => ({ error: `Request to URLHaus failed, ${e.message}`}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getPhishTankResult = async (url) => {
|
||||||
|
try {
|
||||||
|
const encodedUrl = Buffer.from(url).toString('base64');
|
||||||
|
const endpoint = `https://checkurl.phishtank.com/checkurl/?url=${encodedUrl}`;
|
||||||
|
const headers = {
|
||||||
|
'User-Agent': 'phishtank/web-check',
|
||||||
|
};
|
||||||
|
const response = await axios.post(endpoint, null, { headers, timeout: 3000 });
|
||||||
|
const parsed = await xml2js.parseStringPromise(response.data, { explicitArray: false });
|
||||||
|
return parsed.response.results;
|
||||||
|
} catch (error) {
|
||||||
|
return { error: `Request to PhishTank failed: ${error.message}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCloudmersiveResult = async (url) => {
|
||||||
|
try {
|
||||||
|
const endpoint = 'https://api.cloudmersive.com/virus/scan/website';
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Apikey': process.env.CLOUDMERSIVE_API_KEY,
|
||||||
|
};
|
||||||
|
const data = `Url=${encodeURIComponent(url)}`;
|
||||||
|
const response = await axios.post(endpoint, data, { headers });
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
return { error: `Request to Cloudmersive failed: ${error.message}` };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
try {
|
||||||
|
const urlHaus = await getUrlHausResult(url);
|
||||||
|
const phishTank = await getPhishTankResult(url);
|
||||||
|
const cloudmersive = await getCloudmersiveResult(url);
|
||||||
|
const safeBrowsing = await getGoogleSafeBrowsingResult(url);
|
||||||
|
if (urlHaus.error && phishTank.error && cloudmersive.error && safeBrowsing.error) {
|
||||||
|
throw new Error(`All requests failed - ${urlHaus.error} ${phishTank.error} ${cloudmersive.error} ${safeBrowsing.error}`);
|
||||||
|
}
|
||||||
|
return JSON.stringify({ urlHaus, phishTank, cloudmersive, safeBrowsing });
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
28
api/tls.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
|
const MOZILLA_TLS_OBSERVATORY_API = 'https://tls-observatory.services.mozilla.com/api/v1';
|
||||||
|
|
||||||
|
const handler = async (url) => {
|
||||||
|
try {
|
||||||
|
const domain = new URL(url).hostname;
|
||||||
|
const scanResponse = await axios.post(`${MOZILLA_TLS_OBSERVATORY_API}/scan?target=${domain}`);
|
||||||
|
const scanId = scanResponse.data.scan_id;
|
||||||
|
|
||||||
|
if (typeof scanId !== 'number') {
|
||||||
|
return {
|
||||||
|
statusCode: 500,
|
||||||
|
body: JSON.stringify({ 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),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { error: error.message };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,55 +1,31 @@
|
|||||||
const traceroute = require('traceroute');
|
const traceroute = require('traceroute');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
const executeTraceroute = async (urlString, context) => {
|
||||||
const urlString = event.queryStringParameters.url;
|
// Parse the URL and get the hostname
|
||||||
|
const urlObject = url.parse(urlString);
|
||||||
|
const host = urlObject.hostname;
|
||||||
|
|
||||||
try {
|
if (!host) {
|
||||||
if (!urlString) {
|
throw new Error('Invalid URL provided');
|
||||||
throw new Error('URL parameter is missing!');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the URL and get the hostname
|
// Traceroute with callback
|
||||||
const urlObject = url.parse(urlString);
|
const result = await new Promise((resolve, reject) => {
|
||||||
const host = urlObject.hostname;
|
traceroute.trace(host, (err, hops) => {
|
||||||
|
if (err || !hops) {
|
||||||
if (!host) {
|
reject(err || new Error('No hops found'));
|
||||||
throw new Error('Invalid URL provided');
|
} else {
|
||||||
}
|
resolve(hops);
|
||||||
|
|
||||||
// Traceroute with callback
|
|
||||||
const result = await new Promise((resolve, reject) => {
|
|
||||||
traceroute.trace(host, (err, hops) => {
|
|
||||||
if (err || !hops) {
|
|
||||||
reject(err || new Error('No hops found'));
|
|
||||||
} else {
|
|
||||||
resolve(hops);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if remaining time is less than 8.8 seconds, then reject promise
|
|
||||||
if (context.getRemainingTimeInMillis() < 8800) {
|
|
||||||
reject(new Error('Lambda is about to timeout'));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
message: "Traceroute completed!",
|
||||||
body: JSON.stringify({
|
result,
|
||||||
message: "Traceroute completed!",
|
};
|
||||||
result,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const message = err.code === 'ENOENT'
|
|
||||||
? 'Traceroute command is not installed on the host.'
|
|
||||||
: err.message;
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: message,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(executeTraceroute);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
const dns = require('dns').promises;
|
const dns = require('dns').promises;
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
|
|
||||||
exports.handler = async (event) => {
|
const handler = async (url, event, context) => {
|
||||||
const url = new URL(event.queryStringParameters.url);
|
|
||||||
try {
|
try {
|
||||||
const txtRecords = await dns.resolveTxt(url.hostname);
|
const parsedUrl = new URL(url);
|
||||||
|
|
||||||
|
const txtRecords = await dns.resolveTxt(parsedUrl.hostname);
|
||||||
|
|
||||||
// Parsing and formatting TXT records into a single object
|
// Parsing and formatting TXT records into a single object
|
||||||
const readableTxtRecords = txtRecords.reduce((acc, recordArray) => {
|
const readableTxtRecords = txtRecords.reduce((acc, recordArray) => {
|
||||||
@@ -16,15 +18,15 @@ exports.handler = async (event) => {
|
|||||||
return { ...acc, ...recordObject };
|
return { ...acc, ...recordObject };
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return {
|
return readableTxtRecords;
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(readableTxtRecords),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error);
|
if (error.code === 'ERR_INVALID_URL') {
|
||||||
return {
|
throw new Error(`Invalid URL ${error}`);
|
||||||
statusCode: 500,
|
} else {
|
||||||
body: JSON.stringify({ error: error.message }),
|
throw error;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(handler);
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
const net = require('net');
|
const net = require('net');
|
||||||
const psl = require('psl');
|
const psl = require('psl');
|
||||||
// const { URL } = require('url');
|
const axios = require('axios');
|
||||||
|
const middleware = require('./_common/middleware');
|
||||||
const errorResponse = (message, statusCode = 444) => {
|
|
||||||
return {
|
|
||||||
statusCode: statusCode,
|
|
||||||
body: JSON.stringify({ error: message }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const getBaseDomain = (url) => {
|
const getBaseDomain = (url) => {
|
||||||
let protocol = '';
|
let protocol = '';
|
||||||
@@ -22,55 +15,7 @@ const getBaseDomain = (url) => {
|
|||||||
return protocol + parsed.domain;
|
return protocol + parsed.domain;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.handler = async function(event, context) {
|
|
||||||
let url = event.queryStringParameters.url;
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
return errorResponse('URL query parameter is required.', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
||||||
url = 'http://' + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hostname;
|
|
||||||
try {
|
|
||||||
hostname = getBaseDomain(new URL(url).hostname);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(`Unable to parse URL: ${error}`, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const client = net.createConnection({ port: 43, host: 'whois.internic.net' }, () => {
|
|
||||||
client.write(hostname + '\r\n');
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = '';
|
|
||||||
client.on('data', (chunk) => {
|
|
||||||
data += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('end', () => {
|
|
||||||
try {
|
|
||||||
const parsedData = parseWhoisData(data);
|
|
||||||
resolve({
|
|
||||||
statusCode: 200,
|
|
||||||
body: JSON.stringify(parsedData),
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
resolve(errorResponse(error.message));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (err) => {
|
|
||||||
resolve(errorResponse(err.message, 500));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseWhoisData = (data) => {
|
const parseWhoisData = (data) => {
|
||||||
|
|
||||||
if (data.includes('No match for')) {
|
if (data.includes('No match for')) {
|
||||||
return { error: 'No matches found for domain in internic database'};
|
return { error: 'No matches found for domain in internic database'};
|
||||||
}
|
}
|
||||||
@@ -100,3 +45,65 @@ const parseWhoisData = (data) => {
|
|||||||
return parsedData;
|
return parsedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchFromInternic = async (hostname) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const client = net.createConnection({ port: 43, host: 'whois.internic.net' }, () => {
|
||||||
|
client.write(hostname + '\r\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
let data = '';
|
||||||
|
client.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('end', () => {
|
||||||
|
try {
|
||||||
|
const parsedData = parseWhoisData(data);
|
||||||
|
resolve(parsedData);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFromMyAPI = async (hostname) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('https://whois-api-zeta.vercel.app/', {
|
||||||
|
domain: hostname
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data from your API:', error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchWhoisData = async (url) => {
|
||||||
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||||
|
url = 'http://' + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hostname;
|
||||||
|
try {
|
||||||
|
hostname = getBaseDomain(new URL(url).hostname);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Unable to parse URL: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [internicData, whoisData] = await Promise.all([
|
||||||
|
fetchFromInternic(hostname),
|
||||||
|
fetchFromMyAPI(hostname)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
internicData,
|
||||||
|
whoisData
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.handler = middleware(fetchWhoisData);
|
||||||
@@ -4,5 +4,5 @@ services:
|
|||||||
container_name: Web-Check
|
container_name: Web-Check
|
||||||
image: lissy93/web-check
|
image: lissy93/web-check
|
||||||
ports:
|
ports:
|
||||||
- 8888:8888
|
- 3000:3000
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
22
package.json
@@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "netlify dev",
|
"dev": "netlify dev",
|
||||||
"serve": "netlify serve --offline",
|
"serve": "node server",
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
@@ -35,9 +35,14 @@
|
|||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
"@types/react-simple-maps": "^3.0.0",
|
"@types/react-simple-maps": "^3.0.0",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/styled-components": "^5.1.26",
|
||||||
|
"aws-serverless-express": "^3.4.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"chrome-aws-lambda": "^10.1.0",
|
"chrome-aws-lambda": "^10.1.0",
|
||||||
|
"chromium": "^3.0.3",
|
||||||
|
"connect-history-api-fallback": "^2.0.0",
|
||||||
|
"csv-parser": "^3.0.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
"flatted": "^3.2.7",
|
"flatted": "^3.2.7",
|
||||||
"follow-redirects": "^1.15.2",
|
"follow-redirects": "^1.15.2",
|
||||||
"got": "^13.0.0",
|
"got": "^13.0.0",
|
||||||
@@ -46,7 +51,7 @@
|
|||||||
"perf_hooks": "^0.0.1",
|
"perf_hooks": "^0.0.1",
|
||||||
"psl": "^1.9.0",
|
"psl": "^1.9.0",
|
||||||
"puppeteer": "^20.9.0",
|
"puppeteer": "^20.9.0",
|
||||||
"puppeteer-core": "^20.9.0",
|
"puppeteer-core": "^21.0.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-masonry-css": "^1.0.16",
|
"react-masonry-css": "^1.0.16",
|
||||||
@@ -54,12 +59,14 @@
|
|||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-simple-maps": "^3.0.0",
|
"react-simple-maps": "^3.0.0",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
|
"recharts": "^2.7.3",
|
||||||
"styled-components": "^6.0.5",
|
"styled-components": "^6.0.5",
|
||||||
"traceroute": "^1.0.0",
|
"traceroute": "^1.0.0",
|
||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
"wappalyzer": "^6.10.63",
|
"unzipper": "^0.10.14",
|
||||||
|
"wappalyzer": "^6.10.65",
|
||||||
"web-vitals": "^3.4.0",
|
"web-vitals": "^3.4.0",
|
||||||
"xml2js": "^0.6.0"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -82,5 +89,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"outDir": "./dist"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
public/banner.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
@@ -7,15 +7,29 @@
|
|||||||
<meta name="theme-color" content="#9fef00" />
|
<meta name="theme-color" content="#9fef00" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="All-in-one OSINT tool, for quickly checking a websites data"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<title>Web Check</title>
|
<title>Web Check</title>
|
||||||
<script defer data-domain="web-check.as93.net" src="https://no-track.as93.net/js/script.js"></script>
|
<script defer data-domain="web-check.as93.net" src="https://no-track.as93.net/js/script.js"></script>
|
||||||
|
|
||||||
|
<!-- OpenGraph Social Tags -->
|
||||||
|
<meta property="og:title" content="Web Check">
|
||||||
|
<meta property="og:description" content="All-in-one Website OSINT Scanner">
|
||||||
|
<meta property="og:image" content="/banner.png">
|
||||||
|
<meta property="og:url" content="https://web-check.as93.net">
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>All-in-one OSINT tool, for quickly checking a websites data</noscript>
|
<noscript>
|
||||||
|
<b>Welcome to Web-Check, the free and open source tool for viewing all available information about a website.</b><br />
|
||||||
|
Get started by entering a URL, and clicking the "Scan" button, or view the code and docs
|
||||||
|
on <a href="https://github.com/lissy93/web-check">GitHub</a>.<br />
|
||||||
|
<small>Licensed under MIT, ©️ <a href="https://aliciasykes.com">Alicia Sykes</a> 2023.</small>
|
||||||
|
<br /><br />
|
||||||
|
JavaScript is required to continue, please enable it in your browser.
|
||||||
|
</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
116
server.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const awsServerlessExpress = require('aws-serverless-express');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const historyApiFallback = require('connect-history-api-fallback');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
// Execute the lambda function
|
||||||
|
const executeHandler = async (handler, req) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const callback = (err, response) => err ? reject(err) : resolve(response);
|
||||||
|
const promise = handler(req, {}, callback);
|
||||||
|
|
||||||
|
if (promise && typeof promise.then === 'function') {
|
||||||
|
promise.then(resolve).catch(reject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Array of all the lambda function file names
|
||||||
|
const fileNames = fs.readdirSync(dirPath, { withFileTypes: true })
|
||||||
|
.filter(dirent => dirent.isFile() && dirent.name.endsWith('.js'))
|
||||||
|
.map(dirent => dirent.name);
|
||||||
|
|
||||||
|
const handlers = {};
|
||||||
|
|
||||||
|
fileNames.forEach(file => {
|
||||||
|
const routeName = file.split('.')[0];
|
||||||
|
const route = `${API_DIR}/${routeName}`;
|
||||||
|
const handler = require(path.join(dirPath, file)).handler;
|
||||||
|
|
||||||
|
handlers[route] = handler;
|
||||||
|
|
||||||
|
app.get(route, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { statusCode = 200, body = '' } = await executeHandler(handler, req);
|
||||||
|
res.status(statusCode).json(JSON.parse(body));
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeout = (ms, jobName = null) => {
|
||||||
|
return new Promise((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error(`Timed out after the ${ms/1000} second limit${jobName ? `, when executing the ${jobName} task` : ''}`));
|
||||||
|
}, ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/api', async (req, res) => {
|
||||||
|
const results = {};
|
||||||
|
const { url } = req.query;
|
||||||
|
const maxExecutionTime = process.env.API_TIMEOUT_LIMIT || 15000;
|
||||||
|
|
||||||
|
const handlerPromises = Object.entries(handlers).map(async ([route, handler]) => {
|
||||||
|
const routeName = route.replace(`${API_DIR}/`, '');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await Promise.race([
|
||||||
|
executeHandler(handler, { query: { url } }),
|
||||||
|
timeout(maxExecutionTime, routeName)
|
||||||
|
]);
|
||||||
|
results[routeName] = JSON.parse((result || {}).body);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
results[routeName] = { error: err.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(handlerPromises);
|
||||||
|
res.json(results);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle SPA routing
|
||||||
|
app.use(historyApiFallback({
|
||||||
|
rewrites: [
|
||||||
|
{ from: /^\/api\/.*$/, to: function(context) { return context.parsedUrl.path; } },
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Serve up the GUI - if build dir exists, and GUI feature enabled
|
||||||
|
if (process.env.DISABLE_GUI && process.env.DISABLE_GUI !== 'false') {
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.status(500).send(
|
||||||
|
'Welcome to Web-Check!<br />Access the API endpoints at '
|
||||||
|
+'<a href="/api"><code>/api</code></a>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else if (!fs.existsSync(guiPath)) {
|
||||||
|
app.get('*', (req, res) => {
|
||||||
|
res.status(500).send(
|
||||||
|
'Welcome to Web-Check!<br />Looks like the GUI app has not yet been compiled, '
|
||||||
|
+'run <code>yarn build</code> to continue, then restart the server.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else { // GUI enabled, and build files present, let's go!!
|
||||||
|
app.use(express.static(guiPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create serverless express server
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
const server = awsServerlessExpress.createServer(app).listen(port, () => {
|
||||||
|
console.log(`Server is running on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.handler = (event, context) => {
|
||||||
|
awsServerlessExpress.proxy(server, event, context);
|
||||||
|
};
|
||||||
179
serverless.yml
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
service: web-check-api
|
||||||
|
|
||||||
|
provider:
|
||||||
|
name: aws
|
||||||
|
runtime: nodejs14.x
|
||||||
|
region: us-east-1
|
||||||
|
|
||||||
|
functions:
|
||||||
|
dnssec:
|
||||||
|
handler: api/dnssec.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/dnssec
|
||||||
|
method: get
|
||||||
|
linkedPages:
|
||||||
|
handler: api/linked-pages.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/linked-pages
|
||||||
|
method: get
|
||||||
|
robotsTxt:
|
||||||
|
handler: api/robots-txt.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/robots-txt
|
||||||
|
method: get
|
||||||
|
ssl:
|
||||||
|
handler: api/ssl.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/ssl
|
||||||
|
method: get
|
||||||
|
whois:
|
||||||
|
handler: api/whois.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/whois
|
||||||
|
method: get
|
||||||
|
carbon:
|
||||||
|
handler: api/carbon.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/carbon
|
||||||
|
method: get
|
||||||
|
features:
|
||||||
|
handler: api/features.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/features
|
||||||
|
method: get
|
||||||
|
mailConfig:
|
||||||
|
handler: api/mail-config.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/mail-config
|
||||||
|
method: get
|
||||||
|
screenshot:
|
||||||
|
handler: api/screenshot.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/screenshot
|
||||||
|
method: get
|
||||||
|
status:
|
||||||
|
handler: api/status.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/status
|
||||||
|
method: get
|
||||||
|
cookies:
|
||||||
|
handler: api/cookies.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/cookies
|
||||||
|
method: get
|
||||||
|
getIp:
|
||||||
|
handler: api/get-ip.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/get-ip
|
||||||
|
method: get
|
||||||
|
ports:
|
||||||
|
handler: api/ports.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/ports
|
||||||
|
method: get
|
||||||
|
securityTxt:
|
||||||
|
handler: api/security-txt.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/security-txt
|
||||||
|
method: get
|
||||||
|
techStack:
|
||||||
|
handler: api/tech-stack.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/tech-stack
|
||||||
|
method: get
|
||||||
|
dnsServer:
|
||||||
|
handler: api/dns-server.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/dns-server
|
||||||
|
method: get
|
||||||
|
headers:
|
||||||
|
handler: api/headers.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/headers
|
||||||
|
method: get
|
||||||
|
quality:
|
||||||
|
handler: api/quality.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/quality
|
||||||
|
method: get
|
||||||
|
sitemap:
|
||||||
|
handler: api/sitemap.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/sitemap
|
||||||
|
method: get
|
||||||
|
traceRoute:
|
||||||
|
handler: api/trace-route.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/trace-route
|
||||||
|
method: get
|
||||||
|
dns:
|
||||||
|
handler: api/dns.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/dns
|
||||||
|
method: get
|
||||||
|
hsts:
|
||||||
|
handler: api/hsts.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/hsts
|
||||||
|
method: get
|
||||||
|
redirects:
|
||||||
|
handler: api/redirects.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/redirects
|
||||||
|
method: get
|
||||||
|
socialTags:
|
||||||
|
handler: api/social-tags.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/social-tags
|
||||||
|
method: get
|
||||||
|
txtRecords:
|
||||||
|
handler: api/txt-records.handler
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: api/txt-records
|
||||||
|
method: get
|
||||||
|
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
# - serverless-webpack
|
||||||
|
- serverless-domain-manager
|
||||||
|
- serverless-offline
|
||||||
|
|
||||||
|
custom:
|
||||||
|
webpack:
|
||||||
|
webpackConfig: 'api/_common/aws-webpack.config.js'
|
||||||
|
includeModules: true
|
||||||
|
|
||||||
|
customDomain:
|
||||||
|
domainName: example.com
|
||||||
|
basePath: 'api'
|
||||||
|
stage: ${self:provider.stage}
|
||||||
|
createRoute53Record: true
|
||||||
|
|
||||||
|
serverless-offline:
|
||||||
|
prefix: ''
|
||||||
|
httpPort: 3000
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import styled from 'styled-components';
|
import styled, { keyframes } from 'styled-components';
|
||||||
import colors from 'styles/colors';
|
import colors from 'styles/colors';
|
||||||
import { InputSize, applySize } from 'styles/dimensions';
|
import { InputSize, applySize } from 'styles/dimensions';
|
||||||
|
|
||||||
|
type LoadState = 'loading' | 'success' | 'error';
|
||||||
|
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
@@ -10,6 +12,7 @@ interface ButtonProps {
|
|||||||
fgColor?: string,
|
fgColor?: string,
|
||||||
styles?: string,
|
styles?: string,
|
||||||
title?: string,
|
title?: string,
|
||||||
|
loadState?: LoadState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledButton = styled.button<ButtonProps>`
|
const StyledButton = styled.button<ButtonProps>`
|
||||||
@@ -19,6 +22,9 @@ const StyledButton = styled.button<ButtonProps>`
|
|||||||
font-family: PTMono;
|
font-family: PTMono;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: -moz-available;
|
width: -moz-available;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
box-shadow: 3px 3px 0px ${colors.fgShadowColor};
|
box-shadow: 3px 3px 0px ${colors.fgShadowColor};
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 5px 5px 0px ${colors.fgShadowColor};
|
box-shadow: 5px 5px 0px ${colors.fgShadowColor};
|
||||||
@@ -36,8 +42,29 @@ const StyledButton = styled.button<ButtonProps>`
|
|||||||
${props => props.styles}
|
${props => props.styles}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
||||||
|
const spinAnimation = keyframes`
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
`;
|
||||||
|
const SimpleLoader = styled.div`
|
||||||
|
border: 4px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 4px solid ${colors.background};
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
animation: ${spinAnimation} 1s linear infinite;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Loader = (props: { loadState: LoadState }) => {
|
||||||
|
if (props.loadState === 'loading') return <SimpleLoader />
|
||||||
|
if (props.loadState === 'success') return <span>✔</span>
|
||||||
|
if (props.loadState === 'error') return <span>✗</span>
|
||||||
|
return <span></span>;
|
||||||
|
};
|
||||||
|
|
||||||
const Button = (props: ButtonProps): JSX.Element => {
|
const Button = (props: ButtonProps): JSX.Element => {
|
||||||
const { children, size, bgColor, fgColor, onClick, styles, title } = props;
|
const { children, size, bgColor, fgColor, onClick, styles, title, loadState } = props;
|
||||||
return (
|
return (
|
||||||
<StyledButton
|
<StyledButton
|
||||||
onClick={onClick || (() => null) }
|
onClick={onClick || (() => null) }
|
||||||
@@ -47,6 +74,7 @@ const Button = (props: ButtonProps): JSX.Element => {
|
|||||||
styles={styles}
|
styles={styles}
|
||||||
title={title?.toString()}
|
title={title?.toString()}
|
||||||
>
|
>
|
||||||
|
{ loadState && <Loader loadState={loadState} /> }
|
||||||
{children}
|
{children}
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
);
|
);
|
||||||
|
|||||||