platform/lib/src/auth_token.dart

123 lines
3.1 KiB
Dart
Raw Normal View History

2016-09-21 23:09:23 +00:00
import 'dart:collection';
import 'dart:convert';
import 'package:angel_framework/angel_framework.dart';
import 'package:crypto/crypto.dart';
2017-01-20 23:15:21 +00:00
/// Calls [BASE64URL], but also works for strings with lengths
/// that are *not* multiples of 4.
String decodeBase64(String str) {
var output = str.replaceAll('-', '+').replaceAll('_', '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw 'Illegal base64url string!"';
}
return UTF8.decode(BASE64URL.decode(output));
}
2016-09-21 23:09:23 +00:00
class AuthToken {
final SplayTreeMap<String, String> _header =
new SplayTreeMap.from({"alg": "HS256", "typ": "JWT"});
String ipAddress;
DateTime issuedAt;
num lifeSpan;
var userId;
2017-01-13 15:50:38 +00:00
Map<String, dynamic> payload = {};
2016-09-21 23:09:23 +00:00
AuthToken(
2017-01-13 15:50:38 +00:00
{this.ipAddress,
this.lifeSpan: -1,
this.userId,
DateTime issuedAt,
Map<String, dynamic> payload: const {}}) {
2016-09-21 23:09:23 +00:00
this.issuedAt = issuedAt ?? new DateTime.now();
2017-01-13 15:50:38 +00:00
this.payload.addAll(payload ?? {});
2016-09-21 23:09:23 +00:00
}
2017-01-13 15:50:38 +00:00
factory AuthToken.fromJson(String json) =>
new AuthToken.fromMap(JSON.decode(json));
2016-09-21 23:09:23 +00:00
factory AuthToken.fromMap(Map data) {
return new AuthToken(
ipAddress: data["aud"],
lifeSpan: data["exp"],
issuedAt: DateTime.parse(data["iat"]),
2017-01-13 15:50:38 +00:00
userId: data["sub"],
payload: data["pld"] ?? {});
2016-09-21 23:09:23 +00:00
}
2017-01-20 23:15:21 +00:00
factory AuthToken.parse(String jwt) {
var split = jwt.split(".");
if (split.length != 3)
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
var payloadString = decodeBase64(split[1]);
return new AuthToken.fromMap(JSON.decode(payloadString));
}
2016-09-21 23:09:23 +00:00
factory AuthToken.validate(String jwt, Hmac hmac) {
var split = jwt.split(".");
if (split.length != 3)
2017-01-13 15:50:38 +00:00
throw new AngelHttpException.notAuthenticated(message: "Invalid JWT.");
2016-09-21 23:09:23 +00:00
2017-01-20 23:15:21 +00:00
// var headerString = decodeBase64(split[0]);
var payloadString = decodeBase64(split[1]);
2016-09-21 23:09:23 +00:00
var data = split[0] + "." + split[1];
var signature = BASE64URL.encode(hmac.convert(data.codeUnits).bytes);
if (signature != split[2])
2017-01-13 15:50:38 +00:00
throw new AngelHttpException.notAuthenticated(
2016-09-21 23:09:23 +00:00
message: "JWT payload does not match hashed version.");
return new AuthToken.fromMap(JSON.decode(payloadString));
}
String serialize(Hmac hmac) {
var headerString = BASE64URL.encode(JSON.encode(_header).codeUnits);
2017-01-13 15:50:38 +00:00
var payloadString = BASE64URL.encode(JSON.encode(toJson()).codeUnits);
2016-09-21 23:09:23 +00:00
var data = headerString + "." + payloadString;
var signature = hmac.convert(data.codeUnits).bytes;
return data + "." + BASE64URL.encode(signature);
}
Map toJson() {
2017-01-13 15:50:38 +00:00
return _splayify({
2016-09-21 23:09:23 +00:00
"iss": "angel_auth",
"aud": ipAddress,
"exp": lifeSpan,
"iat": issuedAt.toIso8601String(),
2017-01-13 15:50:38 +00:00
"sub": userId,
"pld": _splayify(payload)
2016-09-21 23:09:23 +00:00
});
}
}
2017-01-13 15:50:38 +00:00
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;
}