"""Check a GoAsk webhook's signature in Python (standard library only).

    ok = verify(raw_body_bytes, headers, "whsec_...")

`headers` is a dict of the request headers (lower-case names). Returns the parsed event, or raises
ValueError. The pip package `standardwebhooks` does the same if you prefer a library.
"""
import base64, hashlib, hmac, json, time


def verify(body: bytes, headers: dict, secret: str, tolerance: int = 300) -> dict:
    msg_id = headers.get("webhook-id", "")
    ts = headers.get("webhook-timestamp", "")
    sigs = headers.get("webhook-signature", "")
    if not (msg_id and ts and sigs):
        raise ValueError("missing webhook headers")
    if abs(time.time() - int(ts)) > tolerance:
        raise ValueError("timestamp too old or in the future")
    key = base64.b64decode(secret.split("_", 1)[1] if secret.startswith("whsec_") else secret)
    signed = f"{msg_id}.{ts}.".encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    # During a secret change there are two signatures, separated by a space: one match is enough.
    for part in sigs.split(" "):
        version, _, sig = part.partition(",")
        if version == "v1" and hmac.compare_digest(sig, expected):
            return json.loads(body)
    raise ValueError("no matching signature")
