From 920ab64410787a1224ede722ab1988a9d5e53ddb Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 18 Jun 2023 16:02:57 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20Came=20back=20to=20this=20projec?= =?UTF-8?q?t=20after=20a=20year....=20implemented=20lots=20of=20things?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Form/Card.tsx | 1 - src/components/Results/BuiltWith.tsx | 69 ++++++++++++++++++++++ src/components/Results/HostNames.tsx | 3 +- src/components/Results/ServerInfo.tsx | 7 +-- src/components/Results/ServerLocation.tsx | 2 +- src/components/Results/WhoIs.tsx | 71 +++++++++++++++++++++++ src/pages/Results.tsx | 40 +++++++++++-- src/utils/get-keys.ts | 5 +- src/utils/result-processor.ts | 53 +++++++++++++---- 9 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 src/components/Results/BuiltWith.tsx create mode 100644 src/components/Results/WhoIs.tsx diff --git a/src/components/Form/Card.tsx b/src/components/Form/Card.tsx index 60a0cdd..c7f66c7 100644 --- a/src/components/Form/Card.tsx +++ b/src/components/Form/Card.tsx @@ -8,7 +8,6 @@ export const Card = styled.section` box-shadow: 4px 4px 0px ${colors.bgShadowColor}; border-radius: 8px; padding: 1rem; - margin: 1rem; `; // interface CardProps { diff --git a/src/components/Results/BuiltWith.tsx b/src/components/Results/BuiltWith.tsx new file mode 100644 index 0000000..781c1d5 --- /dev/null +++ b/src/components/Results/BuiltWith.tsx @@ -0,0 +1,69 @@ + +import styled from 'styled-components'; +import { TechnologyGroup, Technology } from 'utils/result-processor'; +import colors from 'styles/colors'; +import Card from 'components/Form/Card'; +import Heading from 'components/Form/Heading'; + +const Outer = styled(Card)` + grid-row: span 2 +`; + +const Row = styled.div` + display: flex; + justify-content: space-between; + padding: 0.25rem; + &:not(:last-child) { border-bottom: 1px solid ${colors.primary}; } + span.lbl { font-weight: bold; } + span.val { + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const DataRow = (props: { lbl: string, val: string }) => { + const { lbl, val } = props; + return ( + + {lbl} + {val} + + ); +}; + +const ListRow = (props: { list: Technology[], title: string }) => { + const { list, title } = props; + return ( + <> + {title} + { list.map((entry: Technology, index: number) => { + return ( + { entry.Name } + )} + )} + +); +} + +const BuiltWithCard = (props: { technologies: TechnologyGroup[] }): JSX.Element => { + // const { created, updated, expires, nameservers } = whois; + const { technologies } = props; + return ( + + Technologies + { technologies.map((group: TechnologyGroup) => { + return ( + + ); + })} + {/* { created && } + { updated && } + { expires && } + { nameservers && } */} + + ); +} + +export default BuiltWithCard; diff --git a/src/components/Results/HostNames.tsx b/src/components/Results/HostNames.tsx index 60db257..2720c00 100644 --- a/src/components/Results/HostNames.tsx +++ b/src/components/Results/HostNames.tsx @@ -6,7 +6,8 @@ import Card from 'components/Form/Card'; import Heading from 'components/Form/Heading'; const Outer = styled(Card)` - max-width: 24rem; + max-height: 20rem; + overflow: auto; `; const Row = styled.div` diff --git a/src/components/Results/ServerInfo.tsx b/src/components/Results/ServerInfo.tsx index 376d7b2..ddac52e 100644 --- a/src/components/Results/ServerInfo.tsx +++ b/src/components/Results/ServerInfo.tsx @@ -5,9 +5,7 @@ import colors from 'styles/colors'; import Card from 'components/Form/Card'; import Heading from 'components/Form/Heading'; -const Outer = styled(Card)` - max-width: 24rem; -`; +const Outer = styled(Card)``; const Row = styled.div` display: flex; @@ -34,7 +32,7 @@ const DataRow = (props: { lbl: string, val: string }) => { }; const ServerInfoCard = (info: ServerInfo): JSX.Element => { - const { org, asn, isp, os } = info; + const { org, asn, isp, os, ports } = info; return ( Server Info @@ -42,6 +40,7 @@ const ServerInfoCard = (info: ServerInfo): JSX.Element => { { (isp && isp !== org) && } { os && } { asn && } + { ports && } ); } diff --git a/src/components/Results/ServerLocation.tsx b/src/components/Results/ServerLocation.tsx index cbbdb50..50bebf5 100644 --- a/src/components/Results/ServerLocation.tsx +++ b/src/components/Results/ServerLocation.tsx @@ -9,7 +9,7 @@ import Flag from 'components/misc/Flag'; import { TextSizes } from 'styles/typography'; const Outer = styled(Card)` - max-width: 24rem; + grid-row: span 2 `; const Row = styled.div` diff --git a/src/components/Results/WhoIs.tsx b/src/components/Results/WhoIs.tsx new file mode 100644 index 0000000..df41820 --- /dev/null +++ b/src/components/Results/WhoIs.tsx @@ -0,0 +1,71 @@ + +import styled from 'styled-components'; +import { Whois } from 'utils/result-processor'; +import colors from 'styles/colors'; +import Card from 'components/Form/Card'; +import Heading from 'components/Form/Heading'; + +const Outer = styled(Card)``; + +const Row = styled.div` + display: flex; + justify-content: space-between; + padding: 0.25rem; + &:not(:last-child) { border-bottom: 1px solid ${colors.primary}; } + span.lbl { font-weight: bold; } + span.val { + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const formatDate = (dateString: string): string => { + const date = new Date(dateString); + const formatter = new Intl.DateTimeFormat('en-GB', { + day: 'numeric', + month: 'long', + year: 'numeric' + }); + return formatter.format(date); +} + +const DataRow = (props: { lbl: string, val: string }) => { + const { lbl, val } = props; + return ( + + {lbl} + {val} + + ); +}; + +const ListRow = (props: { list: string[], title: string }) => { + const { list, title } = props; + return ( + <> + {title} + { list.map((entry: string, index: number) => { + return ( + { entry } + )} + )} + +); +} + +const ServerInfoCard = (whois: Whois): JSX.Element => { + const { created, updated, expires, nameservers } = whois; + return ( + + Who Is Info + { created && } + { updated && } + { expires && } + { nameservers && } + + ); +} + +export default ServerInfoCard; diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx index b319a92..b06e12c 100644 --- a/src/pages/Results.tsx +++ b/src/pages/Results.tsx @@ -8,6 +8,8 @@ import Card from 'components/Form/Card'; import ServerLocationCard from 'components/Results/ServerLocation'; import ServerInfoCard from 'components/Results/ServerInfo'; import HostNamesCard from 'components/Results/HostNames'; +import WhoIsCard from 'components/Results/WhoIs'; +import BuiltWithCard from 'components/Results/BuiltWith'; import keys from 'utils/get-keys'; import { determineAddressType, AddressType } from 'utils/address-type-checker'; @@ -15,6 +17,8 @@ import { getLocation, ServerLocation, getServerInfo, ServerInfo, getHostNames, HostNames, + makeTechnologies, TechnologyGroup, + Whois, } from 'utils/result-processor'; const ResultsOuter = styled.div` @@ -24,13 +28,19 @@ const ResultsOuter = styled.div` const ResultsContent = styled.section` width: 95vw; - display: flex; - flex-wrap: wrap; + + display: grid; + grid-auto-flow: dense; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 1rem; + margin: auto; + width: calc(100% - 2rem); `; const Header = styled(Card)` margin: 1rem; display: flex; + flex-wrap: wrap; align-items: baseline; justify-content: space-between; padding: 0.5rem 1rem; @@ -45,6 +55,8 @@ interface ResultsType { const Results = (): JSX.Element => { const [ results, setResults ] = useState({}); const [ locationResults, setLocationResults ] = useState(); + const [ whoIsResults, setWhoIsResults ] = useState(); + const [ technologyResults, setTechnologyResults ] = useState(); const [ ipAddress, setIpAddress ] = useState(undefined); const [ addressType, setAddressType ] = useState('empt'); const { address } = useParams(); @@ -62,7 +74,6 @@ const Results = (): JSX.Element => { const fetchIpAddress = () => { fetch(`/find-url-ip?address=${address}`) .then(function(response) { - console.log(response); response.json().then(jsonData => { console.log('Get IP Address', jsonData); setIpAddress(jsonData.ip); @@ -107,6 +118,7 @@ const Results = (): JSX.Element => { fetch(`https://api.shodan.io/shodan/host/${ipAddress}?key=${apiKey}`) .then(response => response.json()) .then(response => { + console.log(response); if (!response.error) applyShodanResults(response) }) .catch(err => console.error(err)); @@ -117,11 +129,29 @@ const Results = (): JSX.Element => { fetchShodanData(); } }, [ipAddress]); + + /* Get BuiltWith tech stack */ + useEffect(() => { + const apiKey = keys.builtWith; + const endpoint = `https://api.builtwith.com/v21/api.json?KEY=${apiKey}&LOOKUP=${address}`; + fetch(endpoint) + .then(response => response.json()) + .then(response => { + console.log(response); + setTechnologyResults(makeTechnologies(response)); + }); + }, [address]); /* Get WhoIs info for a given domain name */ useEffect(() => { const applyWhoIsResults = (response: any) => { - console.log('WhoIs Response', response); + const whoIsResults: Whois = { + created: response.date_created, + expires: response.date_expires, + updated: response.date_updated, + nameservers: response.nameservers, + }; + setWhoIsResults(whoIsResults); } const fetchWhoIsData = () => { const apiKey = keys.whoApi; @@ -148,6 +178,8 @@ const Results = (): JSX.Element => { { locationResults && } { results.serverInfo && } { results.hostNames && } + { whoIsResults && } + { technologyResults && } ); diff --git a/src/utils/get-keys.ts b/src/utils/get-keys.ts index 301f29e..4ef07ec 100644 --- a/src/utils/get-keys.ts +++ b/src/utils/get-keys.ts @@ -1,7 +1,8 @@ const keys = { - shodan: process.env.SHODAN_API_KEY, - whoApi: process.env.WHO_API_KEY, + shodan: process.env.REACT_APP_SHODAN_API_KEY, + whoApi: process.env.REACT_APP_WHO_API_KEY, + builtWith: process.env.REACT_APP_BUILT_WITH_API_KEY, }; export default keys; diff --git a/src/utils/result-processor.ts b/src/utils/result-processor.ts index 0f390ee..f5b84ce 100644 --- a/src/utils/result-processor.ts +++ b/src/utils/result-processor.ts @@ -19,6 +19,13 @@ export interface ServerLocation { countryPopulation: number, }; +export interface Whois { + created: string, + expires: string, + updated: string, + nameservers: string[], +} + export const getLocation = (response: any): ServerLocation => { return { city: response.city, @@ -48,6 +55,8 @@ export interface ServerInfo { asn: string, isp: string, os?: string, + ip?: string, + ports?: string, }; export const getServerInfo = (response: any): ServerInfo => { @@ -56,6 +65,8 @@ export const getServerInfo = (response: any): ServerInfo => { asn: response.asn, isp: response.isp, os: response.os, + ip: response.ip_str, + ports: response.ports.toString(), }; }; @@ -67,17 +78,37 @@ export interface HostNames { export const getHostNames = (response: any): HostNames => { const { hostnames, domains } = response; const results: HostNames = { - domains: [], - hostnames: [], + domains: domains || [], + hostnames: hostnames || [], }; - if (!hostnames || !domains) return results; - hostnames.forEach((host: string) => { - if (domains.includes(host)) { - results.domains.push(host); - } else { - results.hostnames.push(host); - } - }); - return results; }; + +export interface Technology { + Categories?: string[]; + Parent?: string; + Name: string; + Description: string; + Link: string; + Tag: string; + FirstDetected: number; + LastDetected: number; + IsPremium: string; +} + +export interface TechnologyGroup { + tag: string; + technologies: Technology[]; +} + +export const makeTechnologies = (response: any): TechnologyGroup[] => { + let flatArray = response.Results[0].Result.Paths + .reduce((accumulator: any, obj: any) => accumulator.concat(obj.Technologies), []); + let technologies = flatArray.reduce((groups: any, item: any) => { + let tag = item.Tag; + if (!groups[tag]) groups[tag] = []; + groups[tag].push(item); + return groups; + }, {}); + return technologies; +};