This commit is contained in:
thosakwe 2017-01-13 10:50:38 -05:00
parent ee72bf748c
commit 771a4e8a5c
8 changed files with 72 additions and 25 deletions

View file

@ -1,6 +1,6 @@
# angel_auth # angel_auth
[![version 1.1.0-dev+16](https://img.shields.io/badge/version-1.1.0--dev+16-red.svg)](https://pub.dartlang.org/packages/angel_auth) [![version 1.1.0-dev+17](https://img.shields.io/badge/version-1.1.0--dev+17-red.svg)](https://pub.dartlang.org/packages/angel_auth)
![build status](https://travis-ci.org/angel-dart/auth.svg?branch=master) ![build status](https://travis-ci.org/angel-dart/auth.svg?branch=master)
A complete authentication plugin for Angel. Inspired by Passport. A complete authentication plugin for Angel. Inspired by Passport.

View file

@ -11,27 +11,35 @@ class AuthToken {
DateTime issuedAt; DateTime issuedAt;
num lifeSpan; num lifeSpan;
var userId; var userId;
Map<String, dynamic> payload = {};
AuthToken( AuthToken(
{this.ipAddress, this.lifeSpan: -1, this.userId, DateTime issuedAt}) { {this.ipAddress,
this.lifeSpan: -1,
this.userId,
DateTime issuedAt,
Map<String, dynamic> payload: const {}}) {
this.issuedAt = issuedAt ?? new DateTime.now(); this.issuedAt = issuedAt ?? new DateTime.now();
this.payload.addAll(payload ?? {});
} }
factory AuthToken.fromJson(String json) => new AuthToken.fromMap(JSON.decode(json)); factory AuthToken.fromJson(String json) =>
new AuthToken.fromMap(JSON.decode(json));
factory AuthToken.fromMap(Map data) { factory AuthToken.fromMap(Map data) {
return new AuthToken( return new AuthToken(
ipAddress: data["aud"], ipAddress: data["aud"],
lifeSpan: data["exp"], lifeSpan: data["exp"],
issuedAt: DateTime.parse(data["iat"]), issuedAt: DateTime.parse(data["iat"]),
userId: data["sub"]); userId: data["sub"],
payload: data["pld"] ?? {});
} }
factory AuthToken.validate(String jwt, Hmac hmac) { factory AuthToken.validate(String jwt, Hmac hmac) {
var split = jwt.split("."); var split = jwt.split(".");
if (split.length != 3) if (split.length != 3)
throw new AngelHttpException.NotAuthenticated(message: "Invalid JWT."); throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
// var headerString = new String.fromCharCodes(BASE64URL.decode(split[0])); // var headerString = new String.fromCharCodes(BASE64URL.decode(split[0]));
var payloadString = new String.fromCharCodes(BASE64URL.decode(split[1])); var payloadString = new String.fromCharCodes(BASE64URL.decode(split[1]));
@ -39,7 +47,7 @@ class AuthToken {
var signature = BASE64URL.encode(hmac.convert(data.codeUnits).bytes); var signature = BASE64URL.encode(hmac.convert(data.codeUnits).bytes);
if (signature != split[2]) if (signature != split[2])
throw new AngelHttpException.NotAuthenticated( throw new AngelHttpException.notAuthenticated(
message: "JWT payload does not match hashed version."); message: "JWT payload does not match hashed version.");
return new AuthToken.fromMap(JSON.decode(payloadString)); return new AuthToken.fromMap(JSON.decode(payloadString));
@ -47,19 +55,37 @@ class AuthToken {
String serialize(Hmac hmac) { String serialize(Hmac hmac) {
var headerString = BASE64URL.encode(JSON.encode(_header).codeUnits); var headerString = BASE64URL.encode(JSON.encode(_header).codeUnits);
var payloadString = BASE64URL.encode(JSON.encode(this).codeUnits); var payloadString = BASE64URL.encode(JSON.encode(toJson()).codeUnits);
var data = headerString + "." + payloadString; var data = headerString + "." + payloadString;
var signature = hmac.convert(data.codeUnits).bytes; var signature = hmac.convert(data.codeUnits).bytes;
return data + "." + BASE64URL.encode(signature); return data + "." + BASE64URL.encode(signature);
} }
Map toJson() { Map toJson() {
return new SplayTreeMap.from({ return _splayify({
"iss": "angel_auth", "iss": "angel_auth",
"aud": ipAddress, "aud": ipAddress,
"exp": lifeSpan, "exp": lifeSpan,
"iat": issuedAt.toIso8601String(), "iat": issuedAt.toIso8601String(),
"sub": userId "sub": userId,
"pld": _splayify(payload)
}); });
} }
} }
SplayTreeMap _splayify(Map map) {
var data = {};
map.forEach((k, v) {
data[k] = _splay(v);
});
return new SplayTreeMap.from(data);
}
_splay(value) {
if (value is Iterable) {
return value.map(_splay).toList();
} else if (value is Map)
return _splayify(value);
else
return value;
}

View file

@ -10,7 +10,7 @@ class RequireAuthorizationMiddleware extends AngelMiddleware {
bool _reject(ResponseContext res) { bool _reject(ResponseContext res) {
if (throwError) { if (throwError) {
res.statusCode = HttpStatus.FORBIDDEN; res.statusCode = HttpStatus.FORBIDDEN;
throw new AngelHttpException.Forbidden(); throw new AngelHttpException.forbidden();
} else } else
return false; return false;
} }

View file

@ -95,7 +95,7 @@ class AngelAuth extends AngelPlugin {
} }
if (req.ip != null && req.ip != token.ipAddress) if (req.ip != null && 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.");
} }
@ -107,7 +107,7 @@ class AngelAuth extends AngelPlugin {
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()))
throw new AngelHttpException.Forbidden(message: "Expired JWT."); throw new AngelHttpException.forbidden(message: "Expired JWT.");
} else if (debug) { } else if (debug) {
print('This token has an infinite life span.'); print('This token has an infinite life span.');
} }
@ -161,7 +161,7 @@ class AngelAuth extends AngelPlugin {
if (debug) print('Found JWT: $jwt'); 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);
@ -174,7 +174,7 @@ class AngelAuth extends AngelPlugin {
.ip}'); .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.");
} }
@ -216,7 +216,7 @@ class AngelAuth extends AngelPlugin {
} }
if (e is AngelHttpException) rethrow; if (e is AngelHttpException) rethrow;
throw new AngelHttpException.BadRequest(message: "Malformed JWT"); throw new AngelHttpException.badRequest(message: "Malformed JWT");
} }
} }
@ -262,7 +262,7 @@ class AngelAuth extends AngelPlugin {
} }
Future authenticationFailure(RequestContext req, ResponseContext res) async { Future authenticationFailure(RequestContext req, ResponseContext res) async {
throw new AngelHttpException.NotAuthenticated(); throw new AngelHttpException.notAuthenticated();
} }
logout([AngelAuthOptions options]) { logout([AngelAuthOptions options]) {

View file

@ -56,7 +56,7 @@ class LocalAuthStrategy extends AuthStrategy {
verificationResult = verificationResult =
await verifier(usrPassMatch.group(1), usrPassMatch.group(2)); await verifier(usrPassMatch.group(1), usrPassMatch.group(2));
} else } else
throw new AngelHttpException.BadRequest(errors: [invalidMessage]); throw new AngelHttpException.badRequest(errors: [invalidMessage]);
} }
} }
@ -86,7 +86,7 @@ class LocalAuthStrategy extends AuthStrategy {
} else if (verificationResult != null && verificationResult != false) { } else if (verificationResult != null && verificationResult != false) {
return verificationResult; return verificationResult;
} else { } else {
throw new AngelHttpException.NotAuthenticated(); throw new AngelHttpException.notAuthenticated();
} }
} }
} }

