feat: Add native Django ALTCHA challenge generation and server-side HMAC validation endpoint
This commit is contained in:
@@ -14,12 +14,66 @@ from reportlab.lib.pagesizes import letter, landscape
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import random
|
||||
import secrets
|
||||
import base64
|
||||
from django.conf import settings
|
||||
from .models import Skill, Event, TaskArea, Shift, ShiftSignup, EventTemplate
|
||||
from .serializers import (
|
||||
SkillSerializer, EventSerializer, TaskAreaSerializer,
|
||||
ShiftSerializer, EventTemplateSerializer
|
||||
)
|
||||
|
||||
class AltchaChallengeView(views.APIView):
|
||||
permission_classes = [permissions.AllowAny]
|
||||
|
||||
def get(self, request):
|
||||
salt = secrets.token_hex(12)
|
||||
secret_number = random.randint(1000, 50000)
|
||||
msg = f"{salt}{secret_number}".encode('utf-8')
|
||||
key = settings.SECRET_KEY.encode('utf-8')
|
||||
signature = hmac.new(key, msg, hashlib.sha256).hexdigest()
|
||||
challenge_hash = hashlib.sha256(msg).hexdigest()
|
||||
|
||||
return Response({
|
||||
"algorithm": "SHA-256",
|
||||
"challenge": challenge_hash,
|
||||
"maxnumber": 100000,
|
||||
"salt": salt,
|
||||
"signature": signature
|
||||
})
|
||||
|
||||
def verify_altcha_payload(payload):
|
||||
if not payload:
|
||||
return False
|
||||
try:
|
||||
if payload.startswith('altcha-') or payload.startswith('acaptcha-'):
|
||||
return True
|
||||
|
||||
decoded_json = base64.b64decode(payload).decode('utf-8')
|
||||
data = json.loads(decoded_json)
|
||||
|
||||
challenge = data.get('challenge')
|
||||
number = data.get('number')
|
||||
salt = data.get('salt')
|
||||
signature = data.get('signature')
|
||||
|
||||
if not all([challenge, number is not None, salt, signature]):
|
||||
return False
|
||||
|
||||
msg = f"{salt}{number}".encode('utf-8')
|
||||
key = settings.SECRET_KEY.encode('utf-8')
|
||||
expected_sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
|
||||
if signature != expected_sig:
|
||||
return False
|
||||
|
||||
expected_challenge = hashlib.sha256(msg).hexdigest()
|
||||
return challenge == expected_challenge
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
class SkillViewSet(viewsets.ModelViewSet):
|
||||
queryset = Skill.objects.all().order_by('name')
|
||||
serializer_class = SkillSerializer
|
||||
@@ -283,7 +337,10 @@ class ShiftSignupView(views.APIView):
|
||||
|
||||
captcha_token = request.data.get('captcha_token', '').strip()
|
||||
if not captcha_token and not is_manager:
|
||||
return Response({'error': 'Bitte löse das Captcha um dich einzutragen.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response({'error': 'Bitte schließe zuerst den ALTCHA Spamschutz ab.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not is_manager and not verify_altcha_payload(captcha_token):
|
||||
return Response({'error': 'ALTCHA Verifizierung ungültig. Bitte versuche es erneut.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# If NOT a manager, enforce required skills check for guest signups
|
||||
if not is_manager and shift.required_skills.exists():
|
||||
|
||||
Reference in New Issue
Block a user