gemini (kostenintensives) app-aufbauen

This commit is contained in:
br committed 2026-08-01 16:42:30 +02:00
1 parent c74bca1e13
commit 8232f4d8f7
11 files changed
+659 -519

No files matched your search

+19 -144
View File
@@ -42,6 +42,8 @@ from werkzeug.exceptions import BadRequest, Unauthorized, Forbidden, NotFound
from models import db, Tenant, FormConfig, Submission, SubmissionStatus
from services.recaptcha import RecaptchaService
from services.tenant_service import TenantService
from services.submission_service import SubmissionService
logger = logging.getLogger(__name__)
@@ -50,63 +52,6 @@ logger = logging.getLogger(__name__)
api_bp = Blueprint('api', __name__)
def validate_origin(origin: str, tenant: Tenant) -> bool:
"""
Validate that the request origin matches allowed origins for the tenant.
:param origin: Request origin/referer
:param tenant: Tenant object to check against
:return: True if origin is allowed, False otherwise
"""
if not origin:
return False
# Remove trailing slash and protocol
origin = origin.rstrip('/')
allowed_origins = tenant.get_allowed_origins_list()
# Check for wildcard
if '*' in allowed_origins:
return True
# Exact match
if origin in allowed_origins:
return True
# Check if origin matches any allowed domain (with subdomains)
for allowed in allowed_origins:
if allowed.startswith('*'):
# Wildcard subdomain match
domain = allowed[1:] # Remove leading *
if origin.endswith(domain):
return True
return False
def get_tenant_by_public_key(public_key: str) -> Optional[Tenant]:
"""
Get tenant by public key.
:param public_key: Public API key
:return: Tenant object or None
"""
return Tenant.query.filter_by(public_key=public_key).first()
def hash_ip(ip: str) -> str:
"""
Create a hash of an IP address for privacy-preserving storage.
:param ip: IP address string
:return: SHA256 hash of the IP
"""
if not ip:
return ''
return hashlib.sha256(ip.encode()).hexdigest()
@api_bp.before_request
def before_api_request():
"""Before request handler for API routes."""
@@ -186,7 +131,7 @@ def handshake():
origin = request.headers.get('Origin') or request.headers.get('Referer') or ''
# Find tenant by public key
tenant = get_tenant_by_public_key(public_key)
tenant = TenantService.get_tenant_by_public_key(public_key)
if not tenant:
logger.warning(f'Handshake failed: Invalid public key {public_key[:8]}...')
@@ -196,7 +141,7 @@ def handshake():
if not tenant.is_active():
raise Forbidden('Tenant is not active')
if not validate_origin(origin, tenant):
if not TenantService.validate_origin(origin, tenant):
logger.warning(f'Handshake failed: Invalid origin {origin} for tenant {tenant.id}')
raise Forbidden('Origin not allowed for this tenant')
@@ -253,7 +198,7 @@ def get_form(form_id):
raise BadRequest('Public key required')
# Find tenant by public key
tenant = get_tenant_by_public_key(public_key)
tenant = TenantService.get_tenant_by_public_key(public_key)
if not tenant:
raise Unauthorized('Invalid public key')
@@ -300,7 +245,7 @@ def submit_form(form_id):
raise BadRequest('Public key required')
# Find tenant by public key
tenant = get_tenant_by_public_key(public_key)
tenant = TenantService.get_tenant_by_public_key(public_key)
if not tenant:
logger.warning(f'Submission failed: Invalid public key {public_key[:8]}...')
@@ -311,22 +256,10 @@ def submit_form(form_id):
# Validate origin
origin = request.headers.get('Origin') or request.headers.get('Referer') or ''
if not validate_origin(origin, tenant):
if not TenantService.validate_origin(origin, tenant):
logger.warning(f'Submission failed: Invalid origin {origin} for tenant {tenant.id}')
raise Forbidden('Origin not allowed')
# Get form
form = FormConfig.query.filter_by(
id=form_id,
tenant_id=tenant.id
).first()
if not form:
raise NotFound('Form not found')
if not form.is_active:
raise Forbidden('Form is not active')
# Get form data from request
try:
if request.content_type == 'application/json':
@@ -337,78 +270,20 @@ def submit_form(form_id):
logger.error(f'Failed to parse form data: {e}')
raise BadRequest('Invalid form data format')
if not payload:
raise BadRequest('No form data provided')
# Validate reCAPTCHA if enabled
recaptcha_score = None
recaptcha_passed = None
if form.settings.get('recaptcha_enabled', True) if form.settings else True:
recaptcha_token = payload.get('g-recaptcha-response') or request.headers.get('X-ReCAPTCHA-Token')
if recaptcha_token:
recaptcha_service = RecaptchaService()
is_valid, score = recaptcha_service.verify(
recaptcha_token,
request.remote_addr
)
recaptcha_score = score
recaptcha_passed = is_valid
if not is_valid:
logger.warning(f'Submission rejected: reCAPTCHA failed with score {score}')
else:
# If no token provided, use dev mode score if in dev mode
if current_app.config.get('DEV_MODE', False):
recaptcha_score = current_app.config.get('MOCK_RECAPTCHA_SCORE', 0.9)
recaptcha_passed = True
# Create submission
submission_id = f'sub_{form_id}_{str(uuid.uuid4())[:8]}'
submission = Submission(
id=submission_id,
form_config_id=form.id,
tenant_id=tenant.id,
recaptcha_token = payload.get('g-recaptcha-response') or request.headers.get('X-ReCAPTCHA-Token') if payload else None
response, status_code = SubmissionService.process_submission(
form_id=form_id,
tenant=tenant,
origin=origin,
payload=payload,
ip_hash=hash_ip(request.remote_addr),
ip_address=request.remote_addr,
user_agent=request.user_agent.string if request.user_agent else None,
referer=origin,
recaptcha_score=recaptcha_score,
recaptcha_passed=recaptcha_passed,
submission_metadata={
'origin': origin,
'user_agent': request.user_agent.string if request.user_agent else None,
'content_type': request.content_type
}
recaptcha_token=recaptcha_token,
content_type=request.content_type
)
# Set status based on reCAPTCHA
if recaptcha_passed is False:
submission.status = SubmissionStatus.SPAM
else:
submission.status = SubmissionStatus.PENDING
db.session.add(submission)
db.session.commit()
logger.info(f'Form submission: {submission_id} for form {form_id}, tenant {tenant.id}')
# Prepare response
response = {
'status': 'success',
'submission_id': submission.id,
'message': form.settings.get('success_message', 'Form submitted successfully!') if form.settings else 'Form submitted successfully!'
}
# If reCAPTCHA failed, indicate in response
if submission.status == SubmissionStatus.SPAM:
response['status'] = 'spam'
response['message'] = 'Submission flagged as spam'
logger.warning(f'Spam submission: {submission_id}')
return jsonify(response)
return jsonify(response), status_code
@api_bp.route('/validate/recaptcha', methods=['POST'])
@@ -483,7 +358,7 @@ def get_tenant_stats(tenant_id):
if not public_key:
raise BadRequest('Public key required')
tenant = get_tenant_by_public_key(public_key)
tenant = TenantService.get_tenant_by_public_key(public_key)
if not tenant or tenant.id != tenant_id:
raise Unauthorized('Invalid public key or tenant mismatch')