Feat: Update CAPTCHA service to ALTCHA self-hosted.
This commit is contained in:
@@ -14,65 +14,66 @@ Defines Flask-WTF form for handling user input on Contact Us page.
|
||||
# internal
|
||||
# from business_objects.store.product_category import Filters_Product_Category # circular
|
||||
# from models.model_view_store import Model_View_Store # circular
|
||||
from models.model_view_base import Model_View_Base
|
||||
from forms.base import Form_Base
|
||||
# external
|
||||
from flask import Flask, render_template, request, flash, redirect, url_for, current_app
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, TextAreaField, SubmitField, HiddenField, BooleanField
|
||||
from wtforms import StringField, TextAreaField, SubmitField, HiddenField, BooleanField, Field
|
||||
from wtforms.validators import DataRequired, Email, ValidationError
|
||||
import markupsafe
|
||||
from flask_wtf.recaptcha import RecaptchaField
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import requests
|
||||
import json
|
||||
import hmac
|
||||
import hashlib
|
||||
from altcha import verify_solution
|
||||
import base64
|
||||
import urllib.parse
|
||||
|
||||
"""
|
||||
def validate_altcha(form, field):
|
||||
if not field.data:
|
||||
raise ValidationError('Please complete the ALTCHA challenge')
|
||||
|
||||
try:
|
||||
# Decode the base64-encoded payload
|
||||
payload_json = base64.b64decode(field.data).decode('utf-8')
|
||||
payload = json.loads(payload_json)
|
||||
class ALTCHAValidator:
|
||||
def __init__(self, message=None):
|
||||
self.message = message or 'ALTCHA verification failed'
|
||||
|
||||
# Verify ALTCHA response
|
||||
if not payload.get('verified', False):
|
||||
raise ValidationError('ALTCHA verification failed')
|
||||
def __call__(self, form, field):
|
||||
altcha_data = field.data
|
||||
|
||||
# Verify signature
|
||||
verification_data = payload.get('verificationData', '')
|
||||
received_signature = payload.get('signature', '')
|
||||
algorithm = payload.get('algorithm', 'SHA-256')
|
||||
if not altcha_data:
|
||||
raise ValidationError(self.message)
|
||||
|
||||
# Calculate the hash of verification data
|
||||
verification_hash = hashlib.sha256(verification_data.encode()).digest()
|
||||
|
||||
# Calculate HMAC signature
|
||||
hmac_key = current_app.config['ALTCHA_SECRET_KEY'].encode()
|
||||
calculated_signature = hmac.new(
|
||||
hmac_key,
|
||||
verification_hash,
|
||||
getattr(hashlib, algorithm.lower().replace('-', ''))
|
||||
).hexdigest()
|
||||
|
||||
if calculated_signature != received_signature:
|
||||
raise ValidationError('Invalid ALTCHA signature')
|
||||
try:
|
||||
# The data is base64 encoded JSON
|
||||
try:
|
||||
# First try to decode it as JSON directly (if it's not base64 encoded)
|
||||
altcha_payload = json.loads(altcha_data)
|
||||
except json.JSONDecodeError:
|
||||
# If direct JSON decoding fails, try base64 decoding first
|
||||
decoded_data = base64.b64decode(altcha_data).decode('utf-8')
|
||||
altcha_payload = json.loads(decoded_data)
|
||||
|
||||
ok, err = verify_solution(altcha_payload, current_app.config["ALTCHA_SECRET_KEY"], check_expires=True)
|
||||
|
||||
# Optional: If using the spam filter, you could parse verification_data
|
||||
# and reject submissions classified as spam
|
||||
# Example:
|
||||
parsed_data = dict(urllib.parse.parse_qsl(verification_data))
|
||||
if parsed_data.get('classification') == 'BAD':
|
||||
raise ValidationError('This submission was classified as spam')
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"ALTCHA validation error: {str(e)}")
|
||||
raise ValidationError('ALTCHA validation failed')
|
||||
"""
|
||||
if err or not ok:
|
||||
raise ValidationError(self.message + ': ' + (err or 'Invalid solution'))
|
||||
|
||||
except Exception as e:
|
||||
raise ValidationError(f'Invalid ALTCHA data: {str(e)}')
|
||||
|
||||
class ALTCHAField(Field):
|
||||
def __init__(self, label='', validators=None, **kwargs):
|
||||
validators = validators or []
|
||||
validators.append(ALTCHAValidator())
|
||||
|
||||
super(ALTCHAField, self).__init__(label, validators, **kwargs)
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
html = f"""
|
||||
<altcha-widget
|
||||
challengeurl="/get-challenge"
|
||||
auto="onload"
|
||||
id="{self.id}"
|
||||
name="{self.name}">
|
||||
</altcha-widget>
|
||||
"""
|
||||
return markupsafe.Markup(html)
|
||||
|
||||
|
||||
class Form_Contact(FlaskForm):
|
||||
email = StringField('Email')
|
||||
@@ -81,5 +82,6 @@ class Form_Contact(FlaskForm):
|
||||
message = TextAreaField('Message')
|
||||
receive_marketing = BooleanField('I would like to receive marketing emails.')
|
||||
# recaptcha = RecaptchaField()
|
||||
altcha = HiddenField('ALTCHA') # , validators=[validate_altcha]
|
||||
# altcha = HiddenField('ALTCHA') # , validators=[validate_altcha]
|
||||
altcha = ALTCHAField('Verification')
|
||||
submit = SubmitField('Send Message')
|
||||
|
||||
Reference in New Issue
Block a user