platform/packages/auth/lib/src/auth_token.dart

137 lines
3.5 KiB
Dart
Raw Normal View History

2016-09-21 23:09:23 +00:00
import 'dart:collection';
2021-05-14 11:09:48 +00:00
import 'package:angel3_framework/angel3_framework.dart';
2018-09-11 22:14:33 +00:00
import 'dart:convert';
2016-09-21 23:09:23 +00:00
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!"';
}
2018-06-27 16:36:31 +00:00
return utf8.decode(base64Url.decode(output));
2017-01-20 23:15:21 +00:00
}
2016-09-21 23:09:23 +00:00
class AuthToken {
final SplayTreeMap<String, String> _header =
2021-03-20 23:51:20 +00:00
SplayTreeMap.from({'alg': 'HS256', 'typ': 'JWT'});
2016-09-21 23:09:23 +00:00
2021-03-20 23:51:20 +00:00
String? ipAddress;
late DateTime issuedAt;
2021-05-09 11:16:15 +00:00
num lifeSpan;
2016-09-21 23:09:23 +00:00
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,
2019-01-05 23:54:48 +00:00
this.lifeSpan = -1,
2017-01-13 15:50:38 +00:00
this.userId,
2021-03-20 23:51:20 +00:00
DateTime? issuedAt,
2019-01-05 23:54:48 +00:00
Map payload = const {}}) {
2019-04-19 07:50:04 +00:00
this.issuedAt = issuedAt ?? DateTime.now();
2021-05-09 11:16:15 +00:00
this.payload.addAll(payload.keys
.fold({}, ((out, k) => out?..[k.toString()] = payload[k])) ??
{});
/*
2021-04-10 11:23:57 +00:00
this.payload.addAll(payload.keys.fold(
2021-03-20 23:51:20 +00:00
{},
((out, k) => out..[k.toString()] = payload[k])
as Map<String, dynamic>? Function(
Map<String, dynamic>?, dynamic)) ??
{});
2021-05-09 11:16:15 +00:00
*/
2016-09-21 23:09:23 +00:00
}
2018-06-27 16:36:31 +00:00
factory AuthToken.fromJson(String jsons) =>
2019-04-19 07:50:04 +00:00
AuthToken.fromMap(json.decode(jsons) as Map);
2016-09-21 23:09:23 +00:00
factory AuthToken.fromMap(Map data) {
2019-04-19 07:50:04 +00:00
return AuthToken(
2021-03-20 23:51:20 +00:00
ipAddress: data['aud'].toString(),
2021-05-09 11:16:15 +00:00
lifeSpan: data['exp'] as num,
2021-03-20 23:51:20 +00:00
issuedAt: DateTime.parse(data['iat'].toString()),
userId: data['sub'],
2021-05-09 11:16:15 +00:00
payload: data['pld'] as Map);
2016-09-21 23:09:23 +00:00
}
2017-01-20 23:15:21 +00:00
factory AuthToken.parse(String jwt) {
2021-03-20 23:51:20 +00:00
var split = jwt.split('.');
2017-01-20 23:15:21 +00:00
2021-03-20 23:51:20 +00:00
if (split.length != 3) {
throw AngelHttpException.notAuthenticated(message: 'Invalid JWT.');
}
2017-01-20 23:15:21 +00:00
var payloadString = decodeBase64(split[1]);
2019-04-19 07:50:04 +00:00
return AuthToken.fromMap(json.decode(payloadString) as Map);
2017-01-20 23:15:21 +00:00
}
2016-09-21 23:09:23 +00:00
factory AuthToken.validate(String jwt, Hmac hmac) {
2021-03-20 23:51:20 +00:00
var split = jwt.split('.');
2016-09-21 23:09:23 +00:00
2021-03-20 23:51:20 +00:00
if (split.length != 3) {
throw 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]);
2021-03-20 23:51:20 +00:00
var data = split[0] + '.' + split[1];
2018-06-27 16:36:31 +00:00
var signature = base64Url.encode(hmac.convert(data.codeUnits).bytes);
2016-09-21 23:09:23 +00:00
2021-03-20 23:51:20 +00:00
if (signature != split[2]) {
2019-04-19 07:50:04 +00:00
throw AngelHttpException.notAuthenticated(
2021-03-20 23:51:20 +00:00
message: 'JWT payload does not match hashed version.');
}
2016-09-21 23:09:23 +00:00
2019-04-19 07:50:04 +00:00
return AuthToken.fromMap(json.decode(payloadString) as Map);
2016-09-21 23:09:23 +00:00
}
String serialize(Hmac hmac) {
2018-06-27 16:36:31 +00:00
var headerString = base64Url.encode(json.encode(_header).codeUnits);
var payloadString = base64Url.encode(json.encode(toJson()).codeUnits);
2021-03-20 23:51:20 +00:00
var data = headerString + '.' + payloadString;
2016-09-21 23:09:23 +00:00
var signature = hmac.convert(data.codeUnits).bytes;
2021-03-20 23:51:20 +00:00
return data + '.' + base64Url.encode(signature);
2016-09-21 23:09:23 +00:00
}
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);
});
2019-04-19 07:50:04 +00:00
return SplayTreeMap.from(data);
2017-01-13 15:50:38 +00:00
}
2021-03-20 23:51:20 +00:00
dynamic _splay(value) {
2017-01-13 15:50:38 +00:00
if (value is Iterable) {
return value.map(_splay).toList();
2021-03-20 23:51:20 +00:00
} else if (value is Map) {
2017-01-13 15:50:38 +00:00
return _splayify(value);
2021-03-20 23:51:20 +00:00
} else {
2017-01-13 15:50:38 +00:00
return value;
2021-03-20 23:51:20 +00:00
}
2017-01-13 15:50:38 +00:00
}