Skip to content

Commit 071ef9d

Browse files
committed
Improve webhook request validation and test coverage
1 parent 8053c92 commit 071ef9d

File tree

2 files changed

+59
-10
lines changed

2 files changed

+59
-10
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from datetime import datetime
2+
3+
import pytest
4+
5+
import lightspark
6+
7+
8+
class TestWebhooks:
9+
def test_valid_data_parses(self):
10+
data = b'{"event_type": "NODE_STATUS", "event_id": "1615c8be5aa44e429eba700db2ed8ca5", "timestamp": "2023-05-17T23:56:47.874449+00:00", "entity_id": "lightning_node:01882c25-157a-f96b-0000-362d42b64397"}'
11+
secret = "3gZ5oQQUASYmqQNuEk0KambNMVkOADDItIJjzUlAWjX"
12+
hex_digest = "62a8829aeb48b4142533520b1f7f86cdb1ee7d718bf3ea15bc1c662d4c453b74"
13+
14+
webhook = lightspark.WebhookEvent.verify_and_parse(data, hex_digest, secret)
15+
16+
assert (
17+
webhook.entity_id == "lightning_node:01882c25-157a-f96b-0000-362d42b64397"
18+
)
19+
assert webhook.event_id == "1615c8be5aa44e429eba700db2ed8ca5"
20+
assert webhook.event_type == lightspark.WebhookEventType.NODE_STATUS
21+
assert webhook.timestamp == datetime.fromisoformat(
22+
"2023-05-17T23:56:47.874449+00:00"
23+
)
24+
25+
@pytest.mark.parametrize(
26+
"hex_digest",
27+
[
28+
pytest.param("deadbeef", id="wrong length"),
29+
pytest.param("a" * 64, id="incorrect"),
30+
pytest.param("NotAHexString", id="not hex"),
31+
pytest.param(
32+
"62a8829aeb48b4142533520b1f7f86cdb1ee7d718bf3ea15bc1c662d4c453b74"
33+
+ "qq",
34+
id="extra bytes",
35+
),
36+
],
37+
)
38+
def test_invalid_data_raises_value_error(self, hex_digest: str):
39+
data = b'{"event_type": "NODE_STATUS", "event_id": "1615c8be5aa44e429eba700db2ed8ca5", "timestamp": "2023-05-17T23:56:47.874449+00:00", "entity_id": "lightning_node:01882c25-157a-f96b-0000-362d42b64397"}'
40+
secret = "3gZ5oQQUASYmqQNuEk0KambNMVkOADDItIJjzUlAWjX"
41+
42+
with pytest.raises(ValueError):
43+
lightspark.WebhookEvent.verify_and_parse(data, hex_digest, secret)
44+
45+
def test_invalid_data_type_raises_type_error(self):
46+
data = 1
47+
hex_digest = "deadbeef"
48+
secret = "3gZ5oQQUASYmqQNuEk0KambNMVkOADDItIJjzUlAWjX"
49+
50+
with pytest.raises(TypeError):
51+
lightspark.WebhookEvent.verify_and_parse(data, hex_digest, secret) # type: ignore

lightspark/webhooks.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,14 @@ class WebhookEvent:
2222

2323
@classmethod
2424
def verify_and_parse(
25-
cls, data: bytes, hexdigest: str, webhook_secret: str
25+
cls, data: bytes, hex_digest: str, webhook_secret: str
2626
) -> "WebhookEvent":
27-
"""Verifies the signature and parses the message into a
28-
WebhookEvent object.
27+
"""Verifies the signature and parses the message into a WebhookEvent object.
2928
3029
Args:
3130
data: the POST message body received by the webhook.
32-
hexdigest: the message signature sent in the
33-
`lightspark-signature` header.
34-
webhook_secret: the webhook secret configured at the
35-
Lightspark API configuration.
31+
hex_digest: the message signature sent in the `lightspark-signature` header.
32+
webhook_secret: the webhook secret configured in the Lightspark API configuration.
3633
3734
Returns:
3835
A parsed WebhookEvent object.
@@ -45,8 +42,9 @@ def verify_and_parse(
4542

4643
sig = hmac.new(
4744
webhook_secret.encode("ascii"), msg=data, digestmod=hashlib.sha256
48-
)
49-
if sig.hexdigest().lower() != hexdigest.lower():
45+
)
46+
47+
if not hmac.compare_digest(sig.hexdigest(), hex_digest):
5048
raise ValueError("Webhook message hash does not match signature")
5149

5250
return cls.parse(data)
@@ -73,5 +71,5 @@ def parse(cls, data: bytes) -> "WebhookEvent":
7371
event_id=event["event_id"],
7472
timestamp=datetime.fromisoformat(event["timestamp"]),
7573
entity_id=event["entity_id"],
76-
wallet_id=event["wallet_id"] if "wallet_id" in event else None,
74+
wallet_id=event.get("wallet_id", None),
7775
)

0 commit comments

Comments
 (0)