fix: add proxy & payload changes

This commit is contained in:
eva
2026-02-13 04:37:25 +00:00
parent c669c01f1b
commit ba28b37717
7 changed files with 218 additions and 159 deletions
+4
View File
@@ -0,0 +1,4 @@
# if your running on cloudflare workers, you will need to supply a http proxy
ENABLE_WORKER_PROXY=false
WORKER_PROXY_TOKEN=
WORKER_PROXY_URL=
+7
View File
@@ -0,0 +1,7 @@
{
"imports": {
"hono": "npm:hono@^4.11.9",
"zod": "npm:zod",
"@hono/zod-validator": "npm:@hono/zod-validator"
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"version": "5",
"specifiers": {
"npm:@hono/zod-validator@*": "0.7.6_hono@4.11.9_zod@4.3.6",
"npm:hono@^4.11.9": "4.11.9",
"npm:zod@*": "4.3.6"
},
"npm": {
"@hono/zod-validator@0.7.6_hono@4.11.9_zod@4.3.6": {
"integrity": "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw==",
"dependencies": [
"hono",
"zod"
]
},
"hono@4.11.9": {
"integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="
},
"zod@4.3.6": {
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="
}
},
"workspace": {
"dependencies": [
"npm:@hono/zod-validator@*",
"npm:hono@^4.11.9",
"npm:zod@*"
]
}
}
+84
View File
@@ -0,0 +1,84 @@
import { Hono } from 'hono';
import { ContentfulStatusCode } from 'hono/utils/http-status';
import crypto from 'node:crypto';
import { zValidator } from '@hono/zod-validator';
import z from 'zod';
const app = new Hono();
const proxyToken = Deno.env.get('PROXY_TOKEN');
const proxyUrlTemplate = Deno.env.get('PROXY_URL');
if (!proxyToken) {
console.error('failed to find PROXY_TOKEN environment variable');
Deno.exit(1);
}
if (!proxyUrlTemplate) {
console.error('failed to find PROXY_URL environment variable');
Deno.exit(1);
}
function timingSafeEquals(a: string, b: string) {
const aBuf = new TextEncoder().encode(a);
const bBuf = new TextEncoder().encode(b);
if (aBuf.length !== bBuf.length) {
return false;
}
return crypto.timingSafeEqual(aBuf, bBuf);
}
app.post(
'/proxy',
zValidator(
'json',
z.object({
method: z.enum(['GET', 'POST', 'PATCH', 'PUT', 'DELETE']),
url: z.url(),
headers: z.record(z.string(), z.string()),
body: z.string().nullable(),
sessionId: z.string()
})
),
async (c) => {
const header = c.req.header('x-proxy-token');
if (!header || !timingSafeEquals(proxyToken, header)) {
return c.json(
{
code: 20017,
message: "The Maze isn't meant for you 👽👽👽"
},
403
);
}
const body = c.req.valid('json');
const client = Deno.createHttpClient({
proxy: {
url: proxyUrlTemplate.replaceAll('{sessionId}', body.sessionId)
}
});
console.log(proxyUrlTemplate.replaceAll('{sessionId}', body.sessionId));
const res = await fetch(body.url, {
method: body.method,
headers: {
...body.headers
},
body: body.body,
client
});
const resBody = await res.arrayBuffer();
const resHeaders: Record<string, string> = {};
for (const [key, value] of res.headers.entries()) {
resHeaders[key] = value;
}
return c.body(resBody, res.status as ContentfulStatusCode, resHeaders);
}
);
export default app;
+4 -3
View File
@@ -476,7 +476,8 @@ async function verify(qrCodeUrlStr: string) {
const laplacianBlurScores = Array.from({ length: 300 }, () => randomFloat(10, 300, 15));
const laplacianMinScore = Math.min(...laplacianBlurScores);
const laplacianMaxScore = Math.max(...laplacianBlurScores);
const laplacianAvgScore = laplacianBlurScores.reduce((sum, score) => sum + score) / laplacianBlurScores.length;
const laplacianAvgScore =
laplacianBlurScores.reduce((sum, score) => sum + score) / laplacianBlurScores.length;
let payload = {
request_type: 'complete_transaction',
@@ -685,9 +686,9 @@ async function verify(qrCodeUrlStr: string) {
isCameraPermissionGranted: true,
completionTime,
deferredComputationStartedAt:
randomInt(10000, 14000) + Number(Math.random().toFixed(randomInt(1, 3))),
currentTime + randomInt(20, 80) + Number(Math.random().toFixed(randomInt(1, 3))),
instructionCompletionTime: randomInt(10000, 14000),
initialAdjustmentTime: randomInt(10000, 14000),
initialAdjustmentTime: randomInt(100, 500),
completionState: 'COMPLETE',
unfinishedInstructions: Object.fromEntries(
[
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import QRCodeScanner from '$lib/components/QRCodeScanner.svelte';
const IS_PATCHED = true;
const IS_PATCHED = false;
</script>
<div class="mx-auto w-screen max-w-6xl items-center p-5 pb-16">
+88 -155
View File
@@ -1,12 +1,8 @@
import { ENABLE_WORKER_PROXY, WORKER_PROXY_TOKEN, WORKER_PROXY_URL } from '$env/static/private';
import type { RequestEvent } from './$types';
import { Buffer } from 'node:buffer';
const BASE_URL = 'https://eu-west-1.faceassure.com';
const K_ID_DEPLOYMENT_ID = '20260212204349-d873b84-production';
const K_ID_PRIVATELY_ACTION_ID = '40b20a9df4b9c190d3b926d1c6b859c42ca73cb038';
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 jsonResponse = (body: unknown, status: number = 200, extraHeaders: object = {}) =>
new Response(JSON.stringify(body), {
@@ -22,6 +18,31 @@ const randomFloat = (min: number, max: number, decimals = 15) =>
parseFloat((Math.random() * (max - min) + min).toFixed(decimals));
const randomChoice = <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];
async function fetchWithWorkerProxy(
sessionId: string,
url: string,
init: RequestInit
): Promise<Response> {
if (ENABLE_WORKER_PROXY !== 'true') {
return await fetch(url, init);
}
return await fetch(WORKER_PROXY_URL, {
method: 'POST',
headers: {
'x-proxy-token': WORKER_PROXY_TOKEN,
'content-type': 'application/json'
},
body: JSON.stringify({
method: init.method ?? 'GET',
url: url,
headers: init.headers,
body: init.body ?? null,
sessionId
})
});
}
function generateId(id: string, sub: string, sessionId: string, delimiter = '|') {
let s = 0;
for (let a = '' + id + delimiter + sub + delimiter + sessionId, i = 0; i < a.length; i++) {
@@ -334,6 +355,7 @@ async function generateKIDProof(grant: string, attemptId: string) {
}
async function verify(
sessionId: string,
userAgent: string,
location: ReturnType<typeof getRandomLocation>,
token: string
@@ -372,22 +394,26 @@ async function verify(
if (parts.length !== 3) throw `token is an invalid jwt `;
const jwtPayload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
const sessionRes = await fetch(`${BASE_URL}/age-services/d-privately-age-services`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...commonHeaders
},
body: JSON.stringify({
request_type: 'generate_new_session',
transaction_id: jwtPayload.jti,
api_key: null,
api_secret: null,
token,
longURL: null,
userAgent: userAgent
})
});
const sessionRes = await fetchWithWorkerProxy(
sessionId,
`${BASE_URL}/age-services/d-privately-age-services`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
...commonHeaders
},
body: JSON.stringify({
request_type: 'generate_new_session',
transaction_id: jwtPayload.jti,
api_key: null,
api_secret: null,
token,
longURL: null,
userAgent: userAgent
})
}
);
if (!sessionRes.ok)
throw `failed to generate new session (status=${sessionRes.status}, body=${JSON.stringify(await sessionRes.text())})`;
@@ -490,7 +516,8 @@ async function verify(
const laplacianBlurScores = Array.from({ length: 300 }, () => randomFloat(10, 300, 15));
const laplacianMinScore = Math.min(...laplacianBlurScores);
const laplacianMaxScore = Math.max(...laplacianBlurScores);
const laplacianAvgScore = laplacianBlurScores.reduce((sum, score) => sum + score) / laplacianBlurScores.length;
const laplacianAvgScore =
laplacianBlurScores.reduce((sum, score) => sum + score) / laplacianBlurScores.length;
let payload = {
request_type: 'complete_transaction',
@@ -699,9 +726,9 @@ async function verify(
isCameraPermissionGranted: true,
completionTime,
deferredComputationStartedAt:
randomInt(10000, 14000) + Number(Math.random().toFixed(randomInt(1, 3))),
currentTime + randomInt(20, 80) + Number(Math.random().toFixed(randomInt(1, 3))),
instructionCompletionTime: randomInt(10000, 14000),
initialAdjustmentTime: randomInt(10000, 14000),
initialAdjustmentTime: randomInt(100, 500),
completionState: 'COMPLETE',
unfinishedInstructions: Object.fromEntries(
[
@@ -779,21 +806,25 @@ async function verify(
const encryptionData = await encryptPayload(sessionData.nonce, payload);
payload = Object.assign(payload, encryptionData);
const completeRes = await fetch(`${BASE_URL}/age-services/d-privately-age-services`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': userAgent,
accept: '*/*',
'accept-language': location.lang,
'access-control-allow-origin': '*',
priority: 'u=1, i',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site'
},
body: JSON.stringify(payload)
});
const completeRes = await fetchWithWorkerProxy(
sessionId,
`${BASE_URL}/age-services/d-privately-age-services`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': userAgent,
accept: '*/*',
'accept-language': location.lang,
'access-control-allow-origin': '*',
priority: 'u=1, i',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site'
},
body: JSON.stringify(payload)
}
);
if (!completeRes.ok)
throw `failed to complete transaction (status=${completeRes.status}, body=${JSON.stringify(await completeRes.text())})`;
@@ -804,112 +835,10 @@ async function verify(
export const POST = async (event: RequestEvent) => {
const userAgent = generateUserAgent();
const location = getRandomLocation();
const sessionId = String(randomInt(1000000000, 1999999999));
const { type, identifier }: { type: string; identifier: string } = await event.request.json();
if (type === 'webview') {
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);
}
const kIdToken = webviewUrl.searchParams.get('token');
if (!kIdToken) {
return jsonResponse({ error: 'no k-id token in webview url' }, 400);
}
const parts = kIdToken.split('.');
if (parts.length !== 3) {
return jsonResponse({ error: 'invalid k-id jwt' }, 400);
}
const payload = JSON.parse(atob(parts[1]));
const attemptId = crypto.randomUUID();
// fetch the webview first
const res = await fetch(webviewUrl, {
headers: {
'User-Agent': userAgent,
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'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 privatelySsrGrantMatch = (await res.text()).match(
/privatelySsrGrant\\?":\\?"(eyJ[\w-]+\.[\w-]+\.[\w-]+)/
);
if (!privatelySsrGrantMatch) {
return jsonResponse({ error: 'failed to match privately ssr grant' }, 500);
}
const grant = privatelySsrGrantMatch[1];
const proof = await generateKIDProof(grant, attemptId);
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, proof, grant }
])
});
if (!privatelyActionRes.ok) {
return jsonResponse(
{
error: `failed to execute k-id privately action (status=${privatelyActionRes.status})`
},
500
);
}
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 {
@@ -923,19 +852,23 @@ export const POST = async (event: RequestEvent) => {
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'
const res = await fetchWithWorkerProxy(
sessionId,
`${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);
return jsonResponse({ error: `failed to get shortlink (status=${res.status})` }, 400);
}
const data = await res.json();
@@ -945,10 +878,10 @@ export const POST = async (event: RequestEvent) => {
return jsonResponse({ error: 'token not found in original url' }, 500);
}
await verify(userAgent, location, token);
await verify(sessionId, userAgent, location, token);
return jsonResponse({ success: true });
}
return jsonResponse({ error: 'unexpected type' }, 400);
};
};