Skip to content

EzCloudflare #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"lint:fix": "eslint ./src/ --fix"
},
"devDependencies": {
"@types/node": "^24.0.3",
"@types/bun": "^1.2.17",
"@types/node": "^24.0.3",
"@typescript-eslint/eslint-plugin": "^8.34.1",
"@typescript-eslint/parser": "^8.34.1",
"eslint": "^9.29.0",
Expand Down
5 changes: 4 additions & 1 deletion src/commands/manverify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ export default {

await db.setData(member.id, ip);
await grantRole(interaction.guild, member.id, memberRoles);
await logWebhook(interaction.client, `<@!${user.id}> was manually verified by <@!${interaction.user.id}>.`);
await logWebhook(
interaction.client,
`<@!${user.id}> was manually verified by <@!${interaction.user.id}>.`
);
const embed = new EmbedBuilder()
.setTitle("Manually verified.")
.setDescription(
Expand Down
7 changes: 5 additions & 2 deletions src/commands/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default {
embeds: [],
components: [],
});
};
}

const mainId = await db.checkIp(data.ip);

Expand Down Expand Up @@ -135,7 +135,10 @@ export default {
if (i.customId === "confirm") {
await db.setData(user.id, data.ip);
await grantRole(interaction.guild!, user.id, memberRoles);
await logWebhook(interaction.client, `<@!${user.id}> was manually verified by <@!${interaction.user.id}>.`);
await logWebhook(
interaction.client,
`<@!${user.id}> was manually verified by <@!${interaction.user.id}>.`
);

//@ts-expect-error
const formatter = new Intl.ListFormat("en", {
Expand Down
31 changes: 24 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { Hono } from "hono";
import { serveStatic } from "hono/bun";
import * as db from "./db/db.ts";
import * as oauth from "./util/oauth.ts";
import { getIpData } from "./util/ip.ts";
import { getIpData, getIp } from "./util/ip.ts";
import { checkRole, grantRole, logWebhook } from "./util/discordManager.ts";

// Initialize the Discord client
const client = new Client({
intents: [
Expand Down Expand Up @@ -63,7 +64,8 @@ app.get("/callback", async (c) => {
if (!code) {
return c.redirect("/error.html");
}
const ip = c.req.header("X-Forwarded-For");
const ip = await getIp(c);

if (!ip) {
return c.redirect("/error.html");
}
Expand All @@ -86,33 +88,48 @@ app.get("/callback", async (c) => {

if (await checkRole(guild, id, mutedRole)) {
console.log(`${id} tried verifying with muted role`);
await logWebhook(client, `<@!${id}> tried to verify while having the muted role.`);
await logWebhook(
client,
`<@!${id}> tried to verify while having the muted role.`
);
return c.redirect("/altflagged.html");
}

if (await checkRole(guild, id, altRole)) {
console.log(`${id} tried verifying with alternate role`);
await logWebhook(client, `<@!${id}> tried to verify while having the alternate role.`);
await logWebhook(
client,
`<@!${id}> tried to verify while having the alternate role.`
);
return c.redirect("/altflagged.html");
}

const mainId = await db.checkIp(ip);

if (mainId && id !== mainId) {
await logWebhook(client, `<@!${id}> was flagged as an alt account. Their main is <@!${mainId}>.`);
await logWebhook(
client,
`<@!${id}> was flagged as an alt account. Their main is <@!${mainId}>.`
);
grantRole(guild, id, altRole);
return c.redirect("/altflagged.html");
}

const ipData = await getIpData(ip);

if (ipData.mobile) {
await logWebhook(client, `<@!${id}> Is trying to verify over a potential mobile data connection.`);
await logWebhook(
client,
`<@!${id}> Is trying to verify over a potential mobile data connection.`
);
return c.redirect("/mobile.html");
}

if (ipData.proxy || ipData.hosting) {
await logWebhook(client, `<@!${id}> attempted to verify over a proxy or VPN.`);
await logWebhook(
client,
`<@!${id}> attempted to verify over a proxy or VPN.`
);
return c.redirect("/flagged.html");
}

Expand Down
7 changes: 5 additions & 2 deletions src/public/altflagged.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@
<h1 class="text-5xl font-extrabold text-yellow-400">Flagged</h1>
<p class="text-gray-300">
Verification failed. We have detected you have already verified
yourself on this IP Address. <strong>If you believe this was a mistake, create
a ticket and explain your situation</strong>.
yourself on this IP Address.
<strong
>If you believe this was a mistake, create a ticket and explain your
situation</strong
>.
</p>
<a href="/login">
<button
Expand Down
8 changes: 3 additions & 5 deletions src/util/discordManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,12 @@ export async function checkRole(
return member.roles.cache.some((role) => role.id === roleID);
}

export async function logWebhook(
client: Client,
content: string,
) {
export async function logWebhook(client: Client, content: string) {
if (!client.user) return;
const webhookClient = new WebhookClient({ url: process.env.WEBHOOK_URL });
const username = client.user.username ?? "Unknown";
const avatarURL = client.user.avatarURL() || "https://i.imgur.com/AfFp7pu.png";
const avatarURL =
client.user.avatarURL() || "https://i.imgur.com/AfFp7pu.png";
webhookClient.send({
username,
avatarURL,
Expand Down
78 changes: 78 additions & 0 deletions src/util/ip.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,81 @@
// dont copy the patented and copyrighted EzCloudflare TM Solution for hono.
// (joke)
import { getConnInfo } from "hono/bun";
import ipaddr from "./ipaddr_polyfill";
import type { IPv4, IPv6 } from "./ipaddr_polyfill";
import type { Context } from "hono";

function setSome<T>(
set: Set<T>,
predicate: (value: T, index: number, set: Set<T>) => boolean,
thisArg?: any
): boolean {
let index = 0;
for (const value of set) {
if (predicate.call(thisArg, value, index, set)) {
return true;
}
index++;
}
return false;
}

const cfCiders = new Set<[IPv4 | IPv6, number]>();

async function loadCfCidrs() {
const [v4, v6] = await Promise.all([
fetch("https://www.cloudflare.com/ips-v4")
.then((r) => r.text())
.then((t) => t.trim().split("\n")),
fetch("https://www.cloudflare.com/ips-v6")
.then((r) => r.text())
.then((t) => t.trim().split("\n")),
]);

[...v4, ...v6].forEach((cidr) => {
try {
cfCiders.add(ipaddr.parseCIDR(cidr));
} catch {}
});
}

async function isCloudflare(ip: string): Promise<boolean> {
if (!cfCiders.size) await loadCfCidrs();
try {
const addr = ipaddr.parse(ip);
return setSome(cfCiders, ([range, bits]) => addr.match(range, bits));
} catch {
return false;
}
}

function isLoopback(ip: string): boolean {
try {
return ipaddr.parse(ip).range() === "loopback";
} catch {
return false;
}
}

export async function getIp(ctx: Context): Promise<string | undefined> {
let ip = getConnInfo(ctx)?.remote?.address;

if (ip && isLoopback(ip)) {
const xff = ctx.req.header("x-forwarded-for");
if (xff) {
const parts = xff.split(",").map((ip) => ip.trim());
if (parts.length > 0) ip = parts[parts.length - 1];
}
}

if (ip && (await isCloudflare(ip))) {
ip =
ctx.req.header("cf-connecting-ip") ?? ctx.req.header("x-real-ip") ?? ip;
}

return ip;
}

export async function getIpData(ip: string) {
const query = await fetch(`http://ip-api.com/json/${ip}?fields=66842623`);
const data = await query.json();
Expand Down
Loading