From c9f45d391bef409c417aa0272d6dd1d5e8de2537 Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Fri, 15 Mar 2024 08:28:52 +0100 Subject: [PATCH 1/3] :heavy_plus_sign: Added very_good_analysis --- analysis_options.yaml | 44 ++++++++++++++----------------------------- pubspec.yaml | 2 +- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index dee8927..fd66045 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,30 +1,14 @@ -# This file configures the static analysis results for your project (errors, -# warnings, and lints). -# -# This enables the 'recommended' set of lints from `package:lints`. -# This set helps identify many issues that may lead to problems when running -# or consuming Dart code, and enforces writing Dart using a single, idiomatic -# style and format. -# -# If you want a smaller set of lints you can change this to specify -# 'package:lints/core.yaml'. These are just the most critical lints -# (the recommended set includes the core lints). -# The core lints are also what is used by pub.dev for scoring packages. - -include: package:lints/recommended.yaml - -# Uncomment the following section to specify additional rules. - -# linter: -# rules: -# - camel_case_types - -# analyzer: -# exclude: -# - path/to/excluded/files/** - -# For more information about the core and recommended set of lints, see -# https://dart.dev/go/core-lints - -# For additional information about configuring this file, see -# https://dart.dev/guides/language/analysis-options +include: package:very_good_analysis/analysis_options.yaml + +analyzer: + errors: + unawaited_futures: warning + avoid_void_async: warning + missing_return: error + missing_required_param: error + invalid_annotation_target: info + + language: + strict-casts: true + strict-inference: true + strict-raw-types: true \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index d6585a4..d799861 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,6 @@ dependencies: dev_dependencies: http: ^1.2.1 - lints: ">=2.1.1 <4.0.0" + very_good_analysis: ^5.1.0 mocktail: ^1.0.3 test: ^1.25.2 From 62fb70d07678f24c41609c656888159ec2ef23e3 Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Fri, 15 Mar 2024 08:29:24 +0100 Subject: [PATCH 2/3] :memo: Updated public member documentation --- lib/src/extensions/request.dart | 12 +++-- lib/src/oauth_authenticator.dart | 20 ++++++-- lib/src/oauth_chopper.dart | 37 +++++++------- lib/src/oauth_grant.dart | 80 +++++++++++++++++++++++------- lib/src/oauth_interceptor.dart | 4 ++ lib/src/oauth_token.dart | 49 +++++++++++++++--- lib/src/storage/oauth_storage.dart | 3 +- test/oauth_interceptor_test.dart | 2 +- 8 files changed, 150 insertions(+), 57 deletions(-) diff --git a/lib/src/extensions/request.dart b/lib/src/extensions/request.dart index 0d2f758..ddf82b7 100644 --- a/lib/src/extensions/request.dart +++ b/lib/src/extensions/request.dart @@ -1,9 +1,11 @@ import 'package:chopper/chopper.dart'; +/// Helper extension to easily apply a authorization header to a request. extension ChopperRequest on Request { - Request addAuthorizationHeader(String token) { - final newHeaders = Map.from(headers); - newHeaders['Authorization'] = 'Bearer $token'; - return copyWith(headers: newHeaders); - } + /// Adds a authorization header with a bearer [token] to the request. + Request addAuthorizationHeader(String token) => applyHeader( + this, + 'Authorization', + 'Bearer $token', + ); } diff --git a/lib/src/oauth_authenticator.dart b/lib/src/oauth_authenticator.dart index ee3b0ac..9aa753f 100644 --- a/lib/src/oauth_authenticator.dart +++ b/lib/src/oauth_authenticator.dart @@ -4,20 +4,32 @@ import 'package:chopper/chopper.dart'; import 'package:oauth_chopper/oauth_chopper.dart'; import 'package:oauth_chopper/src/extensions/request.dart'; +/// Callback for error handling. typedef OnErrorCallback = void Function(Object, StackTrace); -/// OAuthAuthenticator provides a authenticator that handles OAuth authorizations. +/// {@template authenticator} +/// OAuthAuthenticator provides a authenticator that handles +/// OAuth authorizations. /// When the provided credentials are invalid it tries to refresh them. -/// Can throw a exceptions if no [onError] is passed. When [onError] is passed exception will be passed to [onError] +/// Can throw a exceptions if no [onError] is passed. When [onError] is passed +/// exception will be passed to [onError] +/// {@endtemplate} class OAuthAuthenticator extends Authenticator { + /// {@macro authenticator} OAuthAuthenticator(this.oauthChopper, this.onError); + /// Callback for error handling. final OnErrorCallback? onError; + /// The [OAuthChopper] instance to get the token from and + /// to refresh the token. final OAuthChopper oauthChopper; @override - FutureOr authenticate(Request request, Response response, - [Request? originalRequest]) async { + FutureOr authenticate( + Request request, + Response response, [ + Request? originalRequest, + ]) async { final token = await oauthChopper.token; if (response.statusCode == 401 && token != null) { try { diff --git a/lib/src/oauth_chopper.dart b/lib/src/oauth_chopper.dart index 3427df1..abca3b2 100644 --- a/lib/src/oauth_chopper.dart +++ b/lib/src/oauth_chopper.dart @@ -8,6 +8,7 @@ import 'package:oauth_chopper/src/oauth_token.dart'; import 'package:oauth_chopper/src/storage/memory_storage.dart'; import 'package:oauth_chopper/src/storage/oauth_storage.dart'; +/// {@template oauth_chopper} /// OAuthChopper client for configuring OAuth authentication with [Chopper]. /// /// For example: @@ -18,7 +19,21 @@ import 'package:oauth_chopper/src/storage/oauth_storage.dart'; /// secret: secret, /// ); /// ``` +/// {@endtemplate} class OAuthChopper { + /// {@macro oauth_chopper} + OAuthChopper({ + required this.authorizationEndpoint, + required this.identifier, + required this.secret, + this.endSessionEndpoint, + + /// OAuth storage for storing credentials. + /// By default it will use a in memory storage [MemoryStorage]. For persisting the credentials implement a custom [OAuthStorage]. + /// See [OAuthStorage] for more information. + OAuthStorage? storage, + }) : _storage = storage ?? MemoryStorage(); + /// OAuth authorization endpoint. final Uri authorizationEndpoint; @@ -36,24 +51,10 @@ class OAuthChopper { /// See [OAuthStorage] for more information. final OAuthStorage _storage; - OAuthChopper({ - required this.authorizationEndpoint, - required this.identifier, - required this.secret, - this.endSessionEndpoint, - - /// OAuth storage for storing credentials. - /// By default it will use a in memory storage [MemoryStorage]. For persisting the credentials implement a custom [OAuthStorage]. - /// See [OAuthStorage] for more information. - OAuthStorage? storage, - }) : _storage = storage ?? MemoryStorage(); - /// Get stored [OAuthToken]. Future get token async { final credentialsJson = await _storage.fetchCredentials(); - return credentialsJson != null - ? OAuthToken.fromJson(credentialsJson) - : null; + return credentialsJson != null ? OAuthToken.fromJson(credentialsJson) : null; } /// Provides an [OAuthAuthenticator] instance. @@ -75,8 +76,7 @@ class OAuthChopper { if (credentialsJson == null) return null; final credentials = Credentials.fromJson(credentialsJson); try { - final newCredentials = - await credentials.refresh(identifier: identifier, secret: secret); + final newCredentials = await credentials.refresh(identifier: identifier, secret: secret); await _storage.saveCredentials(newCredentials.toJson()); return OAuthToken.fromCredentials(newCredentials); } on AuthorizationException { @@ -92,8 +92,7 @@ class OAuthChopper { /// /// Throws an exception if the grant fails. Future requestGrant(OAuthGrant grant) async { - final credentials = - await grant.handle(authorizationEndpoint, identifier, secret); + final credentials = await grant.handle(authorizationEndpoint, identifier, secret); await _storage.saveCredentials(credentials); diff --git a/lib/src/oauth_grant.dart b/lib/src/oauth_grant.dart index b7f56a3..922beaf 100644 --- a/lib/src/oauth_grant.dart +++ b/lib/src/oauth_grant.dart @@ -1,23 +1,38 @@ import 'package:oauth2/oauth2.dart' as oauth; -abstract class OAuthGrant { +/// {@template oauth_grant} +/// Interface for a OAuth grant. +/// Grants are used to obtain credentials from an authorization server. +/// {@endtemplate} +abstract interface class OAuthGrant { + /// {@macro oauth_grant} const OAuthGrant(); - Future handle( - Uri authorizationEndpoint, String identifier, String secret); + /// Obtains credentials from an authorization server. + Future handle(Uri authorizationEndpoint, String identifier, String secret); } +/// {@template resource_owner_password_grant} /// Obtains credentials using a [resource owner password grant](https://tools.ietf.org/html/rfc6749#section-1.3.3). -class ResourceOwnerPasswordGrant extends OAuthGrant { +/// +/// This grant uses the resource owner's [username] and [password] to obtain +/// credentials. +/// {@endtemplate} +class ResourceOwnerPasswordGrant implements OAuthGrant { + /// {@macro resource_owner_password_grant} + const ResourceOwnerPasswordGrant({ + required this.username, + required this.password, + }); + + /// Username used for obtaining credentials. final String username; - final String password; - const ResourceOwnerPasswordGrant( - {required this.username, required this.password}); + /// Password used for obtaining credentials. + final String password; @override - Future handle( - Uri authorizationEndpoint, String identifier, String secret) async { + Future handle(Uri authorizationEndpoint, String identifier, String secret) async { final client = await oauth.resourceOwnerPasswordGrant( authorizationEndpoint, username, @@ -29,13 +44,15 @@ class ResourceOwnerPasswordGrant extends OAuthGrant { } } +/// {@template client_credentials_grant} /// Obtains credentials using a [client credentials grant](https://tools.ietf.org/html/rfc6749#section-1.3.4). -class ClientCredentialsGrant extends OAuthGrant { +/// {@endtemplate} +class ClientCredentialsGrant implements OAuthGrant { + /// {@macro client_credentials_grant} const ClientCredentialsGrant(); @override - Future handle( - Uri authorizationEndpoint, String identifier, String secret) async { + Future handle(Uri authorizationEndpoint, String identifier, String secret) async { final client = await oauth.clientCredentialsGrant( authorizationEndpoint, identifier, @@ -45,8 +62,11 @@ class ClientCredentialsGrant extends OAuthGrant { } } +/// {@template authorization_code_grant} /// Obtains credentials using a [authorization code grant](https://tools.ietf.org/html/rfc6749#section-1.3.1). -class AuthorizationCodeGrant extends OAuthGrant { +/// {@endtemplate} +class AuthorizationCodeGrant implements OAuthGrant { + /// {@macro authorization_code_grant} const AuthorizationCodeGrant({ required this.tokenEndpoint, required this.scopes, @@ -55,26 +75,48 @@ class AuthorizationCodeGrant extends OAuthGrant { required this.listen, }); + /// A URL provided by the authorization server that this library uses to + /// obtain long-lasting credentials. + /// + /// This will usually be listed in the authorization server's OAuth2 API + /// documentation. final Uri tokenEndpoint; + + /// The redirect URL where the resource owner will redirect to. final Uri redirectUrl; + + /// The specific permissions being requested from the authorization server may + /// be specified via [scopes]. final List scopes; + /// Callback used for redirect the authorizationUrl given by the authorization + /// server. final Future Function(Uri authorizationUri) redirect; + /// Callback used for listening for the redirectUrl. final Future Function(Uri redirectUri) listen; @override Future handle( - Uri authorizationEndpoint, String identifier, String secret) async { + Uri authorizationEndpoint, + String identifier, + String secret, + ) async { final grant = oauth.AuthorizationCodeGrant( identifier, authorizationEndpoint, tokenEndpoint, ); - var authorizationUrl = - grant.getAuthorizationUrl(redirectUrl, scopes: scopes); + + final authorizationUrl = grant.getAuthorizationUrl( + redirectUrl, + scopes: scopes, + ); + await redirect(authorizationUrl); - var responseUrl = await listen(redirectUrl); - oauth.Client client = - await grant.handleAuthorizationResponse(responseUrl.queryParameters); + final responseUrl = await listen(redirectUrl); + + final oauth.Client client = await grant.handleAuthorizationResponse( + responseUrl.queryParameters, + ); return client.credentials.toJson(); } diff --git a/lib/src/oauth_interceptor.dart b/lib/src/oauth_interceptor.dart index 42d421b..5729a8c 100644 --- a/lib/src/oauth_interceptor.dart +++ b/lib/src/oauth_interceptor.dart @@ -4,12 +4,16 @@ import 'package:chopper/chopper.dart'; import 'package:oauth_chopper/oauth_chopper.dart'; import 'package:oauth_chopper/src/extensions/request.dart'; +/// {@template oauth_interceptor} /// OAuthInterceptor is responsible for adding 'Authorization' header to requests. /// The header is only added if there is a token available. When no token is available no header is added. /// Its added as a Bearer token. +/// {@endtemplate} class OAuthInterceptor implements RequestInterceptor { + /// {@macro oauth_interceptor} OAuthInterceptor(this.oauthChopper); + /// The [OAuthChopper] instance to get the token from. final OAuthChopper oauthChopper; @override diff --git a/lib/src/oauth_token.dart b/lib/src/oauth_token.dart index 9121a2c..e240739 100644 --- a/lib/src/oauth_token.dart +++ b/lib/src/oauth_token.dart @@ -1,14 +1,10 @@ import 'package:oauth2/oauth2.dart'; +/// {@template oauth_token} +/// A wrapper around [Credentials] to provide a more convenient API. +/// {@endtemplate} class OAuthToken { - final String accessToken; - final String? refreshToken; - final DateTime? expiration; - final String? idToken; - - bool get isExpired => - expiration != null && DateTime.now().isAfter(expiration!); - + /// {@macro oauth_token} const OAuthToken._( this.accessToken, this.refreshToken, @@ -16,15 +12,52 @@ class OAuthToken { this.idToken, ); + /// Creates a new instance of [OAuthToken] from a JSON string. + /// {@macro oauth_token} factory OAuthToken.fromJson(String json) { final credentials = Credentials.fromJson(json); return OAuthToken.fromCredentials(credentials); } + /// Creates a new instance of [OAuthToken] from [Credentials]. + /// {@macro oauth_token} factory OAuthToken.fromCredentials(Credentials credentials) => OAuthToken._( credentials.accessToken, credentials.refreshToken, credentials.expiration, credentials.idToken, ); + + /// The token that is sent to the resource server to prove the authorization + /// of a client. + final String accessToken; + + /// The token that is sent to the authorization server to refresh the + /// credentials. + /// + /// This may be `null`, indicating that the credentials can't be refreshed. + final String? refreshToken; + + /// The date at which these credentials will expire. + /// + /// This is likely to be a few seconds earlier than the server's idea of the + /// expiration date. + final DateTime? expiration; + + /// The token that is received from the authorization server to enable + /// End-Users to be Authenticated, contains Claims, represented as a + /// JSON Web Token (JWT). + /// + /// This may be `null`, indicating that the 'openid' scope was not + /// requested (or not supported). + /// + /// [spec]: https://openid.net/specs/openid-connect-core-1_0.html#IDToken + final String? idToken; + + /// Whether the token is expired. + bool get isExpired => + expiration != null && + DateTime.now().isAfter( + expiration!, + ); } diff --git a/lib/src/storage/oauth_storage.dart b/lib/src/storage/oauth_storage.dart index 093dfb2..9e1edb6 100644 --- a/lib/src/storage/oauth_storage.dart +++ b/lib/src/storage/oauth_storage.dart @@ -1,6 +1,7 @@ import 'dart:async'; -abstract class OAuthStorage { +/// Interface for storage of OAuth credentials. +abstract interface class OAuthStorage { const OAuthStorage(); /// Fetch stored credentials. diff --git a/test/oauth_interceptor_test.dart b/test/oauth_interceptor_test.dart index 7735660..c2910b1 100644 --- a/test/oauth_interceptor_test.dart +++ b/test/oauth_interceptor_test.dart @@ -59,7 +59,7 @@ void main() { // arrange when(() => mockOAuthChopper.token).thenAnswer((_) async => null); final interceptor = OAuthInterceptor(mockOAuthChopper); - final expected = {}; + final expected = {}; // act final result = await interceptor.onRequest(testRequest); From 8232eeab0d51dddcbde664171fff4f36d9d3f6ab Mon Sep 17 00:00:00 2001 From: Job Guldemeester Date: Fri, 15 Mar 2024 08:39:00 +0100 Subject: [PATCH 3/3] :rotating_light: Fixed lint warnings --- example/oauth_chopper_example.dart | 4 ++- lib/oauth_chopper.dart | 2 +- lib/src/oauth_authenticator.dart | 1 + lib/src/oauth_chopper.dart | 33 ++++++++++++------ lib/src/oauth_grant.dart | 22 +++++++++--- lib/src/oauth_interceptor.dart | 6 ++-- lib/src/storage/memory_storage.dart | 1 + lib/src/storage/oauth_storage.dart | 3 +- pubspec.yaml | 2 +- test/oauth_authenticator_test.dart | 38 ++++++++++++--------- test/oauth_chopper_test.dart | 49 +++++++++++++++------------ test/oauth_interceptor_test.dart | 3 ++ test/storage/memory_storage_test.dart | 24 ++++++------- 13 files changed, 117 insertions(+), 71 deletions(-) diff --git a/example/oauth_chopper_example.dart b/example/oauth_chopper_example.dart index 8ec0fda..f9bbfba 100644 --- a/example/oauth_chopper_example.dart +++ b/example/oauth_chopper_example.dart @@ -1,4 +1,6 @@ -// ignore_for_file: unused_local_variable +// ignore because its a example. +// ignore_for_file: unused_local_variable, prefer_const_declarations, +// ignore_for_file: prefer_const_constructors import 'package:chopper/chopper.dart'; import 'package:oauth_chopper/oauth_chopper.dart'; diff --git a/lib/oauth_chopper.dart b/lib/oauth_chopper.dart index fd981df..544b4f2 100644 --- a/lib/oauth_chopper.dart +++ b/lib/oauth_chopper.dart @@ -1,4 +1,4 @@ -/// OAuthChopper for configuring OAuth authentication with [Chopper]. +/// OAuthChopper for configuring OAuth authentication with Chopper. /// /// More dartdocs go here. library oauth_chopper; diff --git a/lib/src/oauth_authenticator.dart b/lib/src/oauth_authenticator.dart index 9aa753f..e0e0203 100644 --- a/lib/src/oauth_authenticator.dart +++ b/lib/src/oauth_authenticator.dart @@ -20,6 +20,7 @@ class OAuthAuthenticator extends Authenticator { /// Callback for error handling. final OnErrorCallback? onError; + /// The [OAuthChopper] instance to get the token from and /// to refresh the token. final OAuthChopper oauthChopper; diff --git a/lib/src/oauth_chopper.dart b/lib/src/oauth_chopper.dart index abca3b2..0d35e0b 100644 --- a/lib/src/oauth_chopper.dart +++ b/lib/src/oauth_chopper.dart @@ -9,7 +9,7 @@ import 'package:oauth_chopper/src/storage/memory_storage.dart'; import 'package:oauth_chopper/src/storage/oauth_storage.dart'; /// {@template oauth_chopper} -/// OAuthChopper client for configuring OAuth authentication with [Chopper]. +/// OAuthChopper client for configuring OAuth authentication with Chopper. /// /// For example: /// ```dart @@ -29,7 +29,8 @@ class OAuthChopper { this.endSessionEndpoint, /// OAuth storage for storing credentials. - /// By default it will use a in memory storage [MemoryStorage]. For persisting the credentials implement a custom [OAuthStorage]. + /// By default it will use a in memory storage [MemoryStorage]. + /// For persisting the credentials implement a custom [OAuthStorage]. /// See [OAuthStorage] for more information. OAuthStorage? storage, }) : _storage = storage ?? MemoryStorage(); @@ -47,20 +48,24 @@ class OAuthChopper { final String secret; /// OAuth storage for storing credentials. - /// By default it will use a in memory storage. For persisting the credentials implement a custom [OAuthStorage]. + /// By default it will use a in memory storage. For persisting the credentials + /// implement a custom [OAuthStorage]. /// See [OAuthStorage] for more information. final OAuthStorage _storage; /// Get stored [OAuthToken]. Future get token async { final credentialsJson = await _storage.fetchCredentials(); - return credentialsJson != null ? OAuthToken.fromJson(credentialsJson) : null; + return credentialsJson != null + ? OAuthToken.fromJson(credentialsJson) + : null; } /// Provides an [OAuthAuthenticator] instance. - /// The authenticator can throw exceptions when OAuth authentication fails. If [onError] is provided exceptions will be passed to [onError] and not be thrown. + /// The authenticator can throw exceptions when OAuth authentication fails. + /// If [onError] is provided exceptions will be passed to [onError] and not be + /// thrown. OAuthAuthenticator authenticator({ - /// When provided [onError] handles exceptions if thrown. OnErrorCallback? onError, }) => OAuthAuthenticator(this, onError); @@ -68,15 +73,18 @@ class OAuthChopper { /// Provides an [OAuthInterceptor] instance. OAuthInterceptor get interceptor => OAuthInterceptor(this); - /// Tries to refresh the available credentials and returns a new [OAuthToken] instance. - /// Throws an exception when refreshing fails. If the exception is a [AuthorizationException] it clears the storage. + /// Tries to refresh the available credentials and returns a new [OAuthToken] + /// instance. + /// Throws an exception when refreshing fails. If the exception is a + /// [AuthorizationException] it clears the storage. /// See [Credentials.refresh] Future refresh() async { final credentialsJson = await _storage.fetchCredentials(); if (credentialsJson == null) return null; final credentials = Credentials.fromJson(credentialsJson); try { - final newCredentials = await credentials.refresh(identifier: identifier, secret: secret); + final newCredentials = + await credentials.refresh(identifier: identifier, secret: secret); await _storage.saveCredentials(newCredentials.toJson()); return OAuthToken.fromCredentials(newCredentials); } on AuthorizationException { @@ -85,14 +93,17 @@ class OAuthChopper { } } - /// Request an [OAuthGrant] and stores the credentials in the [storage]. + /// Request an [OAuthGrant] and stores the credentials in the + /// [_storage]. + /// /// Currently supported grants: /// - [ResourceOwnerPasswordGrant] /// - [ClientCredentialsGrant] /// /// Throws an exception if the grant fails. Future requestGrant(OAuthGrant grant) async { - final credentials = await grant.handle(authorizationEndpoint, identifier, secret); + final credentials = + await grant.handle(authorizationEndpoint, identifier, secret); await _storage.saveCredentials(credentials); diff --git a/lib/src/oauth_grant.dart b/lib/src/oauth_grant.dart index 922beaf..3faa6ea 100644 --- a/lib/src/oauth_grant.dart +++ b/lib/src/oauth_grant.dart @@ -9,7 +9,11 @@ abstract interface class OAuthGrant { const OAuthGrant(); /// Obtains credentials from an authorization server. - Future handle(Uri authorizationEndpoint, String identifier, String secret); + Future handle( + Uri authorizationEndpoint, + String identifier, + String secret, + ); } /// {@template resource_owner_password_grant} @@ -32,7 +36,11 @@ class ResourceOwnerPasswordGrant implements OAuthGrant { final String password; @override - Future handle(Uri authorizationEndpoint, String identifier, String secret) async { + Future handle( + Uri authorizationEndpoint, + String identifier, + String secret, + ) async { final client = await oauth.resourceOwnerPasswordGrant( authorizationEndpoint, username, @@ -52,7 +60,11 @@ class ClientCredentialsGrant implements OAuthGrant { const ClientCredentialsGrant(); @override - Future handle(Uri authorizationEndpoint, String identifier, String secret) async { + Future handle( + Uri authorizationEndpoint, + String identifier, + String secret, + ) async { final client = await oauth.clientCredentialsGrant( authorizationEndpoint, identifier, @@ -88,9 +100,11 @@ class AuthorizationCodeGrant implements OAuthGrant { /// The specific permissions being requested from the authorization server may /// be specified via [scopes]. final List scopes; + /// Callback used for redirect the authorizationUrl given by the authorization /// server. final Future Function(Uri authorizationUri) redirect; + /// Callback used for listening for the redirectUrl. final Future Function(Uri redirectUri) listen; @@ -114,7 +128,7 @@ class AuthorizationCodeGrant implements OAuthGrant { await redirect(authorizationUrl); final responseUrl = await listen(redirectUrl); - final oauth.Client client = await grant.handleAuthorizationResponse( + final client = await grant.handleAuthorizationResponse( responseUrl.queryParameters, ); diff --git a/lib/src/oauth_interceptor.dart b/lib/src/oauth_interceptor.dart index 5729a8c..fe9cbd3 100644 --- a/lib/src/oauth_interceptor.dart +++ b/lib/src/oauth_interceptor.dart @@ -5,8 +5,10 @@ import 'package:oauth_chopper/oauth_chopper.dart'; import 'package:oauth_chopper/src/extensions/request.dart'; /// {@template oauth_interceptor} -/// OAuthInterceptor is responsible for adding 'Authorization' header to requests. -/// The header is only added if there is a token available. When no token is available no header is added. +/// OAuthInterceptor is responsible for adding 'Authorization' header to +/// requests. +/// The header is only added if there is a token available. When no token is +/// available no header is added. /// Its added as a Bearer token. /// {@endtemplate} class OAuthInterceptor implements RequestInterceptor { diff --git a/lib/src/storage/memory_storage.dart b/lib/src/storage/memory_storage.dart index 0e5dd92..66eff15 100644 --- a/lib/src/storage/memory_storage.dart +++ b/lib/src/storage/memory_storage.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:oauth_chopper/oauth_chopper.dart'; +/// A simple in-memory storage for OAuth credentials. class MemoryStorage implements OAuthStorage { String? _credentials; diff --git a/lib/src/storage/oauth_storage.dart b/lib/src/storage/oauth_storage.dart index 9e1edb6..147907b 100644 --- a/lib/src/storage/oauth_storage.dart +++ b/lib/src/storage/oauth_storage.dart @@ -7,7 +7,8 @@ abstract interface class OAuthStorage { /// Fetch stored credentials. FutureOr fetchCredentials(); - /// Save newly obtained credentials. This is called when authentication or refreshing tokens succeeds. + /// Save newly obtained credentials. This is called when authentication or + /// refreshing tokens succeeds. FutureOr saveCredentials(String? credentialsJson); /// Clear any stored credential. This is called when authentication fails. diff --git a/pubspec.yaml b/pubspec.yaml index d799861..f5aa0d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,6 @@ dependencies: dev_dependencies: http: ^1.2.1 - very_good_analysis: ^5.1.0 mocktail: ^1.0.3 test: ^1.25.2 + very_good_analysis: ^5.1.0 diff --git a/test/oauth_authenticator_test.dart b/test/oauth_authenticator_test.dart index 12d3023..73e0217 100644 --- a/test/oauth_authenticator_test.dart +++ b/test/oauth_authenticator_test.dart @@ -1,3 +1,6 @@ +// ignore so the test is easier to read. +// ignore_for_file: avoid_redundant_argument_values + import 'dart:io'; import 'package:chopper/chopper.dart'; @@ -28,7 +31,7 @@ void main() { test('only refresh on unauthorized and token', () async { // arrange - when(() => mockOAuthChopper.refresh()).thenAnswer((_) async => testToken); + when(mockOAuthChopper.refresh).thenAnswer((_) async => testToken); when(() => mockOAuthChopper.token).thenAnswer((_) async => testToken); final authenticator = OAuthAuthenticator(mockOAuthChopper, null); final expected = {'Authorization': 'Bearer token'}; @@ -38,13 +41,13 @@ void main() { await authenticator.authenticate(testRequest, unauthorizedResponse); // assert - verify(() => mockOAuthChopper.refresh()).called(1); + verify(mockOAuthChopper.refresh).called(1); expect(result?.headers, expected); }); test("Don't refresh on authorized", () async { // arrange - when(() => mockOAuthChopper.refresh()).thenAnswer((_) async => testToken); + when(mockOAuthChopper.refresh).thenAnswer((_) async => testToken); when(() => mockOAuthChopper.token).thenAnswer((_) async => testToken); final authenticator = OAuthAuthenticator(mockOAuthChopper, null); @@ -53,13 +56,13 @@ void main() { await authenticator.authenticate(testRequest, authorizedResponse); // assert - verifyNever(() => mockOAuthChopper.refresh()); + verifyNever(mockOAuthChopper.refresh); expect(result, null); }); test("Don't refresh on token not available", () async { // arrange - when(() => mockOAuthChopper.refresh()).thenAnswer((_) async => testToken); + when(mockOAuthChopper.refresh).thenAnswer((_) async => testToken); when(() => mockOAuthChopper.token).thenAnswer((_) async => null); final authenticator = OAuthAuthenticator(mockOAuthChopper, null); @@ -68,13 +71,13 @@ void main() { await authenticator.authenticate(testRequest, unauthorizedResponse); // assert - verifyNever(() => mockOAuthChopper.refresh()); + verifyNever(mockOAuthChopper.refresh); expect(result, null); }); test("Don't add headers on failed refresh", () async { // arrange - when(() => mockOAuthChopper.refresh()).thenAnswer((_) async => null); + when(mockOAuthChopper.refresh).thenAnswer((_) async => null); when(() => mockOAuthChopper.token).thenAnswer((_) async => testToken); final authenticator = OAuthAuthenticator(mockOAuthChopper, null); @@ -83,31 +86,34 @@ void main() { await authenticator.authenticate(testRequest, unauthorizedResponse); // assert - verify(() => mockOAuthChopper.refresh()).called(1); + verify(mockOAuthChopper.refresh).called(1); expect(result, null); }); - test("Exception thrown if onError is null", () async { + test('Exception thrown if onError is null', () async { // arrange - when(() => mockOAuthChopper.refresh()).thenThrow(FormatException('failed')); + when(mockOAuthChopper.refresh).thenThrow(const FormatException('failed')); when(() => mockOAuthChopper.token).thenAnswer((_) async => testToken); final authenticator = OAuthAuthenticator(mockOAuthChopper, null); // act // assert expect( - () async => - await authenticator.authenticate(testRequest, unauthorizedResponse), - throwsFormatException); + () async => + await authenticator.authenticate(testRequest, unauthorizedResponse), + throwsFormatException, + ); }); - test("Exception not thrown if onError is supplied", () async { + test('Exception not thrown if onError is supplied', () async { // arrange FormatException? result; - when(() => mockOAuthChopper.refresh()).thenThrow(FormatException('failed')); + when(mockOAuthChopper.refresh).thenThrow(const FormatException('failed')); when(() => mockOAuthChopper.token).thenAnswer((_) async => testToken); final authenticator = OAuthAuthenticator( - mockOAuthChopper, (e, s) => result = e as FormatException); + mockOAuthChopper, + (e, s) => result = e as FormatException, + ); // act final responseResult = diff --git a/test/oauth_chopper_test.dart b/test/oauth_chopper_test.dart index 93fa29d..c529568 100644 --- a/test/oauth_chopper_test.dart +++ b/test/oauth_chopper_test.dart @@ -13,7 +13,7 @@ void main() { final storageMock = MockOAuthStorage(); final grantMock = MockOAuthGrant(); - final testJson = ''' + const testJson = ''' { "accessToken": "accesToken", "refreshToken": "refreshToken", @@ -27,9 +27,10 @@ void main() { test('oauth_chopper returns interceptor which contains oauth_chopper', () { // arrange final oauthChopper = OAuthChopper( - authorizationEndpoint: Uri.parse('endpoint'), - identifier: 'identifier', - secret: 'secret'); + authorizationEndpoint: Uri.parse('endpoint'), + identifier: 'identifier', + secret: 'secret', + ); // act final inteceptor = oauthChopper.interceptor; @@ -41,9 +42,10 @@ void main() { test('oauth_chopper returns authenticator which contains oauth_chopper', () { // arrange final oauthChopper = OAuthChopper( - authorizationEndpoint: Uri.parse('endpoint'), - identifier: 'identifier', - secret: 'secret'); + authorizationEndpoint: Uri.parse('endpoint'), + identifier: 'identifier', + secret: 'secret', + ); // act final authenticator = oauthChopper.authenticator(); @@ -54,12 +56,13 @@ void main() { test('Returns token from storage', () async { // arrange - when(() => storageMock.fetchCredentials()).thenAnswer((_) => testJson); + when(storageMock.fetchCredentials).thenAnswer((_) => testJson); final oauthChopper = OAuthChopper( - authorizationEndpoint: Uri.parse('endpoint'), - identifier: 'identifier', - secret: 'secret', - storage: storageMock); + authorizationEndpoint: Uri.parse('endpoint'), + identifier: 'identifier', + secret: 'secret', + storage: storageMock, + ); // act final token = await oauthChopper.token; @@ -72,12 +75,13 @@ void main() { test('Returns no token if not in storage', () async { // arrange - when(() => storageMock.fetchCredentials()).thenAnswer((_) => null); + when(storageMock.fetchCredentials).thenAnswer((_) => null); final oauthChopper = OAuthChopper( - authorizationEndpoint: Uri.parse('endpoint'), - identifier: 'identifier', - secret: 'secret', - storage: storageMock); + authorizationEndpoint: Uri.parse('endpoint'), + identifier: 'identifier', + secret: 'secret', + storage: storageMock, + ); // act final token = await oauthChopper.token; @@ -86,16 +90,17 @@ void main() { expect(token, null); }); - test("Successful grant is stored", () async { + test('Successful grant is stored', () async { // arrange when(() => storageMock.saveCredentials(any())).thenAnswer((_) => null); when(() => grantMock.handle(any(), any(), any())) .thenAnswer((_) async => testJson); final oauthChopper = OAuthChopper( - authorizationEndpoint: Uri.parse('endpoint'), - identifier: 'identifier', - secret: 'secret', - storage: storageMock); + authorizationEndpoint: Uri.parse('endpoint'), + identifier: 'identifier', + secret: 'secret', + storage: storageMock, + ); // act final token = await oauthChopper.requestGrant(grantMock); diff --git a/test/oauth_interceptor_test.dart b/test/oauth_interceptor_test.dart index c2910b1..6be61d1 100644 --- a/test/oauth_interceptor_test.dart +++ b/test/oauth_interceptor_test.dart @@ -1,3 +1,6 @@ +// ignore so the test is easier to read. +// ignore_for_file: avoid_redundant_argument_values + import 'package:chopper/chopper.dart'; import 'package:mocktail/mocktail.dart'; import 'package:oauth2/oauth2.dart'; diff --git a/test/storage/memory_storage_test.dart b/test/storage/memory_storage_test.dart index 308f55e..5faf71b 100644 --- a/test/storage/memory_storage_test.dart +++ b/test/storage/memory_storage_test.dart @@ -4,10 +4,10 @@ import 'package:test/test.dart'; void main() { test('Store & read a value', () async { // arrange - final storage = MemoryStorage(); + final storage = MemoryStorage() - // act - storage.saveCredentials('test'); + // act + ..saveCredentials('test'); final result = await storage.fetchCredentials(); // assert @@ -16,10 +16,10 @@ void main() { test('Store, update & read a value', () async { // arrange - final storage = MemoryStorage(); + final storage = MemoryStorage() - // act - storage.saveCredentials('test'); + // act + ..saveCredentials('test'); final result1 = await storage.fetchCredentials(); storage.saveCredentials('test2'); final result2 = await storage.fetchCredentials(); @@ -31,10 +31,10 @@ void main() { test('Store, clear, store & read a value', () async { // arrange - final storage = MemoryStorage(); + final storage = MemoryStorage() - // act - storage.saveCredentials('test'); + // act + ..saveCredentials('test'); await storage.clear(); storage.saveCredentials('test2'); final result = await storage.fetchCredentials(); @@ -45,10 +45,10 @@ void main() { test('Store, clear & read a value', () async { // arrange - final storage = MemoryStorage(); + final storage = MemoryStorage() - // act - storage.saveCredentials('test'); + // act + ..saveCredentials('test'); await storage.clear(); final result = await storage.fetchCredentials();