diff --git a/lib/src/cryptographic.dart b/lib/src/cryptographic.dart index 5d8c800..3d6b455 100644 --- a/lib/src/cryptographic.dart +++ b/lib/src/cryptographic.dart @@ -110,4 +110,35 @@ class Cryptographic { ); return words.join(' '); } + + /// Returns JWT-like token structure. + /// + /// [payload] is optional JWT payload (claims). If none provided default + /// payload is used. + /// + /// [algorithm] is optional JWT algorithm (default is "HS256"). + /// + /// Example: + /// ```dart + /// Cryptographic().jwt(); // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI3NjZmZGFjNi0yNjU3LTQ1NTQtYTNiMy01MGI2ODIyOTRmNmUiLCJuYW1lIjoiVGVzdCBVc2VyIiwiaWF0IjoxNzY3OTY5NDM2LCJleHAiOjE3Njc5NzMwMzZ9.OLkALspgIX982pMKceErTpSOwNLSv-Uhtyu-ZO7yi5U" + /// ``` + String jwt({Map? payload, String algorithm = 'HS256'}) { + final header = {'alg': algorithm, 'typ': 'JWT'}; + + final now = DateTime.now(); + final iat = now.millisecondsSinceEpoch ~/ 1000; + final exp = iat + 3600; + + payload ??= {'sub': uuid, 'name': 'Test User', 'iat': iat, 'exp': exp}; + + final headerJson = jsonEncode(header); + final payloadJson = jsonEncode(payload); + + final base64UrlEncoder = utf8.fuse(base64Url); + final header64 = base64UrlEncoder.encode(headerJson).replaceAll('=', ''); + final payload64 = base64UrlEncoder.encode(payloadJson).replaceAll('=', ''); + final signature = base64UrlEncode(tokenBytes()).replaceAll('=', ''); + + return '$header64.$payload64.$signature'; + } } diff --git a/test/src/cryptographic_test.dart b/test/src/cryptographic_test.dart index 0f7165e..44d1e3b 100644 --- a/test/src/cryptographic_test.dart +++ b/test/src/cryptographic_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:imitatio/imitatio.dart'; import 'package:test/test.dart'; @@ -84,5 +86,75 @@ void main() { expect(result.split(' ').length, inInclusiveRange(12, 24)); expect(seededCrypto.mnemonicPhrase, equals(seededCrypto.mnemonicPhrase)); }); + + test('returns JWT with default params', () { + final result = crypto.jwt(); + final parts = result.split('.'); + expect(parts.length, equals(3)); + + final headerJson = base64.decode(base64Url.normalize(parts[0])); + final header = + jsonDecode(utf8.decode(headerJson)) as Map; + expect(header['alg'], equals('HS256')); + expect(header['typ'], equals('JWT')); + + final payloadJson = base64.decode(base64Url.normalize(parts[1])); + final payload = + jsonDecode(utf8.decode(payloadJson)) as Map; + expect(payload['sub'], isNotEmpty); + expect(payload['name'], equals('Test User')); + expect(payload['iat'], isA()); + expect(payload['exp'], isA()); + + expect(seededCrypto.jwt(), equals(seededCrypto.jwt())); + }); + + test('returns JWT with provided algorithm', () { + const algorithm = 'HS512'; + + final result = crypto.jwt(algorithm: algorithm); + final parts = result.split('.'); + expect(parts.length, equals(3)); + + final headerJson = base64.decode(base64Url.normalize(parts[0])); + final header = + jsonDecode(utf8.decode(headerJson)) as Map; + expect(header['alg'], equals(algorithm)); + + expect( + seededCrypto.jwt(algorithm: algorithm), + equals(seededCrypto.jwt(algorithm: algorithm)), + ); + }); + + test('returns JWT with provided payload', () { + const payloads = [ + {'user_id': 123, 'role': 'admin'}, + { + 'sub': 'user@example.com', + 'permissions': ['read', 'write'], + }, + { + 'custom_field': 'value', + 'nested': {'key': 'value'}, + }, + {'empty': null, 'bool': true, "number": 42}, + ]; + + for (final p in payloads) { + final result = crypto.jwt(payload: p); + final parts = result.split('.'); + expect(parts.length, equals(3)); + + final payloadJson = base64.decode(base64Url.normalize(parts[1])); + final payload = + jsonDecode(utf8.decode(payloadJson)) as Map; + expect(payload, equals(p)); + expect( + seededCrypto.jwt(payload: p), + equals(seededCrypto.jwt(payload: p)), + ); + } + }); }); }