mirror of
https://github.com/xyzeva/k-id-age-verifier.git
synced 2026-09-27 10:51:45 +02:00
feat: finish up frontend and add qr code verification
This commit is contained in:
@@ -464,33 +464,13 @@ async function verify(qrCodeUrlStr: string) {
|
||||
distancing: {
|
||||
screenDetectionConfidence: Array.from({ length: 2 }, () => randomFloat(0.01, 0.03)),
|
||||
screenFaceOverlap: [0, 0],
|
||||
screenBoundingBoxes: [
|
||||
[],
|
||||
[
|
||||
{
|
||||
x: 1,
|
||||
y: 60,
|
||||
width: 158,
|
||||
height: 335
|
||||
}
|
||||
]
|
||||
],
|
||||
screenBoundingBoxes: [Array.from({ length: 2 }, generateBoundingBox)],
|
||||
alternativeScore: Array.from({ length: 2 }, () => randomFloat(0.2, 0.9))
|
||||
},
|
||||
closing: {
|
||||
screenDetectionConfidence: Array.from({ length: 2 }, () => randomFloat(0.01, 0.03)),
|
||||
screenFaceOverlap: [0, 0],
|
||||
screenBoundingBoxes: [
|
||||
[
|
||||
{
|
||||
x: 370,
|
||||
y: 233,
|
||||
width: 58,
|
||||
height: 85
|
||||
}
|
||||
],
|
||||
[]
|
||||
],
|
||||
screenBoundingBoxes: [Array.from({ length: 2 }, generateBoundingBox)],
|
||||
alternativeScore: Array.from({ length: 2 }, () => randomFloat(0.2, 0.9))
|
||||
},
|
||||
postChallenge: {
|
||||
@@ -579,9 +559,9 @@ async function verify(qrCodeUrlStr: string) {
|
||||
txFinishedInLandscapeMode: false
|
||||
},
|
||||
initializationCharacteristics: {
|
||||
cropperInitTime: 2718,
|
||||
coreInitTime: 6746,
|
||||
pageLoadTime: 602.0999999996275,
|
||||
cropperInitTime: randomInt(150, 250),
|
||||
coreInitTime: randomInt(800, 1000),
|
||||
pageLoadTime: randomInt(250, 350),
|
||||
from_qr_scan: false,
|
||||
blendShapesAvailable: true
|
||||
},
|
||||
|
||||
@@ -4,4 +4,8 @@
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>discord/twitch/kick/snapchat age verifier</title>
|
||||
</svelte:head>
|
||||
|
||||
{@render children()}
|
||||
|
||||
+77
-9
@@ -1,17 +1,24 @@
|
||||
<svelte:head>
|
||||
<title>discord age verifier</title>
|
||||
</svelte:head>
|
||||
<script lang="ts">
|
||||
let qrCodeUrl = $state<string | null>(null);
|
||||
let qrCodeError = $state<string | null>(null);
|
||||
let qrCodeSuccess = $state(false);
|
||||
</script>
|
||||
|
||||
<div class="mx-auto w-screen max-w-6xl items-center p-5 pb-16">
|
||||
<h1 class="mt-16 text-center text-3xl font-extrabold">discord age verifier</h1>
|
||||
<p class="text-center">age verifies your discord account automatically as an adult</p>
|
||||
<h1 class="mt-16 text-center text-3xl font-extrabold">
|
||||
discord/twitch/kick/snapchat age verifier
|
||||
</h1>
|
||||
<p class="text-center">
|
||||
age verifies your account automatically as an adult on any website using k-id
|
||||
</p>
|
||||
<p class="mt-4 text-center text-white/50">
|
||||
made by <a class="underline" href="https://eva.ac" target="_blank">xyzeva</a> and
|
||||
made by <a class="underline" href="https://kibty.town" target="_blank">xyzeva</a> and
|
||||
<a class="underline" href="https://github.com/Dziurwa14" target="_blank">Dziurwa</a>, greetz to
|
||||
<a class="underline" href="https://amplitudes.me/" target="_blank">amplitudes</a> (for previous work)
|
||||
</p>
|
||||
|
||||
<p class="mt-8 text-left">
|
||||
<h2 class="mt-8 text-2xl font-bold">how to verify on discord</h2>
|
||||
<p class="mt-4 text-left">
|
||||
it <span class="font-bold">doesn't matter</span> if you are in the UK or similar region that
|
||||
currently has access to this, this will verify your account for the future global rollout in
|
||||
march aswell as current. to use, simply paste this script into your discord console by going to
|
||||
@@ -48,7 +55,7 @@ findByProps = (...props) => {
|
||||
api = findByProps('Bo','oh').Bo
|
||||
|
||||
// send a api request to discord /age-verification/verify and then redirect the page to our website
|
||||
window.location.href = `https://discord-verifier.eva.ac/webview?url=${encodeURIComponent((await api.post({ url: '/age-verification/verify', body: { method: 3 }})).body.verification_webview_url)}`</pre>
|
||||
window.location.href = `https://age-verifier.kibty.town/webview?url=${encodeURIComponent((await api.post({ url: '/age-verification/verify', body: { method: 3 }})).body.verification_webview_url)}`</pre>
|
||||
<p class="text-center text-white/50">
|
||||
(feel free to read the code, we made it readable and we have nothing to hide)
|
||||
</p>
|
||||
@@ -59,7 +66,68 @@ window.location.href = `https://discord-verifier.eva.ac/webview?url=${encod
|
||||
</p>
|
||||
<p class="mt-2 text-left">congrats! your discord account is now age verified.</p>
|
||||
|
||||
<h2 class="mt-12 text-2xl font-bold">how does this work</h2>
|
||||
<h2 class="mt-4 text-2xl font-bold">
|
||||
how to verify on other platforms (twitch, kick, snapchat, ...others)
|
||||
</h2>
|
||||
<p>
|
||||
navigate to the age verification page and choose selfie, from there, get the url of the qr code
|
||||
and put it in this input box, and press verify
|
||||
</p>
|
||||
|
||||
<div class="mt-4 flex gap-4">
|
||||
<input
|
||||
class="w-full border-2 border-white/50 p-2"
|
||||
bind:value={qrCodeUrl}
|
||||
placeholder="https://..."
|
||||
/>
|
||||
<button
|
||||
class="w-24 border-2 border-white/50 p-2 hover:cursor-pointer"
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
qrCodeError = null;
|
||||
qrCodeSuccess = false;
|
||||
|
||||
if (!qrCodeUrl) {
|
||||
qrCodeError = "you didn't enter a qr code url";
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'qr_link',
|
||||
identifier: qrCodeUrl
|
||||
})
|
||||
})
|
||||
.then(async (r) => {
|
||||
if (!r.ok) {
|
||||
qrCodeError = await r.text().catch(() => 'unexpected error');
|
||||
return;
|
||||
}
|
||||
|
||||
qrCodeSuccess = true;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('error sending verify request', e);
|
||||
qrCodeError = 'unexpected error, please check your browser console.';
|
||||
});
|
||||
}}>verify</button
|
||||
>
|
||||
</div>
|
||||
{#if qrCodeSuccess}
|
||||
<p class="mt-4 text-green-500">
|
||||
your account has successfully been verified. go back to the site tab to continue
|
||||
</p>
|
||||
{:else if qrCodeError}
|
||||
<p class="mt-4 text-red-500">
|
||||
{qrCodeError}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<h2 class="mt-8 text-2xl font-bold">how does this work</h2>
|
||||
<p>
|
||||
k-id, the age verification provider discord uses doesn't store or send your face to the server.
|
||||
instead, it sends a bunch of metadata about your face and general process details. while this is
|
||||
|
||||
@@ -6,7 +6,7 @@ const K_ID_DEPLOYMENT_ID = '20260210222654-016f063-production';
|
||||
const K_ID_PRIVATELY_ACTION_ID = '40dc500368168e3130ea4625c535d5a9bbbf0243f1';
|
||||
const K_ID_NEXT_ROUTER_TREE =
|
||||
'%5B%22%22%2C%7B%22children%22%3A%5B%22verify%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D';
|
||||
const PRIVATELY_URL_REGEX = /(https:\/\/[a-z0-9]+\.cloudfront\.net\/.*)(?=:\{)/
|
||||
const PRIVATELY_URL_REGEX = /(https:\/\/[a-z0-9]+\.cloudfront\.net\/.*)(?=:\{)/;
|
||||
|
||||
const jsonResponse = (body: unknown, status: number = 200, extraHeaders: object = {}) =>
|
||||
new Response(JSON.stringify(body), {
|
||||
@@ -748,7 +748,13 @@ export const POST = async (event: RequestEvent) => {
|
||||
const { type, identifier }: { type: string; identifier: string } = await event.request.json();
|
||||
|
||||
if (type === 'webview') {
|
||||
const webviewUrl = new URL(identifier);
|
||||
let webviewUrl: URL;
|
||||
try {
|
||||
webviewUrl = new URL(identifier);
|
||||
} catch (e) {
|
||||
return jsonResponse({ error: 'error parsing webview url' }, 400);
|
||||
}
|
||||
|
||||
if (webviewUrl.host !== 'family.k-id.com' || webviewUrl.pathname !== '/verify') {
|
||||
return jsonResponse({ error: 'unexpected webview url' }, 400);
|
||||
}
|
||||
@@ -764,42 +770,110 @@ export const POST = async (event: RequestEvent) => {
|
||||
}
|
||||
|
||||
const payload = JSON.parse(atob(parts[1]));
|
||||
|
||||
|
||||
// fetch the webview first
|
||||
await fetch(webviewUrl)
|
||||
const privately = await fetch(webviewUrl, {
|
||||
await fetch(webviewUrl, {
|
||||
headers: {
|
||||
'User-Agent': userAgent,
|
||||
accept: '*/*',
|
||||
'accept-language': location.lang,
|
||||
priority: 'u=1, i',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'cross-site',
|
||||
Origin: 'https://family.k-id.com',
|
||||
Referer: identifier
|
||||
}
|
||||
});
|
||||
|
||||
const privatelyActionRes = await fetch(webviewUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': userAgent,
|
||||
accept: '*/*',
|
||||
'accept-language': location.lang,
|
||||
priority: 'u=1, i',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'cross-site',
|
||||
'Next-Action': K_ID_PRIVATELY_ACTION_ID,
|
||||
'Next-Router-State-Tree': K_ID_NEXT_ROUTER_TREE,
|
||||
'X-Deployment-Id': K_ID_DEPLOYMENT_ID,
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://family.k-id.com',
|
||||
Referer: identifier
|
||||
},
|
||||
body: JSON.stringify([{"verificationId":payload.jti,"useBranding":true,"attemptId":crypto.randomUUID()}])
|
||||
}).then(res => res.text());
|
||||
'User-Agent': userAgent,
|
||||
accept: '*/*',
|
||||
'accept-language': location.lang,
|
||||
priority: 'u=1, i',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'cross-site',
|
||||
'Next-Action': K_ID_PRIVATELY_ACTION_ID,
|
||||
'Next-Router-State-Tree': K_ID_NEXT_ROUTER_TREE,
|
||||
'X-Deployment-Id': K_ID_DEPLOYMENT_ID,
|
||||
'Content-Type': 'application/json',
|
||||
Origin: 'https://family.k-id.com',
|
||||
Referer: identifier
|
||||
},
|
||||
body: JSON.stringify([
|
||||
{ verificationId: payload.jti, useBranding: true, attemptId: crypto.randomUUID() }
|
||||
])
|
||||
});
|
||||
|
||||
const match = privately.match(PRIVATELY_URL_REGEX);
|
||||
if (match) {
|
||||
const privatelyUrl = new URL(match[1]);
|
||||
const privatelyToken = privatelyUrl.searchParams.get('token');
|
||||
if (!privatelyToken) {
|
||||
return jsonResponse({ error: 'no privately token' }, 400);
|
||||
}
|
||||
await verify(userAgent, location, privatelyToken);
|
||||
} else {
|
||||
return jsonResponse({ error: 'no privately url found in response' }, 400);
|
||||
if (!privatelyActionRes.ok) {
|
||||
return jsonResponse(
|
||||
{
|
||||
error: `failed to execute k-id privately action (status=${privatelyActionRes.status})`
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
return jsonResponse({ success: true }, 200);
|
||||
const privatelyActionBody = await privatelyActionRes.text();
|
||||
const match = privatelyActionBody.match(PRIVATELY_URL_REGEX);
|
||||
|
||||
if (!match) {
|
||||
return jsonResponse({ error: 'no privately url found in response' }, 500);
|
||||
}
|
||||
|
||||
const privatelyUrl = new URL(match[1]);
|
||||
const privatelyToken = privatelyUrl.searchParams.get('token');
|
||||
|
||||
if (!privatelyToken) {
|
||||
return jsonResponse({ error: 'no privately token' }, 500);
|
||||
}
|
||||
|
||||
await verify(userAgent, location, privatelyToken);
|
||||
|
||||
return jsonResponse({ success: true });
|
||||
}
|
||||
|
||||
if (type === 'qr_link') {
|
||||
let qrCodeUrl: URL;
|
||||
try {
|
||||
qrCodeUrl = new URL(identifier);
|
||||
} catch (e) {
|
||||
return jsonResponse({ error: 'error parsing qr code url' }, 400);
|
||||
}
|
||||
|
||||
const shortlinkId = qrCodeUrl.searchParams.get('sl');
|
||||
if (!shortlinkId) {
|
||||
return jsonResponse({ error: 'failed to get shortlink id from qr code url' }, 400);
|
||||
}
|
||||
|
||||
const res = await fetch(`${BASE_URL}/shortlinks/${encodeURIComponent(shortlinkId)}`, {
|
||||
headers: {
|
||||
'User-Agent': userAgent,
|
||||
accept: '*/*',
|
||||
'accept-language': location.lang,
|
||||
priority: 'u=1, i',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'cross-site'
|
||||
}
|
||||
});
|
||||
if (!res.ok) {
|
||||
return jsonResponse(`failed to get shortlink (status=${res.status})`, 400);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const originalUrl = new URL(data.Item.original_url.S.replace('#', ''));
|
||||
const token = originalUrl.searchParams.get('token');
|
||||
if (!token) {
|
||||
return jsonResponse({ error: 'token not found in original url' }, 500);
|
||||
}
|
||||
|
||||
await verify(userAgent, location, token);
|
||||
|
||||
return jsonResponse({ success: true });
|
||||
}
|
||||
|
||||
return jsonResponse({ error: 'unexpected type' }, 400);
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen w-screen items-center justify-center text-3xl">
|
||||
<div class="flex min-h-screen w-screen flex-col items-center justify-center text-center">
|
||||
{#if webviewUrl}
|
||||
{#if success}
|
||||
<p class="text-green-500">your account is now age verified. enjoy</p>
|
||||
@@ -41,6 +41,16 @@
|
||||
<p>automatically verifying your age</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<p>invalid url</p>
|
||||
<p>
|
||||
<span class="text-red-500">invalid url</span> <br /> did you mean to go to
|
||||
<a class="underline" href="/">the instructions page?</a>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="absolute bottom-4 flex w-screen justify-center">
|
||||
<p class="text-center text-white/50">
|
||||
made by <a class="underline" href="https://kibty.town" target="_blank">xyzeva</a> and
|
||||
<a class="underline" href="https://github.com/Dziurwa14" target="_blank">Dziurwa</a>, greetz to
|
||||
<a class="underline" href="https://amplitudes.me/" target="_blank">amplitudes</a> (for previous work)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user