Skip to content

Commit

Permalink
fix(gotrue): Add missing members on User json serialization (#512)
Browse files Browse the repository at this point in the history
* refactor(gotrue, supabase_flutter): better logging of session

* fix(gotrue): add missing parameters to toJson() on User

* fixed camel case in user json serializations

* fix: user object can now be json serialized

* revert changes on supabase_flutter

* add test to check session json serialization

* update equality check for session and user objects

* fix: date time should be string when json serializing

* fix test checker

* add equality methods for factor
  • Loading branch information
dshukertjr committed Jun 13, 2023
1 parent 23bed82 commit 70cc835
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 44 deletions.
34 changes: 34 additions & 0 deletions packages/gotrue/lib/src/types/mfa.dart
Expand Up @@ -192,6 +192,40 @@ class Factor {
updatedAt: DateTime.parse(json['updated_at']),
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'friendly_name': friendlyName,
'factor_type': factorType.name,
'status': status.name,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is Factor &&
other.id == id &&
other.friendlyName == friendlyName &&
other.factorType == factorType &&
other.status == status &&
other.createdAt == createdAt &&
other.updatedAt == updatedAt;
}

@override
int get hashCode {
return id.hashCode ^
friendlyName.hashCode ^
factorType.hashCode ^
status.hashCode ^
createdAt.hashCode ^
updatedAt.hashCode;
}
}

enum AuthenticatorAssuranceLevels {
Expand Down
30 changes: 30 additions & 0 deletions packages/gotrue/lib/src/types/session.dart
Expand Up @@ -90,4 +90,34 @@ class Session {
user: user ?? this.user,
);
}

@override
String toString() {
return 'Session(providerToken: $providerToken, providerRefreshToken: $providerRefreshToken, expiresIn: $expiresIn, tokenType: $tokenType, user: $user, accessToken: $accessToken, refreshToken: $refreshToken)';
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is Session &&
other.providerToken == providerToken &&
other.providerRefreshToken == providerRefreshToken &&
other.accessToken == accessToken &&
other.expiresIn == expiresIn &&
other.refreshToken == refreshToken &&
other.tokenType == tokenType &&
other.user == user;
}

@override
int get hashCode {
return providerToken.hashCode ^
providerRefreshToken.hashCode ^
accessToken.hashCode ^
expiresIn.hashCode ^
refreshToken.hashCode ^
tokenType.hashCode ^
user.hashCode;
}
}
171 changes: 127 additions & 44 deletions packages/gotrue/lib/src/types/user.dart
@@ -1,3 +1,6 @@
// ignore_for_file: deprecated_member_use_from_same_package

import 'package:collection/collection.dart';
import 'package:gotrue/src/types/mfa.dart';

