Repository: altcha-org/altcha-lib-py

Issue:
`check_expires=True` produces an error
`extract_params` uses `urllib.parse.parse_qs`, which will return a _list_ for every parameter:
```python
qs = urllib.parse.parse_qs('foo=bar')
# qs = { 'foo': ['bar'] }
```

`extract_params(payload).get('expires')` is then fed into `int()`, which obviously throws, because you cannot convert a list to an int.

Discussion:

Patch:
diff --git a/altcha/altcha.py b/altcha/altcha.py
index b0dab21..0bfd62b 100644
--- a/altcha/altcha.py
+++ b/altcha/altcha.py
@@ -323,7 +323,10 @@ def verify_solution(payload, hmac_key, check_expires):
         return False, None, "Invalid algorithm"
 
     expires = extract_params(payload).get("expires")
-    if check_expires and expires and int(expires) < time.time():
+    try:
+        if check_expires and expires and int(expires[0]) < time.time():
+            return False, None
+    except ValueError:  # Guard against malformed expires
         return False, None
 
     options = ChallengeOptions(


Test patch:
diff --git a/tests/test_altcha.py b/tests/test_altcha.py
index edd74c9..d268088 100644
--- a/tests/test_altcha.py
+++ b/tests/test_altcha.py
@@ -1,3 +1,4 @@
+import datetime
 import hashlib
 import hmac
 import time
@@ -81,6 +82,78 @@ class TestALTCHA(unittest.TestCase):
         result, _ = verify_solution(payload_encoded, self.hmac_key, check_expires=False)
         self.assertFalse(result)
 
+    def test_verify_solution_not_expired(self):
+        options = ChallengeOptions(
+            algorithm="SHA-256",
+            max_number=1000,
+            salt_length=16,
+            hmac_key=self.hmac_key,
+            salt="somesalt",
+            number=123,
+            expires=datetime.datetime.now().astimezone() + datetime.timedelta(minutes=1)
+        )
+        challenge = create_challenge(options)
+        payload = Payload(
+            algorithm="SHA-256",
+            challenge=challenge.challenge,
+            number=123,
+            salt=challenge.salt,
+            signature=challenge.signature,
+        )
+        payload_encoded = base64.b64encode(
+            json.dumps(payload.__dict__).encode()
+        ).decode()
+        result, _ = verify_solution(payload_encoded, self.hmac_key, check_expires=True)
+        self.assertTrue(result)
+
+    def test_verify_solution_expired(self):
+        options = ChallengeOptions(
+            algorithm="SHA-256",
+            max_number=1000,
+            salt_length=16,
+            hmac_key=self.hmac_key,
+            salt="somesalt",
+            number=123,
+            expires=datetime.datetime.now().astimezone() - datetime.timedelta(minutes=1)
+        )
+        challenge = create_challenge(options)
+        payload = Payload(
+            algorithm="SHA-256",
+            challenge=challenge.challenge,
+            number=123,
+            salt=challenge.salt,
+            signature=challenge.signature,
+        )
+        payload_encoded = base64.b64encode(
+            json.dumps(payload.__dict__).encode()
+        ).decode()
+        result, _ = verify_solution(payload_encoded, self.hmac_key, check_expires=True)
+        self.assertFalse(result)
+
+    def test_verify_solution_malformed_expiry(self):
+        options = ChallengeOptions(
+            algorithm="SHA-256",
+            max_number=1000,
+            salt_length=16,
+            hmac_key=self.hmac_key,
+            salt="somesalt",
+            number=123,
+            expires=datetime.datetime.now().astimezone() + datetime.timedelta(minutes=1)
+        )
+        challenge = create_challenge(options)
+        payload = Payload(
+            algorithm="SHA-256",
+            challenge=challenge.challenge,
+            number=123,
+            salt='somesalt?expires=foobar',
+            signature=challenge.signature,
+        )
+        payload_encoded = base64.b64encode(
+            json.dumps(payload.__dict__).encode()
+        ).decode()
+        result, _ = verify_solution(payload_encoded, self.hmac_key, check_expires=True)
+        self.assertFalse(result)
+
     def test_valid_signature(self):
         expire_time = int(time.time()) + 600  # 10 minutes from now
         verification_data = (