Merge pull request #1755 from famedly/krille/custom-refresh-token-lifetime

feat: Implement unpublished MSC custom refresh token lifetime
This commit is contained in:
Krille-chan 2024-05-14 10:49:17 +02:00 committed by GitHub
commit 476d86b425
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 10 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/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;
@ -1812,19 +1823,25 @@ class Client extends MatrixApi {
return;
}
Future<void>? _handleSoftLogoutFuture;
Future<void> _handleSoftLogout() async {
final onSoftLogout = this.onSoftLogout;
if (onSoftLogout == null) return;
onLoginStateChanged.add(LoginState.softLoggedOut);
try {
await onSoftLogout(this);
onLoginStateChanged.add(LoginState.loggedIn);
} catch (e, s) {
Logs().w('Unable to refresh session after soft logout', e, s);
await clear();
rethrow;
}
_handleSoftLogoutFuture ??= () async {
onLoginStateChanged.add(LoginState.softLoggedOut);
try {
await onSoftLogout(this);
onLoginStateChanged.add(LoginState.loggedIn);
} catch (e, s) {
Logs().w('Unable to refresh session after soft logout', e, s);
await clear();
rethrow;
}
}();
await _handleSoftLogoutFuture;
_handleSoftLogoutFuture = null;
}
/// Checks if the token expires in under [expiresIn] time and calls the