Merge branch 'master' into master

This commit is contained in:
Alicia Sykes
2023-08-26 21:28:48 +01:00
committed by GitHub
133 changed files with 7317 additions and 1346 deletions

View File

@@ -5,9 +5,9 @@ import Heading from 'components/Form/Heading';
import Footer from 'components/misc/Footer';
import Nav from 'components/Form/Nav';
import Button from 'components/Form/Button';
import AdditionalResources from 'components/misc/AdditionalResources';
import { StyledCard } from 'components/Form/Card';
import docs, { about, license, fairUse } from 'utils/docs';
import docs, { about, featureIntro, license, fairUse, supportUs } from 'utils/docs';
const AboutContainer = styled.div`
width: 95vw;
@@ -16,6 +16,11 @@ margin: 2rem auto;
padding-bottom: 1rem;
header {
margin 1rem 0;
width: auto;
}
section {
width: auto;
.inner-heading { display: none; }
}
`;
@@ -32,6 +37,9 @@ const Section = styled(StyledCard)`
margin-bottom: 2rem;
overflow: clip;
max-height: 100%;
section {
clear: both;
}
h3 {
font-size: 1.5rem;
}
@@ -71,13 +79,25 @@ const Section = styled(StyledCard)`
}
}
}
.screenshot {
float: right;
break-inside: avoid;
max-width: 300px;
max-height: 28rem;
border-radius: 6px;
.example-screenshot {
float: right;
display: inline-flex;
flex-direction: column;
clear: both;
max-width: 300px;
img {
float: right;
break-inside: avoid;
max-width: 300px;
// max-height: 30rem;
border-radius: 6px;
clear: both;
}
figcaption {
font-size: 0.8rem;
text-align: center;
opacity: 0.7;
}
}
`;
@@ -85,7 +105,6 @@ const makeAnchor = (title: string): string => {
return title.toLowerCase().replace(/[^\w\s]|_/g, "").replace(/\s+/g, "-");
};
const About = (): JSX.Element => {
return (
<div>
@@ -101,10 +120,20 @@ const About = (): JSX.Element => {
{about.map((para, index: number) => (
<p key={index}>{para}</p>
))}
<hr />
<p>
Web-Check is developed and maintained by <a href="https://aliciasykes.com">Alicia Sykes</a>.
It's licensed under the <a href="https://github.com/Lissy93/web-check/blob/master/LICENSE">MIT license</a>,
and is completely free to use, modify and distribute in both personal and commercial settings.<br />
Source code and self-hosting docs are available on <a href="https://github.com/lissy93/web-check">GitHub</a>.
If you've found this service useful, consider <a href="https://github.com/sponsors/Lissy93">sponsoring me</a> from $1/month,
to help with the ongoing hosting and development costs.
</p>
</Section>
<Heading as="h2" size="medium" color={colors.primary}>Features</Heading>
<Section>
{featureIntro.map((fi: string, i: number) => (<p key={i}>{fi}</p>))}
<div className="contents">
<Heading as="h3" size="small" id="#feature-contents" color={colors.primary}>Contents</Heading>
<ul>
@@ -118,9 +147,13 @@ const About = (): JSX.Element => {
</div>
{docs.map((section, sectionIndex: number) => (
<section key={section.title}>
{ sectionIndex > 0 && <hr /> }
<Heading as="h3" size="small" id={makeAnchor(section.title)} color={colors.primary}>{section.title}</Heading>
{section.screenshot &&
<img className="screenshot" src={section.screenshot} alt={`Example Screenshot ${section.title}`} />
{section.screenshot &&
<figure className="example-screenshot">
<img className="screenshot" src={section.screenshot} alt={`Example Screenshot ${section.title}`} />
<figcaption>Fig.{sectionIndex + 1} - Example of {section.title}</figcaption>
</figure>
}
{section.description && <>
<Heading as="h4" size="small">Description</Heading>
@@ -142,11 +175,24 @@ const About = (): JSX.Element => {
))}
</ul>
</>}
{ sectionIndex < docs.length - 1 && <hr /> }
</section>
))}
</Section>
<Heading as="h2" size="medium" color={colors.primary}>API Documentation</Heading>
<Section>
{/* eslint-disable-next-line*/}
<p>// Coming soon...</p>
</Section>
<Heading as="h2" size="medium" color={colors.primary}>Additional Resources</Heading>
<AdditionalResources />
<Heading as="h2" size="medium" color={colors.primary}>Support Us</Heading>
<Section>
{supportUs.map((para, index: number) => (<p dangerouslySetInnerHTML={{__html: para}} />))}
</Section>
<Heading as="h2" size="medium" color={colors.primary}>Terms & Info</Heading>
<Section>
<Heading as="h3" size="small" color={colors.primary}>License</Heading>
@@ -166,6 +212,11 @@ const About = (): JSX.Element => {
<Heading as="h3" size="small" color={colors.primary}>Privacy</Heading>
<p>
Analytics are used on the demo instance (via a self-hosted Plausible instance), this only records the URL you visited but no personal data.
There's also some basic error logging (via a self-hosted GlitchTip instance), this is only used to help me fix bugs.
<br />
<br />
Neither your IP address, browser/OS/hardware info, nor any other data will ever be collected or logged.
(You may verify this yourself, either by inspecting the source code or the using developer tools)
</p>
<hr />
<Heading as="h3" size="small" color={colors.primary}>Support</Heading>

View File

@@ -67,6 +67,9 @@ const SiteFeaturesWrapper = styled(StyledCard)`
width: calc(100% - 2rem);
}
}
@media(max-width: 600px) {
flex-wrap: wrap;
}
}
ul {
-webkit-column-width: 150px;
@@ -179,8 +182,15 @@ const Home = (): JSX.Element => {
</ul>
</div>
<div className="links">
<a href="https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/web-check"><Button>Deploy your own Instance</Button></a>
<a href="https://github.com/lissy93/web-check"><Button>View on GitHub</Button></a>
<a href="https://github.com/lissy93/web-check" title="Check out the source code and documentation on GitHub, and get support or contribute">
<Button>View on GitHub</Button>
</a>
<a href="https://app.netlify.com/start/deploy?repository=https://github.com/lissy93/web-check" title="Deploy your own private or public instance of Web-Check to Netlify">
<Button>Deploy your own</Button>
</a>
<a href="/about#api-documentation" title="View the API documentation, to use Web-Check programmatically">
<Button>API Docs</Button>
</a>
</div>
</SiteFeaturesWrapper>
<Footer isFixed={true} />

View File

@@ -20,6 +20,10 @@ const AboutContainer = styled.div`
color: ${colors.primary};
}
.im-drink { font-size: 6rem; }
header {
width: auto;
margin: 1rem;
}
`;
const HeaderLinkContainer = styled.nav`

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, ReactNode } from 'react';
import { useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import styled from 'styled-components';
import { ToastContainer } from 'react-toastify';
import Masonry from 'react-masonry-css'
@@ -46,14 +46,24 @@ import DnsServerCard from 'components/Results/DnsServer';
import TechStackCard from 'components/Results/TechStack';
import SecurityTxtCard from 'components/Results/SecurityTxt';
import ContentLinksCard from 'components/Results/ContentLinks';
import SocialTagsCard from 'components/Results/SocialTags';
import MailConfigCard from 'components/Results/MailConfig';
import HttpSecurityCard from 'components/Results/HttpSecurity';
import FirewallCard from 'components/Results/Firewall';
import ArchivesCard from 'components/Results/Archives';
import RankCard from 'components/Results/Rank';
import BlockListsCard from 'components/Results/BlockLists';
import ThreatsCard from 'components/Results/Threats';
import TlsCipherSuitesCard from 'components/Results/TlsCipherSuites';
import TlsIssueAnalysisCard from 'components/Results/TlsIssueAnalysis';
import TlsClientSupportCard from 'components/Results/TlsClientSupport';
import keys from 'utils/get-keys';
import { determineAddressType, AddressType } from 'utils/address-type-checker';
import useMotherHook from 'hooks/motherOfAllHooks';
import {
getLocation, ServerLocation,
parseCookies, Cookie,
parseRobotsTxt,
Cookie,
applyWhoIsResults, Whois,
parseShodanResults, ShodanResults
} from 'utils/result-processor';
@@ -79,6 +89,68 @@ const ResultsContent = styled.section`
padding-bottom: 1rem;
`;
const FilterButtons = styled.div`
width: 95vw;
margin: auto;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
.one-half {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
}
button, input, .toggle-filters {
background: ${colors.backgroundLighter};
color: ${colors.textColor};
border: none;
border-radius: 4px;
font-family: 'PTMono';
padding: 0.25rem 0.5rem;
border: 1px solid transparent;
transition: all 0.2s ease-in-out;
}
button, .toggle-filters {
cursor: pointer;
text-transform: capitalize;
box-shadow: 2px 2px 0px ${colors.bgShadowColor};
transition: all 0.2s ease-in-out;
&:hover {
box-shadow: 4px 4px 0px ${colors.bgShadowColor};
color: ${colors.primary};
}
&.selected {
border: 1px solid ${colors.primary};
color: ${colors.primary};
}
}
input:focus {
border: 1px solid ${colors.primary};
outline: none;
}
.clear {
color: ${colors.textColor};
text-decoration: underline;
cursor: pointer;
font-size: 0.8rem;
opacity: 0.8;
}
.toggle-filters {
font-size: 0.8rem;
}
.control-options {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
a {
text-decoration: none;
}
}
`;
const Results = (): JSX.Element => {
const startTime = new Date().getTime();
@@ -88,13 +160,27 @@ const Results = (): JSX.Element => {
const [loadingJobs, setLoadingJobs] = useState<LoadingJob[]>(initialJobs);
const [modalOpen, setModalOpen] = useState(false);
const [modalContent, setModalContent] = useState<ReactNode>(<></>);
const [showFilters, setShowFilters] = useState(false);
const [searchTerm, setSearchTerm] = useState<string>('');
const [tags, setTags] = useState<string[]>([]);
const updateLoadingJobs = useCallback((job: string, newState: LoadingState, error?: string, retry?: () => void, data?: any) => {
const clearFilters = () => {
setTags([]);
setSearchTerm('');
};
const updateTags = (tag: string) => {
// Remove current tag if it exists, otherwise add it
// setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [...tags, tag]);
setTags(tags.includes(tag) ? tags.filter(t => t !== tag) : [tag]);
};
const updateLoadingJobs = useCallback((jobs: string | string[], newState: LoadingState, error?: string, retry?: () => void, data?: any) => {
(typeof jobs === 'string' ? [jobs] : jobs).forEach((job: string) => {
const now = new Date();
const timeTaken = now.getTime() - startTime;
setLoadingJobs((prevJobs) => {
const newJobs = prevJobs.map((loadingJob: LoadingJob) => {
if (loadingJob.name === job) {
if (job.includes(loadingJob.name)) {
return { ...loadingJob, error, state: newState, timeTaken, retry };
}
return loadingJob;
@@ -130,7 +216,8 @@ const Results = (): JSX.Element => {
}
return newJobs;
});
}, []);
});
}, [startTime]);
const parseJson = (response: Response): Promise<any> => {
return new Promise((resolve) => {
@@ -145,6 +232,20 @@ const Results = (): JSX.Element => {
});
};
const urlTypeOnly = ['url'] as AddressType[]; // Many jobs only run with these address types
const api = process.env.REACT_APP_API_ENDPOINT || '/api'; // Where is the API hosted?
// Fetch and parse IP address for given URL
const [ipAddress, setIpAddress] = useMotherHook({
jobId: 'get-ip',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/get-ip?url=${address}`)
.then(res => parseJson(res))
.then(res => res.ip),
});
useEffect(() => {
if (!addressType || addressType === 'empt') {
setAddressType(determineAddressType(address || ''));
@@ -152,75 +253,7 @@ const Results = (): JSX.Element => {
if (addressType === 'ipV4' && address) {
setIpAddress(address);
}
}, []);
const urlTypeOnly = ['url'] as AddressType[]; // Many jobs only run with these address types
const api = '/api';
// Fetch and parse IP address for given URL
const [ipAddress, setIpAddress] = useMotherHook({
jobId: 'get-ip',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/find-url-ip?url=${address}`)
.then(res => parseJson(res))
.then(res => res.ip),
});
// Fetch and parse SSL certificate info
const [sslResults, updateSslResults] = useMotherHook({
jobId: 'ssl',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/ssl-check?url=${address}`).then((res) => parseJson(res)),
});
// Fetch and parse cookies info
const [cookieResults, updateCookieResults] = useMotherHook<{cookies: Cookie[]}>({
jobId: 'cookies',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/get-cookies?url=${address}`)
.then(res => parseJson(res))
.then(res => parseCookies(res.cookies)),
});
// Fetch and parse crawl rules from robots.txt
const [robotsTxtResults, updateRobotsTxtResults] = useMotherHook<{robots: RowProps[]}>({
jobId: 'robots-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/read-robots-txt?url=${address}`)
.then(res => res.text())
.then(res => parseRobotsTxt(res)),
});
// Fetch and parse headers
const [headersResults, updateHeadersResults] = useMotherHook({
jobId: 'headers',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/get-headers?url=${address}`).then(res => parseJson(res)),
});
// Fetch and parse DNS records
const [dnsResults, updateDnsResults] = useMotherHook({
jobId: 'dns',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/get-dns?url=${address}`).then(res => parseJson(res)),
});
// Fetch and parse Lighthouse performance data
const [lighthouseResults, updateLighthouseResults] = useMotherHook({
jobId: 'quality',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/lighthouse-report?url=${address}`)
.then(res => parseJson(res))
.then(res => res?.lighthouseResult || { error: 'No Data'}),
});
}, [address, addressType, setIpAddress]);
// Get IP address location info
const [locationResults, updateLocationResults] = useMotherHook<ServerLocation>({
@@ -232,9 +265,43 @@ const Results = (): JSX.Element => {
.then(res => getLocation(res)),
});
// Fetch and parse SSL certificate info
const [sslResults, updateSslResults] = useMotherHook({
jobId: 'ssl',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/ssl?url=${address}`).then((res) => parseJson(res)),
});
// Run a manual whois lookup on the domain
const [domainLookupResults, updateDomainLookupResults] = useMotherHook({
jobId: 'domain',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/whois?url=${address}`).then(res => parseJson(res)),
});
// Fetch and parse Lighthouse performance data
const [lighthouseResults, updateLighthouseResults] = useMotherHook({
jobId: 'quality',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/quality?url=${address}`)
.then(res => parseJson(res))
.then(res => res?.lighthouseResult || { error: 'No Data'}),
});
// Get the technologies used to build site, using Wappalyzer
const [techStackResults, updateTechStackResults] = useMotherHook({
jobId: 'tech-stack',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/tech-stack?url=${address}`).then(res => parseJson(res)),
});
// Get hostnames and associated domains from Shodan
const [shoadnResults, updateShodanResults] = useMotherHook<ShodanResults>({
jobId: 'hosts',
jobId: ['hosts', 'server-info'],
updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
fetchRequest: () => fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${keys.shodan}`)
@@ -242,12 +309,182 @@ const Results = (): JSX.Element => {
.then(res => parseShodanResults(res)),
});
// Fetch and parse cookies info
const [cookieResults, updateCookieResults] = useMotherHook<{cookies: Cookie[]}>({
jobId: 'cookies',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/cookies?url=${address}`)
.then(res => parseJson(res)),
});
// Fetch and parse headers
const [headersResults, updateHeadersResults] = useMotherHook({
jobId: 'headers',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/headers?url=${address}`).then(res => parseJson(res)),
});
// Fetch and parse DNS records
const [dnsResults, updateDnsResults] = useMotherHook({
jobId: 'dns',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dns?url=${address}`).then(res => parseJson(res)),
});
// Get HTTP security
const [httpSecurityResults, updateHttpSecurityResults] = useMotherHook({
jobId: 'http-security',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/http-security?url=${address}`).then(res => parseJson(res)),
});
// Get social media previews, from a sites social meta tags
const [socialTagResults, updateSocialTagResults] = useMotherHook({
jobId: 'social-tags',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/social-tags?url=${address}`).then(res => parseJson(res)),
});
// Get trace route for a given hostname
const [traceRouteResults, updateTraceRouteResults] = useMotherHook({
jobId: 'trace-route',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/trace-route?url=${address}`).then(res => parseJson(res)),
});
// Get a websites listed pages, from sitemap
const [securityTxtResults, updateSecurityTxtResults] = useMotherHook({
jobId: 'security-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)),
});
// Get the DNS server(s) for a domain, and test DoH/DoT support
const [dnsServerResults, updateDnsServerResults] = useMotherHook({
jobId: 'dns-server',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dns-server?url=${address}`).then(res => parseJson(res)),
});
// Get the WAF and Firewall info for a site
const [firewallResults, updateFirewallResults] = useMotherHook({
jobId: 'firewall',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/firewall?url=${address}`).then(res => parseJson(res)),
});
// Get DNSSEC info
const [dnsSecResults, updateDnsSecResults] = useMotherHook({
jobId: 'dnssec',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dnssec?url=${address}`).then(res => parseJson(res)),
});
// Check if a site is on the HSTS preload list
const [hstsResults, updateHstsResults] = useMotherHook({
jobId: 'hsts',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/hsts?url=${address}`).then(res => parseJson(res)),
});
// Check if a host is present on the URLHaus malware list
const [threatResults, updateThreatResults] = useMotherHook({
jobId: 'threats',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/threats?url=${address}`).then(res => parseJson(res)),
});
// Get mail config for server, based on DNS records
const [mailConfigResults, updateMailConfigResults] = useMotherHook({
jobId: 'mail-config',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/mail-config?url=${address}`).then(res => parseJson(res)),
});
// Get list of archives from the Wayback Machine
const [archivesResults, updateArchivesResults] = useMotherHook({
jobId: 'archives',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/archives?url=${address}`).then(res => parseJson(res)),
});
// Get website's global ranking, from Tranco
const [rankResults, updateRankResults] = useMotherHook({
jobId: 'rank',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/rank?url=${address}`).then(res => parseJson(res)),
});
// Take a screenshot of the website
const [screenshotResult, updateScreenshotResult] = useMotherHook({
jobId: 'screenshot',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/screenshot?url=${address}`).then(res => parseJson(res)),
});
// Get TLS security info, from Mozilla Observatory
const [tlsResults, updateTlsResults] = useMotherHook({
jobId: ['tls-cipher-suites', 'tls-security-config', 'tls-client-support'],
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/tls?url=${address}`).then(res => parseJson(res)),
});
// Fetches URL redirects
const [redirectResults, updateRedirectResults] = useMotherHook({
jobId: 'redirects',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/redirects?url=${address}`).then(res => parseJson(res)),
});
// Get list of links included in the page content
const [linkedPagesResults, updateLinkedPagesResults] = useMotherHook({
jobId: 'linked-pages',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/linked-pages?url=${address}`).then(res => parseJson(res)),
});
// Fetch and parse crawl rules from robots.txt
const [robotsTxtResults, updateRobotsTxtResults] = useMotherHook<{robots: RowProps[]}>({
jobId: 'robots-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/robots-txt?url=${address}`)
.then(res => parseJson(res)),
});
// Get current status and response time of server
const [serverStatusResults, updateServerStatusResults] = useMotherHook({
jobId: 'status',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/status?url=${address}`).then(res => parseJson(res)),
});
// Check for open ports
const [portsResults, updatePortsResults] = useMotherHook({
jobId: 'ports',
updateLoadingJobs,
addressInfo: { address: ipAddress, addressType: 'ipV4', expectedAddressTypes: ['ipV4', 'ipV6'] },
fetchRequest: () => fetch(`${api}/check-ports?url=${ipAddress}`)
fetchRequest: () => fetch(`${api}/ports?url=${ipAddress}`)
.then(res => parseJson(res)),
});
@@ -266,55 +503,15 @@ const Results = (): JSX.Element => {
jobId: 'txt-records',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/get-txt?url=${address}`).then(res => parseJson(res)),
fetchRequest: () => fetch(`${api}/txt-records?url=${address}`).then(res => parseJson(res)),
});
// Fetches URL redirects
const [redirectResults, updateRedirectResults] = useMotherHook({
jobId: 'redirects',
// Check site against DNS blocklists
const [blockListsResults, updateBlockListsResults] = useMotherHook({
jobId: 'block-lists',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/follow-redirects?url=${address}`).then(res => parseJson(res)),
});
// Get current status and response time of server
const [serverStatusResults, updateServerStatusResults] = useMotherHook({
jobId: 'status',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/server-status?url=${address}`).then(res => parseJson(res)),
});
// Get current status and response time of server
const [techStackResults, updateTechStackResults] = useMotherHook({
jobId: 'tech-stack',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/tech-stack?url=${address}`).then(res => parseJson(res)),
});
// Get trace route for a given hostname
const [traceRouteResults, updateTraceRouteResults] = useMotherHook({
jobId: 'trace-route',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/trace-route?url=${address}`).then(res => parseJson(res)),
});
// Fetch carbon footprint data for a given site
const [carbonResults, updateCarbonResults] = useMotherHook({
jobId: 'carbon',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/get-carbon?url=${address}`).then(res => parseJson(res)),
});
// Check if a site is on the HSTS preload list
const [hstsResults, updateHstsResults] = useMotherHook({
jobId: 'hsts',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/check-hsts?url=${address}`).then(res => parseJson(res)),
fetchRequest: () => fetch(`${api}/block-lists?url=${address}`).then(res => parseJson(res)),
});
// Get a websites listed pages, from sitemap
@@ -325,20 +522,12 @@ const Results = (): JSX.Element => {
fetchRequest: () => fetch(`${api}/sitemap?url=${address}`).then(res => parseJson(res)),
});
// Get a websites listed pages, from sitemap
const [screenshotResult, updateScreenshotResult] = useMotherHook({
jobId: 'screenshot',
// Fetch carbon footprint data for a given site
const [carbonResults, updateCarbonResults] = useMotherHook({
jobId: 'carbon',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/screenshot?url=${address}`).then(res => parseJson(res)),
});
// Get a websites listed pages, from sitemap
const [securityTxtResults, updateSecurityTxtResults] = useMotherHook({
jobId: 'security-txt',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/security-txt?url=${address}`).then(res => parseJson(res)),
fetchRequest: () => fetch(`${api}/carbon?url=${address}`).then(res => parseJson(res)),
});
// Get site features from BuiltWith
@@ -346,7 +535,7 @@ const Results = (): JSX.Element => {
jobId: 'features',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/site-features?url=${address}`)
fetchRequest: () => fetch(`${api}/features?url=${address}`)
.then(res => parseJson(res))
.then(res => {
if (res.Errors && res.Errors.length > 0) {
@@ -356,38 +545,6 @@ const Results = (): JSX.Element => {
}),
});
// Get DNSSEC info
const [dnsSecResults, updateDnsSecResults] = useMotherHook({
jobId: 'dnssec',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dns-sec?url=${address}`).then(res => parseJson(res)),
});
// Run a manual whois lookup on the domain
const [domainLookupResults, updateDomainLookupResults] = useMotherHook({
jobId: 'domain',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/whois-lookup?url=${address}`).then(res => parseJson(res)),
});
// Get the DNS server(s) for a domain, and test DoH/DoT support
const [dnsServerResults, updateDnsServerResults] = useMotherHook({
jobId: 'dns-server',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/dns-server?url=${address}`).then(res => parseJson(res)),
});
// Get list of links included in the page content
const [linkedPagesResults, updateLinkedPagesResults] = useMotherHook({
jobId: 'linked-pages',
updateLoadingJobs,
addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly },
fetchRequest: () => fetch(`${api}/content-links?url=${address}`).then(res => parseJson(res)),
});
/* Cancel remaining jobs after 10 second timeout */
useEffect(() => {
const checkJobs = () => {
@@ -401,7 +558,7 @@ const Results = (): JSX.Element => {
return () => {
clearTimeout(timeoutId);
};
}, [loadingJobs, updateLoadingJobs]); // dependencies for the effect
}, [loadingJobs, updateLoadingJobs]);
const makeSiteName = (address: string): string => {
try {
@@ -413,36 +570,269 @@ const Results = (): JSX.Element => {
// A list of state sata, corresponding component and title for each card
const resultCardData = [
{ id: 'location', title: 'Server Location', result: locationResults, Component: ServerLocationCard, refresh: updateLocationResults },
{ id: 'ssl', title: 'SSL Info', result: sslResults, Component: SslCertCard, refresh: updateSslResults },
{ id: 'headers', title: 'Headers', result: headersResults, Component: HeadersCard, refresh: updateHeadersResults },
{ id: 'domain', title: 'Whois', result: domainLookupResults, Component: DomainLookup, refresh: updateDomainLookupResults },
{ id: 'dns', title: 'DNS Records', result: dnsResults, Component: DnsRecordsCard, refresh: updateDnsResults },
{ id: 'hosts', title: 'Host Names', result: shoadnResults?.hostnames, Component: HostNamesCard, refresh: updateShodanResults },
{ id: 'tech-stack', title: 'Tech Stack', result: techStackResults, Component: TechStackCard, refresh: updateTechStackResults },
{ id: 'quality', title: 'Quality Summary', result: lighthouseResults, Component: LighthouseCard, refresh: updateLighthouseResults },
{ id: 'cookies', title: 'Cookies', result: cookieResults, Component: CookiesCard, refresh: updateCookieResults },
{ id: 'trace-route', title: 'Trace Route', result: traceRouteResults, Component: TraceRouteCard, refresh: updateTraceRouteResults },
{ id: 'server-info', title: 'Server Info', result: shoadnResults?.serverInfo, Component: ServerInfoCard, refresh: updateShodanResults },
{ id: 'redirects', title: 'Redirects', result: redirectResults, Component: RedirectsCard, refresh: updateRedirectResults },
{ id: 'robots-txt', title: 'Crawl Rules', result: robotsTxtResults, Component: RobotsTxtCard, refresh: updateRobotsTxtResults },
{ id: 'dnssec', title: 'DNSSEC', result: dnsSecResults, Component: DnsSecCard, refresh: updateDnsSecResults },
{ id: 'status', title: 'Server Status', result: serverStatusResults, Component: ServerStatusCard, refresh: updateServerStatusResults },
{ id: 'ports', title: 'Open Ports', result: portsResults, Component: OpenPortsCard, refresh: updatePortsResults },
{ id: 'security-txt', title: 'Security.Txt', result: securityTxtResults, Component: SecurityTxtCard, refresh: updateSecurityTxtResults },
{ id: 'screenshot', title: 'Screenshot', result: screenshotResult || lighthouseResults?.fullPageScreenshot?.screenshot, Component: ScreenshotCard, refresh: updateScreenshotResult },
{ id: 'txt-records', title: 'TXT Records', result: txtRecordResults, Component: TxtRecordCard, refresh: updateTxtRecordResults },
{ id: 'hsts', title: 'HSTS Check', result: hstsResults, Component: HstsCard, refresh: updateHstsResults },
{ id: 'whois', title: 'Domain Info', result: whoIsResults, Component: WhoIsCard, refresh: updateWhoIsResults },
{ id: 'dns-server', title: 'DNS Server', result: dnsServerResults, Component: DnsServerCard, refresh: updateDnsServerResults },
{ id: 'linked-pages', title: 'Linked Pages', result: linkedPagesResults, Component: ContentLinksCard, refresh: updateLinkedPagesResults },
{ id: 'features', title: 'Site Features', result: siteFeaturesResults, Component: SiteFeaturesCard, refresh: updateSiteFeaturesResults },
{ id: 'sitemap', title: 'Pages', result: sitemapResults, Component: SitemapCard, refresh: updateSitemapResults },
{ id: 'carbon', title: 'Carbon Footprint', result: carbonResults, Component: CarbonFootprintCard, refresh: updateCarbonResults },
{
id: 'location',
title: 'Server Location',
result: locationResults,
Component: ServerLocationCard,
refresh: updateLocationResults,
tags: ['server'],
}, {
id: 'ssl',
title: 'SSL Certificate',
result: sslResults,
Component: SslCertCard,
refresh: updateSslResults,
tags: ['server', 'security'],
}, {
id: 'domain',
title: 'Domain Whois',
result: domainLookupResults,
Component: DomainLookup,
refresh: updateDomainLookupResults,
tags: ['server'],
}, {
id: 'quality',
title: 'Quality Summary',
result: lighthouseResults,
Component: LighthouseCard,
refresh: updateLighthouseResults,
tags: ['client'],
}, {
id: 'tech-stack',
title: 'Tech Stack',
result: techStackResults,
Component: TechStackCard,
refresh: updateTechStackResults,
tags: ['client', 'meta'],
}, {
id: 'server-info',
title: 'Server Info',
result: shoadnResults?.serverInfo,
Component: ServerInfoCard,
refresh: updateShodanResults,
tags: ['server'],
}, {
id: 'cookies',
title: 'Cookies',
result: cookieResults,
Component: CookiesCard,
refresh: updateCookieResults,
tags: ['client', 'security'],
}, {
id: 'headers',
title: 'Headers',
result: headersResults,
Component: HeadersCard,
refresh: updateHeadersResults,
tags: ['client', 'security'],
}, {
id: 'dns',
title: 'DNS Records',
result: dnsResults,
Component: DnsRecordsCard,
refresh: updateDnsResults,
tags: ['server'],
}, {
id: 'hosts',
title: 'Host Names',
result: shoadnResults?.hostnames,
Component: HostNamesCard,
refresh: updateShodanResults,
tags: ['server'],
}, {
id: 'http-security',
title: 'HTTP Security',
result: httpSecurityResults,
Component: HttpSecurityCard,
refresh: updateHttpSecurityResults,
tags: ['security'],
}, {
id: 'social-tags',
title: 'Social Tags',
result: socialTagResults,
Component: SocialTagsCard,
refresh: updateSocialTagResults,
tags: ['client', 'meta'],
}, {
id: 'trace-route',
title: 'Trace Route',
result: traceRouteResults,
Component: TraceRouteCard,
refresh: updateTraceRouteResults,
tags: ['server'],
}, {
id: 'security-txt',
title: 'Security.Txt',
result: securityTxtResults,
Component: SecurityTxtCard,
refresh: updateSecurityTxtResults,
tags: ['security'],
}, {
id: 'dns-server',
title: 'DNS Server',
result: dnsServerResults,
Component: DnsServerCard,
refresh: updateDnsServerResults,
tags: ['server'],
}, {
id: 'firewall',
title: 'Firewall',
result: firewallResults,
Component: FirewallCard,
refresh: updateFirewallResults,
tags: ['server', 'security'],
}, {
id: 'dnssec',
title: 'DNSSEC',
result: dnsSecResults,
Component: DnsSecCard,
refresh: updateDnsSecResults,
tags: ['security'],
}, {
id: 'hsts',
title: 'HSTS Check',
result: hstsResults,
Component: HstsCard,
refresh: updateHstsResults,
tags: ['security'],
}, {
id: 'threats',
title: 'Threats',
result: threatResults,
Component: ThreatsCard,
refresh: updateThreatResults,
tags: ['security'],
}, {
id: 'mail-config',
title: 'Email Configuration',
result: mailConfigResults,
Component: MailConfigCard,
refresh: updateMailConfigResults,
tags: ['server'],
}, {
id: 'archives',
title: 'Archive History',
result: archivesResults,
Component: ArchivesCard,
refresh: updateArchivesResults,
tags: ['meta'],
}, {
id: 'rank',
title: 'Global Ranking',
result: rankResults,
Component: RankCard,
refresh: updateRankResults,
tags: ['meta'],
}, {
id: 'screenshot',
title: 'Screenshot',
result: screenshotResult || lighthouseResults?.fullPageScreenshot?.screenshot,
Component: ScreenshotCard,
refresh: updateScreenshotResult,
tags: ['client', 'meta'],
}, {
id: 'tls-cipher-suites',
title: 'TLS Cipher Suites',
result: tlsResults,
Component: TlsCipherSuitesCard,
refresh: updateTlsResults,
tags: ['server', 'security'],
}, {
id: 'tls-security-config',
title: 'TLS Security Issues',
result: tlsResults,
Component: TlsIssueAnalysisCard,
refresh: updateTlsResults,
tags: ['security'],
}, {
id: 'tls-client-support',
title: 'TLS Handshake Simulation',
result: tlsResults,
Component: TlsClientSupportCard,
refresh: updateTlsResults,
tags: ['security'],
}, {
id: 'redirects',
title: 'Redirects',
result: redirectResults,
Component: RedirectsCard,
refresh: updateRedirectResults,
tags: ['meta'],
}, {
id: 'linked-pages',
title: 'Linked Pages',
result: linkedPagesResults,
Component: ContentLinksCard,
refresh: updateLinkedPagesResults,
tags: ['client', 'meta'],
}, {
id: 'robots-txt',
title: 'Crawl Rules',
result: robotsTxtResults,
Component: RobotsTxtCard,
refresh: updateRobotsTxtResults,
tags: ['meta'],
}, {
id: 'status',
title: 'Server Status',
result: serverStatusResults,
Component: ServerStatusCard,
refresh: updateServerStatusResults,
tags: ['server'],
}, {
id: 'ports',
title: 'Open Ports',
result: portsResults,
Component: OpenPortsCard,
refresh: updatePortsResults,
tags: ['server'],
}, {
id: 'whois',
title: 'Domain Info',
result: whoIsResults,
Component: WhoIsCard,
refresh: updateWhoIsResults,
tags: ['server'],
}, {
id: 'txt-records',
title: 'TXT Records',
result: txtRecordResults,
Component: TxtRecordCard,
refresh: updateTxtRecordResults,
tags: ['server'],
}, {
id: 'block-lists',
title: 'Block Lists',
result: blockListsResults,
Component: BlockListsCard,
refresh: updateBlockListsResults,
tags: ['security', 'meta'],
}, {
id: 'features',
title: 'Site Features',
result: siteFeaturesResults,
Component: SiteFeaturesCard,
refresh: updateSiteFeaturesResults,
tags: ['meta'],
}, {
id: 'sitemap',
title: 'Pages',
result: sitemapResults,
Component: SitemapCard,
refresh: updateSitemapResults,
tags: ['meta'],
}, {
id: 'carbon',
title: 'Carbon Footprint',
result: carbonResults,
Component: CarbonFootprintCard,
refresh: updateCarbonResults,
tags: ['meta'],
},
];
const MakeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => {
const makeActionButtons = (title: string, refresh: () => void, showInfo: (id: string) => void): ReactNode => {
const actions = [
{ label: `Info about ${title}`, onClick: showInfo, icon: 'ⓘ'},
{ label: `Re-fetch ${title} data`, onClick: refresh, icon: '↻'},
@@ -467,7 +857,7 @@ const Results = (): JSX.Element => {
<Nav>
{ address &&
<Heading color={colors.textColor} size="medium">
{ addressType === 'url' && <img width="32px" src={`https://icon.horse/icon/${makeSiteName(address)}`} alt="" /> }
{ addressType === 'url' && <a href={address}><img width="32px" src={`https://icon.horse/icon/${makeSiteName(address)}`} alt="" /></a> }
{makeSiteName(address)}
</Heading>
}
@@ -475,25 +865,60 @@ const Results = (): JSX.Element => {
<ProgressBar loadStatus={loadingJobs} showModal={showErrorModal} showJobDocs={showInfo} />
{ address?.includes(window?.location?.hostname || 'web-check.as93.net') && <SelfScanMsg />}
<Loader show={loadingJobs.filter((job: LoadingJob) => job.state !== 'loading').length < 5} />
<FilterButtons>{ showFilters ? <>
<div className="one-half">
<span className="group-label">Filter by</span>
{['server', 'client', 'meta'].map((tag: string) => (
<button
key={tag}
className={tags.includes(tag) ? 'selected' : ''}
onClick={() => updateTags(tag)}>
{tag}
</button>
))}
{(tags.length > 0 || searchTerm.length > 0) && <span onClick={clearFilters} className="clear">Clear Filters</span> }
</div>
<div className="one-half">
<span className="group-label">Search</span>
<input
type="text"
placeholder="Filter Results"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<span className="toggle-filters" onClick={() => setShowFilters(false)}>Hide</span>
</div>
</> : (
<div className="control-options">
<span className="toggle-filters" onClick={() => setShowFilters(true)}>Show Filters</span>
<a href="#view-download-raw-data"><span className="toggle-filters">Export Data</span></a>
<a href="/about"><span className="toggle-filters">Learn about the Results</span></a>
<a href="/about#additional-resources"><span className="toggle-filters">More tools</span></a>
<a href="https://github.com/lissy93/web-check"><span className="toggle-filters">View GitHub</span></a>
</div>
) }
</FilterButtons>
<ResultsContent>
<Masonry
<Masonry
breakpointCols={{ 10000: 12, 4000: 9, 3600: 8, 3200: 7, 2800: 6, 2400: 5, 2000: 4, 1600: 3, 1200: 2, 800: 1 }}
className="masonry-grid"
columnClassName="masonry-grid-col">
{
resultCardData.map(({ id, title, result, refresh, Component }, index: number) => (
(result && !result.error) ? (
resultCardData
.map(({ id, title, result, tags, refresh, Component }, index: number) => {
const show = (tags.length === 0 || tags.some(tag => tags.includes(tag)))
&& title.toLowerCase().includes(searchTerm.toLowerCase())
&& (result && !result.error);
return show ? (
<ErrorBoundary title={title}>
<Component
key={`${title}-${index}`}
data={{...result}}
title={title}
actionButtons={refresh ? MakeActionButtons(title, refresh, () => showInfo(id)) : undefined}
actionButtons={refresh ? makeActionButtons(title, refresh, () => showInfo(id)) : undefined}
/>
</ErrorBoundary>
) : <></>
))
) : null})
}
</Masonry>
</ResultsContent>