This commit is contained in:
2025-04-01 11:52:46 +02:00
parent f1586abc86
commit c8c30d3838
7 changed files with 270 additions and 122 deletions
+38
View File
@@ -1,3 +1,41 @@
## 1.4.0
- [BREAKING] JWT-related extensions have been reworked.
Old:
```dart
String token = "your.jwt.token";
DateTime? expiry = token.jwtExpiryTime();
String? userId = token.jwtUserId();
String? email = token.jwtEmail();
```
New:
```dart
String token = "your.jwt.token";
DateTime? expiry = token.jwt.expiryTime;
String? userId = token.jwt.userId;
String? email = token.jwt.email;
// Added:
JwtPayload? token.jwt;
String? givenName = token.jwt.givenName;
String? familyName = token.jwt.familyName;
```
Additionally, the exceptions thrown when parsing an invalid JWT have been
updated.
```dart
String email = "invalid".jwtEmail() // Previously threw Exception("invalid token")
String email = "invalid".jwt.email // Now throws InvalidTokenException()
String email = "".jwtEmail() // Previously threw Exception("invalid payload")
String email = "".jwt.email // Now throws InvalidPayloadException()
```
## 1.3.2 ## 1.3.2
- Added `isEmptyOrNull` and `isNotEmptyOrNull` extensions for `List` and `String` objects. These extensions are identical to `isNullOrEmpty` and `isNotNullOrEmpty`, respectively. - Added `isEmptyOrNull` and `isNotEmptyOrNull` extensions for `List` and `String` objects. These extensions are identical to `isNullOrEmpty` and `isNotNullOrEmpty`, respectively.
+27 -4
View File
@@ -14,7 +14,9 @@ providing utility functions and extensions that simplify common tasks.
- **DateTime Extensions**: Adds additional functionality to the `DateTime` - **DateTime Extensions**: Adds additional functionality to the `DateTime`
class, making it easier to format dates and calculate differences. class, making it easier to format dates and calculate differences.
- **String Extensions**: Enhances the `String` class by adding new methods for - **String Extensions**: Enhances the `String` class by adding new methods for
common transformations and checks, including JWT parsing. common transformations and checks.
- **JWT Utilities**: Provides getters to parse a JWT token from a `String`, then
get common properties from it.
- **List Extensions**: Adds a new `unique` operator for filtering `List` items. - **List Extensions**: Adds a new `unique` operator for filtering `List` items.
## Getting Started ## Getting Started
@@ -260,11 +262,18 @@ making it easier to handle JSON Web Tokens directly as `String` objects.
Here are some examples of how these methods can be utilized: Here are some examples of how these methods can be utilized:
- Parse a JWT string
```dart
String token = "your.jwt.token";
final JwtPayload? payload = token.jwt; // Returns the JWT's payload
```
- Extracting the email address (`jwt["sub"]`) - Extracting the email address (`jwt["sub"]`)
```dart ```dart
String jwt = "your.jwt.token"; String jwt = "your.jwt.token";
final String? email = jwt.jwtEmail(); // Returns the email address in the JWT final String? email = jwt.jwt.email; // Returns the email address in the JWT
``` ```
- Extracting the token expiration time (`jwt["exp"]`) - Extracting the token expiration time (`jwt["exp"]`)
@@ -272,14 +281,28 @@ Here are some examples of how these methods can be utilized:
```dart ```dart
String jwt = "your.jwt.token"; String jwt = "your.jwt.token";
// Returns a `DateTime?` when the token expires // Returns a `DateTime?` when the token expires
final DateTime? email = jwt.jwtExpiryTime(); final DateTime? email = jwt.jwt.expiryTime;
``` ```
- Extracting the user ID (`jwt["uid"]`) - Extracting the user ID (`jwt["uid"]`)
```dart ```dart
String jwt = "your.jwt.token"; String jwt = "your.jwt.token";
final String? uid = jwt.jwtUserId(); // Returns the UID value from the token final String? uid = jwt.jwt.userId; // Returns the UID value from the token
```
- Extracting the given name (`jwt["given_name"]`)
```dart
String jwt = "your.jwt.token";
final String? uid = jwt.jwt.givenName; // Returns the given name from the token
```
- Extracting the family name (`jwt["family_name"]`)
```dart
String jwt = "your.jwt.token";
final String? uid = jwt.jwt.familyName; // Returns the family name from the token
``` ```
### String Utilities ### String Utilities
+1 -1
View File
@@ -2,8 +2,8 @@ library arcane_helper_utils;
export "package:arcane_helper_utils/src/extensions/date_time.dart"; export "package:arcane_helper_utils/src/extensions/date_time.dart";
export "package:arcane_helper_utils/src/extensions/dynamic.dart"; export "package:arcane_helper_utils/src/extensions/dynamic.dart";
export "package:arcane_helper_utils/src/extensions/jwt.dart";
export "package:arcane_helper_utils/src/extensions/list.dart"; export "package:arcane_helper_utils/src/extensions/list.dart";
export "package:arcane_helper_utils/src/extensions/string.dart"; export "package:arcane_helper_utils/src/extensions/string.dart";
export "package:arcane_helper_utils/src/extensions/string_jwt.dart";
export "package:arcane_helper_utils/src/utils/json_converter.dart"; export "package:arcane_helper_utils/src/utils/json_converter.dart";
export "package:arcane_helper_utils/src/utils/ticker.dart"; export "package:arcane_helper_utils/src/utils/ticker.dart";
@@ -1,79 +1,12 @@
import "dart:convert"; import "dart:convert";
typedef JwtPayload = Map<String, dynamic>;
/// An extension on `String` to extract useful information from JSON Web Tokens (JWT). /// An extension on `String` to extract useful information from JSON Web Tokens (JWT).
/// ///
/// This extension provides methods to decode a JWT string and retrieve common /// This extension provides methods to decode a JWT string and retrieve common
/// fields like email, expiration time, and user ID. /// fields like email, expiration time, and user ID.
extension JWTUtility on String { extension JWTUtility on String {
/// Extracts the email from the JWT payload.
///
/// This method attempts to parse the JWT and retrieve the `sub` field, which is
/// typically used to store the email of the token owner.
///
/// Returns the email as a `String` if present, or `null` if the parsing fails or the
/// email is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? email = token.jwtEmail();
/// ```
String? jwtEmail() {
try {
final payload = _parseJwt(this);
final String email = payload["sub"] as String;
return email;
} catch (_) {
return null;
}
}
/// Extracts the expiration time from the JWT payload.
///
/// This method retrieves the `exp` field from the JWT, which represents the expiration
/// time as a Unix timestamp. The timestamp is converted to a `DateTime` object.
///
/// Returns the `DateTime` representing the expiration time, or `null` if the parsing
/// fails or the expiration time is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// DateTime? expiry = token.jwtExpiryTime();
/// ```
DateTime? jwtExpiryTime() {
try {
final payload = _parseJwt(this);
final expiry = payload["exp"] as int;
final date = DateTime.fromMillisecondsSinceEpoch(expiry * 1000);
return date;
} catch (_) {
return null;
}
}
/// Extracts the user ID from the JWT payload.
///
/// This method retrieves the `uid` field, which is typically the unique user identifier.
/// This ID can be used in analytics or for tracking purposes.
///
/// Returns the user ID as a `String`, or `null` if the parsing fails or the ID is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? userId = token.jwtUserId();
/// ```
String? jwtUserId() {
try {
final payload = _parseJwt(this);
final String id = payload["uid"] as String;
return id;
} catch (_) {
return null;
}
}
/// Decodes a base64 URL-encoded string. /// Decodes a base64 URL-encoded string.
/// ///
/// This method replaces the URL-safe characters in the base64 string (`-` and `_`) /// This method replaces the URL-safe characters in the base64 string (`-` and `_`)
@@ -111,20 +44,132 @@ extension JWTUtility on String {
/// ///
/// Example: /// Example:
/// ```dart /// ```dart
/// Map<String, dynamic> payload = _parseJwt("your.jwt.token"); /// Map<String, dynamic> payload = parseJwt("your.jwt.token");
/// ``` /// ```
Map<String, dynamic> _parseJwt(String token) { JwtPayload get jwt {
final parts = token.split("."); final parts = this.split(".");
if (parts.length != 3) { if (parts.length != 3) {
throw Exception("invalid token"); throw InvalidTokenException();
} }
final payload = _decodeBase64(parts[1]); final payload = _decodeBase64(parts[1]);
final dynamic payloadMap = json.decode(payload); final payloadMap = json.decode(payload) as JwtPayload?;
if (payloadMap is! Map<String, dynamic>) { if (payloadMap is! JwtPayload) {
throw Exception("invalid payload"); throw InvalidPayloadException();
} }
return payloadMap; return payloadMap;
} }
} }
extension JWTMapUtility on JwtPayload {
/// Extracts the email from the JWT payload.
///
/// This method attempts to parse the JWT and retrieve the `sub` field, which is
/// typically used to store the email of the token owner.
///
/// Returns the email as a `String` if present, or `null` if the parsing fails or the
/// email is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? email = token.jwt.email;
/// ```
String? get email {
try {
return this["sub"] as String;
} catch (_) {
return null;
}
}
/// Extracts the given (first) name from the JWT payload.
///
/// This method attempts to parse the JWT and retrieve the `given_name` field,
/// which is typically used to store the given (first) name of the token owner.
///
/// Returns the given name as a `String` if present, or `null` if the parsing
/// fails or the given name is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? givenName = token.jwt.givenName;
/// ```
String? get givenName {
try {
return this["given_name"] as String;
} catch (_) {
return null;
}
}
/// Extracts the family (last) name from the JWT payload.
///
/// This method attempts to parse the JWT and retrieve the `family_name` field,
/// which is typically used to store the family (last) name of the token owner.
///
/// Returns the family name as a `String` if present, or `null` if the parsing
/// fails or the family name is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? familyName = token.jwt.familyName;
/// ```
String? get familyName {
try {
return this["family_name"] as String;
} catch (_) {
return null;
}
}
/// Extracts the expiration time from the JWT payload.
///
/// This method retrieves the `exp` field from the JWT, which represents the expiration
/// time as a Unix timestamp. The timestamp is converted to a `DateTime` object.
///
/// Returns the `DateTime` representing the expiration time, or `null` if the parsing
/// fails or the expiration time is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// DateTime? expiry = token.jwt.expiryTime;
/// ```
DateTime? get expiryTime {
try {
final expiry = this["exp"] as int;
final date = DateTime.fromMillisecondsSinceEpoch(expiry * 1000);
return date;
} catch (_) {
return null;
}
}
/// Extracts the user ID from the JWT payload.
///
/// This method retrieves the `uid` field, which is typically the unique user identifier.
/// This ID can be used in analytics or for tracking purposes.
///
/// Returns the user ID as a `String`, or `null` if the parsing fails or the ID is not found.
///
/// Example:
/// ```dart
/// String token = "your.jwt.token";
/// String? userId = token.jwt.userId;
/// ```
String? get userId {
try {
return this["uid"] as String;
} catch (_) {
return null;
}
}
}
class InvalidTokenException implements Exception {}
class InvalidPayloadException implements Exception {}
+1 -1
View File
@@ -1,7 +1,7 @@
name: arcane_helper_utils name: arcane_helper_utils
description: Provides a variety of helpful utilities and extensions for Flutter description: Provides a variety of helpful utilities and extensions for Flutter
and Dart. and Dart.
version: 1.3.2 version: 1.4.0
repository: https://github.com/hanskokx/arcane_helper_utils repository: https://github.com/hanskokx/arcane_helper_utils
issue_tracker: https://github.com/hanskokx/arcane_helper_utils/issues issue_tracker: https://github.com/hanskokx/arcane_helper_utils/issues
+82
View File
@@ -0,0 +1,82 @@
import "package:arcane_helper_utils/arcane_helper_utils.dart";
import "package:test/test.dart";
void main() {
// Example JWT token for testing
const String validToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJzdWIiOiJ0ZXN0QGV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJ1aWQiOiIxMjM0NTYiLCJnaXZlbl9uYW1lIjoiZ2l2ZW4iLCJmYW1pbHlfbmFtZSI6ImZhbWlseSJ9."
"Ki_D9fOhv7bLe86j6fdZ1guNGI7ldKSeeANUfinwNtc";
group("JWTUtility", () {
test("jwt getter parses a valid JWT", () {
expect(validToken.jwt, isNotNull);
});
test("jwt getter throws an exception for invalid token", () {
expect(() => "invalid.token".jwt, throwsException);
expect(() => "".jwt, throwsException);
});
test("jwt.email extracts email correctly", () {
expect(validToken.jwt.email, "test@example.com");
});
test("jwt.email throws an exception for invalid token", () {
expect(
() => "invalid.token".jwt.email,
throwsA(isA<InvalidTokenException>()),
);
expect(() => "".jwt.email, throwsA(isA<InvalidTokenException>()));
});
test("jwt.givenName extracts given name correctly", () {
expect(validToken.jwt.givenName, "given");
});
test("jwt.givenName throws an exception for invalid token", () {
expect(
() => "invalid.token".jwt.givenName,
throwsA(isA<InvalidTokenException>()),
);
expect(() => "".jwt.givenName, throwsA(isA<InvalidTokenException>()));
});
test("jwt.familyName extracts family name correctly", () {
expect(validToken.jwt.familyName, "family");
});
test("jwt.familyName returns null for invalid token", () {
expect(
() => "invalid.token".jwt.familyName,
throwsA(isA<InvalidTokenException>()),
);
expect(() => "".jwt.familyName, throwsA(isA<InvalidTokenException>()));
});
test("jwt.expiryTime extracts expiry time correctly", () {
final DateTime? expiry = validToken.jwt.expiryTime;
expect(expiry, isNotNull);
expect(expiry?.year, 2025); // Based on the exp value in the test token
});
test("jwt.expiryTime throws an exception for invalid token", () {
expect(
() => "invalid.token".jwt.expiryTime,
throwsA(isA<InvalidTokenException>()),
);
expect(() => "".jwt.expiryTime, throwsA(isA<InvalidTokenException>()));
});
test("jwt.userId extracts user ID correctly", () {
expect(validToken.jwt.userId, "123456");
});
test("jwt.userId throws an exception for invalid token", () {
expect(
() => "invalid.token".jwt.userId,
throwsA(isA<InvalidTokenException>()),
);
expect(() => "".jwt.userId, throwsA(isA<InvalidTokenException>()));
});
});
}
-40
View File
@@ -1,40 +0,0 @@
import "package:arcane_helper_utils/arcane_helper_utils.dart";
import "package:test/test.dart";
void main() {
// Example JWT token for testing
const String validToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
"eyJzdWIiOiJ0ZXN0QGV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJ1aWQiOiIxMjM0NTYifQ."
"signature";
group("JWTUtility", () {
test("jwtEmail extracts email correctly", () {
expect(validToken.jwtEmail(), "test@example.com");
});
test("jwtEmail returns null for invalid token", () {
expect("invalid.token".jwtEmail(), null);
expect("".jwtEmail(), null);
});
test("jwtExpiryTime extracts expiry time correctly", () {
final DateTime? expiry = validToken.jwtExpiryTime();
expect(expiry, isNotNull);
expect(expiry?.year, 2025); // Based on the exp value in the test token
});
test("jwtExpiryTime returns null for invalid token", () {
expect("invalid.token".jwtExpiryTime(), null);
expect("".jwtExpiryTime(), null);
});
test("jwtUserId extracts user ID correctly", () {
expect(validToken.jwtUserId(), "123456");
});
test("jwtUserId returns null for invalid token", () {
expect("invalid.token".jwtUserId(), null);
expect("".jwtUserId(), null);
});
});
}