This commit is contained in:
Hadi
2025-09-24 17:20:03 +02:00
commit b9fbed9a54
83 changed files with 6241 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
<script lang="ts">
import { cn } from "$lib/utils";
import { onMount, tick } from "svelte";
let {
containerRef = $bindable(),
class:className = "",
fromRef = $bindable(),
toRef = $bindable(),
curvature = 0,
reverse = false, // Include the reverse prop
pathColor = "gray",
pathWidth = 2,
pathOpacity = 0.2,
startXOffset = 0,
startYOffset = 0,
endXOffset = 0,
endYOffset = 0,
}= $props();
let id = crypto.randomUUID().slice(0, 8);
let pathD = $state("");
let svgDimensions = { width: 0, height: 0 };
let updatePath = () => {
if (!containerRef || !fromRef || !toRef) {
return;
}
let containerRect = containerRef?.getBoundingClientRect();
let rectA = fromRef?.getBoundingClientRect();
let rectB = toRef?.getBoundingClientRect();
let svgWidth = containerRect.width;
let svgHeight = containerRect.height;
svgDimensions.width = svgWidth;
svgDimensions.height = svgHeight;
let startX =
rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
let startY =
rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
let endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
let endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
let controlY = startY - curvature;
let d = `M ${startX},${startY} Q ${
(startX + endX) / 2
},${controlY} ${endX},${endY}`;
pathD = d;
};
onMount(async () => {
await tick().then(() => {
updatePath();
const resizeObserver = new ResizeObserver((entries) => {
// For all entries, recalculate the path
for (let entry of entries) {
updatePath();
}
});
// Observe the container element
if (containerRef) {
resizeObserver.observe(containerRef);
}
});
});
</script>
<svg
fill="none"
width={svgDimensions.width}
height={svgDimensions.height}
xmlns="http://www.w3.org/2000/svg"
class={cn(
"pointer-events-none absolute left-0 top-0 transform-gpu stroke-2 animate-pulse",
className,
)}
viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
>
<path
d={pathD}
stroke={pathColor}
stroke-width={pathWidth}
stroke-opacity={pathOpacity}
stroke-linecap="round"
/>
<path
d={pathD}
stroke-width={pathWidth}
stroke={`url(#${id})`}
stroke-opacity="1"
stroke-linecap="round"
/>
<defs>
<linearGradient {id} gradientUnits="userSpaceOnUse" class="transform-gpu">
<stop class="[stop-color:var(--color-primary)]" stop-opacity="0"></stop>
<stop class="[stop-color:var(--color-primary)]"></stop>
<stop offset="32.5%" class="[stop-color:var(--color-primary)]"></stop>
<stop offset="100%" class="[stop-color:var(--color-primary)]" stop-opacity="0"
></stop>
</linearGradient>
</defs>
</svg>

View File

@@ -0,0 +1,125 @@
<script lang="ts">
import { cn } from "$lib/utils";
import { Github, IdCard, Key, User } from "@lucide/svelte";
import AnimatedBeam from "./AnimatedBeam.svelte";
import Circle from "./Circle.svelte";
let containerRef = $state();
let div1Ref = $state();
let div2Ref = $state();
let div3Ref = $state();
let div4Ref = $state();
let div5Ref = $state();
let div6Ref = $state();
let div7Ref = $state();
let className: any = $state("");
export { className as class };
</script>
<div
bind:this={containerRef}
class={cn("relative flex w-full items-center justify-center ", className)}
>
<div
class="flex h-full w-full flex-row justify-between gap-10 max-w-lg items-center"
>
<div class="flex flex-col justify-center gap-2">
<!-- Div 1 -->
<Circle bind:ref={div1Ref}>
<div class="tooltip" data-tip="Leak of user's informations">
<User />
</div>
</Circle>
<!-- Div 2 -->
<Circle bind:ref={div2Ref}>
<div class="tooltip" data-tip="Leak of user's passwords">
<Key />
</div>
</Circle>
<!-- Div 3 -->
<Circle bind:ref={div3Ref}>
<div class="tooltip" data-tip="Leak of personal informations">
<IdCard />
</div>
</Circle>
<!-- Div 4 -->
<Circle bind:ref={div4Ref}>
<div class="tooltip" data-tip="Github recon">
<Github />
</div>
</Circle>
<!-- Div 5 -->
<Circle bind:ref={div5Ref}>
<div class="tooltip" data-tip="Google hunt">
<svg
width="16"
viewBox="0 0 256 262"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
><path
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
fill="#fff"
/><path
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
fill="#fff"
/><path
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"
fill="#fff"
/><path
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
fill="#fff"
/></svg
>
</div>
</Circle>
</div>
<div class="flex flex-col justify-center">
<!-- Div 6 -->
<Circle bind:ref={div6Ref}>
<div class="tooltip" data-tip="Your eleakxir backend">
<svg
width={24}
viewBox="0 0 141 205"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class={cn("fill-primary", className)}
>
<path
d="M69.7444 0C84.49 25.1637 100.559 49.4708 117.95 72.9219C126.451 85.0149 133.113 98.1035 137.934 112.188C144.168 135.735 140.195 157.355 126.014 177.046C109.938 196.591 89.1945 205.53 63.7844 203.865C36.717 200.867 17.4935 187.019 6.11353 162.321C-1.9191 142.522 -2.03583 122.655 5.76294 102.722C9.71019 93.7604 14.3849 85.2295 19.7864 77.1289C35.0593 56.0531 49.5504 34.4338 63.259 12.2705C65.6086 8.27249 67.7699 4.18207 69.7444 0ZM100.596 81.3359C102.957 92.649 102.198 103.751 98.3176 114.642C93.9276 124.99 87.7338 134.105 79.7366 141.987C77.6951 144.434 75.8254 147.005 74.1272 149.7C70.5033 155.43 68.5745 161.682 68.342 168.456C68.1692 175.079 70.6236 180.455 75.7043 184.583C89.1062 183.345 100.267 177.678 109.186 167.58C123.101 149.518 125.672 129.885 116.899 108.682C112.25 99.0223 106.815 89.9071 100.596 81.3359ZM70.095 36.4609C59.5617 53.4793 48.4018 70.0738 36.6145 86.2441C30.2619 94.8335 25.2366 104.183 21.5393 114.291C15.2642 135.159 19.2377 153.74 33.4592 170.034C39.7381 176.533 47.2756 180.798 56.0715 182.83C55.7506 182.699 55.4583 182.524 55.1956 182.305C40.6021 163.32 39.6671 143.687 52.3909 123.406C58.721 114.622 64.9149 105.74 70.9719 96.7617C72.559 94.0549 73.9614 91.2501 75.179 88.3477C81.8825 70.1929 80.1876 52.8971 70.095 36.4609Z"
/>
</svg>
</div>
</Circle>
</div>
<div class="flex flec-col justify-center">
<!-- Div 7 -->
<Circle bind:ref={div7Ref}>
<div class="tooltip" data-tip="This web client">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-monitor-icon lucide-monitor"
><rect width="20" height="14" x="2" y="3" rx="2" /><line
x1="8"
x2="16"
y1="21"
y2="21"
/><line x1="12" x2="12" y1="17" y2="21" /></svg
>
</div>
</Circle>
</div>
</div>
<AnimatedBeam bind:containerRef bind:fromRef={div1Ref} bind:toRef={div6Ref} />
<AnimatedBeam bind:containerRef bind:fromRef={div2Ref} bind:toRef={div6Ref} />
<AnimatedBeam bind:containerRef bind:fromRef={div3Ref} bind:toRef={div6Ref} />
<AnimatedBeam bind:containerRef bind:fromRef={div4Ref} bind:toRef={div6Ref} />
<AnimatedBeam bind:containerRef bind:fromRef={div5Ref} bind:toRef={div6Ref} />
<AnimatedBeam bind:containerRef bind:fromRef={div6Ref} bind:toRef={div7Ref} />
</div>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { cn } from "$lib/utils";
let { children, label ="", ref=$bindable()} = $props();
let className: any = $state("");
export { className as class };
</script>
<div class="tooltip z-10" data-tip={label}>
<div
bind:this={ref}
class={cn(
"bg-base-100 hover:bg-base-200 border-base-200 z-10 flex h-12 w-12 items-center justify-center rounded-full border-2 p-3 transition-all duration-200 cursor-pointer",
className,
)}
>
{@render children()}
</div>
</div>

View File

