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
|
# 2.1.0
|
||||||
* Added `ExternalAuthOptions`.
|
* Added `ExternalAuthOptions`.
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,10 @@ class AuthToken {
|
||||||
|
|
||||||
AuthToken(
|
AuthToken(
|
||||||
{this.ipAddress,
|
{this.ipAddress,
|
||||||
this.lifeSpan: -1,
|
this.lifeSpan = -1,
|
||||||
this.userId,
|
this.userId,
|
||||||
DateTime issuedAt,
|
DateTime issuedAt,
|
||||||
Map payload: const {}}) {
|
Map payload = const {}}) {
|
||||||
this.issuedAt = issuedAt ?? new DateTime.now();
|
this.issuedAt = issuedAt ?? new DateTime.now();
|
||||||
this.payload.addAll(
|
this.payload.addAll(
|
||||||
payload?.keys?.fold({}, (out, k) => out..[k.toString()] = payload[k]) ??
|
payload?.keys?.fold({}, (out, k) => out..[k.toString()] = payload[k]) ??
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:charcode/ascii.dart';
|
import 'package:charcode/ascii.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:quiver_hashcode/hashcode.dart';
|
import 'package:quiver_hashcode/hashcode.dart';
|
||||||
|
|
||||||
|
@ -13,7 +14,11 @@ class ExternalAuthOptions {
|
||||||
/// The user's redirect URI.
|
/// The user's redirect URI.
|
||||||
final Uri redirectUri;
|
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) {
|
if (clientId == null) {
|
||||||
throw new ArgumentError.notNull('clientId');
|
throw new ArgumentError.notNull('clientId');
|
||||||
} else if (clientSecret == null) {
|
} else if (clientSecret == null) {
|
||||||
|
@ -24,12 +29,14 @@ class ExternalAuthOptions {
|
||||||
factory ExternalAuthOptions(
|
factory ExternalAuthOptions(
|
||||||
{@required String clientId,
|
{@required String clientId,
|
||||||
@required String clientSecret,
|
@required String clientSecret,
|
||||||
@required redirectUri}) {
|
@required redirectUri,
|
||||||
|
Iterable<String> scopes = const []}) {
|
||||||
if (redirectUri is String) {
|
if (redirectUri is String) {
|
||||||
return new ExternalAuthOptions._(
|
return new ExternalAuthOptions._(
|
||||||
clientId, clientSecret, Uri.parse(redirectUri));
|
clientId, clientSecret, Uri.parse(redirectUri), scopes.toSet());
|
||||||
} else if (redirectUri is Uri) {
|
} else if (redirectUri is Uri) {
|
||||||
return new ExternalAuthOptions._(clientId, clientSecret, redirectUri);
|
return new ExternalAuthOptions._(
|
||||||
|
clientId, clientSecret, redirectUri, scopes.toSet());
|
||||||
} else {
|
} else {
|
||||||
throw new ArgumentError.value(
|
throw new ArgumentError.value(
|
||||||
redirectUri, 'redirectUri', 'must be a String or Uri');
|
redirectUri, 'redirectUri', 'must be a String or Uri');
|
||||||
|
@ -47,26 +54,34 @@ class ExternalAuthOptions {
|
||||||
clientId: map['client_id'] as String,
|
clientId: map['client_id'] as String,
|
||||||
clientSecret: map['client_secret'] as String,
|
clientSecret: map['client_secret'] as String,
|
||||||
redirectUri: map['redirect_uri'],
|
redirectUri: map['redirect_uri'],
|
||||||
|
scopes: map['scopes'] is Iterable
|
||||||
|
? ((map['scopes'] as Iterable).map((x) => x.toString()))
|
||||||
|
: <String>[],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => hash3(clientId, clientSecret, redirectUri);
|
int get hashCode => hash4(clientId, clientSecret, redirectUri, scopes);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) =>
|
bool operator ==(other) =>
|
||||||
other is ExternalAuthOptions &&
|
other is ExternalAuthOptions &&
|
||||||
other.clientId == clientId &&
|
other.clientId == clientId &&
|
||||||
other.clientSecret == other.clientSecret &&
|
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.
|
/// Creates a copy of this object, with the specified changes.
|
||||||
ExternalAuthOptions copyWith(
|
ExternalAuthOptions copyWith(
|
||||||
{String clientId, String clientSecret, redirectUri}) {
|
{String clientId,
|
||||||
|
String clientSecret,
|
||||||
|
redirectUri,
|
||||||
|
Iterable<String> scopes}) {
|
||||||
return new ExternalAuthOptions(
|
return new ExternalAuthOptions(
|
||||||
clientId: clientId ?? this.clientId,
|
clientId: clientId ?? this.clientId,
|
||||||
clientSecret: clientSecret ?? this.clientSecret,
|
clientSecret: clientSecret ?? this.clientSecret,
|
||||||
redirectUri: redirectUri ?? this.redirectUri,
|
redirectUri: redirectUri ?? this.redirectUri,
|
||||||
|
scopes: (scopes ??= []).followedBy(this.scopes),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,11 +94,12 @@ class ExternalAuthOptions {
|
||||||
///
|
///
|
||||||
/// If [obscureSecret] is `true` (default), then the [clientSecret] will
|
/// If [obscureSecret] is `true` (default), then the [clientSecret] will
|
||||||
/// be replaced by the string `<redacted>`.
|
/// be replaced by the string `<redacted>`.
|
||||||
Map<String, String> toJson({bool obscureSecret = true}) {
|
Map<String, dynamic> toJson({bool obscureSecret = true}) {
|
||||||
return {
|
return {
|
||||||
'client_id': clientId,
|
'client_id': clientId,
|
||||||
'client_secret': obscureSecret ? '<redacted>' : clientSecret,
|
'client_secret': obscureSecret ? '<redacted>' : clientSecret,
|
||||||
'redirect_uri': redirectUri.toString(),
|
'redirect_uri': redirectUri.toString(),
|
||||||
|
'scopes': scopes.toList(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +126,7 @@ class ExternalAuthOptions {
|
||||||
b.write('clientId=$clientId');
|
b.write('clientId=$clientId');
|
||||||
b.write(', clientSecret=$secret');
|
b.write(', clientSecret=$secret');
|
||||||
b.write(', redirectUri=$redirectUri');
|
b.write(', redirectUri=$redirectUri');
|
||||||
|
b.write(', scopes=${scopes.toList()}');
|
||||||
b.write(')');
|
b.write(')');
|
||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ RequestHandler forceBasicAuth<User>({String realm}) {
|
||||||
/// Restricts access to a resource via authentication.
|
/// Restricts access to a resource via authentication.
|
||||||
RequestHandler requireAuthentication<User>() {
|
RequestHandler requireAuthentication<User>() {
|
||||||
return (RequestContext req, ResponseContext res,
|
return (RequestContext req, ResponseContext res,
|
||||||
{bool throwError: true}) async {
|
{bool throwError = true}) async {
|
||||||
bool _reject(ResponseContext res) {
|
bool _reject(ResponseContext res) {
|
||||||
if (throwError) {
|
if (throwError) {
|
||||||
res.statusCode = 403;
|
res.statusCode = 403;
|
||||||
|
|
|
@ -24,7 +24,7 @@ class AngelAuthOptions<User> {
|
||||||
AngelAuthOptions(
|
AngelAuthOptions(
|
||||||
{this.callback,
|
{this.callback,
|
||||||
this.tokenCallback,
|
this.tokenCallback,
|
||||||
this.canRespondWithJson: true,
|
this.canRespondWithJson = true,
|
||||||
this.successRedirect,
|
this.successRedirect,
|
||||||
String this.failureRedirect});
|
String this.failureRedirect});
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,8 +63,8 @@ class AngelAuth<User> {
|
||||||
Hmac get hmac => _hs256;
|
Hmac get hmac => _hs256;
|
||||||
|
|
||||||
String _randomString(
|
String _randomString(
|
||||||
{int length: 32,
|
{int length = 32,
|
||||||
String validChars:
|
String validChars =
|
||||||
"ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}) {
|
"ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}) {
|
||||||
var chars = <int>[];
|
var chars = <int>[];
|
||||||
while (chars.length < length) chars.add(_random.nextInt(validChars.length));
|
while (chars.length < length) chars.add(_random.nextInt(validChars.length));
|
||||||
|
@ -77,13 +77,13 @@ class AngelAuth<User> {
|
||||||
this.serializer,
|
this.serializer,
|
||||||
this.deserializer,
|
this.deserializer,
|
||||||
num jwtLifeSpan,
|
num jwtLifeSpan,
|
||||||
this.allowCookie: true,
|
this.allowCookie = true,
|
||||||
this.allowTokenInQuery: true,
|
this.allowTokenInQuery = true,
|
||||||
this.enforceIp: true,
|
this.enforceIp = true,
|
||||||
this.cookieDomain,
|
this.cookieDomain,
|
||||||
this.cookiePath: '/',
|
this.cookiePath = '/',
|
||||||
this.secureCookies: true,
|
this.secureCookies = true,
|
||||||
this.reviveTokenEndpoint: "/auth/token"})
|
this.reviveTokenEndpoint = "/auth/token"})
|
||||||
: super() {
|
: super() {
|
||||||
_hs256 = new Hmac(sha256, (jwtKey ?? _randomString()).codeUnits);
|
_hs256 = new Hmac(sha256, (jwtKey ?? _randomString()).codeUnits);
|
||||||
_jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1;
|
_jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:http_parser/http_parser.dart';
|
||||||
import 'options.dart';
|
import 'options.dart';
|
||||||
|
|
||||||
/// Displays a default callback page to confirm authentication via popups.
|
/// 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) {
|
return (req, ResponseContext res, String jwt) {
|
||||||
var evt = json.encode(eventName);
|
var evt = json.encode(eventName);
|
||||||
var detail = json.encode({'detail': jwt});
|
var detail = json.encode({'detail': jwt});
|
||||||
|
|
|
@ -23,13 +23,13 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
||||||
String realm;
|
String realm;
|
||||||
|
|
||||||
LocalAuthStrategy(this.verifier,
|
LocalAuthStrategy(this.verifier,
|
||||||
{String this.usernameField: 'username',
|
{String this.usernameField = 'username',
|
||||||
String this.passwordField: 'password',
|
String this.passwordField = 'password',
|
||||||
String this.invalidMessage:
|
String this.invalidMessage =
|
||||||
'Please provide a valid username and password.',
|
'Please provide a valid username and password.',
|
||||||
bool this.allowBasic: true,
|
bool this.allowBasic = true,
|
||||||
bool this.forceBasic: false,
|
bool this.forceBasic = false,
|
||||||
String this.realm: 'Authentication is required.'}) {}
|
String this.realm = 'Authentication is required.'}) {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<User> authenticate(RequestContext req, ResponseContext res,
|
Future<User> authenticate(RequestContext req, ResponseContext res,
|
||||||
|
@ -54,8 +54,8 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
||||||
if (verificationResult == false || verificationResult == null) {
|
if (verificationResult == false || verificationResult == null) {
|
||||||
res
|
res
|
||||||
..statusCode = 401
|
..statusCode = 401
|
||||||
..headers['www-authenticate'] = 'Basic realm="$realm"'
|
..headers['www-authenticate'] = 'Basic realm="$realm"';
|
||||||
..close();
|
await res.close();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: angel_auth
|
name: angel_auth
|
||||||
description: A complete authentication plugin for Angel. Includes support for stateless JWT tokens, Basic Auth, and more.
|
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>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage: https://github.com/angel-dart/angel_auth
|
homepage: https://github.com/angel-dart/angel_auth
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -23,11 +23,13 @@ void main() {
|
||||||
clientId: 'hey',
|
clientId: 'hey',
|
||||||
clientSecret: 'hello',
|
clientSecret: 'hello',
|
||||||
redirectUri: 'https://yes.no',
|
redirectUri: 'https://yes.no',
|
||||||
|
scopes: ['a', 'b'],
|
||||||
),
|
),
|
||||||
new ExternalAuthOptions(
|
new ExternalAuthOptions(
|
||||||
clientId: 'hey',
|
clientId: 'hey',
|
||||||
clientSecret: 'hello',
|
clientSecret: 'hello',
|
||||||
redirectUri: 'https://yes.no',
|
redirectUri: 'https://yes.no',
|
||||||
|
scopes: ['a', 'b'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -113,21 +115,21 @@ void main() {
|
||||||
test('produces correct string', () {
|
test('produces correct string', () {
|
||||||
expect(
|
expect(
|
||||||
options.toString(obscureSecret: false),
|
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', () {
|
test('obscures secret', () {
|
||||||
expect(
|
expect(
|
||||||
options.toString(),
|
options.toString(),
|
||||||
'ExternalAuthOptions(clientId=foo, clientSecret=***, redirectUri=http://example.com)',
|
'ExternalAuthOptions(clientId=foo, clientSecret=***, redirectUri=http://example.com, scopes=[])',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('asteriskCount', () {
|
test('asteriskCount', () {
|
||||||
expect(
|
expect(
|
||||||
options.toString(asteriskCount: 7),
|
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_id': 'foo',
|
||||||
'client_secret': '<redacted>',
|
'client_secret': '<redacted>',
|
||||||
'redirect_uri': 'http://example.com',
|
'redirect_uri': 'http://example.com',
|
||||||
|
'scopes': [],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -151,6 +154,7 @@ void main() {
|
||||||
'client_id': 'foo',
|
'client_id': 'foo',
|
||||||
'client_secret': 'bar',
|
'client_secret': 'bar',
|
||||||
'redirect_uri': 'http://example.com',
|
'redirect_uri': 'http://example.com',
|
||||||
|
'scopes': [],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue