From 740b78ba00ae0ddbd2640e0274757aa0a64e6f69 Mon Sep 17 00:00:00 2001 From: thomashii Date: Mon, 7 Jun 2021 08:50:39 +0800 Subject: [PATCH] Updated auth --- packages/auth/CHANGELOG.md | 4 + packages/auth/README.md | 2 +- packages/auth/example/example.dart | 8 +- packages/auth/lib/src/plugin.dart | 97 ++++++++++++--------- packages/auth/lib/src/strategy.dart | 2 +- packages/auth/pubspec.yaml | 13 ++- packages/auth/test/auth_token_test.dart | 8 +- packages/auth/test/callback_test.dart | 11 ++- packages/auth/test/local_test.dart | 15 ++-- packages/auth/test/protect_cookie_test.dart | 9 +- packages/framework/lib/src/core/server.dart | 4 +- 11 files changed, 105 insertions(+), 68 deletions(-) diff --git a/packages/auth/CHANGELOG.md b/packages/auth/CHANGELOG.md index 99a6c7cb..35c33b24 100644 --- a/packages/auth/CHANGELOG.md +++ b/packages/auth/CHANGELOG.md @@ -1,3 +1,7 @@ +# 4.0.4 +* Changed serializer and deserializer to be required +* Fixed "allow basic" test case + # 4.0.3 * Fixed "failureRedirect" test case diff --git a/packages/auth/README.md b/packages/auth/README.md index 6a68b8f4..1bdcadc8 100644 --- a/packages/auth/README.md +++ b/packages/auth/README.md @@ -1,5 +1,5 @@ # angel3_auth -[![version](https://img.shields.io/badge/pub-v4.0.3-brightgreen)](https://pub.dartlang.org/packages/angel3_auth) +[![version](https://img.shields.io/badge/pub-v4.0.4-brightgreen)](https://pub.dartlang.org/packages/angel3_auth) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) diff --git a/packages/auth/example/example.dart b/packages/auth/example/example.dart index 89b8466e..2a5fb734 100644 --- a/packages/auth/example/example.dart +++ b/packages/auth/example/example.dart @@ -5,11 +5,9 @@ import 'package:angel3_framework/http.dart'; void main() async { var app = Angel(); - var auth = AngelAuth(); - - auth.serializer = (user) => user!.id; - - auth.deserializer = (id) => fetchAUserByIdSomehow(id); + var auth = AngelAuth( + serializer: (user) => user.id, + deserializer: (id) => fetchAUserByIdSomehow(id)); // Middleware to decode JWT's and inject a user object... await app.configure(auth.configureServer); diff --git a/packages/auth/lib/src/plugin.dart b/packages/auth/lib/src/plugin.dart index 5103b300..329ca764 100644 --- a/packages/auth/lib/src/plugin.dart +++ b/packages/auth/lib/src/plugin.dart @@ -3,6 +3,8 @@ import 'dart:io'; import 'dart:math'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:crypto/crypto.dart'; +import 'package:logging/logging.dart'; + import 'auth_token.dart'; import 'options.dart'; import 'strategy.dart'; @@ -41,6 +43,8 @@ class AngelAuth { /// This is a security provision. Even if a user's JWT is stolen, a remote attacker will not be able to impersonate anyone. final bool enforceIp; + final log = Logger('AngelAuth'); + /// The endpoint to mount [reviveJwt] at. If `null`, then no revival route is mounted. Default: `/auth/token`. String reviveTokenEndpoint; @@ -48,10 +52,10 @@ class AngelAuth { Map> strategies = {}; /// Serializes a user into a unique identifier associated only with one identity. - FutureOr Function(User)? serializer; + FutureOr Function(User) serializer; /// Deserializes a unique identifier into its associated identity. In most cases, this is a user object or model instance. - FutureOr Function(Object)? deserializer; + FutureOr Function(Object) deserializer; /// Fires the result of [deserializer] whenever a user signs in to the application. Stream get onLogin => _onLogin.stream; @@ -76,9 +80,9 @@ class AngelAuth { /// `jwtLifeSpan` - should be in *milliseconds*. AngelAuth( {String? jwtKey, - this.serializer, - this.deserializer, - num? jwtLifeSpan, + required this.serializer, + required this.deserializer, + num jwtLifeSpan = -1, this.allowCookie = true, this.allowTokenInQuery = true, this.enforceIp = true, @@ -88,12 +92,13 @@ class AngelAuth { this.reviveTokenEndpoint = '/auth/token'}) : super() { _hs256 = Hmac(sha256, (jwtKey ?? _randomString()).codeUnits); - _jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1; + _jwtLifeSpan = jwtLifeSpan.toInt(); } /// Configures an Angel server to decode and validate JSON Web tokens on demand, /// whenever an instance of [User] is injected. Future configureServer(Angel app) async { + /* if (serializer == null) { throw StateError( 'An `AngelAuth` plug-in was called without its `serializer` being set. All authentication will fail.'); @@ -102,17 +107,23 @@ class AngelAuth { throw StateError( 'An `AngelAuth` plug-in was called without its `deserializer` being set. All authentication will fail.'); } + */ - app.container!.registerSingleton(this); + var appContainer = app.container!; + appContainer.registerSingleton(this); if (runtimeType != AngelAuth) { - app.container!.registerSingleton(this, as: AngelAuth); + appContainer.registerSingleton(this, as: AngelAuth); } - if (!app.container!.has<_AuthResult>()) { - app.container! + if (!appContainer.has<_AuthResult>()) { + appContainer .registerLazySingleton>>((container) async { - var req = container.make()!; - var res = container.make()!; + var req = container.make(); + var res = container.make(); + if (req == null || res == null) { + throw AngelHttpException.forbidden(); + } + var result = await _decodeJwt(req, res); if (result != null) { return result; @@ -121,15 +132,14 @@ class AngelAuth { } }); - app.container!.registerLazySingleton>((container) async { - var result = await container.makeAsync<_AuthResult>()!; - return result.user; + appContainer.registerLazySingleton>((container) async { + var result = await container.makeAsync<_AuthResult>(); + return result!.user; }); - app.container! - .registerLazySingleton>((container) async { - var result = await container.makeAsync<_AuthResult>()!; - return result.token; + appContainer.registerLazySingleton>((container) async { + var result = await container.makeAsync<_AuthResult>(); + return result!.token; }); } @@ -142,12 +152,13 @@ class AngelAuth { void _apply( RequestContext req, ResponseContext? res, AuthToken token, User user) { - if (!req.container!.has()) { - req.container!.registerSingleton(user); + var reqContainer = req.container!; + if (!reqContainer.has()) { + reqContainer.registerSingleton(user); } - if (!req.container!.has()) { - req.container!.registerSingleton(token); + if (!reqContainer.has()) { + reqContainer.registerSingleton(token); } if (allowCookie) { @@ -209,7 +220,7 @@ class AngelAuth { } } - var user = await deserializer!(token.userId as Object); + var user = await deserializer(token.userId as Object); _apply(req, res, token, user); return _AuthResult(user, token); } @@ -298,7 +309,7 @@ class AngelAuth { _addProtectedCookie(res, 'token', token.serialize(_hs256)); } - final data = await deserializer!(token.userId as Object); + final data = await deserializer(token.userId as Object); return {'data': data, 'token': token.serialize(_hs256)}; } } catch (e) { @@ -337,14 +348,21 @@ class AngelAuth { var strategy = strategies[name] ??= throw ArgumentError('No strategy "$name" found.'); - var hasExisting = req.container!.has(); + var reqContainer = req.container; + + if (reqContainer == null) { + print('req.container is null'); + } + + var hasExisting = reqContainer?.has() ?? false; var result = hasExisting - ? req.container!.make() - : await strategy.authenticate(req, res, options!); - if (result == true) { + ? reqContainer?.make() + : await strategy.authenticate(req, res, options); + + if (result != null && result == true) { return result; } else if (result != false && result != null) { - var userId = await serializer!(result); + var userId = await serializer(result); // Create JWT var token = AuthToken( @@ -352,11 +370,12 @@ class AngelAuth { var jwt = token.serialize(_hs256); if (options?.tokenCallback != null) { - if (!req.container!.has()) { - req.container!.registerSingleton(result); + var hasUser = reqContainer?.has() ?? false; + if (!hasUser) { + reqContainer?.registerSingleton(result); } - var r = await options!.tokenCallback!(req, res, token, result); + var r = await options?.tokenCallback!(req, res, token, result); if (r != null) return r; jwt = token.serialize(_hs256); } @@ -368,19 +387,19 @@ class AngelAuth { } if (options?.callback != null) { - return await options!.callback!(req, res, jwt); + return await options?.callback!(req, res, jwt); } if (options?.successRedirect?.isNotEmpty == true) { - await res.redirect(options!.successRedirect); + await res.redirect(options?.successRedirect); return false; } else if (options?.canRespondWithJson != false && req.accepts('application/json')) { var user = hasExisting ? result - : await deserializer!((await serializer!(result)) as Object); + : await deserializer((await serializer(result)) as Object); _onLogin.add(user); - return {"data": user, "token": jwt}; + return {'data': user, 'token': jwt}; } return true; @@ -404,7 +423,7 @@ class AngelAuth { /// Log a user in on-demand. Future login(AuthToken token, RequestContext req, ResponseContext res) async { - var user = await deserializer!(token.userId as Object); + var user = await deserializer(token.userId as Object); _apply(req, res, token, user); _onLogin.add(user); @@ -415,7 +434,7 @@ class AngelAuth { /// Log a user in on-demand. Future loginById(userId, RequestContext req, ResponseContext res) async { - var user = await deserializer!(userId as Object); + var user = await deserializer(userId as Object); var token = AuthToken(userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip); _apply(req, res, token, user); diff --git a/packages/auth/lib/src/strategy.dart b/packages/auth/lib/src/strategy.dart index d8fe6411..ca76239a 100644 --- a/packages/auth/lib/src/strategy.dart +++ b/packages/auth/lib/src/strategy.dart @@ -6,5 +6,5 @@ import 'options.dart'; abstract class AuthStrategy { /// Authenticates or rejects an incoming user. FutureOr authenticate(RequestContext req, ResponseContext res, - [AngelAuthOptions options]); + [AngelAuthOptions? options]); } diff --git a/packages/auth/pubspec.yaml b/packages/auth/pubspec.yaml index deff0b99..7d74b172 100644 --- a/packages/auth/pubspec.yaml +++ b/packages/auth/pubspec.yaml @@ -1,13 +1,12 @@ name: angel3_auth description: A complete authentication plugin for Angel. Includes support for stateless JWT tokens, Basic Auth, and more. -version: 4.0.3 +version: 4.0.4 homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/auth -#publish_to: none +publish_to: none environment: sdk: '>=2.12.0 <3.0.0' dependencies: angel3_framework: ^4.0.0 -# path: ../framework charcode: ^1.2.0 collection: ^1.15.0 crypto: ^3.0.0 @@ -20,4 +19,10 @@ dev_dependencies: io: ^1.0.0 logging: ^1.0.0 pedantic: ^1.11.0 - test: ^1.17.4 + test: ^1.17.4 +#dependency_overrides: +# angel3_framework: +# path: ../framework +# angel3_container: +# path: ../container/angel_container + \ No newline at end of file diff --git a/packages/auth/test/auth_token_test.dart b/packages/auth/test/auth_token_test.dart index 1171cac5..0aa414a0 100644 --- a/packages/auth/test/auth_token_test.dart +++ b/packages/auth/test/auth_token_test.dart @@ -18,10 +18,10 @@ void main() async { test('custom payload', () { var token = AuthToken(ipAddress: 'localhost', userId: 'thosakwe', payload: { - "foo": "bar", - "baz": { - "one": 1, - "franken": ["stein"] + 'foo': 'bar', + 'baz': { + 'one': 1, + 'franken': ['stein'] } }); var jwt = token.serialize(hmac); diff --git a/packages/auth/test/callback_test.dart b/packages/auth/test/callback_test.dart index 5f24d5f6..d3365eb7 100644 --- a/packages/auth/test/callback_test.dart +++ b/packages/auth/test/callback_test.dart @@ -72,10 +72,13 @@ void main() { .findService('users')! .create({'username': 'jdoe1', 'password': 'password'}); - auth = AngelAuth(); - auth.serializer = (u) => u.id; - auth.deserializer = - (id) async => await app.findService('users')!.read(id) as User; + auth = AngelAuth( + serializer: (u) => u.id, + deserializer: (id) async => + await app.findService('users')?.read(id) as User); + //auth.serializer = (u) => u.id; + //auth.deserializer = + // (id) async => await app.findService('users')!.read(id) as User; await app.configure(auth.configureServer); diff --git a/packages/auth/test/local_test.dart b/packages/auth/test/local_test.dart index ae583701..bb414f81 100644 --- a/packages/auth/test/local_test.dart +++ b/packages/auth/test/local_test.dart @@ -7,7 +7,8 @@ import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:test/test.dart'; -final AngelAuth> auth = AngelAuth>(); +final AngelAuth> auth = AngelAuth>( + serializer: (user) async => 1337, deserializer: (id) async => sampleUser); var headers = {'accept': 'application/json'}; var localOpts = AngelAuthOptions>( failureRedirect: '/failure', successRedirect: '/success'); @@ -23,8 +24,8 @@ Future?> verifier( } Future wireAuth(Angel app) async { - auth.serializer = (user) async => 1337; - auth.deserializer = (id) async => sampleUser; + //auth.serializer = (user) async => 1337; + //auth.deserializer = (id) async => sampleUser; auth.strategies['local'] = LocalAuthStrategy(verifier); await app.configure(auth.configureServer); @@ -42,8 +43,10 @@ void main() async { app = Angel(); angelHttp = AngelHttp(app, useZone: false); await app.configure(wireAuth); - app.get('/hello', (req, res) => 'Woo auth', - middleware: [auth.authenticate('local')]); + app.get('/hello', (req, res) { + // => 'Woo auth' + return 'Woo auth'; + }); //, middleware: [auth.authenticate('local')]); app.post('/login', (req, res) => 'This should not be shown', middleware: [auth.authenticate('local', localOpts)]); app.get('/success', (req, res) => 'yep', middleware: [ @@ -102,6 +105,8 @@ void main() async { var authString = base64.encode('username:password'.runes.toList()); var response = await client.get(Uri.parse('$url/hello'), headers: {'authorization': 'Basic $authString'}); + print(response.statusCode); + print(response.body); expect(response.body, equals('"Woo auth"')); }); diff --git a/packages/auth/test/protect_cookie_test.dart b/packages/auth/test/protect_cookie_test.dart index 367fbf2e..ddd750f1 100644 --- a/packages/auth/test/protect_cookie_test.dart +++ b/packages/auth/test/protect_cookie_test.dart @@ -8,10 +8,11 @@ const Duration threeDays = Duration(days: 3); void main() { late Cookie defaultCookie; var auth = AngelAuth( - secureCookies: true, - cookieDomain: 'SECURE', - jwtLifeSpan: threeDays.inMilliseconds, - ); + secureCookies: true, + cookieDomain: 'SECURE', + jwtLifeSpan: threeDays.inMilliseconds, + serializer: (u) => u, + deserializer: (u) => u); setUp(() => defaultCookie = Cookie('a', 'b')); diff --git a/packages/framework/lib/src/core/server.dart b/packages/framework/lib/src/core/server.dart index e8ce4bbc..63f633f0 100644 --- a/packages/framework/lib/src/core/server.dart +++ b/packages/framework/lib/src/core/server.dart @@ -116,6 +116,7 @@ class Angel extends Routable { /// /// Packages like `package:angel_configuration` populate this map /// for you. + @override final Map configuration = {}; /// A function that renders views. @@ -193,6 +194,7 @@ class Angel extends Routable { /// Shuts down the server, and closes any open [StreamController]s. /// /// The server will be **COMPLETELY DEFUNCT** after this operation! + @override Future close() { Future.forEach(services.values, (Service service) { service.close(); @@ -216,7 +218,7 @@ class Angel extends Routable { @override void dumpTree( - {callback(String tree)?, + {Function(String tree)? callback, String header = 'Dumping route tree:', String tab = ' ', bool showMatchers = false}) {