@@ -0,0 +1,115 @@
<script lang="ts">
import type { Dataleak } from "$src/lib/types";
import { Replace, Search } from "@lucide/svelte";
let {
dataleaks,
perPage = 5,
showColumns = false,
}: {
dataleaks: Dataleak[];
perPage?: number;
showColumns?: boolean;
} = $props();
let page = $state(1);
let filter = $state("");
let filteredDataleaks = $state<Dataleak[]>(dataleaks);
let paginatedDataleaks = $state<Dataleak[]>([]);
let totalPages = $state(0);
$effect(() => {
if (filter.trim() === "") {
filteredDataleaks = dataleaks;
} else {
const lowerFilter = filter.toLowerCase();
filteredDataleaks = dataleaks.filter((item) =>
item.Name.toLowerCase().includes(lowerFilter),
);
}
page = 1;
});
$effect(() => {
if (filteredDataleaks) {
totalPages = Math.ceil(filteredDataleaks.length / perPage);
const start = (page - 1) * perPage;
const end = start + perPage;
paginatedDataleaks = filteredDataleaks.slice(start, end);
if (page > totalPages) {
page = totalPages > 0 ? totalPages : 1;
}
}
});
function previousPage() {
if (page > 1) {
page--;
}
}
function nextPage() {
if (page < totalPages) {
page++;
}
}
</script>
<div class="my-4 flex flex-col gap-2">
<label class="input input-xs w-full">
<Search size={12} />
<input class="grow" placeholder="Filter" bind:value={filter} />
</label>
<div class="overflow-x-auto">
<table class="table">
<!-- head -->
<thead>
<tr>
<th>Name</th>
<th>Number of rows</th>
{#if showColumns}
<th>Columns</th>
{/if}
</tr>
</thead>
<tbody>
{#if paginatedDataleaks.length > 0}
{#each paginatedDataleaks as item}
<tr class="hover:bg-base-300">
<th>
{item.Name}
</th>
<td>{item.Length.toLocaleString("fr")}</td>
{#if showColumns}
<td class="capitalize">
{item.Columns.map((col) => col.replace(/_/g, " ")).join(", ")}
</td>
{/if}
</tr>
{/each}
{:else}
<tr class="hover:bg-base-300">
<td colspan="2" class="text-center leading-9"
><span class="text-3xl">(·.·)</span><br />No data wells found</td
>
</tr>
{/if}
</tbody>
</table>
</div>
{#if totalPages > 1}
<div class="join m-auto mt-5">
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
>«</button
>
<button class="join-item btn">Page {page} / {totalPages}</button>
<button
class="join-item btn"
onclick={nextPage}
disabled={page === totalPages}>»</button
>
</div>
{/if}
</div>

View File

@@ -0,0 +1,130 @@
<script lang="ts">
import type { History } from "$src/lib/types";
import { formatDate } from "$src/lib/utils";
import { Search } from "@lucide/svelte";
import { navigate, p } from "sv-router/generated";
let { history, perPage = 5 }: { history: History; perPage?: number } =
$props();
let page = $state(1);
let filter = $state("");
let filteredHistory = $state<History>(history);
let paginatedHistory = $state<History>([]);
let totalPages = $state(0);
$effect(() => {
if (filter.trim() === "") {
filteredHistory = history;
} else {
const lowerFilter = filter.toLowerCase();
filteredHistory = history.filter((item) =>
item.Query.Text.toLowerCase().includes(lowerFilter),
);
}
page = 1;
});
$effect(() => {
if (filteredHistory) {
totalPages = Math.ceil(filteredHistory.length / perPage);
const start = (page - 1) * perPage;
const end = start + perPage;
paginatedHistory = filteredHistory.slice(start, end);
if (page > totalPages) {
page = totalPages > 0 ? totalPages : 1;
}
}
});
function previousPage() {
if (page > 1) {
page--;
}
}
function nextPage() {
if (page < totalPages) {
page++;
}
}
</script>
<div class="my-4 flex flex-col gap-2">
<label class="input input-xs w-full">
<Search size={12} />
<input class="grow" placeholder="Filter" bind:value={filter} />
</label>
<div class="overflow-x-auto">
<table class="table">
<!-- head -->
<thead>
<tr>
<th>Query</th>
<th>Results</th>
<th>Status</th>
<th>Date</th>
<th></th>
</tr>
</thead>
<tbody>
{#if paginatedHistory.length > 0}
{#each paginatedHistory as item}
<tr class="hover:bg-base-300">
<th>
<button
onclick={() => {
navigate(`/search/:id`, { params: { id: item.Id } });
}}
class="btn btn-link p-0 no-underline text-base-content"
>
{item.Query.Text}
</button>
</th>
<td>{item.Results}</td>
<td
><div
class="badge badge-xs"
class:badge-success={item.Status === "completed"}
class:badge-warning={item.Status === "pending"}
>
{item.Status}
</div></td
>
<td>{formatDate(item.Date)}</td>
<td
onclick={() => {
navigate(`/search/:id`, { params: { id: item.Id } });
}}
><button class="btn btn-xs btn-square"
><Search size={11} /></button
></td
>
</tr>
{/each}
{:else}
<tr class="hover:bg-base-300">
<td colspan="5" class="text-center leading-9"
><span class="text-3xl">(·.·)</span><br />No history found</td
>
</tr>
{/if}
</tbody>
</table>
</div>
{#if totalPages > 1}
<div class="join m-auto mt-5">
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
>«</button
>
<button class="join-item btn">Page {page} / {totalPages}</button>
<button
class="join-item btn"
onclick={nextPage}
disabled={page === totalPages}>»</button
>
</div>
{/if}
</div>

View File

@@ -0,0 +1,46 @@
<script lang="ts"></script>
<div>
<p>
Eleakxir's search engine is designed to be both fast and flexible, letting
you find what you need in multiple ways.
</p>
<h3 class="h3 mt-4 mb-2">Search Modes</h3>
<p>
<span class="text-primary font-semibold">All:</span> This is the default mode.
It searches for your query across a set of standard columns like email, username,
and phone number. This is the fastest and most efficient way to find a specific
user or account.
</p>
<p>
<span class="text-primary font-semibold">Specific column:</span>
This mode lets you choose a specific column to search within, such as email or
username. It's useful when you know exactly where the data you're looking for
is stored.
</p>
<p>
<span class="text-primary font-semibold">Full Text:</span> This mode combines
all available columns into a single, large text field and searches within it.
It's great for finding data that might be in an unexpected column, but it's way
slower.
</p>
<h3 class="h3 mt-4 mb-2">Query Matching</h3>
<p>
<span class="text-primary font-semibold">Standard Search:</span> By default,
Eleakxir uses a "fuzzy" search. This means it will find results where your search
terms are part of a larger string. For example, searching for 1234 would find
john.doe@1234.com.
</p>
<p>
<span class="text-primary font-semibold">Exact Match:</span> When you enable
"Exact Match," the search will only return results where the data in a column
is an exact match for your search term. This is useful for finding specific,
unique values.
</p>
</div>

View File

@@ -0,0 +1,313 @@
<script lang="ts">
import Accordion from "$src/lib/components/accordion.svelte";
import Table from "$src/lib/components/table.svelte";
import type { GithubResult } from "$src/lib/types";
import { FlattenObject } from "$src/lib/utils";
import {
Building,
ExternalLink,
GitCommitVertical,
Handshake,
Key,
Mail,
UserRoundPen,
} from "@lucide/svelte";
const { githubResult }: { githubResult: GithubResult } = $props();
</script>
{#if githubResult.UsernameResult}
<div class="w-full">
<div class="flex flex-wrap gap-5">
<div class="avatar">
<div class="w-24 h-24 rounded-xl">
<img
src={githubResult.UsernameResult.User.AvatarURL}
alt="Avatar of {githubResult.UsernameResult.User.Username}"
/>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex flex-col">
<h3 class="h3">{githubResult.UsernameResult.User.Name}</h3>
<p class="text-base-content/60">
@{githubResult.UsernameResult.User.Username}
</p>
</div>
<p class="max-w-sm">{githubResult.UsernameResult.User.Bio}</p>
</div>
</div>
<div class="card card-border border-neutral shadow my-8">
<div class="grid">
<Table
row={{
publicRepos: githubResult.UsernameResult.User.PublicRepos,
followers: githubResult.UsernameResult.User.Followers,
following: githubResult.UsernameResult.User.Following,
createdAt: new Date(
githubResult.UsernameResult.User.CreatedAt,
).toLocaleDateString(),
email: githubResult.UsernameResult.User.Email,
location: githubResult.UsernameResult.User.Location,
company: githubResult.UsernameResult.User.Company,
url:
"https://github.com/" + githubResult.UsernameResult.User.Username,
}}
/>
</div>
</div>
{#if githubResult.UsernameResult.Socials && githubResult.UsernameResult.Socials.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">Social Links</h4>
<ul class="flex gap-4 flex-col mt-4 mb-6">
{#each githubResult.UsernameResult.Socials as social}
<a href={social.URL} target="_blank" rel="noopener noreferrer">
<div class="badge bg-base-300">
<ExternalLink size={12} />
{social.URL}
</div>
</a>
{/each}
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.CloseFriends && githubResult.UsernameResult.CloseFriends.length > 0}
<div class="mt-4">
<ul class="list bg-base-100 rounded-box shadow-md">
<Accordion
icon={Handshake}
title={"Close Friends"}
subtitle={ githubResult.UsernameResult.CloseFriends.length + " close friends found"}
>
<Table
row={githubResult.UsernameResult.CloseFriends}
/>
</Accordion>
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.Orgs && githubResult.UsernameResult.Orgs.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">Organizations</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
<Accordion
icon={Building}
title="Organizations"
subtitle={"Found " + githubResult.UsernameResult.Orgs.length + " organizations"}
>
<Table
row={githubResult.UsernameResult.Orgs}
/>
</Accordion>
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.Commits && githubResult.UsernameResult.Commits.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">Commits</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
{#each githubResult.UsernameResult.Commits as commit}
<Accordion
icon={GitCommitVertical}
title={commit.Name + " <" + commit.Email + ">"}
subtitle={"Occurrences: " + commit.Occurrences}
>
<Table
row={{
name: commit.Name,
email: commit.Email,
url: "https://github.com/" + commit.FirstFoundIn,
occurrences: commit.Occurrences,
}}
/>
</Accordion>
{/each}
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.SshKeys && githubResult.UsernameResult.SshKeys.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">SSH Keys</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
{#each githubResult.UsernameResult.SshKeys as key}
<Accordion
icon={Key}
title={"Created At: " +
new Date(key.CreatedAt).toLocaleDateString()}
subtitle={"Last Used: " +
(key.LastUsed !== "0001-01-01 00:00:00 +0000 UTC"
? new Date(key.LastUsed).toLocaleDateString()
: "Never")}
>
<pre class="overflow-x-auto p-2 bg-base-200 rounded"><code
class="break-all">{key.Key}</code
></pre>
</Accordion>
{/each}
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.SshSigningKeys && githubResult.UsernameResult.SshSigningKeys.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">SSH Signing Keys</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
{#each githubResult.UsernameResult.SshSigningKeys as key}
<Accordion
icon={Key}
title={key.Title}
subtitle={"Created At: " + key.CreatedAt}
>
<pre class="overflow-x-auto p-2 bg-base-200 rounded"><code
class="break-all">{key.Key}</code
></pre>
</Accordion>
{/each}
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.GpgKeys && githubResult.UsernameResult.GpgKeys.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">GPG Keys</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
{#each githubResult.UsernameResult.GpgKeys as key}
<Accordion
icon={Key}
title={key.Emails && key.Emails.length > 0 ? key.Emails[0].Email : key.KeyID}
subtitle={"Created At: " + key.CreatedAt}
>
<Table
row={FlattenObject(key)}
/>
</Accordion>
{/each}
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.DeepScan}
{#if githubResult.UsernameResult.DeepScan.Authors && githubResult.UsernameResult.DeepScan.Authors.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">Deep scan authors</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
<Accordion
icon={UserRoundPen}
title="Authors"
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Authors.length + " authors"
}
>
<Table
row={githubResult.UsernameResult.DeepScan.Authors}
/>
</Accordion>
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.DeepScan.Emails && githubResult.UsernameResult.DeepScan.Emails.length > 0}
<div class="mt-4">
<h4 class="h4 mb-2">Deep scan emails</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
<Accordion
icon={Mail}
title="Emails"
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Emails.length + " emails"
}
>
<Table
row={githubResult.UsernameResult.DeepScan.Emails}
/>
</Accordion>
</ul>
</div>
{/if}
{#if githubResult.UsernameResult.DeepScan.Secrets && githubResult.UsernameResult.DeepScan.Secrets.length > 0}
{@const flattenedSecrets = githubResult.UsernameResult.DeepScan.Secrets.map(FlattenObject)}
<div class="mt-4">
<h4 class="h4 mb-2">Deep scan secrets</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
<Accordion
icon={Mail}
title="Secrets"
subtitle={"Found " + githubResult.UsernameResult.DeepScan.Secrets.length + " secrets"
}
>
<Table
row={flattenedSecrets}
/>
</Accordion>
</ul>
</div>
{/if}
{/if}
</div>
{:else if githubResult.EmailResult}
<div class="w-full">
{#if githubResult.EmailResult.Spoofing}
<h4 class="h4 mb-4">From spoofing</h4>
<div class="flex flex-wrap gap-5">
<div class="avatar">
<div class="w-24 h-24 rounded-xl">
<img
src={githubResult.EmailResult.Spoofing.AvatarURL}
alt="Avatar of {githubResult.EmailResult.Spoofing.Username}"
/>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-2">
<h4 class="h4">@{githubResult.EmailResult.Spoofing.Username}</h4>
{#if githubResult.EmailResult.Spoofing.Name}
<p>
<strong>Name:</strong>
{githubResult.EmailResult.Spoofing.Name}
</p>
{/if}
{#if githubResult.EmailResult.Spoofing.Email}
<p>
<strong>Public email:</strong>
{githubResult.EmailResult.Spoofing.Email}
</p>
{/if}
{#if githubResult.EmailResult.Target}
<p class="break-all">
<strong>Primary email:</strong>
{githubResult.EmailResult.Target}
</p>
{/if}
<a
href={githubResult.EmailResult.Spoofing.Url}
class="link link-primary flex gap-2 items-center"
target="_blank"
>
{githubResult.EmailResult.Spoofing.Url}
<ExternalLink size={12} />
</a>
</div>
</div>
</div>
{/if}
{#if githubResult.EmailResult.Commits}
<div class="mt-4">
<h4 class="h4 mb-2">Commits</h4>
<ul class="list bg-base-100 rounded-box shadow-md">
{#each githubResult.EmailResult.Commits as commit}
<Accordion
icon={GitCommitVertical}
title={commit.Username && commit.Username !== ""
? commit.Name + " (@" + commit.Username + ")"
: commit.Name}
subtitle={"Occurrences: " + commit.Occurrences}
>
<Table
row={{
name: commit.Name,
username: commit.Username,
email: commit.Email,
first_found_in: commit.FirstFoundIn,
occurrences: commit.Occurrences,
}}
/>
</Accordion>
{/each}
</ul>
</div>
{/if}
</div>
{/if}

View File

@@ -0,0 +1,100 @@
<script lang="ts">
import Table from "$src/lib/components/table.svelte";
import { ChevronDown, ChevronUp, Database, Key, Mail } from "@lucide/svelte";
const { row }: { row: Record<string, string> } = $props();
let isOpen = $state<boolean>(false);
function getDomain(dataleakName: string) {
const firstPart = dataleakName.split(" ")[0].toLowerCase();
const domainRegex =
/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/;
if (domainRegex.test(firstPart)) {
return firstPart;
}
return null;
}
function getHighlightedContent(row: Record<string, string>): string {
const prioritizedKeys = [
"email",
"username",
"full_name",
"first_name",
"last_name",
"phone",
"password",
"address",
];
for (const key of prioritizedKeys) {
if (row[key]) {
return row[key];
}
}
for (const key in row) {
if (row[key]) {
return row[key];
}
}
return "No content";
}
</script>
<button
class="list-row hover:bg-base-300/75 text-left"
class:bg-base-300={isOpen}
class:rounded-b-none={isOpen}
onclick={() => {
isOpen = !isOpen;
}}
>
<div>
{#if getDomain(row["source"])}
<img
src="https://icons.duckduckgo.com/ip3/{getDomain(row['source'])}.ico"
class="size-10 rounded-box bg-neutral"
alt="Favicon de {getDomain(row['source'])}"
/>
{:else if row["password"] !== null}
<div
class="size-10 rounded-box bg-neutral items-center justify-center flex"
>
<Key />
</div>
{:else if row["email"] !== null}
<div
class="size-10 rounded-box bg-neutral items-center justify-center flex"
>
<Mail />
</div>
{:else}
<div
class="size-10 rounded-box bg-neutral items-center justify-center flex"
>
<Database />
</div>
{/if}
</div>
<div>
<div>{getHighlightedContent(row)}</div>
<div class="text-xs uppercase font-semibold opacity-60">
{row["source"]}
</div>
</div>
<div class="btn btn-square btn-ghost">
{#if isOpen}
<ChevronUp size={12} />
{:else}
<ChevronDown size={12} />
{/if}
</div>
</button>
{#if isOpen}
<li class="list-row flex bg-base-200 rounded-t-none mb-2">
<Table {row} />
</li>
{/if}

View File

@@ -0,0 +1,72 @@
<script lang="ts">
import type { Result } from "$src/lib/types";
import Row from "./row.svelte";
const { result }: { result: Result } = $props();
let page = $state(1);
let totalPages = $state(0);
const perPage = 20;
let paginated = $state<Record<string, string>[]>([]);
$effect(() => {
if (result && result.LeakResult.Rows) {
totalPages = Math.ceil(result.LeakResult.Rows.length / perPage);
const start = (page - 1) * perPage;
const end = start + perPage;
paginated = result.LeakResult.Rows.slice(start, end);
if (page > totalPages) {
page = totalPages > 0 ? totalPages : 1;
}
}
});
function goToFirstPage() {
page = 1;
top.scrollIntoView();
}
function previousPage() {
if (page > 1) {
page--;
top.scrollIntoView();
}
}
function nextPage() {
if (page < totalPages) {
page++;
top.scrollIntoView();
}
}
let top: any = $state();
</script>
<div bind:this={top} class="absolute -mt-[100px]"></div>
{#if result}
<ul class="list bg-base-100 rounded-box shadow-md">
{#each paginated as row (row)}
<Row {row} />
{/each}
</ul>
{#if totalPages > 1}
<div class="join m-auto mt-5">
<button class="join-item btn" onclick={previousPage} disabled={page === 1}
>«</button
>
<button class="join-item btn" onclick={goToFirstPage}
>Page {page} / {totalPages}</button
>
<button
class="join-item btn"
onclick={nextPage}
disabled={page === totalPages}>»</button
>
</div>
{/if}
{:else}
No result
{/if}

View File

@@ -0,0 +1,54 @@
<script lang="ts">
import type { Result } from "$src/lib/types";
import { formatDate } from "$src/lib/utils";
import { BadgeInfo, Clock, File } from "@lucide/svelte";
const { result }: { result: Result } = $props();
let nresult = $state(0);
$effect(() => {
const r = [
result.LeakResult.Rows?.length | 0,
result.GithubResult.EmailResult?.Commits?.length | 0,
result.GithubResult.EmailResult?.Spoofing ? 1 : 0,
result.GithubResult.UsernameResult?.Commits?.length | 0,
];
nresult = r.reduce((a, b) => a + b, 0);
});
</script>
<div class="stats stats-vertical md:stats-horizontal">
<div class="stat">
<div class="stat-figure text-secondary">
<File />
</div>
<div class="stat-title">Results</div>
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
{nresult.toLocaleString("fr")}
{#if result.Status === "pending"}
<span class="loading loading-dots loading-xs ml-2"></span>
{/if}
</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<Clock />
</div>
<div class="stat-title">Date</div>
<div class="stat-value">
{formatDate(result.Date)}
</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<BadgeInfo />
</div>
<div class="stat-title">Status</div>
<div class="stat-value" class:animate-pulse={result.Status === "pending"}>
{result.Status}
{#if result.Status === "pending"}
<span class="loading loading-dots loading-xs ml-2"></span>
{/if}
</div>
</div>
</div>

View File

@@ -0,0 +1,104 @@
<script lang="ts">
import { serverPassword, serverUrl } from "$src/lib/stores/server";
import { cn } from "$src/lib/utils";
import { Equal, EqualNot, Search } from "@lucide/svelte";
import axios from "axios";
import { navigate } from "sv-router/generated";
import { toast } from "svelte-sonner";
const {
initialQuery = "",
initialFilter = "all",
initialExactMatch = false,
}: {
initialQuery?: string;
initialFilter?: string;
initialExactMatch?: boolean;
} = $props();
let filters = [
"all",
"username",
"email",
"name",
"phone",
"url",
"password",
"password hash",
"full_text",
];
let activeFilter = $state<string>(initialFilter);
let query = $state<string>(initialQuery);
let exactMatch = $state<boolean>(initialExactMatch);
function NewSearch() {
axios
.post(
`${$serverUrl}/search`,
{ Text: query, Column: activeFilter, ExactMatch: exactMatch },
{
headers: {
"Content-Type": "application/json",
"X-Password": $serverPassword,
},
},
)
.then((r) => {
const id = r.data.Id;
window.location.href = `/search/${id}`;
})
.catch((e) => {
if (e.response.data.Error !== undefined) {
toast.error(e.response.data.Error);
} else {
toast.error("An error occurred");
}
});
}
</script>
<div class="flex gap-5 flex-col">
<div
class="flex gap-3 justify-start items-center w-full overflow-y-hidden overflow-x-auto"
>
{#each filters as filter}
<button
class={cn(
"btn btn-md capitalize",
activeFilter === filter
? "btn-primary"
: "btn-ghost btn-neutral text-base-content/80 hover:text-neutral-content",
)}
onclick={() => (activeFilter = filter)}>{filter.replace("_", " ")}</button
>
{/each}
</div>
<form
class="join w-full"
onsubmit={(e) => {
e.preventDefault();
NewSearch();
}}
>
<label class="grow input input-xl input-primary join-item w-full">
<Search size={16} />
<input
class="grow input-xl"
type="text"
bind:value={query}
placeholder="Search..."
required
/>
<div class="tooltip" data-tip="Exact Match">
<label class="toggle text-base-content toggle-xs">
<input type="checkbox" bind:checked={exactMatch} />
<EqualNot aria-label="disable" size={12} />
<Equal aria-label="enabled" size={12} />
</label>
</div>
</label>
<button class="btn btn-primary btn-xl join-item">Search</button>
</form>
</div>

View File

@@ -0,0 +1,83 @@
<script lang="ts">
import type { Server } from "$src/lib/types";
let { serverInfo }: { serverInfo: Server } = $props();
</script>
<div class="my-4">
<div class="overflow-x-auto">
<table class="table">
<!-- head -->
<thead>
<tr>
<th>Service</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr class="hover:bg-base-300">
<th> Data wells lookup </th>
<td>
{#if serverInfo.Dataleaks.length !== 0}
<div class="inline-grid *:[grid-area:1/1] mr-2">
<div class="status status-success"></div>
<div class="status status-success"></div>
</div>
Active
{:else}
<div class="inline-grid *:[grid-area:1/1] mr-2">
<div class="status status-error animate-ping"></div>
<div class="status status-error"></div>
</div>
Inactive
{/if}
</td>
</tr>
<tr class="hover:bg-base-300">
<th class="flex flex-wrap gap-2 items-center">
Github recon
{#if serverInfo.Settings.GithubTokenLoaded === true}
<div class="badge badge-xs badge-neutral">Token</div>
{/if}
{#if serverInfo.Settings.GithubDeepMode === true}
<div class="badge badge-xs badge-neutral">Deep Mode</div>
{/if}
</th>
<td>
{#if serverInfo.Settings.GithubRecon === true}
<div class="inline-grid *:[grid-area:1/1] mr-2">
<div class="status status-success"></div>
<div class="status status-success"></div>
</div>
Active
{:else}
<div class="inline-grid *:[grid-area:1/1] mr-2">
<div class="status status-error animate-ping"></div>
<div class="status status-error"></div>
</div>
Inactive
{/if}
</td>
</tr>
<tr class="hover:bg-base-300">
<th>Google hunt</th>
<td>
{#if serverInfo.Settings.GithubRecon === true}
<div class="inline-grid *:[grid-area:1/1] mr-2">
<div class="status status-success"></div>
<div class="status status-success"></div>
</div>
Active
{:else}
<div class="inline-grid *:[grid-area:1/1] mr-2">
<div class="status status-error animate-ping"></div>
<div class="status status-error"></div>
</div>
Inactive
{/if}
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,46 @@
<script lang="ts">
import type { Server } from "$src/lib/types";
import { Database, File, Save } from "@lucide/svelte";
const { serverInfo }: { serverInfo: Server | null } = $props();
function mbToGb(mb: number): number {
return Math.round((mb / 1024) * 100) / 100;
}
</script>
<div class="stats stats-vertical md:stats-horizontal">
<div class="stat">
<div class="stat-figure text-secondary">
<File />
</div>
<div class="stat-title">Rows available</div>
<div class="stat-value">
{serverInfo?.TotalRows
? serverInfo.TotalRows.toLocaleString("fr")
: "-- --- --- ---"}
</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<Database />
</div>
<div class="stat-title">Data wells available</div>
<div class="stat-value">
{serverInfo?.TotalDataleaks
? serverInfo.TotalDataleaks.toLocaleString("fr")
: "---"}
</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<Save />
</div>
<div class="stat-title">Storage used</div>
<div class="stat-value">
{serverInfo?.TotalSize
? mbToGb(serverInfo.TotalSize).toLocaleString("fr") + " Gb"
: "--- Gb"}
</div>
</div>
</div>