Skip to content

Commit 7a39401

Browse files
author
Peter Bryant
committed
Make token retrieval optionally async
1 parent bf0426a commit 7a39401

10 files changed

+55
-45
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 2.0.0
2+
3+
- BREAKING: `TokenStorage` now provides the option to asynchronously retrieve tokens.
4+
- Fix internal lint errors
5+
- Upgrade dependencies
6+
17
## 1.0.2
28

39
- Automatically refreshes expired client tokens

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Easily authenticate using OAuth 2.0 client/password grants.
1414
Install passputter from [pub.dev](https://pub.dev/packages/passputter):
1515

1616
```yaml
17-
passputter: ^1.0.2
17+
passputter: ^2.0.0
1818
```
1919
2020
## ✅ Prerequisites
@@ -47,7 +47,7 @@ class HiveTokenStorage implements TokenStorage {
4747
static const _userTokenKey = 'userToken';
4848
4949
@override
50-
OAuthToken? get clientToken {
50+
FutureOr<OAuthToken?> get clientToken async {
5151
final tokenMap = _box.get(_clientTokenKey);
5252
if (tokenMap != null) {
5353
return OAuthToken.fromJson(tokenMap);
@@ -57,7 +57,7 @@ class HiveTokenStorage implements TokenStorage {
5757
}
5858
5959
@override
60-
OAuthToken? get userToken {
60+
FutureOr<OAuthToken?> get userToken async {
6161
final tokenMap = _box.get(_userTokenKey);
6262
if (tokenMap != null) {
6363
return OAuthToken.fromJson(tokenMap);

lib/src/client_token_interceptor.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// 📦 Package imports:
22
import 'package:clock/clock.dart';
33
import 'package:dio/dio.dart';
4-
54
// 🌎 Project imports:
65
import 'package:passputter/passputter.dart';
76
import 'package:passputter/src/oauth_api_interface.dart';
@@ -38,7 +37,7 @@ class ClientTokenInterceptor extends Interceptor {
3837
RequestOptions options,
3938
RequestInterceptorHandler handler,
4039
) async {
41-
final token = tokenStorage.clientToken;
40+
final token = await tokenStorage.clientToken;
4241
if (token == null) {
4342
try {
4443
// No token saved; get another one.

lib/src/oauth_api_impl.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// 📦 Package imports:
22
import 'package:dio/dio.dart';
3-
43
// 🌎 Project imports:
54
import 'package:passputter/src/oauth_api_interface.dart';
65
import 'package:passputter/src/oauth_token.dart';
@@ -23,7 +22,7 @@ class OAuthApiImpl implements OAuthApiInterface {
2322
required String clientId,
2423
required String clientSecret,
2524
}) async {
26-
final r = await client.post(
25+
final r = await client.post<String>(
2726
endpoint,
2827
data: <String, String>{
2928
'client_id': clientId,
@@ -33,7 +32,7 @@ class OAuthApiImpl implements OAuthApiInterface {
3332
options: Options(contentType: Headers.formUrlEncodedContentType),
3433
);
3534

36-
return OAuthToken.fromJson(r.data);
35+
return OAuthToken.fromJson(r.data!);
3736
}
3837

3938
@override
@@ -42,7 +41,7 @@ class OAuthApiImpl implements OAuthApiInterface {
4241
required String clientId,
4342
required String clientSecret,
4443
}) async {
45-
final r = await client.post(
44+
final r = await client.post<String>(
4645
endpoint,
4746
data: <String, String>{
4847
'refresh_token': refreshToken,
@@ -53,7 +52,7 @@ class OAuthApiImpl implements OAuthApiInterface {
5352
options: Options(contentType: Headers.formUrlEncodedContentType),
5453
);
5554

56-
return OAuthToken.fromJson(r.data);
55+
return OAuthToken.fromJson(r.data!);
5756
}
5857

5958
@override
@@ -63,7 +62,7 @@ class OAuthApiImpl implements OAuthApiInterface {
6362
required String clientId,
6463
required String clientSecret,
6564
}) async {
66-
final r = await client.post(
65+
final r = await client.post<String>(
6766
endpoint,
6867
data: <String, String>{
6968
'username': username,
@@ -75,6 +74,6 @@ class OAuthApiImpl implements OAuthApiInterface {
7574
options: Options(contentType: Headers.formUrlEncodedContentType),
7675
);
7776

78-
return OAuthToken.fromJson(r.data);
77+
return OAuthToken.fromJson(r.data!);
7978
}
8079
}

lib/src/oauth_token.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import 'dart:convert';
33

44
// 📦 Package imports:
55
import 'package:clock/clock.dart';
6+
import 'package:meta/meta.dart';
67

78
/// An authentication token.
9+
@immutable
810
class OAuthToken {
911
/// Constructs an [OAuthToken]
1012
const OAuthToken({
@@ -18,19 +20,19 @@ class OAuthToken {
1820
Map<String, dynamic> map, [
1921
Clock clock = const Clock(),
2022
]) {
21-
final expiresIn = map['expires_in'];
23+
final expiresIn = map['expires_in'] as int?;
2224
return OAuthToken(
23-
token: map['access_token'],
25+
token: map['access_token'] as String,
2426
expiresAt: expiresIn != null
2527
? clock.now().add(Duration(seconds: expiresIn))
2628
: null,
27-
refreshToken: map['refresh_token'],
29+
refreshToken: map['refresh_token'] as String?,
2830
);
2931
}
3032

3133
/// Constructs an [OAuthToken] from a JSON [source]
3234
factory OAuthToken.fromJson(String source) =>
33-
OAuthToken.fromMap(json.decode(source));
35+
OAuthToken.fromMap(json.decode(source) as Map<String, dynamic>);
3436

3537
/// The token used to authenticate requests.
3638
///

lib/src/token_storage.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// 🌎 Project imports:
2+
import 'dart:async';
3+
24
import 'oauth_token.dart';
35

46
/// Handles storage and retrieval of [OAuthToken]s.
57
abstract class TokenStorage {
68
/// Retrieves the currently saved client token if it exists, or none.
7-
OAuthToken? get clientToken;
9+
FutureOr<OAuthToken?> get clientToken;
810

911
/// Retrieves the currently saved user token if it exists, or none.
10-
OAuthToken? get userToken;
12+
FutureOr<OAuthToken?> get userToken;
1113

1214
/// Saves a new client [token].
1315
///

lib/src/user_token_interceptor.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// 📦 Package imports:
22
import 'package:clock/clock.dart';
33
import 'package:dio/dio.dart';
4-
54
// 🌎 Project imports:
65
import 'package:passputter/passputter.dart';
76
import 'package:passputter/src/oauth_api_interface.dart';
@@ -38,7 +37,7 @@ class UserTokenInterceptor extends Interceptor {
3837
RequestOptions options,
3938
RequestInterceptorHandler handler,
4039
) async {
41-
final token = tokenStorage.userToken;
40+
final token = await tokenStorage.userToken;
4241
if (token != null) {
4342
if (token.expiresAt != null && token.expiresAt!.isBefore(clock.now())) {
4443
final refreshToken = token.refreshToken;
@@ -62,7 +61,6 @@ class UserTokenInterceptor extends Interceptor {
6261
return handler.reject(
6362
DioError(
6463
requestOptions: options,
65-
type: DioErrorType.other,
6664
error: TokenExpiredException(token),
6765
),
6866
);

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: passputter
22
description: Easily authenticate using OAuth 2.0 client/password grants.
3-
version: 1.0.2
3+
version: 2.0.0
44
repository: https://github.com/netsells/passputter
55

66
environment:
@@ -13,7 +13,7 @@ dependencies:
1313
dev_dependencies:
1414
import_sorter: ^4.5.0
1515
mock_web_server: ^5.0.0-nullsafety.1
16-
mocktail: ^0.1.2
16+
mocktail: ^0.2.0
1717
pretty_dio_logger: ^1.2.0-beta-1
1818
test: ^1.17.3
1919
time: ^2.0.0

test/src/oauth_api_impl_test.dart

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
// 📦 Package imports:
22
import 'package:dio/dio.dart';
33
import 'package:mocktail/mocktail.dart';
4-
import 'package:test/test.dart';
5-
64
// 🌎 Project imports:
75
import 'package:passputter/src/oauth_api_impl.dart';
86
import 'package:passputter/src/oauth_token.dart';
7+
import 'package:test/test.dart';
98

109
class MockDio extends Mock implements Dio {}
1110

@@ -28,7 +27,7 @@ void main() {
2827
group('getClientToken', () {
2928
test('successfully returns token', () async {
3029
when(
31-
() => dio.post(
30+
() => dio.post<String>(
3231
endpoint,
3332
data: <String, String>{
3433
'client_id': clientId,
@@ -41,10 +40,12 @@ void main() {
4140
(_) async => Response(
4241
requestOptions: RequestOptions(path: endpoint),
4342
statusCode: 200,
44-
data: '''{
43+
data: '''
44+
{
4545
"access_token": "token",
4646
"refresh_token": "refresh"
47-
}''',
47+
}
48+
''',
4849
),
4950
);
5051

@@ -64,7 +65,7 @@ void main() {
6465

6566
test('throws DioError if one is thrown by request', () async {
6667
when(
67-
() => dio.post(
68+
() => dio.post<String>(
6869
endpoint,
6970
data: <String, String>{
7071
'client_id': clientId,
@@ -76,7 +77,7 @@ void main() {
7677
).thenAnswer((_) => Future.error(tError));
7778

7879
expect(
79-
() async => await oAuthApi.getClientToken(
80+
() async => oAuthApi.getClientToken(
8081
clientId: clientId,
8182
clientSecret: clientSecret,
8283
),
@@ -90,7 +91,7 @@ void main() {
9091

9192
test('successfully returns token', () async {
9293
when(
93-
() => dio.post(
94+
() => dio.post<String>(
9495
endpoint,
9596
data: <String, String>{
9697
'refresh_token': refreshToken,
@@ -104,10 +105,12 @@ void main() {
104105
(_) async => Response(
105106
requestOptions: RequestOptions(path: endpoint),
106107
statusCode: 200,
107-
data: '''{
108+
data: '''
109+
{
108110
"access_token": "token",
109111
"refresh_token": "refresh"
110-
}''',
112+
}
113+
''',
111114
),
112115
);
113116

@@ -128,7 +131,7 @@ void main() {
128131

129132
test('throws DioError if one is thrown by request', () async {
130133
when(
131-
() => dio.post(
134+
() => dio.post<String>(
132135
endpoint,
133136
data: <String, String>{
134137
'refresh_token': refreshToken,
@@ -141,7 +144,7 @@ void main() {
141144
).thenAnswer((_) => Future.error(tError));
142145

143146
expect(
144-
() async => await oAuthApi.getRefreshedToken(
147+
() async => oAuthApi.getRefreshedToken(
145148
refreshToken: refreshToken,
146149
clientId: clientId,
147150
clientSecret: clientSecret,
@@ -157,7 +160,7 @@ void main() {
157160

158161
test('successfully returns token', () async {
159162
when(
160-
() => dio.post(
163+
() => dio.post<String>(
161164
endpoint,
162165
data: <String, String>{
163166
'username': username,
@@ -172,10 +175,12 @@ void main() {
172175
(_) async => Response(
173176
requestOptions: RequestOptions(path: endpoint),
174177
statusCode: 200,
175-
data: '''{
178+
data: '''
179+
{
176180
"access_token": "token",
177181
"refresh_token": "refresh"
178-
}''',
182+
}
183+
''',
179184
),
180185
);
181186

@@ -197,7 +202,7 @@ void main() {
197202

198203
test('throws DioError if one is thrown by request', () async {
199204
when(
200-
() => dio.post(
205+
() => dio.post<String>(
201206
endpoint,
202207
data: <String, String>{
203208
'username': username,
@@ -211,7 +216,7 @@ void main() {
211216
).thenAnswer((_) => Future.error(tError));
212217

213218
expect(
214-
() async => await oAuthApi.getUserToken(
219+
() async => oAuthApi.getUserToken(
215220
username: username,
216221
password: password,
217222
clientId: clientId,

test/src/user_token_interceptor_test.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
import 'package:clock/clock.dart';
33
import 'package:dio/dio.dart';
44
import 'package:mocktail/mocktail.dart';
5-
import 'package:test/test.dart';
6-
import 'package:time/time.dart';
7-
85
// 🌎 Project imports:
96
import 'package:passputter/passputter.dart';
107
import 'package:passputter/src/oauth_api_interface.dart';
118
import 'package:passputter/src/oauth_token.dart';
129
import 'package:passputter/src/token_expired_exception.dart';
10+
import 'package:test/test.dart';
11+
import 'package:time/time.dart';
1312

1413
class MockOAuthApi extends Mock implements OAuthApiInterface {}
1514

@@ -23,7 +22,7 @@ void main() {
2322
late UserTokenInterceptor interceptor;
2423

2524
setUpAll(() {
26-
registerFallbackValue<DioError>(
25+
registerFallbackValue(
2726
DioError(
2827
requestOptions: RequestOptions(
2928
path: 'path',
@@ -36,7 +35,7 @@ void main() {
3635
tokenStorage = InMemoryTokenStorage();
3736
oAuthApi = MockOAuthApi();
3837
handler = MockHandler();
39-
clock = Clock.fixed(DateTime(2021, 1, 1));
38+
clock = Clock.fixed(DateTime(2021));
4039
interceptor = UserTokenInterceptor(
4140
tokenStorage: tokenStorage,
4241
oAuthApi: oAuthApi,

0 commit comments

Comments
 (0)