From 9e99008ada7631280eace06055c98d76df5597ea Mon Sep 17 00:00:00 2001 From: cogwheel Date: Thu, 8 Oct 2026 15:05:31 +0530 Subject: [PATCH] Keep a route's headers on the HTTPS upgrade its probe follows A route probe follows an upgrade to HTTPS on the server's host, as the API client does, but the health client dropped the route's own headers -- an access token for the proxy in front, say -- from any request away from the route's origin, that upgrade included. The server answered the probe's second request with its sign-in, and a working route was taken not to answer. The headers now go where the API client sends them: the route's origin, and that upgrade on its host. The logout fence still keeps the cookie off. --- .../lib/services/connectivity_service.dart | 11 +++- .../connectivity_service_lifecycle_test.dart | 51 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/conduit_core/lib/services/connectivity_service.dart b/packages/conduit_core/lib/services/connectivity_service.dart index d5d421f14..017e0c7fd 100644 --- a/packages/conduit_core/lib/services/connectivity_service.dart +++ b/packages/conduit_core/lib/services/connectivity_service.dart @@ -696,8 +696,15 @@ Dio createConnectivityHealthClient( dio.interceptors.add( InterceptorsWrapper( onRequest: (options, handler) { - if (ConnectivityService._originKey(options.uri) != - ConnectivityService._originKey(serverUri)) { + // Kept where the API client keeps them: the server's own origin, + // or an upgrade to HTTPS on its host that a probe follows. + final target = options.uri; + final sameServer = + ConnectivityService._originKey(target) == + ConnectivityService._originKey(serverUri) || + (serverUri != null && + isCredentialSafeRedirectTarget(serverUri, target)); + if (!sameServer) { options.headers.removeWhere( (name, _) => customHeaderNames.contains(name.toLowerCase()), ); diff --git a/test/core/services/connectivity_service_lifecycle_test.dart b/test/core/services/connectivity_service_lifecycle_test.dart index 82405f725..0b65baefb 100644 --- a/test/core/services/connectivity_service_lifecycle_test.dart +++ b/test/core/services/connectivity_service_lifecycle_test.dart @@ -223,6 +223,28 @@ void main() { }, _RealHttpOverrides()); }); + // A probe follows an upgrade to HTTPS on the server's host, as the API + // client does; the route's own headers go with it, and nowhere else. + test('health client keeps the route headers on an upgrade to HTTPS', () async { + final dio = createConnectivityHealthClient( + const ServerConfig( + id: 'route', + name: 'Route', + url: 'http://chat.example', + customHeaders: {'CF-Access-Client-Id': 'route-id'}, + ), + ); + addTearDown(dio.close); + final sent = {}; + dio.httpClientAdapter = _HeaderRecorder(sent); + + await dio.getUri(Uri.parse('https://chat.example/health')); + await dio.getUri(Uri.parse('https://other.example/health')); + + check(sent['https://chat.example/health']).equals('route-id'); + check(sent['https://other.example/health']).isNull(); + }); + group('a route probe', () { /// Serves [handle] on a loopback port for the length of the test. Future serve( @@ -734,3 +756,32 @@ final class _FailingHealthAdapter implements _RequestCountingAdapter { } final class _RealHttpOverrides extends HttpOverrides {} + +/// Records the route header each request went out with, by URL. +final class _HeaderRecorder implements HttpClientAdapter { + _HeaderRecorder(this.sent); + + final Map sent; + + @override + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ) async { + sent[options.uri.toString()] = options.headers.entries + .where((entry) => entry.key.toLowerCase() == 'cf-access-client-id') + .map((entry) => entry.value) + .firstOrNull; + return ResponseBody.fromString( + '{"status":true}', + HttpStatus.ok, + headers: { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + ); + } + + @override + void close({bool force = false}) {} +}