Skip to content

Commit

Permalink
afpi: Ensure Dart expiresAt uses the UTC time zone (#331)
Browse files Browse the repository at this point in the history
  • Loading branch information
Widcket authored Nov 1, 2023
1 parent f81dc2e commit b7c5a60
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 36 deletions.
4 changes: 2 additions & 2 deletions auth0_flutter_platform_interface/lib/src/credentials.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Credentials {
idToken: result['idToken'] as String,
accessToken: result['accessToken'] as String,
refreshToken: result['refreshToken'] as String?,
expiresAt: DateTime.parse(result['expiresAt'] as String),
expiresAt: DateTime.parse(result['expiresAt'] as String).toUtc(),
scopes: Set<String>.from(result['scopes'] as List<Object?>),
user: UserProfile.fromMap(Map<String, dynamic>.from(
result['userProfile'] as Map<dynamic, dynamic>)),
Expand All @@ -74,7 +74,7 @@ class Credentials {
'idToken': idToken,
'accessToken': accessToken,
'refreshToken': refreshToken,
'expiresAt': expiresAt.toIso8601String(),
'expiresAt': expiresAt.toUtc().toIso8601String(),
'scopes': scopes.toList(),
'tokenType': tokenType,
};
Expand Down
95 changes: 65 additions & 30 deletions auth0_flutter_platform_interface/test/credentials_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,86 @@ import 'package:intl/intl.dart';
void main() {
initializeDateFormatting();

test('Credentials throws when expiresAt Locale set to ar', () async {
final dateTime = DateTime(2022);
final isoDateTimeString = _formatISOTime(dateTime, 'ar');

expect(
() => Credentials.fromMap({
group('Credentials.fromMap', () {
test('expiresAt is a UTC DateTime', () async {
const isoDateTimeString = '2023-11-01T22:16:35.760Z';
final credentials = Credentials.fromMap({
'accessToken': 'accessToken',
'idToken': 'idToken',
'refreshToken': 'refreshToken',
'expiresAt': isoDateTimeString,
'scopes': ['a'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer',
}),
throwsA(isA<FormatException>()),
);
});

expect(credentials.expiresAt.isUtc, true);
});

test('Credentials throws when expiresAt Locale set to ar', () async {
final dateTime = DateTime(2022);
final isoDateTimeString = _formatISOTime(dateTime, 'ar');

expect(
() => Credentials.fromMap({
'accessToken': 'accessToken',
'idToken': 'idToken',
'refreshToken': 'refreshToken',
'expiresAt': isoDateTimeString,
'scopes': ['a'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer',
}),
throwsA(isA<FormatException>()),
);
});

test('Credentials does not throw when expiresAt Locale set to US',
() async {
initializeDateFormatting();
final dateTime = DateTime(2022);
final isoDateTimeString = _formatISOTime(dateTime, 'en_US');

expect(
Credentials.fromMap({
'accessToken': 'accessToken',
'idToken': 'idToken',
'refreshToken': 'refreshToken',
'expiresAt': isoDateTimeString,
'scopes': ['a'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer',
}),
isA<Credentials>(),
);
});
});

test('Credentials does not throw when expiresAt Locale set to US', () async {
initializeDateFormatting();
final dateTime = DateTime(2022);
final isoDateTimeString = _formatISOTime(dateTime, 'en_US');
group('toMap', () {
test('expiresAt is a ISO 8601 date with UTC time zone', () async {
final dateTime = DateTime(2023, 11, 1, 22, 16, 35, 760);
final credentials = Credentials(
accessToken: 'accessToken',
idToken: 'idToken',
refreshToken: 'refreshToken',
expiresAt: dateTime,
scopes: {'a'},
user: const UserProfile(sub: '123', name: 'John Doe'),
tokenType: 'Bearer');

expect(
Credentials.fromMap({
'accessToken': 'accessToken',
'idToken': 'idToken',
'refreshToken': 'refreshToken',
'expiresAt': isoDateTimeString,
'scopes': ['a'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer',
}),
isA<Credentials>(),
);
expect(credentials.toMap()['expiresAt'], '2023-11-01T22:16:35.760Z');
});
});
}

String _formatISOTime(final DateTime date, final String locale) {
final duration = date.timeZoneOffset;
final stringDate = DateFormat('yyyy-MM-ddTHH:mm:ss.mmm', locale).format(date);
final hours = duration.inHours.toString().padLeft(2, '0');
final minutes =
(duration.inMinutes - (duration.inHours * 60)).toString().padLeft(2, '0');
if (duration.isNegative) {
// ignore: lines_longer_than_80_chars
return "${DateFormat('yyyy-MM-ddTHH:mm:ss.mmm', locale).format(date)}-${duration.inHours.toString().padLeft(2, '0')}${(duration.inMinutes - (duration.inHours * 60)).toString().padLeft(2, '0')}";
} else {
// ignore: lines_longer_than_80_chars
return "${DateFormat('yyyy-MM-ddTHH:mm:ss.mmm', locale).format(date)}+${duration.inHours.toString().padLeft(2, '0')}${(duration.inMinutes - (duration.inHours * 60)).toString().padLeft(2, '0')}";
return '$stringDate-$hours$minutes';
}
return '$stringDate+$hours$minutes';
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class MethodCallHandler {
static const Map<dynamic, dynamic> loginResultRequired = {
'accessToken': 'accessToken',
'idToken': 'idToken',
'expiresAt': '2022-01-01',
'expiresAt': '2023-11-01T22:16:35.760Z',
'scopes': ['a', 'b'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer'
Expand Down Expand Up @@ -47,7 +47,7 @@ class MethodCallHandler {
'accessToken': 'accessToken',
'idToken': 'idToken',
'refreshToken': 'refreshToken',
'expiresAt': '2022-01-01',
'expiresAt': '2023-11-01T22:16:35.760Z',
'scopes': ['a', 'b'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class MethodCallHandler {
static const Map<dynamic, dynamic> loginResultRequired = {
'accessToken': 'accessToken',
'idToken': 'idToken',
'expiresAt': '2022-01-01',
'expiresAt': '2023-11-01T22:16:35.760Z',
'scopes': ['a', 'b'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class MethodCallHandler {
static const Map<dynamic, dynamic> credentials = {
'accessToken': 'accessToken',
'idToken': 'idToken',
'expiresAt': '2022-01-01',
'expiresAt': '2023-11-01T22:16:35.760Z',
'scopes': ['a', 'b'],
'userProfile': {'sub': '123', 'name': 'John Doe'},
'tokenType': 'Bearer'
Expand Down

0 comments on commit b7c5a60

Please sign in to comment.