class User {
Expand Down Expand Up @@ -54,54 +57,121 @@ class User {
if (json['id'] == null) {
return null;
}

return User(
id: json['id'] as String,
appMetadata: json['app_metadata'] as Map<String, dynamic>,
id: json['id'] ?? '',
appMetadata: json['app_metadata'] as Map<String, dynamic>? ?? {},
userMetadata: json['user_metadata'] as Map<String, dynamic>?,
aud: json['aud'] as String,
confirmationSentAt: json['confirmation_sent_at'] as String?,
recoverySentAt: json['recovery_sent_at'] as String?,
emailChangeSentAt: json['email_change_sent_at'] as String?,
newEmail: json['new_email'] as String?,
invitedAt: json['invited_at'] as String?,
actionLink: json['action_link'] as String?,
email: json['email'] as String?,
phone: json['phone'] as String?,
createdAt: json['created_at'] as String,
// ignore: deprecated_member_use_from_same_package
confirmedAt: json['confirmed_at'] as String?,
emailConfirmedAt: json['email_confirmed_at'] as String?,
phoneConfirmedAt: json['phone_confirmed_at'] as String?,
lastSignInAt: json['last_sign_in_at'] as String?,
role: json['role'] as String?,
updatedAt: json['updated_at'] as String?,
identities:
(json['identities'] as List?)?.cast<Map<String, dynamic>>().map((e) {
return UserIdentity.fromMap(e);
}).toList(),
factors:
(json['factors'] as List?)?.cast<Map<String, dynamic>>().map((e) {
return Factor.fromJson(e);
}).toList(),
aud: json['aud'] ?? '',
confirmationSentAt: json['confirmation_sent_at'],
recoverySentAt: json['recovery_sent_at'],
emailChangeSentAt: json['email_change_sent_at'],
newEmail: json['new_email'],
invitedAt: json['invited_at'],
actionLink: json['action_link'],
email: json['email'],
phone: json['phone'],
createdAt: json['created_at'] ?? '',
confirmedAt: json['confirmed_at'],
emailConfirmedAt: json['email_confirmed_at'],
phoneConfirmedAt: json['phone_confirmed_at'],
lastSignInAt: json['last_sign_in_at'],
role: json['role'],
updatedAt: json['updated_at'],
identities: json['identities'] != null
? List<UserIdentity>.from(
json['identities']?.map((x) => UserIdentity.fromMap(x)))
: null,
factors: json['factors'] != null
? List<Factor>.from(json['factors']?.map((x) => Factor.fromJson(x)))
: null,
);
}

Map<String, dynamic> toJson() => {
'id': id,
'app_metadata': appMetadata,
'user_metadata': userMetadata,
'aud': aud,
'email': email,
'phone': phone,
'created_at': createdAt,
// ignore: deprecated_member_use_from_same_package
'confirmed_at': confirmedAt,
'email_confirmed_at': emailConfirmedAt,
'phone_confirmed_at': phoneConfirmedAt,
'last_sign_in_at': lastSignInAt,
'role': role,
'updated_at': updatedAt,
};
Map<String, dynamic> toJson() {
return {
'id': id,
'app_metadata': appMetadata,
'user_metadata': userMetadata,
'aud': aud,
'confirmation_sent_at': confirmationSentAt,
'recovery_sent_at': recoverySentAt,
'email_change_sent_at': emailChangeSentAt,
'new_email': newEmail,
'invited_at': invitedAt,
'action_link': actionLink,
'email': email,
'phone': phone,
'created_at': createdAt,
'confirmed_at': confirmedAt,
'email_confirmed_at': emailConfirmedAt,
'phone_confirmed_at': phoneConfirmedAt,
'last_sign_in_at': lastSignInAt,
'role': role,
'updated_at': updatedAt,
'identities': identities?.map((identity) => identity.toJson()).toList(),
'factors': factors?.map((factor) => factor.toJson()).toList(),
};
}

@override
String toString() {
return 'User(id: $id, appMetadata: $appMetadata, userMetadata: $userMetadata, aud: $aud, confirmationSentAt: $confirmationSentAt, recoverySentAt: $recoverySentAt, emailChangeSentAt: $emailChangeSentAt, newEmail: $newEmail, invitedAt: $invitedAt, actionLink: $actionLink, email: $email, phone: $phone, createdAt: $createdAt, confirmedAt: $confirmedAt, emailConfirmedAt: $emailConfirmedAt, phoneConfirmedAt: $phoneConfirmedAt, lastSignInAt: $lastSignInAt, role: $role, updatedAt: $updatedAt, identities: $identities, factors: $factors)';
}

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
final collectionEquals = const DeepCollectionEquality().equals;

return other is User &&
other.id == id &&
collectionEquals(other.appMetadata, appMetadata) &&
collectionEquals(other.userMetadata, userMetadata) &&
other.aud == aud &&
other.confirmationSentAt == confirmationSentAt &&
other.recoverySentAt == recoverySentAt &&
other.emailChangeSentAt == emailChangeSentAt &&
other.newEmail == newEmail &&
other.invitedAt == invitedAt &&
other.actionLink == actionLink &&
other.email == email &&
other.phone == phone &&
other.createdAt == createdAt &&
other.confirmedAt == confirmedAt &&
other.emailConfirmedAt == emailConfirmedAt &&
other.phoneConfirmedAt == phoneConfirmedAt &&
other.lastSignInAt == lastSignInAt &&
other.role == role &&
other.updatedAt == updatedAt &&
collectionEquals(other.identities, identities) &&
collectionEquals(other.factors, factors);
}

@override
int get hashCode {
return id.hashCode ^
appMetadata.hashCode ^
userMetadata.hashCode ^
aud.hashCode ^
confirmationSentAt.hashCode ^
recoverySentAt.hashCode ^
emailChangeSentAt.hashCode ^
newEmail.hashCode ^
invitedAt.hashCode ^
actionLink.hashCode ^
email.hashCode ^
phone.hashCode ^
createdAt.hashCode ^
confirmedAt.hashCode ^
emailConfirmedAt.hashCode ^
phoneConfirmedAt.hashCode ^
lastSignInAt.hashCode ^
role.hashCode ^
updatedAt.hashCode ^
identities.hashCode ^
factors.hashCode;
}
}

class UserIdentity {
Expand Down Expand Up @@ -155,6 +225,18 @@ class UserIdentity {
);
}

Map<String, dynamic> toJson() {
return {
'id': id,
'user_id': userId,
'identity_data': identityData,
'provider': provider,
'created_at': createdAt,
'last_sign_in_at': lastSignInAt,
'updated_at': updatedAt,
};
}

@override
String toString() {
return 'UserIdentity(id: $id, userId: $userId, identityData: $identityData, provider: $provider, createdAt: $createdAt, lastSignInAt: $lastSignInAt, updatedAt: $updatedAt)';
Expand All @@ -163,11 +245,12 @@ class UserIdentity {
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
final mapEquals = const DeepCollectionEquality().equals;

return other is UserIdentity &&
other.id == id &&
other.userId == userId &&
other.identityData == identityData &&
mapEquals(other.identityData, identityData) &&
other.provider == provider &&
other.createdAt == createdAt &&
other.lastSignInAt == lastSignInAt &&
Expand Down
9 changes: 9 additions & 0 deletions packages/gotrue/test/src/gotrue_mfa_api_test.dart
Expand Up @@ -129,6 +129,15 @@ void main() {
totpEntry!.timestamp.difference(DateTime.now()) < Duration(seconds: 2),
true);
});

test('Session object can be properly json serielized', () async {
await client.signInWithPassword(password: password, email: email2);
await client.mfa.challengeAndVerify(factorId: factorId2, code: getTOTP());
final response = await client.refreshSession();
final session = response.session;
final deserializedSession = Session.fromJson(session!.toJson());
expect(session, deserializedSession);
});
}

String getTOTP() {
Expand Down

0 comments on commit 70cc835

Please sign in to comment.