Fixed bugs

This commit is contained in:
Tobe O 2018-09-11 18:03:35 -04:00
parent a6b08ae7c4
commit cba439e773
8 changed files with 86 additions and 77 deletions

View file

@ -1,3 +1,8 @@
# 2.0.0-alpha.1
* Made `AuthStrategy` generic.
* `AngelAuth.strategies` is now a `Map<String, AuthStrategy<User>>`.
* Removed `AuthStrategy.canLogout`.
# 2.0.0-alpha
* Depend on Dart 2 and Angel 2.
* Remove `dart2_constant`.

View file

@ -9,11 +9,11 @@ import 'options.dart';
import 'strategy.dart';
/// Handles authentication within an Angel application.
class AngelAuth<T> {
class AngelAuth<User> {
Hmac _hs256;
int _jwtLifeSpan;
final StreamController<T> _onLogin = new StreamController<T>(),
_onLogout = new StreamController<T>();
final StreamController<User> _onLogin = new StreamController<User>(),
_onLogout = new StreamController<User>();
Math.Random _random = new Math.Random.secure();
final RegExp _rgxBearer = new RegExp(r"^Bearer");
@ -46,19 +46,19 @@ class AngelAuth<T> {
String reviveTokenEndpoint;
/// A set of [AuthStrategy] instances used to authenticate users.
List<AuthStrategy> strategies = [];
Map<String, AuthStrategy<User>> strategies = {};
/// Serializes a user into a unique identifier associated only with one identity.
UserSerializer<T> serializer;
UserSerializer<User> serializer;
/// Deserializes a unique identifier into its associated identity. In most cases, this is a user object or model instance.
UserDeserializer<T> deserializer;
UserDeserializer<User> deserializer;
/// Fires the result of [deserializer] whenever a user signs in to the application.
Stream<T> get onLogin => _onLogin.stream;
Stream<User> get onLogin => _onLogin.stream;
/// Fires `req.user`, which is usually the result of [deserializer], whenever a user signs out of the application.
Stream<T> get onLogout => _onLogout.stream;
Stream<User> get onLogout => _onLogout.stream;
/// The [Hmac] being used to encode JWT's.
Hmac get hmac => _hs256;
@ -110,10 +110,12 @@ class AngelAuth<T> {
}
void _apply(
RequestContext req, ResponseContext res, AuthToken token, T user) {
RequestContext req, ResponseContext res, AuthToken token, User user) {
if (!req.container.has<User>()) {
req.container
..registerSingleton<AuthToken>(token)
..registerSingleton<T>(user);
..registerSingleton<User>(user);
}
if (allowCookie == true) {
_addProtectedCookie(res, 'token', token.serialize(_hs256));
@ -265,15 +267,13 @@ class AngelAuth<T> {
for (int i = 0; i < names.length; i++) {
var name = names[i];
AuthStrategy strategy = strategies.firstWhere(
(AuthStrategy x) => x.name == name,
orElse: () =>
throw new ArgumentError('No strategy "$name" found.'));
var strategy = strategies[name] ??=
throw new ArgumentError('No strategy "$name" found.');
var hasExisting = req.container.has<T>();
var hasExisting = req.container.has<User>();
var result = hasExisting
? req.container.make<T>()
: await strategy.authenticate(req, res, options) as T;
? req.container.make<User>()
: await strategy.authenticate(req, res, options);
if (result == true)
return result;
else if (result != false) {
@ -285,7 +285,10 @@ class AngelAuth<T> {
var jwt = token.serialize(_hs256);
if (options?.tokenCallback != null) {
req.container.registerSingleton<T>(result);
if (!req.container.has<User>()) {
req.container.registerSingleton<User>(result);
}
var r = await options.tokenCallback(req, res, token, result);
if (r != null) return r;
jwt = token.serialize(_hs256);
@ -355,20 +358,8 @@ class AngelAuth<T> {
/// Log an authenticated user out.
RequestHandler logout([AngelAuthOptions options]) {
return (RequestContext req, ResponseContext res) async {
for (AuthStrategy strategy in strategies) {
if (!(await strategy.canLogout(req, res))) {
if (options != null &&
options.failureRedirect != null &&
options.failureRedirect.isNotEmpty) {
res.redirect(options.failureRedirect);
}
return false;
}
}
if (req.container.has<T>()) {
var user = req.container.make<T>();
if (req.container.has<User>()) {
var user = req.container.make<User>();
_onLogout.add(user);
}

View file

@ -1,10 +1,14 @@
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
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'}) {
return (req, ResponseContext res, String jwt) async {
return (req, ResponseContext res, String jwt) {
var evt = json.encode(eventName);
var detail = json.encode({'detail': jwt});
res
..contentType = new MediaType('text', 'html')
..write('''
@ -14,7 +18,7 @@ AngelAuthCallback confirmPopupAuthentication({String eventName: 'token'}) {
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Authentication Success</title>
<script>
var ev = new CustomEvent('$eventName', { detail: '$jwt' });
var ev = new CustomEvent($evt, $detail);
window.opener.dispatchEvent(ev);
window.close();
</script>

View file

@ -7,15 +7,14 @@ import '../strategy.dart';
bool _validateString(String str) => str != null && str.isNotEmpty;
/// Determines the validity of an incoming username and password.
typedef Future LocalAuthVerifier(String username, String password);
typedef FutureOr<User> LocalAuthVerifier<User>(
String username, String password);
class LocalAuthStrategy extends AuthStrategy {
class LocalAuthStrategy<User> extends AuthStrategy<User> {
RegExp _rgxBasic = new RegExp(r'^Basic (.+)$', caseSensitive: false);
RegExp _rgxUsrPass = new RegExp(r'^([^:]+):(.+)$');
@override
String name = 'local';
LocalAuthVerifier verifier;
LocalAuthVerifier<User> verifier;
String usernameField;
String passwordField;
String invalidMessage;
@ -23,7 +22,7 @@ class LocalAuthStrategy extends AuthStrategy {
final bool forceBasic;
String realm;
LocalAuthStrategy(LocalAuthVerifier this.verifier,
LocalAuthStrategy(this.verifier,
{String this.usernameField: 'username',
String this.passwordField: 'password',
String this.invalidMessage:
@ -33,15 +32,10 @@ class LocalAuthStrategy extends AuthStrategy {
String this.realm: 'Authentication is required.'}) {}
@override
Future<bool> canLogout(RequestContext req, ResponseContext res) async {
return true;
}
@override
Future authenticate(RequestContext req, ResponseContext res,
Future<User> authenticate(RequestContext req, ResponseContext res,
[AngelAuthOptions options_]) async {
AngelAuthOptions options = options_ ?? new AngelAuthOptions();
var verificationResult;
User verificationResult;
if (allowBasic) {
String authHeader = req.headers.value('authorization') ?? "";
@ -62,7 +56,7 @@ class LocalAuthStrategy extends AuthStrategy {
..statusCode = 401
..headers['www-authenticate'] = 'Basic realm="$realm"'
..close();
return false;
return null;
}
return verificationResult;
@ -82,17 +76,15 @@ class LocalAuthStrategy extends AuthStrategy {
if (options.failureRedirect != null &&
options.failureRedirect.isNotEmpty) {
res.redirect(options.failureRedirect, code: 401);
return false;
return null;
}
if (forceBasic) {
res
..statusCode = 401
..headers['www-authenticate'] = 'Basic realm="$realm"'
..close();
res.headers['www-authenticate'] = 'Basic realm="$realm"';
throw new AngelHttpException.notAuthenticated();
}
return false;
return null;
} else if (verificationResult != null && verificationResult != false) {
return verificationResult;
} else {

View file

@ -3,13 +3,8 @@ import 'package:angel_framework/angel_framework.dart';
import 'options.dart';
/// A function that handles login and signup for an Angel application.
abstract class AuthStrategy {
String name;
abstract class AuthStrategy<User> {
/// Authenticates or rejects an incoming user.
Future authenticate(RequestContext req, ResponseContext res,
FutureOr<User> authenticate(RequestContext req, ResponseContext res,
[AngelAuthOptions options]);
/// Determines whether a signed-in user can log out or not.
Future<bool> canLogout(RequestContext req, ResponseContext res);
}

View file

@ -1,6 +1,6 @@
name: angel_auth
description: A complete authentication plugin for Angel.
version: 2.0.0-alpha
version: 2.0.0-alpha.1
author: Tobe O <thosakwe@gmail.com>
homepage: https://github.com/angel-dart/angel_auth
environment:

View file

@ -11,6 +11,23 @@ class User extends Model {
String username, password;
User({this.username, this.password});
static User parse(Map map) {
return new User(
username: map['username'] as String,
password: map['password'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'username': username,
'password': password,
'created_at': createdAt?.toIso8601String(),
'updated_at': updatedAt?.toIso8601String()
};
}
}
main() {
@ -59,14 +76,16 @@ main() {
await app.configure(auth.configureServer);
app.fallback(auth.decodeJwt);
auth.strategies.add(new LocalAuthStrategy((username, password) async {
final List<User> users = await app.service('users').index();
final found = users.firstWhere(
auth.strategies['local'] =
new LocalAuthStrategy((username, password) async {
var users = (await app
.service('users')
.index()
.then((it) => it.map<User>(User.parse).toList())) as Iterable<User>;
return users.firstWhere(
(user) => user.username == username && user.password == password,
orElse: () => null);
return found != null ? found : false;
}));
});
app.post(
'/login',
@ -79,8 +98,10 @@ main() {
app.chain([
(req, res) {
if (!req.container.has<User>()) {
req.container.registerSingleton<User>(
new User(username: req.params['name']?.toString()));
}
return true;
}
]).post(

View file

@ -13,18 +13,18 @@ AngelAuthOptions localOpts = new AngelAuthOptions(
failureRedirect: '/failure', successRedirect: '/success');
Map<String, String> sampleUser = {'hello': 'world'};
Future verifier(String username, String password) async {
Future<Map<String, String>> verifier(String username, String password) async {
if (username == 'username' && password == 'password') {
return sampleUser;
} else
return false;
return null;
}
Future wireAuth(Angel app) async {
auth.serializer = (user) async => 1337;
auth.deserializer = (id) async => sampleUser;
auth.strategies.add(new LocalAuthStrategy(verifier));
auth.strategies['local'] = new LocalAuthStrategy(verifier);
await app.configure(auth.configureServer);
app.fallback(auth.decodeJwt);
}
@ -103,10 +103,11 @@ main() async {
test('force basic', () async {
auth.strategies.clear();
auth.strategies
.add(new LocalAuthStrategy(verifier, forceBasic: true, realm: 'test'));
auth.strategies['local'] =
new LocalAuthStrategy(verifier, forceBasic: true, realm: 'test');
var response = await client.get("$url/hello", headers: headers);
print(response.headers);
print('Body <${response.body}>');
expect(response.headers['www-authenticate'], equals('Basic realm="test"'));
});
}