Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## Upcoming Changes

#### 🐛 Bug Fixes

- **Reject non-finite DID token `iat` / `ext` / `nbf`**
- `json.loads` accepts `NaN` and `Infinity`. Comparisons against those values are always false, so `ext: Infinity` never expired and `nbf: NaN` never blocked use.
- Strings such as `"never"` now raise `DIDTokenMalformed` instead of a raw `TypeError`.

---

## `2.5.0` - 2026-06-19

#### 🚀 New Features
Expand Down
29 changes: 29 additions & 0 deletions magic_admin/resources/token.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import base64
import json
import math

from eth_account.messages import encode_defunct
from web3.auto import w3
Expand All @@ -17,6 +18,14 @@
EXPECTED_DID_TOKEN_CONTENT_LENGTH = 2


def _is_finite_unix_seconds(value):
return (
isinstance(value, (int, float))
and not isinstance(value, bool)
and math.isfinite(value)
)


class Token(ResourceComponent):
required_fields = frozenset(
[
Expand Down Expand Up @@ -52,6 +61,14 @@ def _check_required_fields(cls, claim):
),
)

for field in ("iat", "ext", "nbf"):
if not _is_finite_unix_seconds(claim[field]):
raise DIDTokenMalformed(
message='DID token "{}" must be a finite Unix timestamp.'.format(
field,
),
)

@classmethod
def decode(cls, did_token):
"""
Expand Down Expand Up @@ -148,6 +165,18 @@ def validate(cls, did_token):
"with a suitable value.",
)

for field in ("ext", "nbf"):
if not _is_finite_unix_seconds(claim.get(field)):
raise DIDTokenMalformed(
message='DID token "{}" must be a finite Unix timestamp.'.format(
field,
),
)
if "iat" in claim and not _is_finite_unix_seconds(claim["iat"]):
raise DIDTokenMalformed(
message='DID token "iat" must be a finite Unix timestamp.',
)

recovered_address = w3.eth.account.recover_message(
encode_defunct(
text=json.dumps(claim, separators=(",", ":")),
Expand Down
44 changes: 43 additions & 1 deletion tests/unit/resources/token_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class TestToken:

@staticmethod
def _generate_claim(fields=None):
return {field: mock.ANY for field in fields or Token.required_fields}
return {field: 1 for field in fields or Token.required_fields}

def test_required_fields(self):
assert (
Expand All @@ -39,6 +39,24 @@ def test_check_required_fields_raises_error(self):
def test_check_required_fields_passes(self):
Token._check_required_fields(self._generate_claim())

def test_check_required_fields_rejects_non_finite_ext(self):
claim = self._generate_claim()
claim["ext"] = float("inf")

with pytest.raises(DIDTokenMalformed) as e:
Token._check_required_fields(claim)

assert str(e.value) == 'DID token "ext" must be a finite Unix timestamp.'

def test_check_required_fields_rejects_nan_nbf(self):
claim = self._generate_claim()
claim["nbf"] = float("nan")

with pytest.raises(DIDTokenMalformed) as e:
Token._check_required_fields(claim)

assert str(e.value) == 'DID token "nbf" must be a finite Unix timestamp.'

def test_get_issuer_passes(self):
mocked_claim = {"iss": self.issuer}

Expand Down Expand Up @@ -286,6 +304,30 @@ def test_validate_raises_error_if_did_token_has_no_expiration(self, setup_mocks)
" token with a suitable value."
)

def test_validate_raises_error_if_ext_is_not_finite(self, setup_mocks):
setup_mocks.claim["ext"] = float("inf")

with pytest.raises(DIDTokenMalformed) as e:
Token.validate(self.did_token)

assert str(e.value) == 'DID token "ext" must be a finite Unix timestamp.'

def test_validate_raises_error_if_nbf_is_not_finite(self, setup_mocks):
setup_mocks.claim["nbf"] = float("nan")

with pytest.raises(DIDTokenMalformed) as e:
Token.validate(self.did_token)

assert str(e.value) == 'DID token "nbf" must be a finite Unix timestamp.'

def test_validate_raises_error_if_ext_is_not_numeric(self, setup_mocks):
setup_mocks.claim["ext"] = "never"

with pytest.raises(DIDTokenMalformed) as e:
Token.validate(self.did_token)

assert str(e.value) == 'DID token "ext" must be a finite Unix timestamp.'

def test_validate_raises_error_if_did_token_used_before_nbf(self, setup_mocks):
setup_mocks.epoch_time_now.return_value = setup_mocks.claim["nbf"] - 1

Expand Down