From 89286318b4e1094685a67fac658495fafee893de Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 21 Jan 2026 12:13:33 -0800 Subject: captcha --- src/routes/(static)/login/+page.server.ts | 10 +++--- src/routes/(static)/login/+page.svelte | 47 ++++++++++++++++++++++++++- src/routes/(static)/register/+page.server.ts | 4 +-- src/routes/(static)/register/+page.svelte | 48 +++++++++++++++++++++++++++- 4 files changed, 101 insertions(+), 8 deletions(-) (limited to 'src/routes/(static)') diff --git a/src/routes/(static)/login/+page.server.ts b/src/routes/(static)/login/+page.server.ts index dc68879a..dacb94b2 100644 --- a/src/routes/(static)/login/+page.server.ts +++ b/src/routes/(static)/login/+page.server.ts @@ -12,13 +12,15 @@ export const load = async ({ parent }) => { export const actions = { login: async ({ cookies, request }) => { const form = await fd(request); - let { username, password, token, loginRedirect, challenge } = form; + let { username, password, token, loginRedirect, challenge, recaptcha } = + form; const user = { - challenge, + challenge, username, password, token, + recaptcha, }; if (loginRedirect === "undefined") loginRedirect = undefined; @@ -35,7 +37,7 @@ export const actions = { nostr: async ({ cookies, fetch, request }) => { const form = await fd(request); - let { challenge, event, loginRedirect } = form; + let { challenge, event, loginRedirect, recaptcha } = form; if (loginRedirect === "null") loginRedirect = undefined; event = JSON.parse(event); @@ -43,7 +45,7 @@ export const actions = { const res = await fetch(`${PUBLIC_COINOS_URL}/nostrAuth`, { method: "POST", - body: JSON.stringify({ challenge, event }), + body: JSON.stringify({ challenge, event, recaptcha }), headers: { "content-type": "application/json", accept: "application/json", diff --git a/src/routes/(static)/login/+page.svelte b/src/routes/(static)/login/+page.svelte index 450425f6..08656949 100644 --- a/src/routes/(static)/login/+page.svelte +++ b/src/routes/(static)/login/+page.svelte @@ -2,7 +2,7 @@ import { browser } from "$app/environment"; import handler from "$lib/handler"; import { applyAction, deserialize } from "$app/forms"; - import { onMount, tick } from "svelte"; + import { onDestroy, onMount, tick } from "svelte"; import { fly } from "svelte/transition"; import { enhance } from "$app/forms"; import Pinpad from "$comp/Pinpad.svelte"; @@ -37,6 +37,7 @@ }); let { challenge } = $derived(data); + let recaptchaSiteKey = "6LfCd8YkAAAAANmVJgzN3SQY3n3fv1RhiS5PgMYM"; let token = $state(""); let cancel = () => (need2fa = false); @@ -53,6 +54,17 @@ let revealPassword = $state(false); + const getRecaptchaToken = () => + new Promise((resolve, reject) => { + if (!browser || !grecaptcha) return reject(new Error("captcha unavailable")); + grecaptcha.ready(() => { + grecaptcha + .execute(recaptchaSiteKey, { action: "login" }) + .then(resolve) + .catch(reject); + }); + }); + async function handleSubmit(e) { e.preventDefault(); let data = new FormData(this); @@ -62,6 +74,14 @@ data.set(k, user[k]); } + try { + const recaptcha = await getRecaptchaToken(); + data.set("recaptcha", recaptcha); + } catch (err) { + fail(err.message || "captcha failed"); + return; + } + const response = await fetch("?/login", { method: "POST", body: data, @@ -104,10 +124,12 @@ const formData = new FormData(); try { + const recaptcha = await getRecaptchaToken(); formData.append("loginRedirect", redirect); formData.append("token", token); formData.append("event", JSON.stringify(signedEvent)); formData.append("challenge", challenge); + formData.append("recaptcha", recaptcha); let response = await fetch("/login?/nostr", { method: "POST", @@ -121,8 +143,31 @@ fail(e.message); } }; + + 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(); + } + }); + +
diff --git a/src/routes/(static)/register/+page.server.ts b/src/routes/(static)/register/+page.server.ts index 47ff8d27..d2dbe299 100644 --- a/src/routes/(static)/register/+page.server.ts +++ b/src/routes/(static)/register/+page.server.ts @@ -15,11 +15,11 @@ export const actions = { default: async ({ cookies, request }) => { const ip = request.headers.get("cf-connecting-ip"); const form = await fd(request); - const { picture, username, password, challenge } = form; + const { picture, username, password, challenge, recaptcha } = form; let { loginRedirect } = form; if (loginRedirect === "undefined") loginRedirect = undefined; - const user = { picture, username, password, challenge }; + const user = { picture, username, password, challenge, recaptcha }; return register(user, ip, cookies, loginRedirect); }, }; diff --git a/src/routes/(static)/register/+page.svelte b/src/routes/(static)/register/+page.svelte index 9e255b8e..f1ac582b 100644 --- a/src/routes/(static)/register/+page.svelte +++ b/src/routes/(static)/register/+page.svelte @@ -1,5 +1,5 @@ + + {#if need2fa} {/if} -- cgit v1.2.3