diff --git a/lib/src/jwt.dart b/lib/src/jwt.dart index b23f2e8..2f607e8 100644 --- a/lib/src/jwt.dart +++ b/lib/src/jwt.dart @@ -2,12 +2,13 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:clock/clock.dart'; import 'package:collection/collection.dart'; import 'algorithms.dart'; import 'exceptions.dart'; -import 'keys.dart'; import 'helpers.dart'; +import 'keys.dart'; class JWT { /// Verify a token. @@ -64,7 +65,7 @@ class JWT { final exp = DateTime.fromMillisecondsSinceEpoch( payload['exp'] * 1000, ); - if (exp.isBefore(DateTime.now())) { + if (exp.isBefore(clock.now())) { throw JWTExpiredException(); } } @@ -74,7 +75,7 @@ class JWT { final nbf = DateTime.fromMillisecondsSinceEpoch( payload['nbf'] * 1000, ); - if (nbf.isAfter(DateTime.now())) { + if (nbf.isAfter(clock.now())) { throw JWTNotActiveException(); } } @@ -87,7 +88,7 @@ class JWT { final iat = DateTime.fromMillisecondsSinceEpoch( payload['iat'] * 1000, ); - if (!iat.isAtSameMomentAs(DateTime.now())) { + if (!iat.isAtSameMomentAs(clock.now())) { throw JWTInvalidException('invalid issue at'); } } @@ -265,12 +266,12 @@ class JWT { try { payload = Map.from(payload); - if (!noIssueAt) payload['iat'] = secondsSinceEpoch(DateTime.now()); + if (!noIssueAt) payload['iat'] = secondsSinceEpoch(clock.now()); if (expiresIn != null) { - payload['exp'] = secondsSinceEpoch(DateTime.now().add(expiresIn)); + payload['exp'] = secondsSinceEpoch(clock.now().add(expiresIn)); } if (notBefore != null) { - payload['nbf'] = secondsSinceEpoch(DateTime.now().add(notBefore)); + payload['nbf'] = secondsSinceEpoch(clock.now().add(notBefore)); } if (audience != null) payload['aud'] = audience!.toJson(); if (subject != null) payload['sub'] = subject; diff --git a/pubspec.yaml b/pubspec.yaml index d609838..be72b9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,9 @@ dependencies: convert: ^3.1.1 collection: ^1.17.1 ed25519_edwards: ^0.3.1 + clock: ^1.1.1 dev_dependencies: + fake_async: ^1.3.1 lints: ^2.1.1 test: ^1.24.6 diff --git a/test/header_test.dart b/test/header_test.dart new file mode 100644 index 0000000..dd85860 --- /dev/null +++ b/test/header_test.dart @@ -0,0 +1,50 @@ +import 'package:clock/clock.dart'; +import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart'; +import 'package:fake_async/fake_async.dart'; +import 'package:test/test.dart'; + +final hsKey = SecretKey('secret passphrase'); + +void main() { + group("JWT Header", () { + //--------------------// + // Expiration (exp) // + //--------------------// + group('exp', () { + test('should be expired', () { + final duration = Duration(hours: 1); + final token = JWT({'foo': 'bar'}).sign(hsKey, expiresIn: duration); + + fakeAsync((async) { + async.elapse(duration); + expect( + () => JWT.verify(token, hsKey), + throwsA(isA()), + ); + }); + }); + + test('should be still valid', () { + withClock( + Clock.fixed(DateTime(2023)), + () { + final duration = Duration(hours: 1); + final token = JWT({'foo': 'bar'}).sign(hsKey, expiresIn: duration); + + fakeAsync((async) { + async.elapse(Duration(minutes: 30)); + expect( + JWT.verify(token, hsKey).payload, + equals({ + 'foo': 'bar', + 'iat': 1672527600, + 'exp': 1672531200, + }), + ); + }); + }, + ); + }); + }); + }); +}