This commit is contained in:
thosakwe 2016-10-08 07:39:39 -04:00
parent abebd65e7d
commit afb554fba0
6 changed files with 135 additions and 12 deletions

17
.idea/angel_auth.iml Normal file
View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/packages" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="application" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

38
.idea/misc.xml Normal file
View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
<State>
<id>General</id>
</State>
<State>
<id>XPath</id>
</State>
</expanded-state>
<selected-state>
<State>
<id>AngularJS</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
</project>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/angel_auth.iml" filepath="$PROJECT_DIR$/.idea/angel_auth.iml" />
</modules>
</component>
</project>

View file

@ -18,13 +18,18 @@ class AngelAuth extends AngelPlugin {
final RegExp _rgxBearer = new RegExp(r"^Bearer"); final RegExp _rgxBearer = new RegExp(r"^Bearer");
RequireAuthorizationMiddleware _requireAuth = RequireAuthorizationMiddleware _requireAuth =
new RequireAuthorizationMiddleware(); new RequireAuthorizationMiddleware();
String middlewareName;
bool debug;
bool enforceIp; bool enforceIp;
String reviveTokenEndpoint; String reviveTokenEndpoint;
List<AuthStrategy> strategies = []; List<AuthStrategy> strategies = [];
UserSerializer serializer; UserSerializer serializer;
UserDeserializer deserializer; UserDeserializer deserializer;
String _randomString({int length: 32, String validChars: "ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}) { String _randomString(
{int length: 32,
String validChars:
"ABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"}) {
var chars = <int>[]; var chars = <int>[];
while (chars.length < length) chars.add(_random.nextInt(validChars.length)); while (chars.length < length) chars.add(_random.nextInt(validChars.length));
@ -32,7 +37,14 @@ class AngelAuth extends AngelPlugin {
return new String.fromCharCodes(chars); return new String.fromCharCodes(chars);
} }
AngelAuth({String jwtKey, num jwtLifeSpan, this.enforceIp, this.reviveTokenEndpoint: "/auth/token"}) : super() { AngelAuth(
{String jwtKey,
num jwtLifeSpan,
this.debug: false,
this.enforceIp: true,
this.middlewareName: 'auth',
this.reviveTokenEndpoint: "/auth/token"})
: super() {
_hs256 = new Hmac(sha256, (jwtKey ?? _randomString()).codeUnits); _hs256 = new Hmac(sha256, (jwtKey ?? _randomString()).codeUnits);
_jwtLifeSpan = jwtLifeSpan ?? -1; _jwtLifeSpan = jwtLifeSpan ?? -1;
} }
@ -43,7 +55,7 @@ class AngelAuth extends AngelPlugin {
if (runtimeType != AngelAuth) app.container.singleton(this, as: AngelAuth); if (runtimeType != AngelAuth) app.container.singleton(this, as: AngelAuth);
app.before.add(_decodeJwt); app.before.add(_decodeJwt);
app.registerMiddleware('auth', _requireAuth); app.registerMiddleware(middlewareName, _requireAuth);
if (reviveTokenEndpoint != null) { if (reviveTokenEndpoint != null) {
app.post(reviveTokenEndpoint, _reviveJwt); app.post(reviveTokenEndpoint, _reviveJwt);
@ -51,9 +63,13 @@ class AngelAuth extends AngelPlugin {
} }
_decodeJwt(RequestContext req, ResponseContext res) async { _decodeJwt(RequestContext req, ResponseContext res) async {
if (req.path == reviveTokenEndpoint) { if (req.method == "POST" && req.path == reviveTokenEndpoint) {
// Shouldn't block invalid JWT if we are reviving it // Shouldn't block invalid JWT if we are reviving it
return true;
if (debug)
print('Token revival endpoint accessed.');
return await _reviveJwt(req, res);
} }
String jwt = _getJwt(req); String jwt = _getJwt(req);
@ -80,11 +96,22 @@ class AngelAuth extends AngelPlugin {
return true; return true;
} }
_getJwt(RequestContext req) { _getJwt(RequestContext req) {
if (debug) {
print('Attempting to parse JWT');
}
if (req.headers.value("Authorization") != null) { if (req.headers.value("Authorization") != null) {
return req.headers.value("Authorization").replaceAll(_rgxBearer, "").trim(); if (debug) {
print('Found Auth header');
}
return req.headers
.value("Authorization")
.replaceAll(_rgxBearer, "")
.trim();
} else if (req.cookies.any((cookie) => cookie.name == "token")) { } else if (req.cookies.any((cookie) => cookie.name == "token")) {
print('Request has "token" cookie...');
return req.cookies.firstWhere((cookie) => cookie.name == "token").value; return req.cookies.firstWhere((cookie) => cookie.name == "token").value;
} }
@ -93,33 +120,64 @@ class AngelAuth extends AngelPlugin {
_reviveJwt(RequestContext req, ResponseContext res) async { _reviveJwt(RequestContext req, ResponseContext res) async {
try { try {
if (debug)
print('Attempting to revive JWT...');
var jwt = _getJwt(req); var jwt = _getJwt(req);
if (debug)
print('Found JWT: $jwt');
if (jwt == null) { if (jwt == null) {
throw new AngelHttpException.Forbidden(message: "No JWT provided"); throw new AngelHttpException.Forbidden(message: "No JWT provided");
} else { } else {
var token = new AuthToken.validate(jwt, _hs256); var token = new AuthToken.validate(jwt, _hs256);
if (debug)
print('Validated and deserialized: $token');
if (enforceIp) { if (enforceIp) {
if (debug)
print('Token IP: ${token.ipAddress}. Current request sent from: ${req.ip}');
if (req.ip != token.ipAddress) if (req.ip != token.ipAddress)
throw new AngelHttpException.Forbidden( throw new AngelHttpException.Forbidden(
message: "JWT cannot be accessed from this IP address."); message: "JWT cannot be accessed from this IP address.");
} }
if (token.lifeSpan > -1) { if (token.lifeSpan > -1) {
if (debug) {
print('Checking if token has expired... Life span is ${token.lifeSpan}');
}
token.issuedAt.add(new Duration(milliseconds: token.lifeSpan)); token.issuedAt.add(new Duration(milliseconds: token.lifeSpan));
if (!token.issuedAt.isAfter(new DateTime.now())) { if (!token.issuedAt.isAfter(new DateTime.now())) {
print('Token has indeed expired! Resetting assignment date to current timestamp...');
// Extend its lifespan by changing iat // Extend its lifespan by changing iat
token.issuedAt = new DateTime.now(); token.issuedAt = new DateTime.now();
} else if (debug) {
print('Token has not expired yet.');
} }
} else if(debug) {
print('This token never expires, so it is still valid.');
} }
if (debug) {
print('Final, valid token: ${token.toJson()}');
}
res.cookies.add(new Cookie('token', token.serialize(_hs256)));
return token.toJson(); return token.toJson();
} }
} catch(e) { } catch (e, st) {
if (e is AngelHttpException) if (debug) {
rethrow; print('An error occurred while reviving this token.');
print(e);
print(st);
}
if (e is AngelHttpException) rethrow;
throw new AngelHttpException.BadRequest(message: "Malformed JWT"); throw new AngelHttpException.BadRequest(message: "Malformed JWT");
} }
} }
@ -144,7 +202,8 @@ class AngelAuth extends AngelPlugin {
req.headers.value("accept").contains("*/*") || req.headers.value("accept").contains("*/*") ||
req.headers.value("accept").contains("application/*"))) { req.headers.value("accept").contains("application/*"))) {
return {"data": result, "token": jwt}; return {"data": result, "token": jwt};
} else if (options != null && options.successRedirect != null && } else if (options != null &&
options.successRedirect != null &&
options.successRedirect.isNotEmpty) { options.successRedirect.isNotEmpty) {
return res.redirect(options.successRedirect, code: HttpStatus.OK); return res.redirect(options.successRedirect, code: HttpStatus.OK);
} }

View file

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

1
test/packages Symbolic link
View file

@ -0,0 +1 @@
../packages