revert captcha
This commit is contained in:
parent
519236913e
commit
96cb799cc7
12 changed files with 12 additions and 186 deletions
|
|
@ -27,8 +27,3 @@ PUBLIC_B2_REGION=us-west-002
|
||||||
|
|
||||||
# OpenAI (for AI features)
|
# OpenAI (for AI features)
|
||||||
OPENROUTER_API_KEY=your_openrouter_api_key
|
OPENROUTER_API_KEY=your_openrouter_api_key
|
||||||
|
|
||||||
# Turnstile (for CAPTCHA)
|
|
||||||
# The default ones are for testing purposes only, and will accept any request.
|
|
||||||
PUBLIC_TURNSTILE_SITE_KEY=1x00000000000000000000AA
|
|
||||||
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
|
|
||||||
19
website/package-lock.json
generated
19
website/package-lock.json
generated
|
|
@ -28,8 +28,7 @@
|
||||||
"sharp": "^0.34.2",
|
"sharp": "^0.34.2",
|
||||||
"svelte-apexcharts": "^1.0.2",
|
"svelte-apexcharts": "^1.0.2",
|
||||||
"svelte-confetti": "^2.3.1",
|
"svelte-confetti": "^2.3.1",
|
||||||
"svelte-lightweight-charts": "^2.2.0",
|
"svelte-lightweight-charts": "^2.2.0"
|
||||||
"svelte-turnstile": "^0.11.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@internationalized/date": "^3.8.1",
|
"@internationalized/date": "^3.8.1",
|
||||||
|
|
@ -5417,17 +5416,6 @@
|
||||||
"svelte": "^5.30.2"
|
"svelte": "^5.30.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte-turnstile": {
|
|
||||||
"version": "0.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/svelte-turnstile/-/svelte-turnstile-0.11.0.tgz",
|
|
||||||
"integrity": "sha512-2LFklx9JVsR3fJ7e3fGG1HEAWWEqRq1WfNaVrKgZJ+pzfY2NColiH+wH0kK2yX3DrcGLiJ9vBeTyiLFWotKpLA==",
|
|
||||||
"dependencies": {
|
|
||||||
"turnstile-types": "^1.2.3"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"svelte": "^3.58.0 || ^4.0.0 || ^5.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/svg.draggable.js": {
|
"node_modules/svg.draggable.js": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -5590,11 +5578,6 @@
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/turnstile-types": {
|
|
||||||
"version": "1.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/turnstile-types/-/turnstile-types-1.2.3.tgz",
|
|
||||||
"integrity": "sha512-EDjhDB9TDwda2JRbhzO/kButPio3JgrC3gXMVAMotxldybTCJQVMvPNJ89rcAiN9vIrCb2i1E+VNBCqB8wue0A=="
|
|
||||||
},
|
|
||||||
"node_modules/tw-animate-css": {
|
"node_modules/tw-animate-css": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,7 @@
|
||||||
"sharp": "^0.34.2",
|
"sharp": "^0.34.2",
|
||||||
"svelte-apexcharts": "^1.0.2",
|
"svelte-apexcharts": "^1.0.2",
|
||||||
"svelte-confetti": "^2.3.1",
|
"svelte-confetti": "^2.3.1",
|
||||||
"svelte-lightweight-charts": "^2.2.0",
|
"svelte-lightweight-charts": "^2.2.0"
|
||||||
"svelte-turnstile": "^0.11.0"
|
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-linux-x64-gnu": "*"
|
"@rollup/rollup-linux-x64-gnu": "*"
|
||||||
|
|
|
||||||
4
website/src/app.d.ts
vendored
4
website/src/app.d.ts
vendored
|
|
@ -4,13 +4,11 @@ declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
userSession: User;
|
userSession: User;
|
||||||
turnstileVerified?: boolean;
|
|
||||||
}
|
}
|
||||||
interface PageData {
|
interface PageData {
|
||||||
userSession: User;
|
userSession: User;
|
||||||
turnstileVerified?: boolean;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { };
|
export {};
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { db } from '$lib/server/db';
|
||||||
import { user } from '$lib/server/db/schema';
|
import { user } from '$lib/server/db/schema';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { minesCleanupInactiveGames, minesAutoCashout } from '$lib/server/games/mines';
|
import { minesCleanupInactiveGames, minesAutoCashout } from '$lib/server/games/mines';
|
||||||
import { isTurnstileVerifiedRedis } from '$lib/server/redis';
|
|
||||||
|
|
||||||
async function initializeScheduler() {
|
async function initializeScheduler() {
|
||||||
if (building) return;
|
if (building) return;
|
||||||
|
|
@ -180,12 +179,6 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||||
|
|
||||||
event.locals.userSession = userData;
|
event.locals.userSession = userData;
|
||||||
|
|
||||||
if (session?.user?.id) {
|
|
||||||
event.locals.turnstileVerified = await isTurnstileVerifiedRedis(session.user.id);
|
|
||||||
} else {
|
|
||||||
event.locals.turnstileVerified = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.url.pathname.startsWith('/api/')) {
|
if (event.url.pathname.startsWith('/api/')) {
|
||||||
const response = await svelteKitHandler({ event, resolve, auth });
|
const response = await svelteKitHandler({ event, resolve, auth });
|
||||||
response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@
|
||||||
import { Send, DollarSign, Coins, Loader2 } from 'lucide-svelte';
|
import { Send, DollarSign, Coins, Loader2 } from 'lucide-svelte';
|
||||||
import { PORTFOLIO_DATA } from '$lib/stores/portfolio-data';
|
import { PORTFOLIO_DATA } from '$lib/stores/portfolio-data';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { Turnstile } from 'svelte-turnstile';
|
|
||||||
import { PUBLIC_TURNSTILE_SITE_KEY } from '$env/static/public';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
open = $bindable(false),
|
open = $bindable(false),
|
||||||
|
|
@ -27,9 +24,6 @@
|
||||||
let amount = $state('');
|
let amount = $state('');
|
||||||
let selectedCoinSymbol = $state('');
|
let selectedCoinSymbol = $state('');
|
||||||
let loading = $state(false);
|
let loading = $state(false);
|
||||||
let turnstileToken = $state('');
|
|
||||||
let turnstileError = $state('');
|
|
||||||
let turnstileReset = $state<(() => void) | undefined>(undefined);
|
|
||||||
|
|
||||||
let numericAmount = $derived(parseFloat(amount) || 0);
|
let numericAmount = $derived(parseFloat(amount) || 0);
|
||||||
let hasValidAmount = $derived(numericAmount > 0);
|
let hasValidAmount = $derived(numericAmount > 0);
|
||||||
|
|
@ -63,9 +57,6 @@
|
||||||
|
|
||||||
let isWithinCoinValueLimit = $derived(transferType === 'COIN' ? estimatedValue >= 10 : true);
|
let isWithinCoinValueLimit = $derived(transferType === 'COIN' ? estimatedValue >= 10 : true);
|
||||||
|
|
||||||
const turnstileVerified = $derived(!!$page.data?.turnstileVerified);
|
|
||||||
let optimisticTurnstileVerified = $state(false);
|
|
||||||
|
|
||||||
let canSend = $derived(
|
let canSend = $derived(
|
||||||
hasValidAmount &&
|
hasValidAmount &&
|
||||||
hasValidRecipient &&
|
hasValidRecipient &&
|
||||||
|
|
@ -73,8 +64,7 @@
|
||||||
isWithinCashLimit &&
|
isWithinCashLimit &&
|
||||||
isWithinCoinValueLimit &&
|
isWithinCoinValueLimit &&
|
||||||
!loading &&
|
!loading &&
|
||||||
(transferType === 'CASH' || selectedCoinSymbol.length > 0) &&
|
(transferType === 'CASH' || selectedCoinSymbol.length > 0)
|
||||||
(turnstileVerified || optimisticTurnstileVerified || !!turnstileToken)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
|
|
@ -124,8 +114,7 @@
|
||||||
recipientUsername: recipientUsername.trim(),
|
recipientUsername: recipientUsername.trim(),
|
||||||
type: transferType,
|
type: transferType,
|
||||||
amount: numericAmount,
|
amount: numericAmount,
|
||||||
coinSymbol: transferType === 'COIN' ? selectedCoinSymbol : undefined,
|
coinSymbol: transferType === 'COIN' ? selectedCoinSymbol : undefined
|
||||||
turnstileToken
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -152,9 +141,6 @@
|
||||||
|
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
turnstileToken = '';
|
|
||||||
optimisticTurnstileVerified = true;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error('Transfer failed', {
|
toast.error('Transfer failed', {
|
||||||
description: (e as Error).message
|
description: (e as Error).message
|
||||||
|
|
@ -339,34 +325,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !(turnstileVerified || optimisticTurnstileVerified)}
|
|
||||||
<div>
|
|
||||||
<Turnstile
|
|
||||||
siteKey={PUBLIC_TURNSTILE_SITE_KEY}
|
|
||||||
theme="auto"
|
|
||||||
size="normal"
|
|
||||||
bind:reset={turnstileReset}
|
|
||||||
on:callback={(e: CustomEvent<{ token: string }>) => {
|
|
||||||
turnstileToken = e.detail.token;
|
|
||||||
turnstileError = '';
|
|
||||||
}}
|
|
||||||
on:error={(e: CustomEvent<{ code: string }>) => {
|
|
||||||
turnstileToken = '';
|
|
||||||
turnstileError = e.detail.code || 'Captcha error';
|
|
||||||
}}
|
|
||||||
on:expired={() => {
|
|
||||||
turnstileToken = '';
|
|
||||||
turnstileError = 'Captcha expired';
|
|
||||||
}}
|
|
||||||
execution="render"
|
|
||||||
appearance="always"
|
|
||||||
/>
|
|
||||||
{#if turnstileError}
|
|
||||||
<p class="text-destructive mt-1 text-xs">{turnstileError}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog.Footer class="flex gap-2">
|
<Dialog.Footer class="flex gap-2">
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@
|
||||||
import { TrendingUp, TrendingDown, Loader2 } from 'lucide-svelte';
|
import { TrendingUp, TrendingDown, Loader2 } from 'lucide-svelte';
|
||||||
import { PORTFOLIO_SUMMARY } from '$lib/stores/portfolio-data';
|
import { PORTFOLIO_SUMMARY } from '$lib/stores/portfolio-data';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import { Turnstile } from 'svelte-turnstile';
|
|
||||||
import { PUBLIC_TURNSTILE_SITE_KEY } from '$env/static/public';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
open = $bindable(false),
|
open = $bindable(false),
|
||||||
|
|
@ -27,8 +24,6 @@
|
||||||
|
|
||||||
let amount = $state('');
|
let amount = $state('');
|
||||||
let loading = $state(false);
|
let loading = $state(false);
|
||||||
let turnstileToken = $state('');
|
|
||||||
let turnstileError = $state('');
|
|
||||||
|
|
||||||
let numericAmount = $derived(parseFloat(amount) || 0);
|
let numericAmount = $derived(parseFloat(amount) || 0);
|
||||||
let currentPrice = $derived(coin.currentPrice || 0);
|
let currentPrice = $derived(coin.currentPrice || 0);
|
||||||
|
|
@ -44,14 +39,7 @@
|
||||||
let hasEnoughFunds = $derived(
|
let hasEnoughFunds = $derived(
|
||||||
type === 'BUY' ? numericAmount <= userBalance : numericAmount <= userHolding
|
type === 'BUY' ? numericAmount <= userBalance : numericAmount <= userHolding
|
||||||
);
|
);
|
||||||
const turnstileVerified = $derived(!!$page.data?.turnstileVerified);
|
let canTrade = $derived(hasValidAmount && hasEnoughFunds && !loading);
|
||||||
let optimisticTurnstileVerified = $state(false);
|
|
||||||
|
|
||||||
let showCaptcha = $derived(!(turnstileVerified || optimisticTurnstileVerified));
|
|
||||||
|
|
||||||
let canTrade = $derived(
|
|
||||||
hasValidAmount && hasEnoughFunds && !loading && (!showCaptcha || !!turnstileToken)
|
|
||||||
);
|
|
||||||
|
|
||||||
function calculateEstimate(amount: number, tradeType: 'BUY' | 'SELL', price: number) {
|
function calculateEstimate(amount: number, tradeType: 'BUY' | 'SELL', price: number) {
|
||||||
if (!amount || !price || !coin) return { result: 0 };
|
if (!amount || !price || !coin) return { result: 0 };
|
||||||
|
|
@ -82,8 +70,6 @@
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let turnstileReset = $state<(() => void) | undefined>(undefined);
|
|
||||||
|
|
||||||
async function handleTrade() {
|
async function handleTrade() {
|
||||||
if (!canTrade) return;
|
if (!canTrade) return;
|
||||||
|
|
||||||
|
|
@ -96,8 +82,7 @@
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
type,
|
type,
|
||||||
amount: numericAmount,
|
amount: numericAmount
|
||||||
turnstileToken
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -116,9 +101,6 @@
|
||||||
|
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
handleClose();
|
handleClose();
|
||||||
|
|
||||||
turnstileToken = '';
|
|
||||||
optimisticTurnstileVerified = true;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error('Trade failed', {
|
toast.error('Trade failed', {
|
||||||
description: (e as Error).message
|
description: (e as Error).message
|
||||||
|
|
@ -212,34 +194,6 @@
|
||||||
{type === 'BUY' ? 'Insufficient funds' : 'Insufficient coins'}
|
{type === 'BUY' ? 'Insufficient funds' : 'Insufficient coins'}
|
||||||
</Badge>
|
</Badge>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showCaptcha}
|
|
||||||
<div>
|
|
||||||
<Turnstile
|
|
||||||
siteKey={PUBLIC_TURNSTILE_SITE_KEY}
|
|
||||||
theme="auto"
|
|
||||||
size="normal"
|
|
||||||
bind:reset={turnstileReset}
|
|
||||||
on:callback={(e: CustomEvent<{ token: string }>) => {
|
|
||||||
turnstileToken = e.detail.token;
|
|
||||||
turnstileError = '';
|
|
||||||
}}
|
|
||||||
on:error={(e: CustomEvent<{ code: string }>) => {
|
|
||||||
turnstileToken = '';
|
|
||||||
turnstileError = e.detail.code || 'Captcha error';
|
|
||||||
}}
|
|
||||||
on:expired={() => {
|
|
||||||
turnstileToken = '';
|
|
||||||
turnstileError = 'Captcha expired';
|
|
||||||
}}
|
|
||||||
execution="render"
|
|
||||||
appearance="always"
|
|
||||||
/>
|
|
||||||
{#if turnstileError}
|
|
||||||
<p class="text-destructive mt-1 text-xs">{turnstileError}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog.Footer class="flex gap-2">
|
<Dialog.Footer class="flex gap-2">
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,3 @@ if (!building) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export { client as redis };
|
export { client as redis };
|
||||||
|
|
||||||
const TURNSTILE_PREFIX = 'turnstile:verified:';
|
|
||||||
const TURNSTILE_TTL = 5 * 60; // 5 minutes
|
|
||||||
|
|
||||||
export async function setTurnstileVerifiedRedis(userId: string) {
|
|
||||||
await client.set(`${TURNSTILE_PREFIX}${userId}`, '1', { EX: TURNSTILE_TTL });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function isTurnstileVerifiedRedis(userId: string): Promise<boolean> {
|
|
||||||
return !!(await client.get(`${TURNSTILE_PREFIX}${userId}`));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import { env } from '$env/dynamic/private';
|
|
||||||
|
|
||||||
const TURNSTILE_SECRET = env.TURNSTILE_SECRET_KEY;
|
|
||||||
|
|
||||||
export async function verifyTurnstile(token: string, request: Request): Promise<boolean> {
|
|
||||||
if (!TURNSTILE_SECRET) return false;
|
|
||||||
const ip = request.headers.get('x-forwarded-for') || request.headers.get('cf-connecting-ip') || undefined;
|
|
||||||
const body = new URLSearchParams({
|
|
||||||
secret: TURNSTILE_SECRET,
|
|
||||||
response: token,
|
|
||||||
...(ip ? { remoteip: ip } : {})
|
|
||||||
});
|
|
||||||
const res = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
|
||||||
method: 'POST',
|
|
||||||
body,
|
|
||||||
headers: { 'content-type': 'application/x-www-form-urlencoded' }
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
return !!data.success;
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,5 @@ export const load: LayoutServerLoad = async (event) => {
|
||||||
return {
|
return {
|
||||||
userSession: event.locals.userSession,
|
userSession: event.locals.userSession,
|
||||||
url: event.url.pathname,
|
url: event.url.pathname,
|
||||||
turnstileVerified: event.locals.turnstileVerified ?? false
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -5,8 +5,6 @@ import { coin, userPortfolio, user, transaction, priceHistory } from '$lib/serve
|
||||||
import { eq, and, gte } from 'drizzle-orm';
|
import { eq, and, gte } from 'drizzle-orm';
|
||||||
import { redis } from '$lib/server/redis';
|
import { redis } from '$lib/server/redis';
|
||||||
import { createNotification } from '$lib/server/notification';
|
import { createNotification } from '$lib/server/notification';
|
||||||
import { verifyTurnstile } from '$lib/server/turnstile';
|
|
||||||
import { setTurnstileVerifiedRedis, isTurnstileVerifiedRedis } from '$lib/server/redis';
|
|
||||||
|
|
||||||
async function calculate24hMetrics(coinId: number, currentPrice: number) {
|
async function calculate24hMetrics(coinId: number, currentPrice: number) {
|
||||||
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||||
|
|
@ -55,16 +53,7 @@ export async function POST({ params, request }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { coinSymbol } = params;
|
const { coinSymbol } = params;
|
||||||
const { type, amount, turnstileToken } = await request.json();
|
const { type, amount } = await request.json();
|
||||||
|
|
||||||
const alreadyVerified = await isTurnstileVerifiedRedis(session.user.id);
|
|
||||||
|
|
||||||
if (!alreadyVerified) {
|
|
||||||
if (!turnstileToken || !(await verifyTurnstile(turnstileToken, request))) {
|
|
||||||
throw error(400, 'Captcha verification failed');
|
|
||||||
}
|
|
||||||
await setTurnstileVerifiedRedis(session.user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!['BUY', 'SELL'].includes(type)) {
|
if (!['BUY', 'SELL'].includes(type)) {
|
||||||
throw error(400, 'Invalid transaction type');
|
throw error(400, 'Invalid transaction type');
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import { user, userPortfolio, coin, transaction } from '$lib/server/db/schema';
|
||||||
import { eq, and } from 'drizzle-orm';
|
import { eq, and } from 'drizzle-orm';
|
||||||
import { createNotification } from '$lib/server/notification';
|
import { createNotification } from '$lib/server/notification';
|
||||||
import { formatValue } from '$lib/utils';
|
import { formatValue } from '$lib/utils';
|
||||||
import { verifyTurnstile } from '$lib/server/turnstile';
|
|
||||||
import { setTurnstileVerifiedRedis, isTurnstileVerifiedRedis } from '$lib/server/redis';
|
|
||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
interface TransferRequest {
|
interface TransferRequest {
|
||||||
|
|
@ -24,16 +22,7 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
throw error(401, 'Not authenticated');
|
throw error(401, 'Not authenticated');
|
||||||
} try {
|
} try {
|
||||||
const { recipientUsername, type, amount, coinSymbol, turnstileToken }: TransferRequest & { turnstileToken?: string } = await request.json();
|
const { recipientUsername, type, amount, coinSymbol }: TransferRequest = await request.json();
|
||||||
|
|
||||||
const alreadyVerified = await isTurnstileVerifiedRedis(session.user.id);
|
|
||||||
|
|
||||||
if (!alreadyVerified) {
|
|
||||||
if (!turnstileToken || !(await verifyTurnstile(turnstileToken, request))) {
|
|
||||||
throw error(400, 'Captcha verification failed');
|
|
||||||
}
|
|
||||||
await setTurnstileVerifiedRedis(session.user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!recipientUsername || !type || !amount || typeof amount !== 'number' || !Number.isFinite(amount) || amount <= 0) {
|
if (!recipientUsername || !type || !amount || typeof amount !== 'number' || !Number.isFinite(amount) || amount <= 0) {
|
||||||
throw error(400, 'Invalid transfer parameters');
|
throw error(400, 'Invalid transfer parameters');
|
||||||
|
|
|
||||||
Reference in a new issue