Skip to content

Commit 65e065c

Browse files
feat: add refresh token handling http client
- implement a `MatrixRefreshTokenClient` to await token refresh before dispatching http requests - improve documentation about soft logout logic - fix dartdoc locations about soft logout Fixes: #2027 Signed-off-by: The one with the braid <[email protected]>
1 parent 352b3fa commit 65e065c

File tree

3 files changed

+90
-7
lines changed

3 files changed

+90
-7
lines changed

lib/matrix.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export 'src/utils/matrix_file.dart';
6363
export 'src/utils/matrix_id_string_extension.dart';
6464
export 'src/utils/matrix_localizations.dart';
6565
export 'src/utils/native_implementations.dart';
66+
export 'src/utils/matrix_refresh_token_client.dart';
6667
export 'src/utils/room_enums.dart';
6768
export 'src/utils/room_member_change_type.dart';
6869
export 'src/utils/push_notification.dart';

lib/src/client.dart

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ class Client extends MatrixApi {
9292

9393
ShareKeysWith shareKeysWith;
9494

95+
/// Implement your https://spec.matrix.org/v1.9/client-server-api/#soft-logout
96+
/// logic here.
97+
/// Set this to [Client.refreshAccessToken] for the easiest way to handle the
98+
/// most common reason for soft logouts.
99+
///
100+
/// Please ensure to also provide a [MatrixRefreshTokenClient] as
101+
/// [httpClient] in order to handle soft logout on non-sync calls.
102+
///
103+
/// You may want to wrap the default
104+
/// [Client.refreshAccessToken] implementation with retry logic in case
105+
/// you run into situations where the token refresh may fail due to bad
106+
/// network connectivity.
107+
/// You can also perform a new login here by passing the existing deviceId.
95108
Future<void> Function(Client client)? onSoftLogout;
96109

97110
DateTime? get accessTokenExpiresAt => _accessTokenExpiresAt;
@@ -151,13 +164,19 @@ class Client extends MatrixApi {
151164
)? customImageResizer;
152165

153166
/// Create a client
154-
/// [clientName] = unique identifier of this client
167+
///
168+
/// [clientName]: unique identifier of this client
169+
///
155170
/// [databaseBuilder]: A function that creates the database instance, that will be used.
171+
///
156172
/// [legacyDatabaseBuilder]: Use this for your old database implementation to perform an automatic migration
173+
///
157174
/// [databaseDestroyer]: A function that can be used to destroy a database instance, for example by deleting files from disk.
175+
///
158176
/// [verificationMethods]: A set of all the verification methods this client can handle. Includes:
159177
/// KeyVerificationMethod.numbers: Compare numbers. Most basic, should be supported
160178
/// KeyVerificationMethod.emoji: Compare emojis
179+
///
161180
/// [importantStateEvents]: A set of all the important state events to load when the client connects.
162181
/// To speed up performance only a set of state events is loaded on startup, those that are
163182
/// needed to display a room list. All the remaining state events are automatically post-loaded
@@ -171,24 +190,43 @@ class Client extends MatrixApi {
171190
/// - m.room.canonical_alias
172191
/// - m.room.tombstone
173192
/// - *some* m.room.member events, where needed
193+
///
194+
/// [httpClient]: The inner [Client] used to dispatch any HTTP requests
195+
/// performed by the SDK. The [Client] you pass here will by default be
196+
/// wrapped with a [FixedTimeoutHttpClient] with the specified
197+
/// [defaultNetworkRequestTimeout]. In case you do not wish this wrapper,
198+
/// you can later override the [httpClient] by using the
199+
/// [Client.httpClient] setter.
200+
///
201+
/// In case your homeserver supports refresh tokens, please ensure you
202+
/// provide a [MatrixRefreshTokenClient].
203+
///
174204
/// [roomPreviewLastEvents]: The event types that should be used to calculate the last event
175205
/// in a room for the room list.
206+
///
176207
/// Set [requestHistoryOnLimitedTimeline] to controll the automatic behaviour if the client
177208
/// receives a limited timeline flag for a room.
209+
///
178210
/// If [mxidLocalPartFallback] is true, then the local part of the mxid will be shown
179211
/// if there is no other displayname available. If not then this will return "Unknown user".
212+
///
180213
/// If [formatLocalpart] is true, then the localpart of an mxid will
181214
/// be formatted in the way, that all "_" characters are becomming white spaces and
182215
/// the first character of each word becomes uppercase.
216+
///
183217
/// If your client supports more login types like login with token or SSO, then add this to
184218
/// [supportedLoginTypes]. Set a custom [syncFilter] if you like. By default the app
185219
/// will use lazy_load_members.
220+
///
186221
/// Set [nativeImplementations] to [NativeImplementationsIsolate] in order to
187222
/// enable the SDK to compute some code in background.
223+
///
188224
/// Set [timelineEventTimeout] to the preferred time the Client should retry
189225
/// sending events on connection problems or to `Duration.zero` to disable it.
226+
///
190227
/// Set [customImageResizer] to your own implementation for a more advanced
191228
/// and faster image resizing experience.
229+
///
192230
/// Set [enableDehydratedDevices] to enable experimental support for enabling MSC3814 dehydrated devices.
193231
Client(
194232
this.clientName, {
@@ -222,12 +260,6 @@ class Client extends MatrixApi {
222260
this.shareKeysWith = ShareKeysWith.crossVerifiedIfEnabled,
223261
this.enableDehydratedDevices = false,
224262
this.receiptsPublicByDefault = true,
225-
226-
/// Implement your https://spec.matrix.org/v1.9/client-server-api/#soft-logout
227-
/// logic here.
228-
/// Set this to `refreshAccessToken()` for the easiest way to handle the
229-
/// most common reason for soft logouts.
230-
/// You can also perform a new login here by passing the existing deviceId.
231263
this.onSoftLogout,
232264

233265
/// Experimental feature which allows to send a custom refresh token
@@ -2438,6 +2470,7 @@ class Client extends MatrixApi {
24382470
),
24392471
);
24402472
if (e.error == MatrixError.M_UNKNOWN_TOKEN) {
2473+
// due to race conditions via QUIC, still handle soft_logout here
24412474
if (e.raw.tryGet<bool>('soft_logout') == true) {
24422475
Logs().w(
24432476
'The user has been soft logged out! Calling client.onSoftLogout() if present.',
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'package:http/http.dart' hide Client;
2+
3+
import 'package:matrix/matrix.dart';
4+
5+
class MatrixRefreshTokenClient extends BaseClient {
6+
MatrixRefreshTokenClient({
7+
required this.inner,
8+
required this.client,
9+
});
10+
11+
final Client client;
12+
final BaseClient inner;
13+
14+
@override
15+
Future<StreamedResponse> send(BaseRequest request) async {
16+
Request? req;
17+
if ( // only refresh if
18+
// we are actually initialized
19+
client.onSync.value != null &&
20+
// the request is to the homeserver rather than e.g. IDP
21+
request.url.host == client.homeserver?.host &&
22+
// the request is authenticated
23+
request.headers
24+
.map((k, v) => MapEntry(k.toLowerCase(), v))
25+
.containsKey('authorization') &&
26+
// and last but not least we're logged in
27+
client.isLogged()) {
28+
try {
29+
await client.ensureNotSoftLoggedOut();
30+
} catch (e) {
31+
Logs().w('Could not rotate token before dispatching HTTP request.', e);
32+
}
33+
// in every case ensure we run with the latest bearer token to avoid
34+
// race conditions
35+
finally {
36+
final headers = request.headers;
37+
// hours wasted : unknown :facepalm:
38+
headers.removeWhere((k, _) => k.toLowerCase() == 'authorization');
39+
headers['Authorization'] = 'Bearer ${client.bearerToken!}';
40+
req = Request(request.method, request.url);
41+
req.headers.addAll(headers);
42+
if (request is Request) {
43+
req.bodyBytes = request.bodyBytes;
44+
}
45+
}
46+
}
47+
return inner.send(req ?? request);
48+
}
49+
}

0 commit comments

Comments
 (0)