Skip to content
Draft
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
12 changes: 0 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,6 @@ is documented in `VERSIONING.md`.

## [Unreleased]

### Changed

- Release workflow now publishes to PyPI via **Trusted Publishing (OIDC)**
instead of the `PYPI_API_TOKEN` fallback that shipped `v1.2.0`. Added
`id-token: write` / `attestations: write` permissions to the publish
job and removed the explicit `password:` argument. The `environment:
pypi` gate is preserved. PEP 740 attestations are produced for every
artifact, which were disabled under the token path.
- Bumped `actions/download-artifact` from `v5` (Node 20) to `v8`
(Node 24) to clear the runner deprecation annotation. All other
actions already run on Node 24.

## [1.2.0] - 2026-04-21

## [1.1.3] - 2026-04-21
Expand Down
5 changes: 3 additions & 2 deletions COMPATIBILITY_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@
근거: [python/jwt_rs/api_jws.py](/home/statpan/workspace/pypi_lib/pyjwt-rs/python/jwt_rs/api_jws.py:153)
- [x] detached payload (`b64=False`) 지원
근거: [python/jwt_rs/api_jws.py](/home/statpan/workspace/pypi_lib/pyjwt-rs/python/jwt_rs/api_jws.py:148)
- [-] `json_encoder` 완전 호환
`json.dumps(..., cls=json_encoder)` 는 지원하지만 upstream의 전체 커스텀 알고리즘/객체 조합까지 검증하진 않음
- [x] `json_encoder` 호환
payload / header 모두 `json.dumps(..., cls=json_encoder)` 경로를 사용하며,
datetime claim 변환 이후의 payload 값과 비표준 header 값에 대한 회귀 테스트를 추가해 동작을 고정함

## 4. Decode / Validation Behavior

Expand Down
35 changes: 35 additions & 0 deletions tests/test_upstream_api_jws.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,41 @@ def default(self, o: object) -> str:
assert "some_decimal" in header
assert header["some_decimal"] == "it worked"

def test_custom_json_encoder_handles_nonstandard_header_values(
self, jws: PyJWS, payload: bytes
) -> None:
class CustomJSONEncoder(json.JSONEncoder):
def default(self, o: object) -> list[int]:
assert isinstance(o, set)
return sorted(o)

token = jws.encode(
payload,
HS256_SECRET,
headers={"x-custom": {3, 1, 2}},
json_encoder=CustomJSONEncoder,
)

header, *_ = token.split(".")
header = json.loads(base64url_decode(header))

assert header["x-custom"] == [1, 2, 3]

def test_custom_json_encoder_does_not_bypass_kid_validation(
self, jws: PyJWS, payload: bytes
) -> None:
class CustomJSONEncoder(json.JSONEncoder):
def default(self, o: object) -> str:
return "serialized"

with pytest.raises(InvalidTokenError, match="Key ID header parameter must be a string"):
jws.encode(
payload,
HS256_SECRET,
headers={"kid": object()},
json_encoder=CustomJSONEncoder,
)

def test_encode_headers_parameter_adds_headers(
self, jws: PyJWS, payload: bytes
) -> None:
Expand Down
19 changes: 19 additions & 0 deletions tests/test_upstream_api_jwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,25 @@ def default(self, o: object) -> str:

assert payload == {"some_decimal": "it worked"}

def test_custom_json_encoder_handles_nested_values_after_datetime_conversion(
self, jwt: PyJWT
) -> None:
class CustomJSONEncoder(json.JSONEncoder):
def default(self, o: object) -> list[int]:
assert isinstance(o, set)
return sorted(o)

data = {
"exp": datetime.now(tz=timezone.utc) + timedelta(minutes=5),
"values": {3, 1, 2},
}

token = jwt.encode(data, HS256_SECRET, json_encoder=CustomJSONEncoder)
payload = jwt.decode(token, HS256_SECRET, algorithms=["HS256"])

assert isinstance(payload["exp"], int)
assert payload["values"] == [1, 2, 3]

def test_decode_with_verify_exp_option(
self, jwt: PyJWT, payload: dict[str, object]
) -> None:
Expand Down