aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-02-06 22:36:03 -0800
committerAdam Soltys <[email protected]>2026-02-06 22:36:03 -0800
commit3a27ae85e5167c0745a28061d03c7cd9c4f52230 (patch)
treea6e8838a607ff9f818953e8737b58a7171942bb0 /src
parente03bc9bd0d7360899c5649caca6dc832181ac0dc (diff)
downloadcoinos-ui-3a27ae85e5167c0745a28061d03c7cd9c4f52230.tar.gz
coinos-ui-3a27ae85e5167c0745a28061d03c7cd9c4f52230.zip
pass captcha when sweeping
Diffstat (limited to 'src')
-rw-r--r--src/routes/(app)/fund/[id]/sweep/[...amount]/+page.server.ts74
-rw-r--r--src/routes/(app)/fund/[id]/sweep/[...amount]/+page.svelte118
2 files changed, 146 insertions, 46 deletions
diff --git a/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.server.ts b/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.server.ts
index 78df3461..13ce5e9d 100644
--- a/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.server.ts
+++ b/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.server.ts
@@ -1,9 +1,9 @@
import { randomName, randomPassword } from "$lib/random";
import getRates from "$lib/rates";
-import { auth, get, post, register, sats } from "$lib/utils";
+import { auth, get, login, post, sats } from "$lib/utils";
import { error, redirect } from "@sveltejs/kit";
-export async function load({ cookies, request, params, parent }) {
+export async function load({ cookies, params, parent }) {
let { user } = await parent();
const { id } = params;
let [amount, currency] = params.amount.split("/");
@@ -21,31 +21,59 @@ export async function load({ cookies, request, params, parent }) {
amount = Math.round((amount * sats) / rate);
}
- const username = randomName() + Math.floor(Math.random() * 99) + 1;
- const password = randomPassword();
-
if (!amount) redirect(307, `/fund/${id}`);
- if (!user) {
+
+ if (user) {
+ try {
+ ({ amount } = await post("/take", { amount, id }, auth(cookies)));
+ } catch (e) {
+ redirect(307, `/fund/${id}`);
+ }
+
+ return { amount, currency, id, rate, rates, password: null };
+ }
+
+ return { amount, currency, id, rate, rates, needsCaptcha: true };
+}
+
+export const actions = {
+ default: async ({ cookies, request, params }) => {
+ const { id } = params;
+ const form = await request.formData();
+ const recaptcha = form.get("recaptcha") as string;
+ const amount = Number(form.get("amount"));
const ip = request.headers.get("cf-connecting-ip");
+ const host = request.headers.get("host");
- user = {
- username,
- password,
- fresh: true,
- };
+ const username = randomName() + Math.floor(Math.random() * 99) + 1;
+ const password = randomPassword();
- let redirectUrl = `/fund/${id}/sweep`;
- if (params.amount && !params.currency) redirectUrl += `/${params.amount}`;
- if (params.currency) redirectUrl += `/${params.amount}/params.currency}`;
+ const user = { username, password, fresh: true, recaptcha };
- await register(user, ip, cookies, redirectUrl);
- }
+ const { sk } = await post(
+ "/register",
+ { user },
+ { "cf-connecting-ip": ip },
+ );
- try {
- ({ amount } = await post("/take", { amount, id }, auth(cookies)));
- } catch (e) {
- redirect(307, `/fund/${id}`);
- }
+ await login(user, cookies, ip, host);
- return { amount, currency, id, rate, rates, password };
-}
+ const expires = new Date();
+ expires.setSeconds(expires.getSeconds() + 21000000);
+ cookies.set("sk", sk, {
+ path: "/",
+ expires,
+ httpOnly: false,
+ sameSite: "lax",
+ });
+
+ let swept;
+ try {
+ swept = await post("/take", { amount, id }, auth(cookies));
+ } catch (e) {
+ redirect(307, `/fund/${id}`);
+ }
+
+ return { amount: swept.amount, password, username };
+ },
+};
diff --git a/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.svelte b/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.svelte
index 7a7d5402..fbe8e6ca 100644
--- a/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.svelte
+++ b/src/routes/(app)/fund/[id]/sweep/[...amount]/+page.svelte
@@ -1,36 +1,108 @@
<script>
- import { onMount } from "svelte";
+ import { PUBLIC_RECAPTCHA_SITE_KEY } from "$env/static/public";
+ import { onDestroy, onMount, tick } from "svelte";
+ import { browser } from "$app/environment";
+ import { enhance } from "$app/forms";
import { password } from "$lib/store";
- import Amount from "$comp/Amount.svelte";
import Success from "$comp/Success.svelte";
- import { scale } from "svelte/transition";
- import { toFiat, f, s } from "$lib/utils";
- import Icon from "$comp/Icon.svelte";
+ import Spinner from "$comp/Spinner.svelte";
import { t } from "$lib/translations";
import { loc } from "$lib/utils";
let { data } = $props();
-
- let { amount, currency, id, user, rate, rates, password: pw } = data;
+ let { amount, currency, id, user, rate, rates, needsCaptcha } = $derived(data);
let locale = $derived(loc(user));
- onMount(() => ($password = pw));
+ let recaptchaToken = $state("");
+ let formEl = $state();
+ let loading = $state(!!needsCaptcha);
+ let swept = $state(!needsCaptcha);
+ let sweptAmount = $state(amount);
+ let sweptUsername = $state(user?.username);
+ let recaptchaSiteKey = PUBLIC_RECAPTCHA_SITE_KEY;
+ let isTor = browser && location.hostname.endsWith(".onion");
+
+ function handleEnhance() {
+ return async ({ result }) => {
+ if (result.type === "success") {
+ sweptAmount = result.data.amount;
+ sweptUsername = result.data.username;
+ $password = result.data.password;
+ loading = false;
+ swept = true;
+ }
+ };
+ }
+
+ onMount(() => {
+ if (!needsCaptcha) {
+ $password = data.password;
+ return;
+ }
+
+ if (isTor || !recaptchaSiteKey) {
+ recaptchaToken = "";
+ tick().then(() => formEl?.requestSubmit());
+ return;
+ }
+
+ let s = document.createElement("script");
+ s.src =
+ "https://www.google.com/recaptcha/api.js?render=" + recaptchaSiteKey;
+ s.onload = () => {
+ grecaptcha.ready(() => {
+ grecaptcha
+ .execute(recaptchaSiteKey, { action: "register" })
+ .then((token) => {
+ recaptchaToken = token;
+ tick().then(() => formEl?.requestSubmit());
+ });
+ });
+ };
+ document.head.appendChild(s);
+ });
+
+ onDestroy(() => {
+ if (!browser) return;
+ const nodeBadge = document.querySelector(".grecaptcha-badge");
+ if (nodeBadge) {
+ document.body.removeChild(nodeBadge.parentNode);
+ }
+ const scriptSelector =
+ "script[src='https://www.google.com/recaptcha/api.js?render=" +
+ recaptchaSiteKey +
+ "']";
+ const script = document.querySelector(scriptSelector);
+ if (script) {
+ script.remove();
+ }
+ });
</script>
-<div class="container px-4 text-center mx-auto">
- <Success
- {amount}
- {rate}
- {currency}
- {locale}
- title={$t("payments.received")}
- />
-</div>
+{#if loading}
+ <div class="flex justify-center items-center h-64">
+ <Spinner />
+ </div>
+ <form method="POST" class="hidden" bind:this={formEl} use:enhance={handleEnhance}>
+ <input type="hidden" name="recaptcha" value={recaptchaToken} />
+ <input type="hidden" name="amount" value={amount} />
+ </form>
+{:else if swept}
+ <div class="container px-4 text-center mx-auto">
+ <Success
+ amount={sweptAmount}
+ {rate}
+ {currency}
+ {locale}
+ title={$t("payments.received")}
+ />
+ </div>
-<a href={`/${user.username}`} aria-label="Continue">
- <div class="opacity-0 w-screen h-screen fixed top-24 left-0 z-50"></div>
-</a>
+ <a href={`/${sweptUsername}`} aria-label="Continue">
+ <div class="opacity-0 w-screen h-screen fixed top-24 left-0 z-50"></div>
+ </a>
-<div class="flex justify-center mt-4">
- {$t("payments.tapAnywhere")}
-</div>
+ <div class="flex justify-center mt-4">
+ {$t("payments.tapAnywhere")}
+ </div>
+{/if}