HerrTuring 2 weeks ago committed by GitHub
commit ea5fe76245
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,26 @@
name: Publish on Docker Hub
on: workflow_dispatch
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login to Docker Hub
run: docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_KEY }}
- name: Build base Sherlock image
run: docker build -t sherlock .
- name: Build and push Sherlock API image
run: cd sherlock-web/api &&
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-api:latest . &&
docker push ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-api:latest
- name: Build and push sherlock-frontend image
run: |
docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-web:latest -f ./sherlock-web/frontend/Dockerfile . &&
docker push ${{ secrets.DOCKERHUB_USERNAME }}/sherlock-web:latest

3
.gitignore vendored

@ -36,3 +36,6 @@ tests/.excluded_sites
# Vim swap files
*.swp
# JS Files
node_modules

@ -13,6 +13,8 @@
   |   
<a href="#docker-notes">Docker Notes</a>
&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;
<a href="#web-ui">Web UI</a>
&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;
<a href="#contributing">Contributing</a>
</p>
@ -133,6 +135,25 @@ You can use the `docker-compose.yml` file from the repository and use this comma
docker-compose run sherlock -o /opt/sherlock/results/text.txt user123
```
### Web UI
Before starting the Web UI you need to have in your machine the base image of sherlock and you can do this by running this code in the root of this project:
(In newer versions of docker you need to use "docker compose" instead.)
```
docker-compose build
```
You can then build and start the web UI by entering "sherlock-web" folder and running the command:
```
docker-compose up
```
The command will create an API that is only accessible by the frontend's server and a web UI that can be acessed by your preffered web browser in port 3000.
Keep in mind this web version has no user credentials or any kind of security check, thus it is not nearly production ready.
## Contributing
We would love to have you help us with the development of Sherlock. Each and every contribution is greatly valued!

@ -3,5 +3,6 @@ version: '2'
services:
sherlock:
build: .
image: sherlock
volumes:
- "./results:/opt/sherlock/results"

@ -0,0 +1,9 @@
FROM sherlock
RUN pip3 install fastapi[all]
COPY main.py /app/
WORKDIR /app/
ENTRYPOINT ["uvicorn", "main:app", "--host=0.0.0.0", "--port=8000", "--reload"]

@ -0,0 +1,58 @@
import subprocess
import sys
from typing import Annotated
from pydantic import BaseModel
from fastapi import FastAPI, Query
from fastapi.responses import StreamingResponse
app = FastAPI()
class Body(BaseModel):
usernames: list[str]
sites: list[str]
f: list[str]
@app.post("/")
async def root(body: Body):
command = ["python3", "/opt/sherlock/sherlock/sherlock.py"]
usernames = body.usernames
sites = body.sites
f = body.f
if usernames:
for name in usernames:
command.append(name)
if sites:
for site in sites:
command.append("--site")
command.append(site)
if f:
for flag in f:
command.append("--"+flag)
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=sys.stderr)
response = []
for line in iter(process.stdout.readline, b''):
line = line.decode("utf-8").rstrip("\n").rstrip("\r")[4:]
if line == "":
continue
if line.startswith("Checking username"):
continue
if line.startswith("Search complete"):
continue
data = line.split(": ")
response.append(dict(name= data[0], url= data[1]))
return response

@ -0,0 +1,18 @@
services:
api:
build: ./api
volumes:
- ./api/main.py:/app/main.py
frontend:
build:
context: ../
dockerfile: ./sherlock-web/frontend/Dockerfile
command: npm run dev
volumes:
- ./frontend/app:/app/app/
- ./frontend/public:/app/public
ports:
- 3000:3000
depends_on:
- api

@ -0,0 +1,18 @@
FROM node:20
WORKDIR /app/
COPY ./sherlock-web/frontend/package.json ./sherlock-web/frontend/package-lock.json ./
RUN npm i
COPY ./sherlock-web/frontend/tsconfig.json ./tsconfig.json
COPY ./sherlock-web/frontend/next-env.d.ts ./next-env.d.ts
COPY ./sherlock-web/frontend/next.config.mjs ./src/next.config.mjs
COPY ./sherlock-web/frontend/app/ ./app/
COPY ./sherlock-web/frontend/public/ ./public/
COPY ./sherlock/resources/data.json ./data.json
RUN npm run build
CMD npm run start

@ -0,0 +1,22 @@
export async function POST(req: Request) {
const data = await req.json();
const { username, sites, withNSFW } = data;
const f = [];
if (withNSFW)
f.push("nsfw");
return await fetch("http://api:8000", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
usernames: [username],
sites,
f
})
});
}

@ -0,0 +1,126 @@
"use client"
import { Grid, Input, Divider, Stack, Checkbox, Button, Link, Typography } from "@mui/joy";
import SiteCheckbox from "./siteCheckbox";
import * as data from "../../data.json";
import { ChangeEvent, FormEvent, useState } from "react";
import { pageList } from "../page";
let sites: pageList = [];
let sitesWithNSFW: pageList = [];
const checkedSitesDefault: Record<string, boolean> = {};
const dataEntries = Object.entries(data).sort(([a], [b]) => a.toUpperCase() > b.toUpperCase() ? 1 : -1);
for (const [name, values] of dataEntries) {
if (name === "default")
continue;
const {url, isNSFW} = values as any;
const parsedData = {name, url};
sitesWithNSFW.push(parsedData);
checkedSitesDefault[name] = false;
if (!isNSFW)
sites.push(parsedData)
}
type FormProps = {
setFoundData: (data: pageList) => void
};
export default function Form({setFoundData}: FormProps) {
const [withNSFW, setWithNSFW] = useState(false);
const [loading, setLoading] = useState(false);
const [checkedSites, setCheckedSites] = useState(checkedSitesDefault);
const clickNSFW = (e: ChangeEvent<HTMLInputElement>) => setWithNSFW(e.target.checked);
const clickCheckAll = (e: ChangeEvent<HTMLInputElement>) => {
const {checked} = e.target;
let newCheckedSites = {...checkedSites};
for (let key in checkedSites)
newCheckedSites[key] = checked;
setCheckedSites(newCheckedSites);
};
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
const response = await fetch("/api/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: e.currentTarget.username.value,
sites: Object.entries(checkedSites).filter(([_, value]) => value).map(([key, _]) => key),
withNSFW
})
});
let parsedData = await response.json();
parsedData = parsedData.sort((a: any, b: any) => a.name.toUpperCase() > b.name.toUpperCase() ? 1 : -1);
parsedData = parsedData.filter((item : any) => (item.url !== "Desired sites not found"));
setFoundData(parsedData);
setLoading(false);
};
const currentSite = withNSFW ? sitesWithNSFW : sites;
const required = !Object.values(checkedSites).some(value => value);
return (
<form style={{ height: "95%"}} onSubmit={onSubmit}>
<Stack direction="column" spacing={2} sx={{height: "100%"}}>
<Input name="username" required />
<Divider>
<Stack direction="row" spacing={3}>
<Checkbox label="With NSFW" onChange={clickNSFW} />
<Checkbox label="Check All" onChange={clickCheckAll} />
</Stack>
</Divider>
<Grid container spacing={2} columns={{ xs: 4, sm: 6, md: 8, lg: 10, xl: 12 }} sx={{maxHeight: "100%", overflow: "auto"}} flexGrow={1}>
{currentSite.map(
({name, url}) => {
const change = (value: boolean) => setCheckedSites({
...checkedSites,
[name]: value
});
return (
<SiteCheckbox
name={name}
url={url}
key={"site-"+name}
checked={checkedSites[name]}
onChange={change}
required={required}
/>
);
}
)}
</Grid>
<Button type="submit" loading={loading}>Send</Button>
</Stack>
</form>
);
}

@ -0,0 +1,29 @@
"use client"
import { Checkbox, Grid, Tooltip } from "@mui/joy"
import { ChangeEvent } from "react";
type props = {
name: string,
url: string,
checked: boolean | undefined,
onChange: (value: boolean) => void,
required: boolean,
}
export default function SiteCheckBox({name, url, checked, onChange, required}: props) {
const change = (e: ChangeEvent<HTMLInputElement>) => onChange(e.target.checked);
return (
<Grid xs={2} sm={2} md={2} lg={2} >
<Tooltip title={url}>
<Checkbox
name={"site["+name+"]"}
label={name}
checked={checked}
onChange={change}
required={required}
/>
</Tooltip>
</Grid>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@ -0,0 +1,16 @@
html,
body,
#__next {
background: #000000;
}
#__next {
width: 100%;
height: 100%;
}
body {
width: calc(100vw - 5vh);
height: 95vh;
margin: 2.5vh;
}

@ -0,0 +1,4 @@
declare module '*.json' {
const value: any;
export default value;
}

@ -0,0 +1,22 @@
export const metadata = {
title: 'Sherlock',
description: 'Find your username in many websites.',
}
import "../node_modules/reset-css/reset.css";
import "./global.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>Sherlock</title>
</head>
<body>{children}</body>
</html>
)
}

@ -0,0 +1,54 @@
"use client"
import { CssVarsProvider } from '@mui/joy/styles';
import '../node_modules/reset-css/reset.css';
import { Box, Button, Grid, Link, Sheet, Stack, Typography } from '@mui/joy';
import Form from './components/form';
import { useState } from 'react';
const mainSheetStyle = {
width: "100vw",
height: "100vh",
padding: "10px",
boxSizing: "border-box",
overflow: "hidden"
};
export type pageList = {
name: string,
url: string
}[];
export default function Home() {
const [foundData, setFoundData] = useState<pageList | null>(null);
return (
<CssVarsProvider defaultMode="dark" modeStorageKey="theme">
<Sheet sx={mainSheetStyle}>
<Typography level="h1" textAlign="center">
Sherlock
</Typography>
{foundData ?
<Stack direction="column" spacing={2} sx={{height: "95%"}}>
<Typography level="h4" textAlign="center">Found sites:</Typography>
<Grid container spacing={2} columns={{ xs: 4, sm: 6, md: 8, lg: 10, xl: 12 }} sx={{height: "95%", overflow: "auto"}}>
{foundData.map(({name, url}) => {
return (
<Grid xs={2} key={"found-"+name}>
<Link href={url} target="_blank" rel="noreferrer" textAlign="center" sx={{width: "100%", display: "block"}}>
{name}
</Link>
</Grid>
)
})}
</Grid>
<Button color="neutral" onClick={() => setFoundData(null)}>Back</Button>
</Stack>
: <Form setFoundData={setFoundData} /> }
</Sheet>
</CssVarsProvider>
);
}

@ -0,0 +1 @@
../../sherlock/resources/data.json

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,27 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@fontsource/inter": "^5.0.16",
"@mui/joy": "^5.0.0-beta.21",
"react": "^18",
"react-dom": "^18",
"next": "14.1.2",
"reset-css": "^5.0.2"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18"
}
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

@ -0,0 +1,39 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Loading…
Cancel
Save