Skip to content

Commit

Permalink
Always set tokenEndpoint using value of authorizationUri from `OA…
Browse files Browse the repository at this point in the history
…uthSettings` during `LoginClient` initialization. (#338)

* Add option to override token endpoint during initialization

* Remove unused imports

* Update doc

* Extract getting credentials to private method

* Always set `tokenEndpoint` using value of `authorizationUri` from `OAuthSettings` during `LoginClient` initialization

* Update packages/login_client/CHANGELOG.md

Co-authored-by: Albert Wolszon <albert.wolszon@leancode.pl>

* Update packages/login_client/lib/src/login_client.dart

Co-authored-by: Albert Wolszon <albert.wolszon@leancode.pl>

* Update docs

* Test whether tokenEndpoint has been replaced

* Use copyWithTokenEndpoint in test

* Compare only tokenEndpoint in test

---------

Co-authored-by: Albert Wolszon <albert.wolszon@leancode.pl>
  • Loading branch information
pdenert and Albert221 authored Aug 23, 2024
1 parent a1b8fc5 commit c94f63d
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 9 deletions.
3 changes: 2 additions & 1 deletion packages/login_client/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.

Expand Down
30 changes: 24 additions & 6 deletions packages/login_client/lib/src/login_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -87,8 +83,11 @@ class LoginClient extends http.BaseClient {
Future<oauth2.Credentials?> get credentials => _credentialsStorage.read();

/// Restores saved credentials from the credentials storage.
///
/// `_oAuthSettings.authorizationUri` is used as the `tokenEndpoint`.
Future<void> initialize() async {
final credentials = await _credentialsStorage.read();
final credentials = await getCredentialsToInitialize();

if (credentials != null) {
_oAuthClient = buildOAuth2ClientFromCredentials(
credentials,
Expand All @@ -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<Credentials?> 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.
Expand Down
16 changes: 16 additions & 0 deletions packages/login_client/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
);
}
}
2 changes: 1 addition & 1 deletion packages/login_client/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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: >-
Expand Down
39 changes: 38 additions & 1 deletion packages/login_client/test/login_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -31,7 +32,10 @@ void main() {

setUpAll(() {
registerFallbackValue(
OAuthSettings(authorizationUri: Uri(), clientId: ''),
OAuthSettings(
authorizationUri: Uri.parse('https://leancode.co'),
clientId: '',
),
);
registerFallbackValue(MockClient());
registerFallbackValue(Credentials(''));
Expand All @@ -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(
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit c94f63d

Please sign in to comment.