were gonna make this webhook shit WORK
This commit is contained in:
1 parent
817323cb62
commit
a8ce535985
3 files changed
+116
-2
No files matched your search
@@ -48,6 +48,18 @@ BRICKSET_API_KEY=your_brickset_api_key
|
|||||||
# Generate with: python convert_to_p7b.py
|
# Generate with: python convert_to_p7b.py
|
||||||
SQUARE_P7B_PATH=./cert.p7b
|
SQUARE_P7B_PATH=./cert.p7b
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Square Webhook Signature Verification
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# CRITICAL: Get these from Square Developer Dashboard → Webhooks
|
||||||
|
# Signature key used to verify webhook authenticity (prevents spoofing)
|
||||||
|
SQUARE_WEBHOOK_SIGNATURE_KEY=your_webhook_signature_key_from_square
|
||||||
|
|
||||||
|
# Your webhook notification URL (must match exactly what's in Square Dashboard)
|
||||||
|
# Example: https://studspider.sh/hook
|
||||||
|
SQUARE_WEBHOOK_URL=https://yourdomain.com/hook
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Server Configuration (Optional)
|
# Server Configuration (Optional)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -38,3 +38,5 @@ win/
|
|||||||
bin/
|
bin/
|
||||||
activate/
|
activate/
|
||||||
build/
|
build/
|
||||||
|
.pem
|
||||||
|
.p7b
|
||||||
+102
-2
@@ -21,6 +21,8 @@ from time import time
|
|||||||
|
|
||||||
time = int(time())
|
time = int(time())
|
||||||
import asquare as sq
|
import asquare as sq
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
|
||||||
# from asquareclass import AsyncSquareClient # type: ignore
|
# from asquareclass import AsyncSquareClient # type: ignore
|
||||||
from abricksetapi import AsyncBricksetAPI
|
from abricksetapi import AsyncBricksetAPI
|
||||||
@@ -28,6 +30,8 @@ from abricksetapi import AsyncBricksetAPI
|
|||||||
print("Loading environment variables...")
|
print("Loading environment variables...")
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
SQUARE_LEGO_CAT_ID = os.environ.get("SQUARE_LEGO_CAT_ID")
|
SQUARE_LEGO_CAT_ID = os.environ.get("SQUARE_LEGO_CAT_ID")
|
||||||
|
SQUARE_WEBHOOK_SIGNATURE_KEY = os.environ.get("SQUARE_WEBHOOK_SIGNATURE_KEY")
|
||||||
|
SQUARE_WEBHOOK_URL = os.environ.get("SQUARE_WEBHOOK_URL")
|
||||||
|
|
||||||
from asyncio_throttle.throttler import Throttler
|
from asyncio_throttle.throttler import Throttler
|
||||||
|
|
||||||
@@ -82,6 +86,51 @@ def valid_uuid(val):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# In-memory store for processed event IDs (idempotency)
|
||||||
|
processed_event_ids = set()
|
||||||
|
MAX_STORED_EVENT_IDS = 10000 # Prevent memory growth
|
||||||
|
|
||||||
|
|
||||||
|
def verify_square_webhook_signature(
|
||||||
|
signature_header: str, body: bytes, signature_key: str, webhook_url: str
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Verify Square webhook signature using HMAC-SHA-256
|
||||||
|
|
||||||
|
Args:
|
||||||
|
signature_header: Value from x-square-hmacsha256-signature header
|
||||||
|
body: Raw request body bytes
|
||||||
|
signature_key: Your webhook signature key from Square Dashboard
|
||||||
|
webhook_url: Your webhook notification URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if signature is valid
|
||||||
|
"""
|
||||||
|
if not signature_key or not webhook_url:
|
||||||
|
logger.error("Square webhook signature key or URL not configured in .env")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Concatenate webhook URL + request body
|
||||||
|
payload = webhook_url.encode("utf-8") + body
|
||||||
|
|
||||||
|
# Generate HMAC-SHA-256 signature
|
||||||
|
computed_signature = hmac.new(
|
||||||
|
signature_key.encode("utf-8"), payload, hashlib.sha256
|
||||||
|
).digest()
|
||||||
|
|
||||||
|
# Decode the signature from the header (base64)
|
||||||
|
import base64
|
||||||
|
|
||||||
|
try:
|
||||||
|
expected_signature = base64.b64decode(signature_header)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to decode signature header: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare signatures using constant-time comparison
|
||||||
|
return hmac.compare_digest(computed_signature, expected_signature)
|
||||||
|
|
||||||
|
|
||||||
async def hook_handler(request: web.Request) -> web.Response:
|
async def hook_handler(request: web.Request) -> web.Response:
|
||||||
"""Handle incoming JSON webhook POSTs on /hook endpoint"""
|
"""Handle incoming JSON webhook POSTs on /hook endpoint"""
|
||||||
try:
|
try:
|
||||||
@@ -95,14 +144,65 @@ async def hook_handler(request: web.Request) -> web.Response:
|
|||||||
)
|
)
|
||||||
return web.Response(status=405, text="Method not allowed. Use POST.")
|
return web.Response(status=405, text="Method not allowed. Use POST.")
|
||||||
|
|
||||||
# Get JSON data from request
|
# Read raw body for signature verification
|
||||||
|
body = await request.read()
|
||||||
|
|
||||||
|
# Verify Square webhook signature
|
||||||
|
signature_header = request.headers.get("x-square-hmacsha256-signature")
|
||||||
|
if SQUARE_WEBHOOK_SIGNATURE_KEY and SQUARE_WEBHOOK_URL:
|
||||||
|
if not signature_header:
|
||||||
|
logger.error(
|
||||||
|
f"{current_time}\tMissing signature header from {request.remote}"
|
||||||
|
)
|
||||||
|
print(f"[@DEBUG] Missing x-square-hmacsha256-signature header")
|
||||||
|
return web.Response(status=401, text="Unauthorized: Missing signature")
|
||||||
|
|
||||||
|
if not verify_square_webhook_signature(
|
||||||
|
signature_header, body, SQUARE_WEBHOOK_SIGNATURE_KEY, SQUARE_WEBHOOK_URL
|
||||||
|
):
|
||||||
|
logger.error(f"{current_time}\tInvalid signature from {request.remote}")
|
||||||
|
print(f"[@DEBUG] Invalid webhook signature!")
|
||||||
|
return web.Response(status=401, text="Unauthorized: Invalid signature")
|
||||||
|
|
||||||
|
print(f"[@DEBUG] ✓ Webhook signature verified")
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"[@DEBUG] ⚠️ Webhook signature verification DISABLED (set SQUARE_WEBHOOK_SIGNATURE_KEY and SQUARE_WEBHOOK_URL in .env)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Parse JSON data
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = json.loads(body.decode("utf-8"))
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
print(f"[@DEBUG] Invalid JSON to /hook from {request.remote}: {str(e)}")
|
print(f"[@DEBUG] Invalid JSON to /hook from {request.remote}: {str(e)}")
|
||||||
logger.error(f"Invalid JSON to /hook from {request.remote}: {str(e)}")
|
logger.error(f"Invalid JSON to /hook from {request.remote}: {str(e)}")
|
||||||
return web.Response(status=400, text="Invalid JSON in request body")
|
return web.Response(status=400, text="Invalid JSON in request body")
|
||||||
|
|
||||||
|
# Check for idempotency (event_id)
|
||||||
|
event_id = data.get("event_id")
|
||||||
|
if event_id:
|
||||||
|
if event_id in processed_event_ids:
|
||||||
|
print(
|
||||||
|
f"[@DEBUG] Duplicate event {event_id} - already processed (idempotency)"
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"{current_time}\tDuplicate event {event_id} from {request.remote}"
|
||||||
|
)
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"message": "Event already processed (idempotency)",
|
||||||
|
"event_id": event_id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add to processed set
|
||||||
|
processed_event_ids.add(event_id)
|
||||||
|
|
||||||
|
# Prevent memory growth - remove oldest if too many
|
||||||
|
if len(processed_event_ids) > MAX_STORED_EVENT_IDS:
|
||||||
|
processed_event_ids.pop()
|
||||||
|
|
||||||
# Log the received webhook with detailed output
|
# Log the received webhook with detailed output
|
||||||
print(f"\n{'='*60}")
|
print(f"\n{'='*60}")
|
||||||
print(f"[@DEBUG] WEBHOOK RECEIVED at {current_time}")
|
print(f"[@DEBUG] WEBHOOK RECEIVED at {current_time}")
|
||||||
|
|||||||
Reference in new issue
Block a user