diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e2eb756..93691bf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.1.1 +* Added `scopes` to `ExternalAuthOptions`. + # 2.1.0 * Added `ExternalAuthOptions`. diff --git a/lib/src/auth_token.dart b/lib/src/auth_token.dart index cf140e8e..05a82e01 100644 --- a/lib/src/auth_token.dart +++ b/lib/src/auth_token.dart @@ -36,10 +36,10 @@ class AuthToken { AuthToken( {this.ipAddress, - this.lifeSpan: -1, + this.lifeSpan = -1, this.userId, DateTime issuedAt, - Map payload: const {}}) { + Map payload = const {}}) { this.issuedAt = issuedAt ?? new DateTime.now(); this.payload.addAll( payload?.keys?.fold({}, (out, k) => out..[k.toString()] = payload[k]) ?? diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index f1f02bb1..89ca8002 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -1,4 +1,5 @@ import 'package:charcode/ascii.dart'; +import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:quiver_hashcode/hashcode.dart'; @@ -13,7 +14,11 @@ class ExternalAuthOptions { /// The user's redirect URI. final Uri redirectUri; - ExternalAuthOptions._(this.clientId, this.clientSecret, this.redirectUri) { + /// The scopes to be passed to the external server. + final Set scopes; + + ExternalAuthOptions._( + this.clientId, this.clientSecret, this.redirectUri, this.scopes) { if (clientId == null) { throw new ArgumentError.notNull('clientId'); } else if (clientSecret == null) { @@ -24,12 +29,14 @@ class ExternalAuthOptions { factory ExternalAuthOptions( {@required String clientId, @required String clientSecret, - @required redirectUri}) { + @required redirectUri, + Iterable scopes = const []}) { if (redirectUri is String) { return new ExternalAuthOptions._( - clientId, clientSecret, Uri.parse(redirectUri)); + clientId, clientSecret, Uri.parse(redirectUri), scopes.toSet()); } else if (redirectUri is Uri) { - return new ExternalAuthOptions._(clientId, clientSecret, redirectUri); + return new ExternalAuthOptions._( + clientId, clientSecret, redirectUri, scopes.toSet()); } else { throw new ArgumentError.value( redirectUri, 'redirectUri', 'must be a String or Uri'); @@ -47,26 +54,34 @@ class ExternalAuthOptions { clientId: map['client_id'] as String, clientSecret: map['client_secret'] as String, redirectUri: map['redirect_uri'], + scopes: map['scopes'] is Iterable + ? ((map['scopes'] as Iterable).map((x) => x.toString())) + : [], ); } @override - int get hashCode => hash3(clientId, clientSecret, redirectUri); + int get hashCode => hash4(clientId, clientSecret, redirectUri, scopes); @override bool operator ==(other) => other is ExternalAuthOptions && other.clientId == clientId && other.clientSecret == other.clientSecret && - other.redirectUri == other.redirectUri; + other.redirectUri == other.redirectUri && + const SetEquality().equals(other.scopes, scopes); /// Creates a copy of this object, with the specified changes. ExternalAuthOptions copyWith( - {String clientId, String clientSecret, redirectUri}) { + {String clientId, + String clientSecret, + redirectUri, + Iterable scopes}) { return new ExternalAuthOptions( clientId: clientId ?? this.clientId, clientSecret: clientSecret ?? this.clientSecret, redirectUri: redirectUri ?? this.redirectUri, + scopes: (scopes ??= []).followedBy(this.scopes), ); } @@ -79,11 +94,12 @@ class ExternalAuthOptions { /// /// If [obscureSecret] is `true` (default), then the [clientSecret] will /// be replaced by the string ``. - Map toJson({bool obscureSecret = true}) { + Map toJson({bool obscureSecret = true}) { return { 'client_id': clientId, 'client_secret': obscureSecret ? '' : clientSecret, 'redirect_uri': redirectUri.toString(), + 'scopes': scopes.toList(), }; } @@ -110,6 +126,7 @@ class ExternalAuthOptions { b.write('clientId=$clientId'); b.write(', clientSecret=$secret'); b.write(', redirectUri=$redirectUri'); + b.write(', scopes=${scopes.toList()}'); b.write(')'); return b.toString(); } diff --git a/lib/src/middleware/require_auth.dart b/lib/src/middleware/require_auth.dart index 98d97d9e..f9f2aa35 100644 --- a/lib/src/middleware/require_auth.dart +++ b/lib/src/middleware/require_auth.dart @@ -14,7 +14,7 @@ RequestHandler forceBasicAuth({String realm}) { /// Restricts access to a resource via authentication. RequestHandler requireAuthentication() { return (RequestContext req, ResponseContext res, - {bool throwError: true}) async { + {bool throwError = true}) async { bool _reject(ResponseContext res) { if (throwError) { res.statusCode = 403; diff --git a/lib/src/options.dart b/lib/src/options.dart index 2f469fde..6b9e9323 100644 --- a/lib/src/options.dart +++ b/lib/src/options.dart @@ -24,7 +24,7 @@ class AngelAuthOptions { AngelAuthOptions( {this.callback, this.tokenCallback, - this.canRespondWithJson: true, + this.canRespondWithJson = true, this.successRedirect, String this.failureRedirect}); } diff --git a/lib/src/plugin.dart b/lib/src/plugin.dart index 6f9de24f..49381dea 100644 --- a/lib/src/plugin.dart +++ b/lib/src/plugin.dart @@ -63,8 +63,8 @@ class AngelAuth { Hmac get hmac => _hs256; String _randomString( - {int length: 32, - String validChars: + {int length = 32, + String validChars = "ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}) { var chars = []; while (chars.length < length) chars.add(_random.nextInt(validChars.length)); @@ -77,13 +77,13 @@ class AngelAuth { this.serializer, this.deserializer, num jwtLifeSpan, - this.allowCookie: true, - this.allowTokenInQuery: true, - this.enforceIp: true, + this.allowCookie = true, + this.allowTokenInQuery = true, + this.enforceIp = true, this.cookieDomain, - this.cookiePath: '/', - this.secureCookies: true, - this.reviveTokenEndpoint: "/auth/token"}) + this.cookiePath = '/', + this.secureCookies = true, + this.reviveTokenEndpoint = "/auth/token"}) : super() { _hs256 = new Hmac(sha256, (jwtKey ?? _randomString()).codeUnits); _jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1; diff --git a/lib/src/popup_page.dart b/lib/src/popup_page.dart index a6e72f76..8722aaa6 100644 --- a/lib/src/popup_page.dart +++ b/lib/src/popup_page.dart @@ -4,7 +4,7 @@ import 'package:http_parser/http_parser.dart'; import 'options.dart'; /// Displays a default callback page to confirm authentication via popups. -AngelAuthCallback confirmPopupAuthentication({String eventName: 'token'}) { +AngelAuthCallback confirmPopupAuthentication({String eventName = 'token'}) { return (req, ResponseContext res, String jwt) { var evt = json.encode(eventName); var detail = json.encode({'detail': jwt}); diff --git a/lib/src/strategies/local.dart b/lib/src/strategies/local.dart index fa6472b6..74fec23f 100644 --- a/lib/src/strategies/local.dart +++ b/lib/src/strategies/local.dart @@ -23,13 +23,13 @@ class LocalAuthStrategy extends AuthStrategy { String realm; LocalAuthStrategy(this.verifier, - {String this.usernameField: 'username', - String this.passwordField: 'password', - String this.invalidMessage: + {String this.usernameField = 'username', + String this.passwordField = 'password', + String this.invalidMessage = 'Please provide a valid username and password.', - bool this.allowBasic: true, - bool this.forceBasic: false, - String this.realm: 'Authentication is required.'}) {} + bool this.allowBasic = true, + bool this.forceBasic = false, + String this.realm = 'Authentication is required.'}) {} @override Future authenticate(RequestContext req, ResponseContext res, @@ -54,8 +54,8 @@ class LocalAuthStrategy extends AuthStrategy { if (verificationResult == false || verificationResult == null) { res ..statusCode = 401 - ..headers['www-authenticate'] = 'Basic realm="$realm"' - ..close(); + ..headers['www-authenticate'] = 'Basic realm="$realm"'; + await res.close(); return null; } diff --git a/pubspec.yaml b/pubspec.yaml index 81da26c7..92fd8fda 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_auth description: A complete authentication plugin for Angel. Includes support for stateless JWT tokens, Basic Auth, and more. -version: 2.1.0 +version: 2.1.1 author: Tobe O homepage: https://github.com/angel-dart/angel_auth environment: diff --git a/test/config_test.dart b/test/config_test.dart index be0e3e33..b0e0acac 100644 --- a/test/config_test.dart +++ b/test/config_test.dart @@ -23,11 +23,13 @@ void main() { clientId: 'hey', clientSecret: 'hello', redirectUri: 'https://yes.no', + scopes: ['a', 'b'], ), new ExternalAuthOptions( clientId: 'hey', clientSecret: 'hello', redirectUri: 'https://yes.no', + scopes: ['a', 'b'], ), ); }); @@ -113,21 +115,21 @@ void main() { test('produces correct string', () { expect( options.toString(obscureSecret: false), - 'ExternalAuthOptions(clientId=foo, clientSecret=bar, redirectUri=http://example.com)', + 'ExternalAuthOptions(clientId=foo, clientSecret=bar, redirectUri=http://example.com, scopes=[])', ); }); test('obscures secret', () { expect( options.toString(), - 'ExternalAuthOptions(clientId=foo, clientSecret=***, redirectUri=http://example.com)', + 'ExternalAuthOptions(clientId=foo, clientSecret=***, redirectUri=http://example.com, scopes=[])', ); }); test('asteriskCount', () { expect( options.toString(asteriskCount: 7), - 'ExternalAuthOptions(clientId=foo, clientSecret=*******, redirectUri=http://example.com)', + 'ExternalAuthOptions(clientId=foo, clientSecret=*******, redirectUri=http://example.com, scopes=[])', ); }); }); @@ -140,6 +142,7 @@ void main() { 'client_id': 'foo', 'client_secret': '', 'redirect_uri': 'http://example.com', + 'scopes': [], }, ); }); @@ -151,6 +154,7 @@ void main() { 'client_id': 'foo', 'client_secret': 'bar', 'redirect_uri': 'http://example.com', + 'scopes': [], }, ); });