Files
simple-proxy/src/utils/turnstile.ts
2023-12-20 14:32:15 +01:00

88 lines
2.4 KiB
TypeScript

import { H3Event, EventHandlerRequest } from 'h3';
import jsonwebtoken from '@tsndr/cloudflare-worker-jwt';
import { getIp } from '@/utils/ip';
const turnstileSecret = process.env.TURNSTILE_SECRET ?? null;
const jwtSecret = process.env.JWT_SECRET ?? null;
const tokenHeader = 'X-Token';
const jwtPrefix = 'jwt|';
const turnstilePrefix = 'turnstile|';
export function isTurnstileEnabled() {
return !!turnstileSecret && !!jwtSecret;
}
export async function makeToken(ip: string) {
if (!jwtSecret) throw new Error('Cannot make token without a secret');
return await jsonwebtoken.sign(
{
ip,
exp: Math.floor(Date.now() / 1000) + 60 * 10, // 10 Minutes
},
jwtSecret,
);
}
export function setTokenHeader(
event: H3Event<EventHandlerRequest>,
token: string,
) {
setHeader(event, tokenHeader, token);
}
export async function createTokenIfNeeded(
event: H3Event<EventHandlerRequest>,
): Promise<null | string> {
if (!isTurnstileEnabled()) return null;
if (!jwtSecret) return null;
const token = event.headers.get(tokenHeader);
if (!token) return null;
if (!token.startsWith(turnstilePrefix)) return null;
return await makeToken(getIp(event));
}
export async function isAllowedToMakeRequest(
event: H3Event<EventHandlerRequest>,
) {
if (!isTurnstileEnabled()) return true;
const token = event.headers.get(tokenHeader);
if (!token) return false;
if (!jwtSecret || !turnstileSecret) return false;
if (token.startsWith(jwtPrefix)) {
const jwtToken = token.slice(jwtPrefix.length);
const isValid = await jsonwebtoken.verify(jwtToken, jwtSecret, {
algorithm: 'HS256',
});
if (!isValid) return false;
const jwtBody = jsonwebtoken.decode<{ ip: string }>(jwtToken);
if (!jwtBody.payload) return false;
if (getIp(event) !== jwtBody.payload.ip) return false;
return true;
}
if (token.startsWith(turnstilePrefix)) {
const turnstileToken = token.slice(turnstilePrefix.length);
const formData = new FormData();
formData.append('secret', turnstileSecret);
formData.append('response', turnstileToken);
formData.append('remoteip', getIp(event));
const result = await fetch(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
{
body: formData,
method: 'POST',
},
);
const outcome: { success: boolean } = await result.json();
return outcome.success;
}
return false;
}