Skip to content

Fix JWT audience validation and request/response mismatches; add offline and live test suites (0.3.0) - #11

Merged
rafaga merged 6 commits into
masterfrom
dev
Nov 4, 2026
Merged

rafaga merged 6 commits into
masterfrom
dev

Conversation

@rafaga

@rafaga rafaga commented Nov 4, 2026

Copy link
Copy Markdown
Owner

Security

  • SSO token validation now checks the audience. validate_jwt accepted any token whose aud listed EVE Online, which every EVE token does, so a token issued to another application passed. It now requires aud to contain this client's ID and returns EsiError::InvalidJWT otherwise.

Fixes

  • POST/PUT requests now send Content-Type: application/json (none was sent before).
  • A client with an access token but no expiration no longer panics on an authenticated request.
  • JWT validation no longer panics on a malformed key set or a token without iss; both return InvalidJWT. A failing status when fetching the keys is reported as InvalidStatusCode.
  • The Host header is no longer forced to login.eveonline.com. It was sent even when token_url pointed somewhere else, which broke custom/local SSO endpoints.
  • create_character_fitting reads the {"fitting_id": n} object ESI sends (it expected a bare number). The return type is still i64.
  • get_bloodlines no longer fails as a whole: ESI sends ship_type_id: null for six bloodlines even though the spec requires it. Found by the new live test.

Breaking changes

  • Bloodline::ship_type_id is now Option<i64>.
  • Fields the spec does not mark as required are now Option: ContainerLogEntry::password_type, and amount plus character_id / corporation_id of the faction warfare leaderboard items.
  • rustls is now always enabled through reqwest; the separate rustls-tls feature was removed.

Added

  • EsiBuilder::metadata_url: address of the SSO metadata document that points to the validation keys (default: EVE SSO), so the validated authenticate flow can run against a local server.

Tests

All of these run offline against a local HTTP server (tests/support), except the live one:

  • tests/requests.rs: method, path, query, body and headers for each macro kind (api_get!/post!/put!/delete!).
  • tests/errors.rs: error statuses, 429 group/Retry-After, the error limit (also when met at the SSO), unreadable bodies, server down, timeout, errors not cached, missing spec.
  • tests/oauth.rs: authorize URL (both flows), refresh, and the validated flow with signed JWTs (valid, legacy issuer, wrong audience, expired, wrong issuer, tampered signature, missing jwks_uri, keys unavailable, PKCE exchange).
  • tests/schema_values.rs: generates values from the OpenAPI spec for every response type and checks they deserialize (required fields only, all fields, nulls, enums, oneOf).
  • src/pkce.rs: S256 challenge checked against the RFC 7636 vector.
  • tests/live.rs (ignored): one public endpoint per group on the real ESI, read into the crate's types. Run with cargo test --test live -- --ignored --nocapture.
  • .github/workflows/live-esi.yml: runs the live test weekly (Wednesdays) and on demand, retrying once.

How to test

cargo test
cargo test --no-default-features
cargo test --test live -- --ignored

…0.3.0)

Security:
* Validate that the `aud` claim of the SSO token contains the client ID.
  `set_audience` accepts a token that lists any of the audiences and every
  token lists "EVE Online", so a token issued for another application was
  accepted.

Fixes:
* Send `Content-Type: application/json` with every request that has a
  body; none was sent before.
* Stop forcing `Host: login.eveonline.com` on the SSO token requests. The
  host now comes from `token_url`, so a custom token URL is addressed
  correctly. With the default URL nothing changes.
* `check_authentication` no longer panics on an access token without an
  expiration: the token is sent and ESI decides if it is still valid.
* Validating the SSO token no longer panics on a malformed key set or on
  a token without an `iss` claim; both return `EsiError::InvalidJWT`. A
  failing status when fetching the keys is `EsiError::InvalidStatusCode`.
* `FittingsGroup::create_character_fitting` reads the answer as the
  object ESI sends (`{"fitting_id": n}`) instead of a bare number. It
  still returns the `i64` ID.

Breaking:
* Fields that the spec does not mark as required are now `Option`:
  `ContainerLogEntry::password_type`, and `amount` plus `character_id` /
  `corporation_id` of `CharacterLeaderboardItem` and
  `CorporationLeaderboardItem`. A real answer that omitted them failed to
  parse as a whole.

Added:
* `EsiBuilder::metadata_url`: the address of the SSO metadata document
  that points to the token validation keys (default: the EVE SSO one).

Tests:
* `tests/support`: a local HTTP server that records what it receives and
  answers per path.
* `tests/requests.rs`: method, path, query, body and headers of each
  kind of endpoint macro (get, post, put, delete).
* `tests/oauth.rs`: authorize URL for both flows, PKCE challenge against
  the verifier, token exchange and refresh, and `authenticate` with a
  signed JWT against a local SSO (`resources/test/keys/jwks.json`).
* `tests/schema_values.rs`: deserializes documents built from the
  response schema of every operation into the Rust type it returns, to
  catch narrow integers, missing `Option`s and enums that lack a value.

Docs:
* CHANGELOG: add the entries above under Unreleased.
Add tests/live.rs, an ignored test that calls one public endpoint of
each group on the real ESI and reads the answer into the type the
crate returns. It catches changes in what ESI sends that the OpenAPI
spec does not show. Run it with:

    cargo test --test live -- --ignored --nocapture

Add .github/workflows/live-esi.yml to run it weekly (Wednesday) and on
demand, trying twice before the job fails.

The test found a real bug: ESI sends `ship_type_id: null` for six
bloodlines although the spec requires it, so `get_bloodlines` failed
as a whole. `Bloodline::ship_type_id` is now `Option<i64>` (breaking
for code that reads it), with a test for the null case.

Also add tests for PKCE (`Pkce::challenge_of`, RFC 7636 S256) and
note the Bloodline fix in the CHANGELOG.
@rafaga
rafaga merged commit 48710d1 into master Nov 4, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant