Add verification through ALTCHA

Open-sourced and self-hosted alternative to Google CAPTCHA services. Requires JS, but auto verifies on page load.
This commit is contained in:
Parker M. 2024-02-18 20:22:28 -06:00
parent 97943471f4
commit 497b0da036
No known key found for this signature in database
GPG Key ID: 95CD2E0C7E329F2A
5 changed files with 64 additions and 2 deletions

8
app/static/altcha.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,9 @@
<!-- Encourage indexing --> <!-- Encourage indexing -->
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
<!-- Add altcha.js file -->
<script async defer src="{{ url_for('static', filename='altcha.js') }}" type="module"></script>
<meta property="og:title" content="Contact | pkrm.dev"/> <meta property="og:title" content="Contact | pkrm.dev"/>
<meta property="og:url" content="https://pkrm.dev/contact"/> <meta property="og:url" content="https://pkrm.dev/contact"/>
<meta property="og:type" content="website"/> <meta property="og:type" content="website"/>
@ -35,6 +38,8 @@
<input type="text" name="email" placeholder="Email"> <input type="text" name="email" placeholder="Email">
<textarea name="message" placeholder="Message"></textarea> <textarea name="message" placeholder="Message"></textarea>
<input type="submit" value="Submit"> <input type="submit" value="Submit">
<!-- Add altcha widget which auto verifies on page load and is invisible -->
<altcha-widget challengeurl="/altcha-challenge" auto="onload" style="display: none; opacity: 0;"></altcha-widget>
</form> </form>
{% if success %} {% if success %}
@ -204,6 +209,10 @@
} }
} }
altcha-widget {
visibility: none;
}
/* Success/Error messages */ /* Success/Error messages */
.success-message { .success-message {
color: #71A172; color: #71A172;

View File

@ -2,10 +2,17 @@ import flask
import os import os
import discord import discord
import dotenv import dotenv
import hashlib
import secrets
import hmac
import json
import base64
import random
app = flask.Flask(__name__) app = flask.Flask(__name__)
dotenv.load_dotenv() dotenv.load_dotenv()
webhook_url = os.getenv("WEBHOOK_URL") webhook_url = os.getenv("WEBHOOK_URL")
hmac_key = random.randbytes(500)
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def index(): def index():
@ -22,6 +29,25 @@ def contact():
if flask.request.method == 'POST': if flask.request.method == 'POST':
try: try:
# Decode payload
data = json.loads(base64.b64decode(flask.request.form['altcha']).decode())
# Validate algorithm
if data['algorithm'] != 'SHA-256':
return flask.render_template('contact.html', error=True)
# Validate challenge
expected_challenge = hashlib.sha256(
(data['salt'] + str(data['number'])).encode()
).hexdigest()
if data['challenge'] != expected_challenge:
return flask.render_template('contact.html', error=True)
# Validate signature
signature = hmac.new(hmac_key, data['challenge'].encode(), hashlib.sha256).hexdigest()
if data['signature'] != signature:
return flask.render_template('contact.html', error=True)
# All checks passed, send off form data
name = flask.request.form['name'] name = flask.request.form['name']
email = flask.request.form['email'] email = flask.request.form['email']
message = flask.request.form['message'] message = flask.request.form['message']
@ -40,6 +66,25 @@ def contact():
except: except:
return flask.render_template('contact.html', error=True) return flask.render_template('contact.html', error=True)
@app.route('/altcha-challenge', methods=['GET'])
def altcha_challenge():
salt = secrets.token_urlsafe(25)
secret_number = random.randint(10000, 50000)
challenge_data = f"{salt}{secret_number}".encode()
challenge = hashlib.sha256(challenge_data).hexdigest()
signature = hmac.new(hmac_key, challenge.encode(), hashlib.sha256).hexdigest()
response = {
'algorithm': 'SHA-256',
'challenge': challenge,
'salt': salt,
'signature': signature
}
return flask.jsonify(response)
@app.route('/pgp', methods=['GET']) @app.route('/pgp', methods=['GET'])
def pgp(): def pgp():
return flask.render_template('pgp.html') return flask.render_template('pgp.html')

View File

@ -1 +1 @@
gunicorn -b 0.0.0.0:2121 run:app gunicorn -b 0.0.0.0:4343 run:app

2
run.py
View File

@ -2,4 +2,4 @@ from app.views import app as application
app=application app=application
if __name__ == '__main__': if __name__ == '__main__':
application.run(port="2121") application.run(port="4343")