Files
mixtape/zero.Core/Identity/UserStore.cs
T
2020-04-15 14:09:52 +02:00

207 lines
5.9 KiB
C#

using Microsoft.AspNetCore.Identity;
using Raven.Client.Documents;
using Raven.Client.Documents.Session;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using zero.Core.Entities;
using zero.Core.Extensions;
namespace zero.Core.Identity
{
public partial class UserStore<TUser> : IUserStore<TUser>, IQueryableUserStore<TUser> where TUser : class, IUser
{
protected IDocumentStore Raven { get; private set; }
public UserStore(IDocumentStore raven)
{
Raven = raven;
}
/// <inheritdoc />
public IQueryable<TUser> Users
{
get
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return session.Query<TUser>();
}
}
}
/// <inheritdoc />
public async Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
// try to reserve the key for the new user
if (!await Raven.ReserveAsync(Constants.Database.ReservationPrefix + user.Email, user.Id))
{
return IdentityResult.Failed(new[]
{
new IdentityError
{
Code = "DuplicateEmail",
Description = $"The email address {user.Email} is already taken."
}
});
}
// This model allows us to lookup a user by name in order to get the id
await session.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 session.SaveChangesAsync(cancellationToken);
}
catch (Exception)
{
// The compare/exchange email reservation is cluster-wide, outside of the session scope.
// We need to manually roll it back.
await Raven.RemoveReservationAsync(Constants.Database.ReservationPrefix + user.Email);
throw;
}
}
return IdentityResult.Success;
}
/// <inheritdoc />
public async Task<IdentityResult> DeleteAsync(TUser user, CancellationToken cancellationToken)
{
// Remove the cluster-wide compare/exchange key.
await Raven.RemoveReservationAsync(Constants.Database.ReservationPrefix + user.Email);
// Delete the user and save it. We must save it because deleting is a cluster-wide operation.
// Only if the deletion succeeds will we remove the cluseter-wide compare/exchange key.
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
session.Delete(user);
await session.SaveChangesAsync(cancellationToken);
}
return IdentityResult.Success;
}
/// <inheritdoc />
public async Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.LoadAsync<TUser>(userId);
}
}
/// <inheritdoc />
public async Task<TUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<TUser>().FirstOrDefaultAsync(x => x.Email == normalizedUserName, cancellationToken);
}
}
/// <inheritdoc />
public Task<string> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken)
{
return Task.FromResult(user.Email);
}
/// <inheritdoc />
public Task<string> GetUserIdAsync(TUser user, CancellationToken cancellationToken)
{
return Task.FromResult(user.Id);
}
/// <inheritdoc />
public Task<string> GetUserNameAsync(TUser user, CancellationToken cancellationToken)
{
return Task.FromResult(user.Email);
}
/// <inheritdoc />
public Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken)
{
user.Email = normalizedName.ToLowerInvariant();
return Task.CompletedTask;
}
/// <inheritdoc />
public async Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken)
{
await SetNormalizedUserNameAsync(user, userName, cancellationToken);
}
/// <inheritdoc />
public async Task<IdentityResult> UpdateAsync(TUser user, CancellationToken cancellationToken)
{
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
TUser source = await session.LoadAsync<TUser>(user.Id, cancellationToken);
if (source == null)
{
return IdentityResult.Failed(new[]
{
new IdentityError
{
Code = "UserNotFound",
Description = $"Could not find stored user with id {user.Id}."
}
});
}
if (source.Email != user.Email)
{
// try to reserve the key for the new user
if (!await Raven.ReserveAsync(Constants.Database.ReservationPrefix + user.Email, user.Id))
{
return IdentityResult.Failed(new[]
{
new IdentityError
{
Code = "DuplicateEmail",
Description = $"The email address {user.Email} is already taken."
}
});
}
// Remove the cluster-wide compare/exchange key.
await Raven.RemoveReservationAsync(Constants.Database.ReservationPrefix + source.Email);
}
}
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
await session.StoreAsync(user, cancellationToken);
await session.SaveChangesAsync(cancellationToken);
}
return IdentityResult.Success;
}
/// <inheritdoc />
void IDisposable.Dispose()
{
}
}
}