From 95459b1c95eb2a78c2dff3af20f961967cdec5c2 Mon Sep 17 00:00:00 2001 From: Boris Tyshkevich Date: Tue, 28 Apr 2026 05:06:24 +0200 Subject: [PATCH] Harden JWTProvider device-flow token poll against non-JSON responses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JWTProvider::deviceCodeLogin parsed each token-endpoint response with Poco::JSON::Parser().parse(...).extract() unconditionally. When the IdP (or a CDN in front of it) returned a non-JSON body — an HTML error page, an empty body on transient TCP/TLS failure, a non- object JSON value — the extract() call threw std::bad_cast and aborted the whole device flow with no useful diagnostic, even though the problem was transient. Wrap parse + extract in try/catch and treat any parse failure as "transient — keep polling". The device flow is by design tolerant of intermittent token-endpoint errors (RFC 8628 §3.5 lists slow_down / authorization_pending as expected during the polling window), and the outer expires_in clamp bounds total retry time, so a missed poll has no worse impact than a slow_down. Emit a warning naming the status code and a body excerpt so operators can debug systemic IdP issues without losing the login. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Boris Tyshkevich --- src/Client/JWTProvider.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Client/JWTProvider.cpp b/src/Client/JWTProvider.cpp index 1ba8188310b8..a266abcd99ca 100644 --- a/src/Client/JWTProvider.cpp +++ b/src/Client/JWTProvider.cpp @@ -146,7 +146,32 @@ void JWTProvider::deviceCodeLogin() std::istream & token_rs = token_session->receiveResponse(token_response); std::string response_body; Poco::StreamCopier::copyToString(token_rs, response_body); - Poco::JSON::Object::Ptr token_object = Poco::JSON::Parser().parse(response_body).extract(); + + /// The bare extract() pattern threw std::bad_cast whenever + /// the IdP returned a non-JSON or non-object body (HTML error page from + /// a CDN, empty response on transient failure, etc.), aborting the + /// whole login with no useful diagnostic. Treat parse failure as + /// "transient — keep polling": the device flow is by design tolerant + /// of intermittent token-endpoint errors, and the loop's outer + /// expires_in clamp bounds the total retry window. + Poco::JSON::Object::Ptr token_object; + try + { + auto parsed = Poco::JSON::Parser().parse(response_body); + token_object = parsed.extract(); + } + catch (const std::exception & e) + { + error_stream << "Warning: token endpoint returned non-JSON response (status " + << static_cast(token_response.getStatus()) << ", " << e.what() + << "); will retry. Body: " << response_body.substr(0, 256) << "\n"; + continue; + } + if (!token_object) + { + error_stream << "Warning: token endpoint returned non-object JSON; will retry.\n"; + continue; + } if (token_response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) {