From 25f34b51647cfb5fa4b261533257c46034d19742 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 10 Jul 2026 12:17:47 +0200 Subject: [PATCH 1/2] fix userId for guest --- .../api/interceptors/auth_interceptor.dart | 7 +- .../interceptors/auth_interceptor_test.dart | 95 +++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 packages/stream_core/test/api/interceptors/auth_interceptor_test.dart diff --git a/packages/stream_core/lib/src/api/interceptors/auth_interceptor.dart b/packages/stream_core/lib/src/api/interceptors/auth_interceptor.dart index ea7a0ed3..b56d7026 100644 --- a/packages/stream_core/lib/src/api/interceptors/auth_interceptor.dart +++ b/packages/stream_core/lib/src/api/interceptors/auth_interceptor.dart @@ -23,7 +23,12 @@ class AuthInterceptor extends QueuedInterceptor { try { final token = await _tokenManager.getToken(); - options.queryParameters['user_id'] = _tokenManager.userId; + // Use the resolved token's own user id rather than + // `_tokenManager.userId`: some token providers (e.g. guest exchanges) + // can resolve to a different id than the one originally requested, and + // this must stay consistent with the identity in the `Authorization` + // header below. + options.queryParameters['user_id'] = token.userId; options.headers['Authorization'] = token.rawValue; options.headers['stream-auth-type'] = token.authType.headerValue; diff --git a/packages/stream_core/test/api/interceptors/auth_interceptor_test.dart b/packages/stream_core/test/api/interceptors/auth_interceptor_test.dart new file mode 100644 index 00000000..0337a76e --- /dev/null +++ b/packages/stream_core/test/api/interceptors/auth_interceptor_test.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; + +import 'package:stream_core/stream_core.dart'; +import 'package:test/test.dart'; + +// A minimal HttpClientAdapter that captures the outgoing RequestOptions and +// always responds with an empty successful response. +class _CapturingHttpClientAdapter implements HttpClientAdapter { + RequestOptions? lastRequest; + + @override + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ) async { + lastRequest = options; + return ResponseBody.fromString( + '{}', + 200, + headers: { + Headers.contentTypeHeader: [Headers.jsonContentType], + }, + ); + } + + @override + void close({bool force = false}) {} +} + +UserToken _generateTestUserToken(String userId) { + String b64UrlNoPad(Object jsonObj) { + final bytes = utf8.encode(jsonEncode(jsonObj)); + return base64Url.encode(bytes).replaceAll('=', ''); + } + + final header = {'alg': 'none', 'typ': 'JWT'}; + final payload = {'user_id': userId}; + + final jwt = '${b64UrlNoPad(header)}.${b64UrlNoPad(payload)}.'; + return UserToken(jwt); +} + +void main() { + group('AuthInterceptor', () { + test( + "uses the resolved token's own user id for the user_id query " + 'parameter, not the id the TokenManager was constructed with', + () async { + // Simulates a token provider (e.g. a guest exchange) that resolves + // to a different id than the one originally requested. + final tokenManager = TokenManager( + userId: 'requested-id', + tokenProvider: TokenProvider.dynamic( + (_) async => _generateTestUserToken('server-assigned-id'), + ), + ); + + final dio = Dio(BaseOptions(baseUrl: 'https://example.com')); + final adapter = _CapturingHttpClientAdapter(); + dio.httpClientAdapter = adapter; + dio.interceptors.add(AuthInterceptor(dio, tokenManager)); + + await dio.get('/test'); + + expect( + adapter.lastRequest?.queryParameters['user_id'], + 'server-assigned-id', + ); + }, + ); + + test( + 'matches the TokenManager userId when the token resolves to the ' + 'same id (regular/anonymous users)', + () async { + final tokenManager = TokenManager( + userId: 'user-123', + tokenProvider: TokenProvider.static( + _generateTestUserToken('user-123'), + ), + ); + + final dio = Dio(BaseOptions(baseUrl: 'https://example.com')); + final adapter = _CapturingHttpClientAdapter(); + dio.httpClientAdapter = adapter; + dio.interceptors.add(AuthInterceptor(dio, tokenManager)); + + await dio.get('/test'); + + expect(adapter.lastRequest?.queryParameters['user_id'], 'user-123'); + }, + ); + }); +} From 234298a391522ce637b168d766755ddcee8bb5ba Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 10 Jul 2026 12:23:42 +0200 Subject: [PATCH 2/2] Add changelog entry --- packages/stream_core/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/stream_core/CHANGELOG.md b/packages/stream_core/CHANGELOG.md index 7e039175..58f95ede 100644 --- a/packages/stream_core/CHANGELOG.md +++ b/packages/stream_core/CHANGELOG.md @@ -4,6 +4,10 @@ - Added `teams` field to `User` class. +### 🐛 Bug Fixes + +- `AuthInterceptor` now sets the `user_id` query parameter from the resolved token's own user id instead of `TokenManager.userId`. This keeps REST calls consistent for token providers that can resolve to a different id than requested (e.g. guest token exchanges). + ## 0.4.0 ### 💥 BREAKING CHANGES