05/31 Fix teller webhook endpoint 2
This commit is contained in:
+38
-2
@@ -271,12 +271,48 @@ def webhook():
|
|||||||
Does NOT auto-import — user must confirm via the UI.
|
Does NOT auto-import — user must confirm via the UI.
|
||||||
"""
|
"""
|
||||||
# Verify Teller webhook signature
|
# Verify Teller webhook signature
|
||||||
|
# Header format: Teller-Signature: t=<timestamp>,v1=<sig1>,v1=<sig2>
|
||||||
|
# Signed message: <timestamp>.<raw_body>
|
||||||
signing_secret = current_app.config.get('TELLER_WEBHOOK_SECRET', '')
|
signing_secret = current_app.config.get('TELLER_WEBHOOK_SECRET', '')
|
||||||
if signing_secret:
|
if signing_secret:
|
||||||
sig_header = request.headers.get('Teller-Signature', '')
|
sig_header = request.headers.get('Teller-Signature', '')
|
||||||
body = request.get_data()
|
body = request.get_data()
|
||||||
expected = hmac.new(signing_secret.encode(), body, hashlib.sha256).hexdigest()
|
|
||||||
if not hmac.compare_digest(f'sha256={expected}', sig_header):
|
if not sig_header:
|
||||||
|
log.warning('[teller] missing Teller-Signature header')
|
||||||
|
return jsonify({'error': 'Missing signature'}), 401
|
||||||
|
|
||||||
|
# Parse header: t=<timestamp>,v1=<sig>,v1=<sig2>...
|
||||||
|
parts = dict(
|
||||||
|
(p.split('=', 1) if '=' in p else (p, ''))
|
||||||
|
for p in sig_header.split(',')
|
||||||
|
)
|
||||||
|
timestamp = parts.get('t', '')
|
||||||
|
# Collect all v1 signatures (may be multiple during key rotation)
|
||||||
|
signatures = [v for k, v in
|
||||||
|
[p.split('=', 1) for p in sig_header.split(',') if p.startswith('v1=')]
|
||||||
|
]
|
||||||
|
|
||||||
|
if not timestamp or not signatures:
|
||||||
|
log.warning('[teller] malformed Teller-Signature header')
|
||||||
|
return jsonify({'error': 'Invalid signature'}), 401
|
||||||
|
|
||||||
|
# Reject replays older than 5 minutes
|
||||||
|
import time
|
||||||
|
try:
|
||||||
|
if abs(time.time() - int(timestamp)) > 300:
|
||||||
|
log.warning('[teller] webhook replay attack detected')
|
||||||
|
return jsonify({'error': 'Timestamp too old'}), 401
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# signed_message = timestamp + "." + raw_body
|
||||||
|
signed_message = f'{timestamp}.'.encode() + body
|
||||||
|
expected = hmac.new(
|
||||||
|
signing_secret.encode(), signed_message, hashlib.sha256
|
||||||
|
).hexdigest()
|
||||||
|
|
||||||
|
if not any(hmac.compare_digest(expected, sig) for sig in signatures):
|
||||||
log.warning('[teller] webhook signature mismatch')
|
log.warning('[teller] webhook signature mismatch')
|
||||||
return jsonify({'error': 'Invalid signature'}), 401
|
return jsonify({'error': 'Invalid signature'}), 401
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user