feat: Implement unpublished MSC custom refresh token lifetime

This commit is contained in:
Krille 2024-04-26 10:19:49 +02:00
parent 5ecff08bf4
commit 3f8a4c8189
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
2 changed files with 68 additions and 1 deletions

View File

@ -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<RefreshResponse> 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<String, Object?>);
}
}

View File

@ -31,6 +31,7 @@ import 'package:random_string/random_string.dart';
import 'package:matrix/encryption.dart'; import 'package:matrix/encryption.dart';
import 'package:matrix/matrix.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/models/timeline_chunk.dart';
import 'package:matrix/src/utils/cached_stream_controller.dart'; import 'package:matrix/src/utils/cached_stream_controller.dart';
import 'package:matrix/src/utils/client_init_exception.dart'; import 'package:matrix/src/utils/client_init_exception.dart';
@ -195,6 +196,11 @@ class Client extends MatrixApi {
/// most common reason for soft logouts. /// most common reason for soft logouts.
/// You can also perform a new login here by passing the existing deviceId. /// You can also perform a new login here by passing the existing deviceId.
this.onSoftLogout, 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 ?? }) : syncFilter = syncFilter ??
Filter( Filter(
room: RoomFilter( room: RoomFilter(
@ -238,6 +244,8 @@ class Client extends MatrixApi {
registerDefaultCommands(); registerDefaultCommands();
} }
Duration? customRefreshTokenLifetime;
/// Fetches the refreshToken from the database and tries to get a new /// Fetches the refreshToken from the database and tries to get a new
/// access token from the server and then stores it correctly. Unlike the /// access token from the server and then stores it correctly. Unlike the
/// pure API call of `Client.refresh()` this handles the complete soft /// 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'); 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; accessToken = tokenResponse.accessToken;
final expiresInMs = tokenResponse.expiresInMs; final expiresInMs = tokenResponse.expiresInMs;