From 53b249da8b5525b126e1d349815631dbf03c07d3 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 19 Apr 2019 05:08:06 -0400 Subject: [PATCH] Use async injection instead of decodeJwt --- CHANGELOG.md | 3 ++ README.md | 13 +++-- example/example.dart | 2 +- lib/src/middleware/require_auth.dart | 14 +++++- lib/src/plugin.dart | 71 ++++++++++++++++++++++++++-- pubspec.yaml | 2 +- test/callback_test.dart | 1 - test/local_test.dart | 1 - 8 files changed, 91 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f464cb27..a7f68d71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.1.4 +* Deprecate `decodeJwt`, in favor of asynchronous injections. + # 2.1.3 * Use `await` on redirects, etc. diff --git a/README.md b/README.md index 3bff33b9..4df0edd0 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,19 @@ Ensure you have read the [wiki](https://github.com/angel-dart/auth/wiki). ```dart configureServer(Angel app) async { - var auth = AngelAuth(); + var auth = AngelAuth(); auth.serializer = ...; auth.deserializer = ...; auth.strategies['local'] = LocalAuthStrategy(...); // POST route to handle username+password app.post('/local', auth.authenticate('local')); + + // Using Angel's asynchronous injections, we can parse the JWT + // on demand. It won't be parsed until we check. + app.get('/profile', ioc((User user) { + print(user.description); + })); // Use a comma to try multiple strategies!!! // @@ -37,11 +43,8 @@ configureServer(Angel app) async { authOptions ); - // Apply angel_auth-specific configuration + // Apply angel_auth-specific configuration. await app.configure(auth.configureServer); - - // Middleware to decode JWT's... - app.use(auth.decodeJwt); } ``` diff --git a/example/example.dart b/example/example.dart index 0061e888..90e09f2b 100644 --- a/example/example.dart +++ b/example/example.dart @@ -12,7 +12,7 @@ main() async { auth.deserializer = (id) => fetchAUserByIdSomehow(id); // Middleware to decode JWT's and inject a user object... - app.fallback(auth.decodeJwt); + await app.configure(auth.configureServer); auth.strategies['local'] = LocalAuthStrategy((username, password) { // Retrieve a user somehow... diff --git a/lib/src/middleware/require_auth.dart b/lib/src/middleware/require_auth.dart index 82f208f2..087ddb27 100644 --- a/lib/src/middleware/require_auth.dart +++ b/lib/src/middleware/require_auth.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:angel_framework/angel_framework.dart'; /// Forces Basic authentication over the requested resource, with the given [realm] name, if no JWT is present. @@ -5,7 +6,13 @@ import 'package:angel_framework/angel_framework.dart'; /// [realm] defaults to `'angel_auth'`. RequestHandler forceBasicAuth({String realm}) { return (RequestContext req, ResponseContext res) async { - if (req.container.has()) return true; + if (req.container.has()) + return true; + else if (req.container.has>()) { + await req.container.makeAsync(); + return true; + } + res.headers['www-authenticate'] = 'Basic realm="${realm ?? 'angel_auth'}"'; throw AngelHttpException.notAuthenticated(); }; @@ -25,7 +32,10 @@ RequestHandler requireAuthentication() { if (req.container.has() || req.method == 'OPTIONS') return true; - else + else if (req.container.has>()) { + await req.container.makeAsync(); + return true; + } else return _reject(res); }; } diff --git a/lib/src/plugin.dart b/lib/src/plugin.dart index 8c9b9d95..702d2f5b 100644 --- a/lib/src/plugin.dart +++ b/lib/src/plugin.dart @@ -89,7 +89,9 @@ class AngelAuth { _jwtLifeSpan = jwtLifeSpan?.toInt() ?? -1; } - Future configureServer(Angel app) async { + /// 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.'); @@ -101,6 +103,30 @@ class AngelAuth { if (runtimeType != AngelAuth) app.container.registerSingleton(this, as: AngelAuth); + if (!app.container.has<_AuthResult>()) { + app.container + .registerLazySingleton>>((container) async { + var req = container.make(); + var res = container.make(); + var result = await _decodeJwt(req, res); + if (result != null) { + return result; + } else { + throw AngelHttpException.forbidden(); + } + }); + + app.container.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; + }); + } + if (reviveTokenEndpoint != null) { app.post(reviveTokenEndpoint, reviveJwt); } @@ -112,7 +138,7 @@ class AngelAuth { void _apply( RequestContext req, ResponseContext res, AuthToken token, User user) { - if (!req.container.has()) { + if (!req.container.has() && !req.container.has>()) { req.container ..registerSingleton(token) ..registerSingleton(user); @@ -123,12 +149,39 @@ class AngelAuth { } } - /// A middleware that decodes a JWT from a request, and injects a corresponding user. + /// DEPRECATED: A middleware that decodes a JWT from a request, and injects a corresponding user. + /// + /// Now that `package:angel_framework` supports asynchronous injections, this middleware + /// is no longer directly necessary. Instead, call [configureServer]. You can then use + /// `makeAsync`, or Angel's injections directly: + /// + /// ```dart + /// var auth = AngelAuth(...); + /// await app.configure(auth.configureServer); + /// + /// app.get('/hmm', (User user) async { + /// // `package:angel_auth` decodes the JWT on demand. + /// print(user.name); + /// }); + /// + /// @Expose('/my') + /// class MyController extends Controller { + /// @Expose('/hmm') + /// String getUsername(User user) => user.name + /// } + /// ``` + @deprecated Future decodeJwt(RequestContext req, ResponseContext res) async { if (req.method == "POST" && req.path == reviveTokenEndpoint) { return await reviveJwt(req, res); + } else { + await _decodeJwt(req, res); + return true; } + } + Future<_AuthResult> _decodeJwt( + RequestContext req, ResponseContext res) async { String jwt = getJwt(req); if (jwt != null) { @@ -148,11 +201,12 @@ class AngelAuth { throw AngelHttpException.forbidden(message: "Expired JWT."); } - final user = await deserializer(token.userId); + var user = await deserializer(token.userId); _apply(req, res, token, user); + return _AuthResult(user, token); } - return true; + return null; } /// Retrieves a JWT from a request, if any was sent at all. @@ -384,3 +438,10 @@ class AngelAuth { }; } } + +class _AuthResult { + final User user; + final AuthToken token; + + _AuthResult(this.user, this.token); +} diff --git a/pubspec.yaml b/pubspec.yaml index aa8b952b..3197da68 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.3 +version: 2.1.4 author: Tobe O homepage: https://github.com/angel-dart/angel_auth environment: diff --git a/test/callback_test.dart b/test/callback_test.dart index b365bd2a..faa939dd 100644 --- a/test/callback_test.dart +++ b/test/callback_test.dart @@ -75,7 +75,6 @@ main() { (id) async => await app.findService('users').read(id) as User; await app.configure(auth.configureServer); - app.fallback(auth.decodeJwt); auth.strategies['local'] = LocalAuthStrategy((username, password) async { var users = await app diff --git a/test/local_test.dart b/test/local_test.dart index cc41d53f..21356388 100644 --- a/test/local_test.dart +++ b/test/local_test.dart @@ -27,7 +27,6 @@ Future wireAuth(Angel app) async { auth.strategies['local'] = LocalAuthStrategy(verifier); await app.configure(auth.configureServer); - app.fallback(auth.decodeJwt); } main() async {