Updated auth
This commit is contained in:
parent
aa3ce41a9a
commit
0d1601980a
11 changed files with 171 additions and 84 deletions
|
@ -15,7 +15,7 @@
|
|||
* Added merge_map and migrated to 2.0.0 (6/6 tests passed)
|
||||
* Added mock_request and migrated to 2.0.0 (5/5 tests)
|
||||
* Migrated angel_framework to 4.0.0 (149/150 tests passed)
|
||||
* Migrated angel_auth to 4.0.0 (29/30 tests passed)
|
||||
* Migrated angel_auth to 4.0.0 (31/31 tests passed)
|
||||
* Migrated angel_configuration to 4.0.0 (6/8 testspassed)
|
||||
* Migrated angel_validate to 4.0.0 (6/7 tests passed)
|
||||
* Migrated json_god to 4.0.0 (13/13 tests passed)
|
||||
|
|
|
@ -1,102 +1,135 @@
|
|||
# 4.0.4
|
||||
* Changed serializer and deserializer to be required
|
||||
* Fixed "allow basic" test case
|
||||
# Change Log
|
||||
|
||||
# 4.0.3
|
||||
* Fixed "failureRedirect" test case
|
||||
## 4.0.4
|
||||
|
||||
# 4.0.2
|
||||
* Added MirrorsReflector to test cases
|
||||
* Changed `serializer` and `deserializer` parameters to be required
|
||||
* Fixed HTTP basic authentication
|
||||
* Passed all 51 unit tests
|
||||
|
||||
## 4.0.3
|
||||
|
||||
* Fixed "failureRedirect" unit test
|
||||
|
||||
## 4.0.2
|
||||
|
||||
* Added MirrorsReflector to unit test
|
||||
|
||||
## 4.0.1
|
||||
|
||||
# 4.0.1
|
||||
* Updated README
|
||||
|
||||
# 4.0.0
|
||||
## 4.0.0
|
||||
|
||||
* Migrated to support Dart SDK 2.12.x NNBD
|
||||
|
||||
# 3.0.0
|
||||
## 3.0.0
|
||||
|
||||
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
||||
|
||||
# 2.1.5+1
|
||||
## 2.1.5+1
|
||||
|
||||
* Fix error in popup page.
|
||||
|
||||
# 2.1.5
|
||||
## 2.1.5
|
||||
|
||||
* Modify `_apply` to honor an existing `User` over `Future<User>`.
|
||||
|
||||
# 2.1.4
|
||||
## 2.1.4
|
||||
|
||||
* Deprecate `decodeJwt`, in favor of asynchronous injections.
|
||||
|
||||
# 2.1.3
|
||||
## 2.1.3
|
||||
|
||||
* Use `await` on redirects, etc.
|
||||
|
||||
# 2.1.2
|
||||
## 2.1.2
|
||||
|
||||
* Change empty cookie string to have double quotes (thanks @korsvanloon).
|
||||
|
||||
# 2.1.1
|
||||
## 2.1.1
|
||||
|
||||
* Added `scopes` to `ExternalAuthOptions`.
|
||||
|
||||
# 2.1.0
|
||||
## 2.1.0
|
||||
|
||||
* Added `ExternalAuthOptions`.
|
||||
|
||||
# 2.0.4
|
||||
## 2.0.4
|
||||
|
||||
* `successRedirect` was previously explicitly returning a `200`; remove this and allow the default `302`.
|
||||
|
||||
# 2.0.3
|
||||
## 2.0.3
|
||||
|
||||
* Updates for streaming parse of request bodies.
|
||||
|
||||
# 2.0.2
|
||||
## 2.0.2
|
||||
|
||||
* Handle `null` return in `authenticate` + `failureRedirect`.
|
||||
|
||||
# 2.0.1
|
||||
## 2.0.1
|
||||
|
||||
* Add generic parameter to `options` on `AuthStrategy.authenticate`.
|
||||
|
||||
# 2.0.0+1
|
||||
## 2.0.0+1
|
||||
|
||||
* Meta update to improve Pub score.
|
||||
|
||||
# 2.0.0
|
||||
## 2.0.0
|
||||
|
||||
* Made `AuthStrategy` generic.
|
||||
* `AngelAuth.strategies` is now a `Map<String, AuthStrategy<User>>`.
|
||||
* Removed `AuthStrategy.canLogout`.
|
||||
* Made `AngelAuthTokenCallback` generic.
|
||||
|
||||
# 2.0.0-alpha
|
||||
## 2.0.0-alpha
|
||||
|
||||
* Depend on Dart 2 and Angel 2.
|
||||
* Remove `dart2_constant`.
|
||||
* Remove `requireAuth`.
|
||||
* Remove `userKey`, instead favoring generic parameters.
|
||||
|
||||
# 1.2.0
|
||||
## 1.2.0
|
||||
|
||||
* Deprecate `requireAuth`, in favor of `requireAuthentication`.
|
||||
* Allow configuring of the `userKey`.
|
||||
* Deprecate `middlewareName`.
|
||||
|
||||
# 1.1.1+6
|
||||
## 1.1.1+6
|
||||
|
||||
* Fix a small logic bug that prevented `LocalAuthStrategy`
|
||||
from correctly propagating the authenticated user when
|
||||
using `Basic` auth.
|
||||
|
||||
# 1.1.1+5
|
||||
## 1.1.1+5
|
||||
|
||||
* Prevent duplication of cookies.
|
||||
* Regenerate the JWT if `tokenCallback` is called.
|
||||
|
||||
# 1.1.1+4
|
||||
## 1.1.1+4
|
||||
|
||||
* Patched `logout` to properly erase cookies
|
||||
* Fixed checking of expired tokens.
|
||||
|
||||
# 1.1.1+3
|
||||
## 1.1.1+3
|
||||
|
||||
* `authenticate` returns the current user, if one is present.
|
||||
|
||||
# 1.1.1+2
|
||||
## 1.1.1+2
|
||||
|
||||
* `_apply` now always sends a `token` cookie.
|
||||
|
||||
# 1.1.1+1
|
||||
## 1.1.1+1
|
||||
|
||||
* Update `protectCookie` to only send `maxAge` when it is not `-1`.
|
||||
|
||||
# 1.1.1
|
||||
## 1.1.1
|
||||
|
||||
* Added `protectCookie`, to better protect data sent in cookies.
|
||||
|
||||
# 1.1.0+2
|
||||
## 1.1.0+2
|
||||
|
||||
* `LocalAuthStrategy` returns `true` on `Basic` authentication.
|
||||
|
||||
# 1.1.0+1
|
||||
## 1.1.0+1
|
||||
|
||||
* Modified `LocalAuthStrategy`'s handling of `Basic` authentication.
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
# angel3_auth
|
||||
# Angel3 Anthentication
|
||||
|
||||
[![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)
|
||||
|
||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/auth/LICENSE)
|
||||
|
||||
A complete authentication plugin for Angel. Inspired by Passport.
|
||||
A complete authentication plugin for Angel3. Inspired by Passport. More details in the [User Guide](https://angel3-docs.dukefirehawk.com/guides/authentication).
|
||||
|
||||
# Wiki
|
||||
[Click here](https://github.com/angel-dart/auth/wiki).
|
||||
## Bundled Strategies
|
||||
|
||||
# Bundled Strategies
|
||||
* Local (with and without Basic Auth)
|
||||
* Find other strategies (Twitter, Google, OAuth2, etc.) on Pub!!!
|
||||
* Find other strategies (Twitter, Google, OAuth2, etc.) on pub
|
||||
|
||||
# Example
|
||||
Ensure you have read the [wiki](https://github.com/angel-dart/auth/wiki).
|
||||
## Example
|
||||
|
||||
Ensure you have read the [User Guide](https://angel3-docs.dukefirehawk.com/guides/authentication).
|
||||
|
||||
```dart
|
||||
configureServer(Angel app) async {
|
||||
|
@ -50,11 +50,11 @@ configureServer(Angel app) async {
|
|||
}
|
||||
```
|
||||
|
||||
# Default Authentication Callback
|
||||
## Default Authentication Callback
|
||||
|
||||
A frequent use case within SPA's is opening OAuth login endpoints in a separate window.
|
||||
[`angel_client`](https://github.com/angel-dart/client)
|
||||
provides a facility for this, which works perfectly with the default callback provided
|
||||
in this package.
|
||||
[`angel3_client`](https://github.com/dukefirehawk/angel/tree/angel3/packages/client)
|
||||
provides a facility for this, which works perfectly with the default callback provided in this package.
|
||||
|
||||
```dart
|
||||
configureServer(Angel app) async {
|
||||
|
@ -78,7 +78,7 @@ configureServer(Angel app) async {
|
|||
```
|
||||
|
||||
This renders a simple HTML page that fires the user's JWT as a `token` event in `window.opener`.
|
||||
`angel_client` [exposes this as a Stream](https://github.com/dukefirehawk/angel/tree/angel3/packages/client#authentication):
|
||||
`angel3_client` [exposes this as a Stream](https://github.com/dukefirehawk/angel/tree/angel3/packages/client#authentication):
|
||||
|
||||
```dart
|
||||
app.authenticateViaPopup('/auth/google').listen((jwt) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:collection';
|
|||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
/// Calls [BASE64URL], but also works for strings with lengths
|
||||
/// that are *not* multiples of 4.
|
||||
|
@ -25,6 +26,8 @@ String decodeBase64(String str) {
|
|||
}
|
||||
|
||||
class AuthToken {
|
||||
static final _log = Logger('AuthToken');
|
||||
|
||||
final SplayTreeMap<String, String> _header =
|
||||
SplayTreeMap.from({'alg': 'HS256', 'typ': 'JWT'});
|
||||
|
||||
|
@ -70,6 +73,7 @@ class AuthToken {
|
|||
var split = jwt.split('.');
|
||||
|
||||
if (split.length != 3) {
|
||||
_log.severe('Invalid JWT');
|
||||
throw AngelHttpException.notAuthenticated(message: 'Invalid JWT.');
|
||||
}
|
||||
|
||||
|
@ -81,6 +85,7 @@ class AuthToken {
|
|||
var split = jwt.split('.');
|
||||
|
||||
if (split.length != 3) {
|
||||
_log.severe('Invalid JWT');
|
||||
throw AngelHttpException.notAuthenticated(message: 'Invalid JWT.');
|
||||
}
|
||||
|
||||
|
@ -90,6 +95,7 @@ class AuthToken {
|
|||
var signature = base64Url.encode(hmac.convert(data.codeUnits).bytes);
|
||||
|
||||
if (signature != split[2]) {
|
||||
_log.severe('JWT payload does not match hashed version');
|
||||
throw AngelHttpException.notAuthenticated(
|
||||
message: 'JWT payload does not match hashed version.');
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import 'package:charcode/ascii.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:quiver/core.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
/// A common class containing parsing and validation logic for third-party authentication configuration.
|
||||
class ExternalAuthOptions {
|
||||
static final _log = Logger('VirtualDirectory');
|
||||
|
||||
/// The user's identifier, otherwise known as an "application id".
|
||||
final String clientId;
|
||||
|
||||
|
@ -31,6 +34,7 @@ class ExternalAuthOptions {
|
|||
return ExternalAuthOptions._(
|
||||
clientId, clientSecret, redirectUri, scopes.toSet());
|
||||
} else {
|
||||
_log.severe('RedirectUri is not valid');
|
||||
throw ArgumentError.value(
|
||||
redirectUri, 'redirectUri', 'must be a String or Uri');
|
||||
}
|
||||
|
@ -46,6 +50,7 @@ class ExternalAuthOptions {
|
|||
var clientId = map['client_id'];
|
||||
var clientSecret = map['client_secret'];
|
||||
if (clientId == null || clientSecret == null) {
|
||||
_log.severe('clientId or clientSecret is null');
|
||||
throw ArgumentError('Invalid clientId and/or clientSecret');
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:angel3_framework/angel3_framework.dart';
|
|||
|
||||
/// Forces Basic authentication over the requested resource, with the given [realm] name, if no JWT is present.
|
||||
///
|
||||
/// [realm] defaults to `'angel_auth'`.
|
||||
/// [realm] defaults to `'angel3_auth'`.
|
||||
RequestHandler forceBasicAuth<User>({String? realm}) {
|
||||
return (RequestContext req, ResponseContext res) async {
|
||||
if (req.container != null) {
|
||||
|
|
|
@ -11,6 +11,8 @@ import 'strategy.dart';
|
|||
|
||||
/// Handles authentication within an Angel application.
|
||||
class AngelAuth<User> {
|
||||
final _log = Logger('AngelAuth');
|
||||
|
||||
late Hmac _hs256;
|
||||
late int _jwtLifeSpan;
|
||||
final StreamController<User> _onLogin = StreamController<User>(),
|
||||
|
@ -43,8 +45,6 @@ class AngelAuth<User> {
|
|||
/// 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;
|
||||
|
||||
|
@ -109,7 +109,7 @@ class AngelAuth<User> {
|
|||
}
|
||||
*/
|
||||
if (app.container == null) {
|
||||
log.severe('Angel.container is null.');
|
||||
_log.severe('Angel3 container is null');
|
||||
throw StateError(
|
||||
'Angel.container is null. All authentication will fail.');
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ class AngelAuth<User> {
|
|||
var req = container.make<RequestContext>();
|
||||
var res = container.make<ResponseContext>();
|
||||
if (req == null || res == null) {
|
||||
_log.severe('RequestContext or responseContext is null');
|
||||
throw AngelHttpException.forbidden();
|
||||
}
|
||||
|
||||
|
@ -133,6 +134,7 @@ class AngelAuth<User> {
|
|||
if (result != null) {
|
||||
return result;
|
||||
} else {
|
||||
_log.severe('JWT is null');
|
||||
throw AngelHttpException.forbidden();
|
||||
}
|
||||
});
|
||||
|
@ -148,7 +150,7 @@ class AngelAuth<User> {
|
|||
});
|
||||
}
|
||||
|
||||
app.post(reviveTokenEndpoint, reviveJwt);
|
||||
app.post(reviveTokenEndpoint, _reviveJwt);
|
||||
|
||||
app.shutdownHooks.add((_) {
|
||||
_onLogin.close();
|
||||
|
@ -158,7 +160,7 @@ class AngelAuth<User> {
|
|||
void _apply(
|
||||
RequestContext req, ResponseContext res, AuthToken token, User user) {
|
||||
if (req.container == null) {
|
||||
log.severe('RequestContext.container is null.');
|
||||
_log.severe('RequestContext.container is null');
|
||||
throw StateError(
|
||||
'RequestContext.container is not set. All authentication will fail.');
|
||||
}
|
||||
|
@ -199,9 +201,9 @@ class AngelAuth<User> {
|
|||
/// }
|
||||
/// ```
|
||||
@deprecated
|
||||
Future decodeJwtOld(RequestContext req, ResponseContext res) async {
|
||||
Future decodeJwt(RequestContext req, ResponseContext res) async {
|
||||
if (req.method == 'POST' && req.path == reviveTokenEndpoint) {
|
||||
return await reviveJwt(req, res);
|
||||
return await _reviveJwt(req, res);
|
||||
} else {
|
||||
await _decodeJwt(req, res);
|
||||
return true;
|
||||
|
@ -217,6 +219,7 @@ class AngelAuth<User> {
|
|||
|
||||
if (enforceIp) {
|
||||
if (req.ip != token.ipAddress) {
|
||||
_log.severe('JWT cannot be accessed from this IP address');
|
||||
throw AngelHttpException.forbidden(
|
||||
message: 'JWT cannot be accessed from this IP address.');
|
||||
}
|
||||
|
@ -227,6 +230,7 @@ class AngelAuth<User> {
|
|||
token.issuedAt.add(Duration(milliseconds: token.lifeSpan.toInt()));
|
||||
|
||||
if (!expiry.isAfter(DateTime.now())) {
|
||||
_log.severe('Expired JWT');
|
||||
throw AngelHttpException.forbidden(message: 'Expired JWT.');
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +254,7 @@ class AngelAuth<User> {
|
|||
}
|
||||
}
|
||||
|
||||
log.info('RequestContext.headers is null');
|
||||
_log.info('RequestContext.headers is null');
|
||||
} else if (allowCookie &&
|
||||
req.cookies.any((cookie) => cookie.name == 'token')) {
|
||||
return req.cookies.firstWhere((cookie) => cookie.name == 'token').value;
|
||||
|
@ -289,7 +293,7 @@ class AngelAuth<User> {
|
|||
}
|
||||
|
||||
/// Attempts to revive an expired (or still alive) JWT.
|
||||
Future<Map<String, dynamic>> reviveJwt(
|
||||
Future<Map<String, dynamic>> _reviveJwt(
|
||||
RequestContext req, ResponseContext res) async {
|
||||
try {
|
||||
var jwt = getJwt(req);
|
||||
|
@ -298,12 +302,15 @@ class AngelAuth<User> {
|
|||
var body = await req.parseBody().then((_) => req.bodyAsMap);
|
||||
jwt = body['token']?.toString();
|
||||
}
|
||||
|
||||
if (jwt == null) {
|
||||
_log.severe('No JWT provided');
|
||||
throw AngelHttpException.forbidden(message: 'No JWT provided');
|
||||
} else {
|
||||
var token = AuthToken.validate(jwt, _hs256);
|
||||
if (enforceIp) {
|
||||
if (req.ip != token.ipAddress) {
|
||||
_log.severe('WT cannot be accessed from this IP address');
|
||||
throw AngelHttpException.forbidden(
|
||||
message: 'JWT cannot be accessed from this IP address.');
|
||||
}
|
||||
|
@ -329,7 +336,10 @@ class AngelAuth<User> {
|
|||
return {'data': data, 'token': token.serialize(_hs256)};
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is AngelHttpException) rethrow;
|
||||
if (e is AngelHttpException) {
|
||||
rethrow;
|
||||
}
|
||||
_log.severe('Malformed JWT');
|
||||
throw AngelHttpException.badRequest(message: 'Malformed JWT');
|
||||
}
|
||||
}
|
||||
|
@ -385,13 +395,13 @@ class AngelAuth<User> {
|
|||
userId: userId, lifeSpan: _jwtLifeSpan, ipAddress: req.ip);
|
||||
var jwt = token.serialize(_hs256);
|
||||
|
||||
if (options?.tokenCallback != null) {
|
||||
if (options != null && options.tokenCallback != null) {
|
||||
var hasUser = reqContainer?.has<User>() ?? false;
|
||||
if (!hasUser) {
|
||||
reqContainer?.registerSingleton<User>(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);
|
||||
}
|
||||
|
@ -402,14 +412,16 @@ class AngelAuth<User> {
|
|||
_addProtectedCookie(res, 'token', jwt);
|
||||
}
|
||||
|
||||
if (options?.callback != null) {
|
||||
return await options?.callback!(req, res, jwt);
|
||||
// Options is not null
|
||||
if (options != null) {
|
||||
if (options.callback != null) {
|
||||
return await options.callback!(req, res, jwt);
|
||||
}
|
||||
|
||||
if (options?.successRedirect?.isNotEmpty == true) {
|
||||
await res.redirect(options?.successRedirect);
|
||||
if (options.successRedirect?.isNotEmpty == true) {
|
||||
await res.redirect(options.successRedirect);
|
||||
return false;
|
||||
} else if (options?.canRespondWithJson != false &&
|
||||
} else if (options.canRespondWithJson &&
|
||||
req.accepts('application/json')) {
|
||||
var user = hasExisting
|
||||
? result
|
||||
|
@ -417,6 +429,14 @@ class AngelAuth<User> {
|
|||
_onLogin.add(user);
|
||||
return {'data': user, 'token': jwt};
|
||||
}
|
||||
// Options is null
|
||||
} else if (hasExisting && req.accepts('application/json')) {
|
||||
var user = hasExisting
|
||||
? result
|
||||
: await deserializer((await serializer(result)) as Object);
|
||||
_onLogin.add(user);
|
||||
return {'data': user, 'token': jwt};
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
@ -426,8 +446,8 @@ class AngelAuth<User> {
|
|||
res.statusCode == 302 ||
|
||||
res.headers.containsKey('location')) {
|
||||
return false;
|
||||
} else if (options?.failureRedirect != null) {
|
||||
await res.redirect(options!.failureRedirect);
|
||||
} else if (options != null && options.failureRedirect != null) {
|
||||
await res.redirect(options.failureRedirect);
|
||||
return false;
|
||||
} else {
|
||||
throw AngelHttpException.notAuthenticated();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:angel3_framework/angel3_framework.dart';
|
||||
import '../options.dart';
|
||||
import '../strategy.dart';
|
||||
|
@ -12,6 +13,8 @@ typedef LocalAuthVerifier<User> = FutureOr<User?> Function(
|
|||
String? username, String? password);
|
||||
|
||||
class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
||||
final _log = Logger('LocalAuthStrategy');
|
||||
|
||||
final RegExp _rgxBasic = RegExp(r'^Basic (.+)$', caseSensitive: false);
|
||||
final RegExp _rgxUsrPass = RegExp(r'^([^:]+):(.+)$');
|
||||
|
||||
|
@ -29,7 +32,9 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
|||
this.invalidMessage = 'Please provide a valid username and password.',
|
||||
this.allowBasic = true,
|
||||
this.forceBasic = false,
|
||||
this.realm = 'Authentication is required.'});
|
||||
this.realm = 'Authentication is required.'}) {
|
||||
_log.info('Using LocalAuthStrategy');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<User?> authenticate(RequestContext req, ResponseContext res,
|
||||
|
@ -51,6 +56,7 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
|||
verificationResult =
|
||||
await verifier(usrPassMatch.group(1), usrPassMatch.group(2));
|
||||
} else {
|
||||
_log.severe('Bad request: $invalidMessage');
|
||||
throw AngelHttpException.badRequest(errors: [invalidMessage]);
|
||||
}
|
||||
|
||||
|
@ -96,6 +102,7 @@ class LocalAuthStrategy<User> extends AuthStrategy<User> {
|
|||
} else if (verificationResult != false) {
|
||||
return verificationResult;
|
||||
} else {
|
||||
_log.info('Not authenticated');
|
||||
throw AngelHttpException.notAuthenticated();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
name: angel3_auth
|
||||
description: A complete authentication plugin for Angel. Includes support for stateless JWT tokens, Basic Auth, and more.
|
||||
version: 4.0.4
|
||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/auth
|
||||
homepage: https://angel3-framework.web.app/
|
||||
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/auth
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
dependencies:
|
||||
|
@ -12,11 +13,11 @@ dependencies:
|
|||
http_parser: ^4.0.0
|
||||
meta: ^1.3.0
|
||||
quiver: ^3.0.0
|
||||
logging: ^1.0.0
|
||||
dev_dependencies:
|
||||
angel3_container: ^3.0.0
|
||||
http: ^0.13.1
|
||||
io: ^1.0.0
|
||||
logging: ^1.0.0
|
||||
pedantic: ^1.11.0
|
||||
test: ^1.17.4
|
||||
|
|
@ -50,7 +50,7 @@ void main() {
|
|||
|
||||
var oldErrorHandler = app.errorHandler;
|
||||
app.errorHandler = (e, req, res) {
|
||||
app.logger!.severe(e.message, e, e.stackTrace ?? StackTrace.current);
|
||||
app.logger?.severe(e.message, e, e.stackTrace ?? StackTrace.current);
|
||||
return oldErrorHandler(e, req, res);
|
||||
};
|
||||
|
||||
|
@ -69,8 +69,8 @@ void main() {
|
|||
});
|
||||
|
||||
await app
|
||||
.findService('users')!
|
||||
.create({'username': 'jdoe1', 'password': 'password'});
|
||||
.findService('users')
|
||||
?.create({'username': 'jdoe1', 'password': 'password'});
|
||||
|
||||
auth = AngelAuth<User>(
|
||||
serializer: (u) => u.id,
|
||||
|
@ -84,11 +84,11 @@ void main() {
|
|||
|
||||
auth.strategies['local'] = LocalAuthStrategy((username, password) async {
|
||||
var users = await app
|
||||
.findService('users')!
|
||||
.index()
|
||||
.findService('users')
|
||||
?.index()
|
||||
.then((it) => it.map<User>((m) => User.parse(m as Map)).toList());
|
||||
|
||||
var result = users.firstWhereOrNull(
|
||||
var result = users?.firstWhereOrNull(
|
||||
(user) => user.username == username && user.password == password);
|
||||
|
||||
return Future.value(result);
|
||||
|
@ -144,6 +144,7 @@ void main() {
|
|||
body: {'username': 'jdoe1', 'password': 'password'},
|
||||
headers: {'accept': 'application/json'});
|
||||
print('Response: ${response.body}');
|
||||
print(response.headers);
|
||||
expect(json.decode(response.body)['data']['username'], equals('foo'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ void main() async {
|
|||
app.get('/hello', (req, res) {
|
||||
// => 'Woo auth'
|
||||
return 'Woo auth';
|
||||
}); //, middleware: [auth.authenticate('local')]);
|
||||
}, 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: [
|
||||
|
@ -54,8 +54,11 @@ void main() async {
|
|||
]);
|
||||
app.get('/failure', (req, res) => 'nope');
|
||||
|
||||
app.logger = Logger('angel_auth')
|
||||
app.logger = Logger('local_test')
|
||||
..onRecord.listen((rec) {
|
||||
print(
|
||||
'${rec.time}: ${rec.level.name}: ${rec.loggerName}: ${rec.message}');
|
||||
|
||||
if (rec.error != null) {
|
||||
print(rec.error);
|
||||
print(rec.stackTrace);
|
||||
|
@ -96,12 +99,23 @@ void main() async {
|
|||
var response = await client.post(Uri.parse('$url/login'),
|
||||
body: json.encode(postData),
|
||||
headers: {'content-type': 'application/json'});
|
||||
print('Login response: ${response.body}');
|
||||
print('Status Code: ${response.statusCode}');
|
||||
print(response.headers);
|
||||
print(response.body);
|
||||
expect(response.headers['location'], equals('/failure'));
|
||||
expect(response.statusCode, equals(401));
|
||||
});
|
||||
|
||||
test('allow basic', () async {
|
||||
test('basic auth without authorization', () async {
|
||||
var response = await client.get(Uri.parse('$url/hello'));
|
||||
print('Status Code: ${response.statusCode}');
|
||||
print(response.headers);
|
||||
print(response.body);
|
||||
expect(response.statusCode, equals(401));
|
||||
});
|
||||
|
||||
//test('allow basic', () async {
|
||||
test('basic auth with authorization', () async {
|
||||
var authString = base64.encode('username:password'.runes.toList());
|
||||
var response = await client.get(Uri.parse('$url/hello'),
|
||||
headers: {'authorization': 'Basic $authString'});
|
||||
|
|
Loading…
Reference in a new issue