feat(api): add contact form endpoint for website enquiries

Add POST /api/v1/contact endpoint to handle contact form submissions
from the marketing website. Includes:
- ContactSchema for validation with HTML sanitization
- Honeypot field for spam protection
- Rate limiting (5 per hour)
- Email notification to info@secuird.tech via NotificationService
This commit is contained in:
2026-04-17 15:55:19 +09:30
parent 7480e9d62b
commit 29d54ca109
4 changed files with 188 additions and 1 deletions
+3 -1
View File
@@ -5,7 +5,9 @@ from flask import Blueprint
api_v1_bp = Blueprint("api_v1", __name__)
# Import route modules to register them
from gatehouse_app.api.v1 import auth, users, organizations, policies, external_auth, departments, principals, ssh, zerotier, sudo, oidc
from gatehouse_app.api.v1 import auth, users, organizations, policies, external_auth, departments, principals, ssh, zerotier, sudo, oidc, contact
from gatehouse_app.api.v1 import superadmin
api_v1_bp.register_blueprint(ssh.ssh_bp)
api_v1_bp.register_blueprint(superadmin.superadmin_bp)
+68
View File
@@ -0,0 +1,68 @@
"""Contact form endpoint for website enquiries."""
import logging
from flask import request, current_app
from marshmallow import ValidationError
from gatehouse_app.api.v1 import api_v1_bp
from gatehouse_app.extensions import limiter
from gatehouse_app.utils.response import api_response
from gatehouse_app.schemas.contact_schema import ContactSchema
from gatehouse_app.services.notification_service import NotificationService
from gatehouse_app.services.email_templates import build_contact_enquiry_html
logger = logging.getLogger(__name__)
# Hardcoded destination for all contact submissions
CONTACT_DESTINATION = "info@secuird.tech"
@api_v1_bp.route("/contact", methods=["POST"])
@limiter.limit("5 per hour")
def contact():
"""Handle contact form submissions from the marketing website.
Accepts: email, name, company, enquiry_type, message, interest_area, _hp.
Sends an email to info@secuird.tech with the enquiry details.
Silently discards submissions where the honeypot field (_hp) is filled.
"""
try:
schema = ContactSchema()
data = schema.load(request.get_json() or {})
except ValidationError as err:
return api_response(
success=False,
message="Invalid request data",
status=400,
error_type="VALIDATION_ERROR",
error_details=err.messages,
)
# Honeypot check — silently succeed without sending
if data.get("_hp"):
logger.info(f"[Contact] Honeypot triggered, ip={request.remote_addr}")
return api_response(message="Thank you for your message!")
enquiry_type = data.get("enquiry_type") or "general"
email = data.get("email") or ""
# Build and send email
html_body = build_contact_enquiry_html(
enquiry_type=enquiry_type,
submitter_email=email,
name=data.get("name"),
company=data.get("company"),
interest_area=data.get("interest_area"),
message=data.get("message"),
)
NotificationService._send_email_async(
to_address=CONTACT_DESTINATION,
subject=f"Secuird Website: {enquiry_type.replace('_', ' ').title()} from {email}",
body=f"New contact enquiry ({enquiry_type}) from {email}",
html_body=html_body,
)
logger.info(f"[Contact] enquiry_type={enquiry_type} ip={request.remote_addr}")
return api_response(message="Thank you for your message!")