diff --git a/packages/login_client/CHANGELOG.md b/packages/login_client/CHANGELOG.md index 30bfdd32..fd583973 100644 --- a/packages/login_client/CHANGELOG.md +++ b/packages/login_client/CHANGELOG.md @@ -1,5 +1,6 @@ -# Unreleased +# 3.2.0 +- Always use `OAuthSettings.authorizationUri` for token endpoint in `LoginClient` initialization (for refreshing the token). - Bump `leancode_lint` dev dependency to `12.0.0`. - Bump `custom_lint` dev dependency to `0.6.4`. diff --git a/packages/login_client/lib/src/login_client.dart b/packages/login_client/lib/src/login_client.dart index 525ba059..e94dee2e 100644 --- a/packages/login_client/lib/src/login_client.dart +++ b/packages/login_client/lib/src/login_client.dart @@ -15,14 +15,10 @@ import 'dart:async'; import 'package:http/http.dart' as http; +import 'package:login_client/login_client.dart'; import 'package:meta/meta.dart'; import 'package:oauth2/oauth2.dart' as oauth2; -import 'credentials_storage/credentials_storage.dart'; -import 'credentials_storage/in_memory_credentials_storage.dart'; -import 'oauth_settings.dart'; -import 'refresh_exception.dart'; -import 'strategies/authorization_strategy.dart'; import 'utils.dart'; typedef _LoggerCallback = void Function(String); @@ -87,8 +83,11 @@ class LoginClient extends http.BaseClient { Future get credentials => _credentialsStorage.read(); /// Restores saved credentials from the credentials storage. + /// + /// `_oAuthSettings.authorizationUri` is used as the `tokenEndpoint`. Future initialize() async { - final credentials = await _credentialsStorage.read(); + final credentials = await getCredentialsToInitialize(); + if (credentials != null) { _oAuthClient = buildOAuth2ClientFromCredentials( credentials, @@ -107,6 +106,25 @@ class LoginClient extends http.BaseClient { } } + /// Restores saved credentials from the credentials storage and sets + /// `tokenEndpoint` to the one provided in the `oAuthSettings`. + Future getCredentialsToInitialize() async { + final credentials = await _credentialsStorage.read(); + + if (credentials != null) { + // Based on oauth package documentation, `Credentials` `tokenEndpoint` may + // be `null`, indicating that the credentials can't be refreshed. + if (credentials.tokenEndpoint != null) { + return credentials + .copyWithTokenEndpoint(_oAuthSettings.authorizationUri); + } + + return credentials; + } + + return null; + } + /// Authorizes the [LoginClient] using the passed `strategy`. /// /// This method will log the [LoginClient] out on the authorization failure. diff --git a/packages/login_client/lib/src/utils.dart b/packages/login_client/lib/src/utils.dart index ac6cc73f..935b1247 100644 --- a/packages/login_client/lib/src/utils.dart +++ b/packages/login_client/lib/src/utils.dart @@ -14,6 +14,7 @@ import 'package:http/http.dart' as http; import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:oauth2/oauth2.dart'; import 'oauth_settings.dart'; @@ -35,3 +36,18 @@ oauth2.Client buildOAuth2ClientFromCredentials( onCredentialsRefreshed: onCredentialsRefreshed, ); } + +/// Extension methods for the [Credentials] class. +extension CredentialsExt on Credentials { + /// Creates a copy of the current [Credentials] with the provided [tokenEndpoint]. + Credentials copyWithTokenEndpoint(Uri newTokenEndpoint) { + return Credentials( + accessToken, + refreshToken: refreshToken, + idToken: idToken, + tokenEndpoint: newTokenEndpoint, + scopes: scopes, + expiration: expiration, + ); + } +} diff --git a/packages/login_client/pubspec.yaml b/packages/login_client/pubspec.yaml index 0fd3d9b5..e7c85c88 100644 --- a/packages/login_client/pubspec.yaml +++ b/packages/login_client/pubspec.yaml @@ -1,5 +1,5 @@ name: login_client -version: 3.1.0 +version: 3.2.0 homepage: https://github.com/leancodepl/flutter_corelibrary/tree/master/packages/login_client repository: https://github.com/leancodepl/flutter_corelibrary description: >- diff --git a/packages/login_client/test/login_client_test.dart b/packages/login_client/test/login_client_test.dart index 4e395700..6e9a41db 100644 --- a/packages/login_client/test/login_client_test.dart +++ b/packages/login_client/test/login_client_test.dart @@ -8,6 +8,7 @@ import 'package:http/http.dart' StreamedRequest, StreamedResponse; import 'package:login_client/login_client.dart'; +import 'package:login_client/src/utils.dart'; import 'package:mocktail/mocktail.dart'; import 'package:oauth2/oauth2.dart'; import 'package:test/test.dart'; @@ -31,7 +32,10 @@ void main() { setUpAll(() { registerFallbackValue( - OAuthSettings(authorizationUri: Uri(), clientId: ''), + OAuthSettings( + authorizationUri: Uri.parse('https://leancode.co'), + clientId: '', + ), ); registerFallbackValue(MockClient()); registerFallbackValue(Credentials('')); @@ -42,6 +46,8 @@ void main() { oAuthSettings = MockOAuthSettings(); when(() => oAuthSettings.clientId).thenReturn('client id'); when(() => oAuthSettings.clientSecret).thenReturn('client secret'); + when(() => oAuthSettings.authorizationUri) + .thenReturn(Uri.parse('https://leancode.co')); credentialsStorage = MockCredentialsStorage(); logger = MockLogger(); loginClient = LoginClient( @@ -73,6 +79,37 @@ void main() { }, ); + test( + 'initialize() reads from storage, tokenEndpoint has been replaced', + () async { + final credentials = Credentials( + 'some token', + tokenEndpoint: Uri.parse('https://example.com'), + ); + + when(() => credentialsStorage.read()) + .thenAnswer((_) async => credentials); + + loginClient.onCredentialsChanged.listen( + expectAsync1((credentials) { + expect( + credentials?.tokenEndpoint, + Uri.parse('https://leancode.co'), + ); + }), + ); + + await loginClient.initialize(); + + expect(loginClient.loggedIn, true); + + verify(() => credentialsStorage.read()).called(1); + verify(() => oAuthSettings.authorizationUri).called(1); + verify(() => logger('Successfully initialized with credentials.')) + .called(1); + }, + ); + test( 'logIn() calls callbacks, saves credentials and logs on success', () async {