Feat: Add reCAPTCHA to auth forms

This commit is contained in:
2025-09-28 12:16:33 -07:00
parent decd2fb485
commit 0e4381938b

View File

@@ -7,6 +7,7 @@
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body class="bg-slate-900 text-slate-200 min-h-screen flex items-center justify-center font-sans overflow-hidden">
@@ -35,7 +36,7 @@
<!-- Auth Forms View -->
<div x-data="authForm()" x-show="view !== 'landing'" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform scale-90" x-transition:enter-end="opacity-100 transform scale-100" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-end="opacity-0 transform scale-90" class="bg-slate-800/50 backdrop-blur-sm p-8 rounded-xl border border-slate-700" style="display: none;">
<button @click="view = 'landing'" class="absolute top-4 left-4 text-slate-400 hover:text-white transition-colors">
<button @click="view = 'landing'; error=''; grecaptcha.reset();" class="absolute top-4 left-4 text-slate-400 hover:text-white transition-colors">
<i data-lucide="arrow-left"></i>
</button>
@@ -45,7 +46,8 @@
<form @submit.prevent="submit('signin')" class="space-y-4">
<input type="text" x-model="username" placeholder="Username" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500">
<input type="password" x-model="password" placeholder="Password" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500">
<p x-text="error" x-show="error" class="text-rose-400 text-sm h-5"></p>
<div class="g-recaptcha flex justify-center" data-sitekey="6LeXhdYrAAAAALW6DdgxNeHU0kwBncdicLnVYvXT"></div>
<p x-text="error" x-show="error" class="text-rose-400 text-sm h-5 !-mt-2 text-center"></p>
<button type="submit" :disabled="loading" class="w-full py-3 font-semibold rounded-lg text-white bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity flex items-center justify-center disabled:opacity-50">
<span x-show="!loading">Login</span>
<i x-show="loading" data-lucide="loader-2" class="animate-spin w-6 h-6"></i>
@@ -59,7 +61,8 @@
<form @submit.prevent="submit('signup')" class="space-y-4">
<input type="text" x-model="username" placeholder="Username" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="password" x-model="password" placeholder="Password" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
<p x-text="error" x-show="error" class="text-rose-400 text-sm h-5"></p>
<div class="g-recaptcha flex justify-center" data-sitekey="6LeXhdYrAAAAALW6DdgxNeHU0kwBncdicLnVYvXT"></div>
<p x-text="error" x-show="error" class="text-rose-400 text-sm h-5 !-mt-2 text-center"></p>
<button type="submit" :disabled="loading" class="w-full py-3 font-semibold rounded-lg text-white bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity flex items-center justify-center disabled:opacity-50">
<span x-show="!loading">Sign Up</span>
<i x-show="loading" data-lucide="loader-2" class="animate-spin w-6 h-6"></i>
@@ -81,7 +84,7 @@
<script>lucide.createIcons();</script>
<script src="https://cdn.jsdelivr.net/npm/scrypt-js@3.0.1/scrypt.min.js"></script>
<script>
function authForm(){return{username:"",password:"",error:"",loading:!1,async submit(e){this.loading=!0,this.error="";try{const t=new TextEncoder,s=t.encode(this.password),o=t.encode(this.username.padEnd(16,"0").substring(0,16)),a=await scrypt.scrypt(s,o,16384,8,1,32),n=Array.from(a).map(e=>e.toString(16).padStart(2,"0")).join(""),i=await fetch(`/api/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:this.username,pass_hash:n})});if(!i.ok)throw new Error(await i.text()||"An error occurred");const r=await i.json();sessionStorage.setItem("pass_hash",n),sessionStorage.setItem("username",r.username),window.location.href="/dash/"}catch(t){this.error=t.message}finally{this.loading=!1}}}}
function authForm(){return{username:"",password:"",error:"",loading:!1,async submit(e){this.loading=!0,this.error="";const c=grecaptcha.getResponse();if(!c)return this.error="Please complete the CAPTCHA.",this.loading=!1,void 0;try{const t=new TextEncoder,s=t.encode(this.password),o=t.encode(this.username.padEnd(16,"0").substring(0,16)),a=await scrypt.scrypt(s,o,16384,8,1,32),n=Array.from(a).map(e=>e.toString(16).padStart(2,"0")).join(""),i=await fetch(`/api/${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:this.username,pass_hash:n,"g-recaptcha-response":c})});if(!i.ok)throw new Error(await i.text()||"An error occurred");const r=await i.json();sessionStorage.setItem("pass_hash",n),sessionStorage.setItem("username",r.username),window.location.href="/dash/"}catch(t){this.error=t.message}finally{this.loading=!1,grecaptcha.reset()}}}}
</script>
</body>
</html>