feat: email verification for free tier signup

- Signup now requires email verification before API key is revealed
- Verification token sent via email (Resend) with console fallback
- GET /verify?token=xxx shows API key in styled HTML page
- Handles expired (24h), invalid, and already-verified tokens
- Frontend modal shows 'check your email' instead of key
- Keeps existing rate limiting
This commit is contained in:
OpenClaw 2026-02-14 18:12:25 +00:00
parent 890b82e5ec
commit 1b20665b0d
7 changed files with 252 additions and 29 deletions

View file

@ -9,6 +9,7 @@ function openSignup() {
document.getElementById('signupModal').classList.add('active');
showState('signupInitial');
document.getElementById('signupError').style.display = 'none';
document.getElementById('signupEmail').value = '';
}
function closeSignup() {
@ -18,6 +19,15 @@ function closeSignup() {
async function submitSignup() {
var errEl = document.getElementById('signupError');
var btn = document.getElementById('signupBtn');
var emailInput = document.getElementById('signupEmail');
var email = emailInput.value.trim();
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
errEl.textContent = 'Please enter a valid email address.';
errEl.style.display = 'block';
return;
}
errEl.style.display = 'none';
btn.disabled = true;
showState('signupLoading');
@ -26,7 +36,7 @@ async function submitSignup() {
var res = await fetch('/v1/signup/free', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({})
body: JSON.stringify({ email: email })
});
var data = await res.json();
@ -38,7 +48,7 @@ async function submitSignup() {
return;
}
document.getElementById('apiKeyText').textContent = data.apiKey;
// Show "check your email" message
showState('signupResult');
} catch (err) {
showState('signupInitial');
@ -86,11 +96,9 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('btn-checkout').addEventListener('click', checkout);
document.getElementById('btn-close-signup').addEventListener('click', closeSignup);
document.getElementById('signupBtn').addEventListener('click', submitSignup);
document.getElementById('copyBtn').addEventListener('click', copyKey);
document.getElementById('signupModal').addEventListener('click', function(e) {
if (e.target === this) closeSignup();
});
// Smooth scroll for nav links
document.querySelectorAll('a[href^="#"]').forEach(function(a) {
a.addEventListener('click', function(e) {
e.preventDefault();