Fixed bugs
This commit is contained in:
parent
a6b08ae7c4
commit
cba439e773
8 changed files with 86 additions and 77 deletions
|
@ -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`.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue