JudahGabriel / RavenDB.Identity

RavenDB Identity provider for ASP.NET Core. Let RavenDB manage your users and logins.
https://www.nuget.org/packages/RavenDB.Identity/1.0.0
MIT License
61 stars 29 forks source link

UpdateAsync does nothing #21

Closed pahlgrimm closed 4 years ago

pahlgrimm commented 4 years ago

Hi I am using your library and CreateAsync works like a charm, but UpdateAsync does just nothing.

pahlgrimm commented 4 years ago

Hi, I have customized the UpdateAsync method and it seems to work (with updating the Compare Exchange values).

Here's my code if you want to add it or if you find error to improve it:

` public async Task UpdateAsync(TUser user, CancellationToken cancellationToken) { ThrowIfNullDisposedCancelled(user, cancellationToken);

        // Make sure we have a valid user name.
        if (string.IsNullOrWhiteSpace(user.UserName))
        {
            throw new ArgumentException("The user's user name can't be null or empty.", nameof(user));
        }

        cancellationToken.ThrowIfCancellationRequested();

        // Clear session because oldUSer would not be loaded from database because it has the same id as user
        DbSession.Advanced.Clear();    

        // We must delete the old ExchangeCompareKey if the user name hast changed
        // Therefore we get the unchanged user from database and compare the old username with the current username
        var oldUser = await DbSession.LoadAsync<TUser>(user.Id,cancellationToken);
        if (user.UserName != oldUser.UserName)
        {
            // See if the user name is already taken.
            // We do this using Raven's compare/exchange functionality, which works cluster-wide.
            // https://ravendb.net/docs/article-page/4.1/csharp/client-api/operations/compare-exchange/overview#creating-a-key
            //
            // Try to reserve a new user name 
            // Note: This operation takes place outside of the session transaction it is a cluster-wide reservation.
            var compareExchangeKey = GetCompareExchangeKeyFromUserName(user.UserName);
            var reserveUserNameOperation = new PutCompareExchangeValueOperation<string>(compareExchangeKey, user.Id, 0);
            var reserveUserNameResult = await DbSession.Advanced.DocumentStore.Operations.SendAsync(reserveUserNameOperation);
            if (!reserveUserNameResult.Successful)
            {
                return IdentityResult.Failed(new[] 
                {
                    new IdentityError
                    {
                        Code = "DuplicateUserName",
                        Description = $"The user name {user.UserName} is already taken."
                    }
                });
            }

            // Remove the cluster-wide compare/exchange key.
            var deletionResult = await DeleteUserNameReservation(oldUser.UserName);
            if (!deletionResult.Successful)
            {
                return IdentityResult.Failed(new[]
                {
                    new IdentityError
                    {
                        Code = "ConcurrencyFailure",
                        Description = "Unable to delete user name compare/exchange value"
                    }
                });
            }

        }

        // Clear session because we won't be able to store user (same id => exception!)
        DbSession.Advanced.Clear();

        // This model allows us to lookup a user by name in order to get the id
        await DbSession.StoreAsync(user, cancellationToken);

        // Because this a a cluster-wide operation due to compare/exchange tokens,
        // we need to save changes here; if we can't store the user, 
        // we need to roll back the email reservation.
        try
        {
            await DbSession.SaveChangesAsync();
        }
        catch (Exception)
        {
            // The compare/exchange email reservation is cluster-wide, outside of the session scope. 
            // We need to manually roll it back.
            await this.DeleteUserEmailReservation(user.Email);
            throw;
        }

        return IdentityResult.Success;
    }

`

I have also customized that the field UserName is used instead of Email.

JudahGabriel commented 4 years ago

Thanks for the error report. I'll have a look.

JudahGabriel commented 4 years ago

Fixed. The fix is available in RavenDB.Identity 6.2.0.2.

Thanks!