View file

@ -1,11 +1,13 @@
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+16 version: 1.0.0-dev+17
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
environment:
sdk: ">=1.19.0"
dependencies: dependencies:
angel_framework: ">=1.0.0-dev <2.0.0" angel_framework: ^1.0.0-dev
crypto: ">=2.0.0 <3.0.0" crypto: ^2.0.0
dev_dependencies: dev_dependencies:
http: ">= 0.11.3 < 0.12.0" http: ^0.11.0
test: ">= 0.12.13 < 0.13.0" test: ^0.12.0

View file

@ -12,5 +12,24 @@ main() async {
var parsed = new AuthToken.validate(jwt, hmac); var parsed = new AuthToken.validate(jwt, hmac);
print(parsed.toJson()); print(parsed.toJson());
expect(parsed.toJson()['aud'], equals(token.ipAddress));
expect(parsed.toJson()['sub'], equals(token.userId));
});
test('custom payload', () {
var token =
new AuthToken(ipAddress: "localhost", userId: "thosakwe", payload: {
"foo": "bar",
"baz": {
"one": 1,
"franken": ["stein"]
}
});
var jwt = token.serialize(hmac);
print(jwt);
var parsed = new AuthToken.validate(jwt, hmac);
print(parsed.toJson());
expect(parsed.toJson()['pld'], equals(token.payload));
}); });
} }

View file

@ -43,7 +43,7 @@ main() {
'/login', '/login',
auth.authenticate('local', auth.authenticate('local',
new AngelAuthOptions(callback: (req, res, token) { new AngelAuthOptions(callback: (req, res, token) {
return res res
..write('Hello!') ..write('Hello!')
..end(); ..end();
}))); })));