firebase / firebase-admin-dotnet

Firebase Admin .NET SDK
https://firebase.google.com/docs/admin/setup
Apache License 2.0
370 stars 131 forks source link

Errors while importing users from the output of ListUsersAsync #419

Open benedict1986 opened 2 months ago

benedict1986 commented 2 months ago

Hi, I am trying to implement the export and import function with C# FirebaseAdmin 3.0.0 with .NET8.0. I am currently facing two issues. I don't know if it belongs to a bug so I could not find a suitable issue type. Please let me know if there is any information missed.

  1. I try to export users with ListUsersAsync and save deserialised ExportedUserRecord objects to a file
var users = new List<ExportedUserRecord>();
var pagedEnumerable = firebaseAuth.ListUsersAsync(null);
await foreach (var user in pagedEnumerable)
{
    users.Add(user);
}

var json = JsonSerializer.Serialize(users); // save this to a file
  1. When I try to seralise the file back to ImportUserRecordArgs, UserMetadata field and UserProviders filed are always null because the fields name in ExportedUserRecord are UserMetaData and ProviderData. So some custom json converts will be required

Here is the converter I write to solve the problem for UserMetaData

    public class UserMetadataConverter : JsonConverter<UserMetadata>
    {
        public override UserMetadata Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            long creationTimestampMillis = 0;
            long lastSignInTimestampMillis = 0;
            DateTime? lastRefreshTimestamp = null;
            var unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                {
                    return new UserMetadata(creationTimestampMillis, lastSignInTimestampMillis, lastRefreshTimestamp);
                }

                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    string propertyName = reader.GetString() ?? string.Empty;
                    reader.Read();

                    switch (propertyName)
                    {
                        case "CreationTimestamp":
                            creationTimestampMillis = (long)(reader.GetDateTime() - unixEpoch).TotalMilliseconds;
                            break;
                        case "LastSignInTimestamp":
                            lastSignInTimestampMillis = (long)(reader.GetDateTime() - unixEpoch).TotalMilliseconds;
                            break;
                        case "LastRefreshTimestamp":
                            lastRefreshTimestamp = reader.TokenType == JsonTokenType.Null ? null : reader.GetDateTime();
                            break;
                    }
                }
            }

            throw new JsonException();
        }

        public override void Write(Utf8JsonWriter writer, UserMetadata value, JsonSerializerOptions options)
        {
            writer.WriteStartObject();
            writer.WriteString("CreationTimestamp", value.CreationTimestamp.ToString());
            writer.WriteString("LastSignInTimestamp", value.LastSignInTimestamp.ToString());
            writer.WriteString("LastRefreshTimestamp", value.LastRefreshTimestamp.ToString());
            writer.WriteEndObject();
        }
    }
  1. After deserialising the json file to ImportUserRecordArgs objects, firebaseAuth.ImportUsersAsync throws errors like following
{
            "field": "users[0].created_at",
            "description": "Invalid value at 'users[0].created_at' (TYPE_INT64), \"2024-06-03T02:53:37.173Z\""
          },
          {
            "field": "users[0].last_login_at",
            "description": "Invalid value at 'users[0].last_login_at' (TYPE_INT64), \"2024-06-03T02:53:37.173Z\""
          },

Error comes from

at FirebaseAdmin.Util.ErrorHandlingHttpClient`1.SendAndReadAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at FirebaseAdmin.Util.ErrorHandlingHttpClient`1.SendAndDeserializeAsync[TResult](HttpRequestMessage request, CancellationToken cancellationToken)
at FirebaseAdmin.Auth.Users.FirebaseUserManager.PostAndDeserializeAsync[TResult](String path, Object body, CancellationToken cancellationToken)
at FirebaseAdmin.Auth.Users.FirebaseUserManager.ImportUsersAsync(IEnumerable`1 users, UserImportOptions options, CancellationToken cancellationToken)
at FirebaseAdmin.Auth.AbstractFirebaseAuth.ImportUsersAsync(IEnumerable`1 users, UserImportOptions options, CancellationToken cancellationToken)

Here is an example of the ImportUserRecordArg object

{FirebaseAdmin.Auth.ImportUserRecordArgs}
    CustomClaims: Count = 0
    Disabled: false
    DisplayName: null
    Email: null
    EmailVerified: false
    PasswordHash: null
    PasswordSalt: null
    PhoneNumber: null
    PhotoUrl: null
    Uid: "abcdefg...example"
    UserMetadata: {FirebaseAdmin.Auth.UserMetadata}
    UserProviders: null

Here is the example of the UserMetadata object

{FirebaseAdmin.Auth.UserMetadata}
    CreationTimestamp: {03/06/2024 02:53:37}
    LastRefreshTimestamp: null
    LastSignInTimestamp: {03/06/2024 02:53:37}

Not sure if the above behaviours are expected?

Thank you

google-oss-bot commented 2 months ago

I found a few problems with this issue: