2.1.1
This commit is contained in:
parent
b660eef2db
commit
46318e5fa6
10 changed files with 57 additions and 33 deletions
|
@ -1,3 +1,6 @@
|
|||
# 2.1.1
|
||||
* Added `scopes` to `ExternalAuthOptions`.
|
||||
|
||||
# 2.1.0
|
||||
* Added `ExternalAuthOptions`.
|
||||
|
||||
|
|
|
@ -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]) ??
|
||||
|
|
|
@ -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<String> 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<String> 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()))
|
||||
: <String>[],
|
||||
);
|
||||
}
|
||||
|
||||
@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<String>().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<String> 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 `<redacted>`.
|
||||
Map<String, String> toJson({bool obscureSecret = true}) {
|
||||
Map<String, dynamic> toJson({bool obscureSecret = true}) {
|
||||
return {
|
||||
'client_id': clientId,
|
||||
'client_secret': obscureSecret ? '<redacted>' : 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();
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ RequestHandler forceBasicAuth<User>({String realm}) {
|
|||
/// Restricts access to a resource via authentication.
|
||||
RequestHandler requireAuthentication<User>() {
|
||||
return (RequestContext req, ResponseContext res,
|
||||
{bool throwError: true}) async {
|
||||
{bool throwError = true}) async {
|
||||
bool _reject(ResponseContext res) {
|
||||
if (throwError) {
|
||||
res.statusCode = 403;
|
||||
|
|
|
@ -24,7 +24,7 @@ class AngelAuthOptions<User> {
|
|||
AngelAuthOptions(
|
||||
{this.callback,
|
||||
this.tokenCallback,
|
||||
this.canRespondWithJson: true,
|
||||
this.canRespondWithJson = true,
|
||||
this.successRedirect,
|
||||
String this.failureRedirect});
|
||||
}
|
||||
|
|
|
@ -63,8 +63,8 @@ class AngelAuth<User> {
|
|||
Hmac get hmac => _hs256;
|
||||
|
||||
String _randomString(
|
||||
{int length: 32,
|
||||
String validChars:
|
||||
{int length = 32,
|
||||
String validChars =
|
||||
"ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}) {
|
||||
var chars = <int>[];
|
||||
while (chars.length < length) chars.add(_random.nextInt(validChars.length));
|
||||
|
@ -77,13 +77,13 @@ class AngelAuth<User> {
|
|||
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;
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -23,13 +23,13 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
|||
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<User> authenticate(RequestContext req, ResponseContext res,
|
||||
|
@ -54,8 +54,8 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_auth
|
||||
environment:
|
||||
|
|
|
@ -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': '<redacted>',
|
||||
'redirect_uri': 'http://example.com',
|
||||
'scopes': [],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -151,6 +154,7 @@ void main() {
|
|||
'client_id': 'foo',
|
||||
'client_secret': 'bar',
|
||||
'redirect_uri': 'http://example.com',
|
||||
'scopes': [],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue