From 3f8a4c818980179ffffeb34f8d55f5afb9181ba9 Mon Sep 17 00:00:00 2001 From: Krille Date: Fri, 26 Apr 2024 10:19:49 +0200 Subject: [PATCH] feat: Implement unpublished MSC custom refresh token lifetime --- ...blished_custom_refresh_token_lifetime.dart | 56 +++++++++++++++++++ lib/src/client.dart | 13 ++++- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 lib/msc_extensions/msc_unpublished_custom_refresh_token_lifetime/msc_unpublished_custom_refresh_token_lifetime.dart diff --git a/lib/msc_extensions/msc_unpublished_custom_refresh_token_lifetime/msc_unpublished_custom_refresh_token_lifetime.dart b/lib/msc_extensions/msc_unpublished_custom_refresh_token_lifetime/msc_unpublished_custom_refresh_token_lifetime.dart new file mode 100644 index 00000000..6a6b5fee --- /dev/null +++ b/lib/msc_extensions/msc_unpublished_custom_refresh_token_lifetime/msc_unpublished_custom_refresh_token_lifetime.dart @@ -0,0 +1,56 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; + +import 'package:matrix/matrix.dart'; + +extension MscUnpublishedCustomRefreshTokenLifetime on MatrixApi { + static const String customFieldKey = 'com.famedly.refresh_token_lifetime_ms'; + + /// Refresh an access token. Clients should use the returned access token + /// when making subsequent API calls, and store the returned refresh token + /// (if given) in order to refresh the new access token when necessary. + /// + /// After an access token has been refreshed, a server can choose to + /// invalidate the old access token immediately, or can choose not to, for + /// example if the access token would expire soon anyways. Clients should + /// not make any assumptions about the old access token still being valid, + /// and should use the newly provided access token instead. + /// + /// The old refresh token remains valid until the new access token or refresh token + /// is used, at which point the old refresh token is revoked. + /// + /// Note that this endpoint does not require authentication via an + /// access token. Authentication is provided via the refresh token. + /// + /// Application Service identity assertion is disabled for this endpoint. + /// + /// [refreshToken] The refresh token + Future refreshWithCustomRefreshTokenLifetime( + String refreshToken, { + /// This allows clients to pass an extra parameter when refreshing a token, + /// which overrides the configured refresh token timeout in the Synapse + /// config. This allows a client to opt into a shorter (or longer) lifetime + /// for their refresh token, which could be used to sign out web sessions + /// with a specific timeout. + /// + /// Experimental implementation in Synapse: + /// https://github.com/famedly/synapse/pull/10 + int? refreshTokenLifetimeMs, + }) async { + final requestUri = Uri(path: '_matrix/client/v3/refresh'); + final request = Request('POST', baseUri!.resolveUri(requestUri)); + request.headers['content-type'] = 'application/json'; + request.bodyBytes = utf8.encode(jsonEncode({ + 'refresh_token': refreshToken, + if (refreshTokenLifetimeMs != null) + customFieldKey: refreshTokenLifetimeMs, + })); + final response = await httpClient.send(request); + final responseBody = await response.stream.toBytes(); + if (response.statusCode != 200) unexpectedResponse(response, responseBody); + final responseString = utf8.decode(responseBody); + final json = jsonDecode(responseString); + return RefreshResponse.fromJson(json as Map); + } +} diff --git a/lib/src/client.dart b/lib/src/client.dart index d3eed5c4..2980bdfa 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -31,6 +31,7 @@ import 'package:random_string/random_string.dart'; import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; +import 'package:matrix/msc_extensions/msc_unpublished_custom_refresh_token_lifetime/msc_unpublished_custom_refresh_token_lifetime.dart'; import 'package:matrix/src/models/timeline_chunk.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/client_init_exception.dart'; @@ -195,6 +196,11 @@ class Client extends MatrixApi { /// most common reason for soft logouts. /// You can also perform a new login here by passing the existing deviceId. this.onSoftLogout, + + /// Experimental feature which allows to send a custom refresh token + /// lifetime to the server which overrides the default one. Needs server + /// support. + this.customRefreshTokenLifetime, }) : syncFilter = syncFilter ?? Filter( room: RoomFilter( @@ -238,6 +244,8 @@ class Client extends MatrixApi { registerDefaultCommands(); } + Duration? customRefreshTokenLifetime; + /// Fetches the refreshToken from the database and tries to get a new /// access token from the server and then stores it correctly. Unlike the /// pure API call of `Client.refresh()` this handles the complete soft @@ -257,7 +265,10 @@ class Client extends MatrixApi { throw Exception('Cannot refresh access token when not logged in'); } - final tokenResponse = await refresh(refreshToken); + final tokenResponse = await refreshWithCustomRefreshTokenLifetime( + refreshToken, + refreshTokenLifetimeMs: customRefreshTokenLifetime?.inMilliseconds, + ); accessToken = tokenResponse.accessToken; final expiresInMs = tokenResponse.expiresInMs;