TODO: Author: RJ
Why I Love Hugo (and Static Sites in General)
I created this site using Hugo, a static site generator. I prefer static sites because of their simplicity, which means things are easy to update and deploy. And since I’m just serving a bunch of HTML files, security is not an issue - there are no real attack vectors against a static site.
One of the Main Challenges: Forms on a Static Site
While purely static hosting is awesome for speed and stability, it doesn’t magically handle contact forms. With a static site, it’s easy to display a form, but capturing and processing that form data requires a server or external service. Plenty of folks plug in something like Google Forms, but I’m a control freak who likes to keep everything in-house—plus, I’m not thrilled about letting third parties see user submissions, or dealing with the styling nightmares these providers often lock you into.
Enter form_handler.py
—My Own Secure Little Backend
Instead of relying on external services, I decided to write a small Flask app called form_handler.py
. Here’s a snippet showing how I generate the form’s CSRF token, which is kind of like a secret handshake at a wizard convention (if wizard conventions existed):
@app.route("/init_form")
@cross_origin(origins=ALLOWED_ORIGINS)
def init_form():
"""Generate and return the data for a new form submission."""
token = secrets.token_hex()
word = secrets.choice(words)
idx = secrets.randbelow(len(word))
answer = word[idx]
cur = get_db().cursor()
cur.execute(f"""INSERT INTO validation_data (date, csrf_token, captcha_answer)
VALUES(datetime('now', 'localtime'), ?, ?);
""", (token, answer))
get_db().commit()
cur.close()
return {
"csrf_token": token,
"word": word,
"pos": idx+1,
"pos_str": pos_translate[idx],
"answer": answer
}
CSRF Protection
When a new form loads, the server embeds a unique token. If you don’t submit that exact token—or if you try to reuse it—my code politely says, “Nope, not happening.” After all, who wants to be vulnerable to cross-site request forgery?
Custom Captcha
No one wants spam (unless you’re thinking about that canned meat). So I also add a custom captcha. We randomly pick a word from a file, ask the user for the nth letter, and confirm it with the server. While we’re at it, there’s a hidden “captcha2” field that must remain empty—if a robot tries to fill it in, that’s basically an admission of guilt.
SQLite Storage
Naturally, I need to store these submissions somewhere. For that, form_handler.py
hangs on to them in an SQLite database. If a submission fails the security checks, I still log it, but mark it as invalid. If it passes, it’s saved for posterity—like a guestbook that’s only visible to me.
Summary
By writing my own backend, I keep my static site approach intact without sacrificing control over how my forms look or behave. Plus, I’m not transferring data to a third-party service (sorry, Google Forms, you’re just not my type). Combining a hidden honeypot field, a CSRF token, and a word-based captcha ensures spam is kept to a minimum—and provides me with a few laughs along the way.