Files
mixtape/zero.Core/Identity/RavenUserStore(TUser).cs
T

349 lines
9.9 KiB
C#
Raw Normal View History

2021-11-19 16:11:12 +01:00
using Microsoft.AspNetCore.Identity;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using System.Security.Claims;
2021-11-20 13:52:28 +01:00
namespace zero.Identity;
2021-11-19 16:11:12 +01:00
2021-12-26 01:42:21 +01:00
public partial class RavenUserStore<TUser> :
EntityStore<TUser>,
2021-11-19 16:11:12 +01:00
IUserStore<TUser>,
IUserEmailStore<TUser>,
IUserLockoutStore<TUser>,
IUserPasswordStore<TUser>,
IUserClaimStore<TUser>,
IUserSecurityStampStore<TUser>,
IProtectedUserStore<TUser>
2021-12-26 01:42:21 +01:00
where TUser : ZeroIdentityUser, new()
2021-11-19 16:11:12 +01:00
{
protected bool Global { get; private set; }
2021-12-26 01:42:21 +01:00
public RavenUserStore(IStoreContext storeContext, bool global = false) : base(storeContext)
2021-11-19 16:11:12 +01:00
{
2021-12-26 01:42:21 +01:00
Global = global;
Config.Database = global ? Options.For<RavenOptions>().Database : null;
Config.IncludeInactive = true;
2021-11-19 16:11:12 +01:00
}
/// <summary>
/// Get the Raven compare/exchange key for this user.
/// This should be unique within the store, otherwise you can't create users with the same email in different stores.
/// </summary>
protected virtual string GetReservationKey(TUser user) => Constants.Database.ReservationPrefix + typeof(TUser) + ':' + user.Email;
// <summary>
/// Scope queries to an optional application or something else
/// </summary>
protected virtual IRavenQueryable<TUser> ScopeQuery(IRavenQueryable<TUser> query) => query;
/// <summary>
/// Whether an email is already reserved
/// </summary>
2021-12-26 01:42:21 +01:00
protected virtual async Task<bool> IsEmailReserved(TUser user, CancellationToken cancellationToken = default)
2021-11-19 16:11:12 +01:00
{
2021-12-26 01:42:21 +01:00
TUser existingUser = await ScopeQuery(Session.Query<TUser>()).FirstOrDefaultAsync(x => x.Email == user.Email, cancellationToken);
2021-11-19 16:11:12 +01:00
return existingUser != null && existingUser.Id != user.Id;
}
/// <inheritdoc />
public async Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken)
{
2021-12-26 01:42:21 +01:00
if (await IsEmailReserved(user, cancellationToken))
2021-11-19 16:11:12 +01:00
{
return IdentityResult.Failed(new IdentityError
{
Code = "DuplicateEmail",
Description = $"The email address {user.Email} is already taken."
});
}
2021-12-26 01:42:21 +01:00
Result<TUser> result = await Create(user);
2021-11-19 16:11:12 +01:00
return IdentityResult.Success;
}
/// <inheritdoc />
public async Task<IdentityResult> DeleteAsync(TUser user, CancellationToken cancellationToken)
{
2021-12-26 01:42:21 +01:00
Result result = await Delete(user);
2021-11-19 16:11:12 +01:00
return IdentityResult.Success;
}
/// <inheritdoc />
public async Task<IdentityResult> UpdateAsync(TUser user, CancellationToken cancellationToken)
{
2021-12-26 01:42:21 +01:00
TUser source = await Load(user.Id);
2021-11-19 16:11:12 +01:00
if (source == null)
{
return IdentityResult.Failed(new IdentityError
{
Code = "UserNotFound",
Description = $"Could not find stored user with id {user.Id}."
});
}
2021-12-26 01:42:21 +01:00
if (source.Email != user.Email && await IsEmailReserved(user, cancellationToken))
2021-11-19 16:11:12 +01:00
{
return IdentityResult.Failed(new IdentityError
{
Code = "DuplicateEmail",
Description = $"The email address {user.Email} is already taken."
});
}
2021-12-26 01:42:21 +01:00
await Update(user);
2021-11-19 16:11:12 +01:00
return IdentityResult.Success;
}
/// <inheritdoc />
public async Task<TUser> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
2021-12-26 01:42:21 +01:00
return await ScopeQuery(Session.Query<TUser>()).FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);
2021-11-19 16:11:12 +01:00
}
/// <inheritdoc />
public async Task<TUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
2021-12-26 01:42:21 +01:00
return await ScopeQuery(Session.Query<TUser>()).FirstOrDefaultAsync(x => x.Username == normalizedUserName, cancellationToken);
2021-11-19 16:11:12 +01:00
}
/// <inheritdoc />
public Task<string> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Username);
/// <inheritdoc />
public Task<string> GetUserIdAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Id);
/// <inheritdoc />
public Task<string> GetUserNameAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Username);
/// <inheritdoc />
public Task SetNormalizedUserNameAsync(TUser user, string normalizedName, CancellationToken cancellationToken)
{
user.Username = normalizedName;
return Task.CompletedTask;
}
/// <inheritdoc />
public async Task SetUserNameAsync(TUser user, string userName, CancellationToken cancellationToken)
{
await SetNormalizedUserNameAsync(user, userName, cancellationToken);
}
/// <inheritdoc />
void IDisposable.Dispose() { }
/*
* ****************************************************
* SECURITY STAMP
* ****************************************************
*/
/// <inheritdoc />
public Task<string> GetSecurityStampAsync(TUser user, CancellationToken cancellationToken)
{
return Task.FromResult(user.SecurityStamp);
}
/// <inheritdoc />
public Task SetSecurityStampAsync(TUser user, string stamp, CancellationToken cancellationToken)
{
user.SecurityStamp = stamp;
return Task.CompletedTask;
}
/*
* ****************************************************
* EMAIL
* ****************************************************
*/
/// <inheritdoc />
public async Task<TUser> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken)
{
2021-12-26 01:42:21 +01:00
return await Session.Query<TUser>().FirstOrDefaultAsync(x => x.Email == normalizedEmail, cancellationToken);
2021-11-19 16:11:12 +01:00
}
/// <inheritdoc />
public Task<string> GetEmailAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Email);
/// <inheritdoc />
public Task<bool> GetEmailConfirmedAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.IsEmailConfirmed);
/// <inheritdoc />
public Task<string> GetNormalizedEmailAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.Email);
/// <inheritdoc />
public Task SetEmailAsync(TUser user, string email, CancellationToken cancellationToken)
{
user.Email = email.ToLowerInvariant();
return Task.CompletedTask;
}
/// <inheritdoc />
public Task SetEmailConfirmedAsync(TUser user, bool confirmed, CancellationToken cancellationToken)
{
user.IsEmailConfirmed = confirmed;
return Task.CompletedTask;
}
/// <inheritdoc />
public Task SetNormalizedEmailAsync(TUser user, string normalizedEmail, CancellationToken cancellationToken)
{
user.Email = normalizedEmail.ToLowerInvariant();
return Task.CompletedTask;
}
/*
* ****************************************************
* LOCKOUT
* ****************************************************
*/
/// <inheritdoc />
public Task<int> GetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.AccessFailedCount);
/// <inheritdoc />
public Task<bool> GetLockoutEnabledAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.LockoutEnabled);
/// <inheritdoc />
public Task<DateTimeOffset?> GetLockoutEndDateAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.LockoutEnd);
/// <inheritdoc />
public Task<int> IncrementAccessFailedCountAsync(TUser user, CancellationToken cancellationToken)
{
user.AccessFailedCount += 1;
return Task.FromResult(user.AccessFailedCount);
}
/// <inheritdoc />
public Task ResetAccessFailedCountAsync(TUser user, CancellationToken cancellationToken)
{
user.AccessFailedCount = 0;
return Task.CompletedTask;
}
/// <inheritdoc />
public Task SetLockoutEnabledAsync(TUser user, bool enabled, CancellationToken cancellationToken)
{
user.LockoutEnabled = enabled;
return Task.CompletedTask;
}
/// <inheritdoc />
public Task SetLockoutEndDateAsync(TUser user, DateTimeOffset? lockoutEnd, CancellationToken cancellationToken)
{
user.LockoutEnd = lockoutEnd;
return Task.CompletedTask;
}
/*
* ****************************************************
* PASSWORD
* ****************************************************
*/
/// <inheritdoc />
public Task<string> GetPasswordHashAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(user.PasswordHash);
/// <inheritdoc />
public Task<bool> HasPasswordAsync(TUser user, CancellationToken cancellationToken) => Task.FromResult(!user.PasswordHash.IsNullOrEmpty());
/// <inheritdoc />
public Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken)
{
user.PasswordHash = passwordHash;
return Task.CompletedTask;
}
/*
* ****************************************************
* CLAIM
* ****************************************************
*/
/// <inheritdoc />
public async Task AddClaimsAsync(TUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
{
user.Claims.AddRange(claims.Select(claim => new UserClaim(claim)));
2021-12-26 01:42:21 +01:00
await Update(user);
2021-11-19 16:11:12 +01:00
}
/// <inheritdoc />
public Task<IList<Claim>> GetClaimsAsync(TUser user, CancellationToken cancellationToken)
{
return Task.FromResult((IList<Claim>)user.Claims.Select(claim => claim.ToClaim()).ToList());
}
/// <inheritdoc />
public async Task<IList<TUser>> GetUsersForClaimAsync(Claim claim, CancellationToken cancellationToken)
{
2021-11-24 15:49:25 +01:00
UserClaim userClaim = new(claim);
2021-12-26 01:42:21 +01:00
return await ScopeQuery(Session.Query<TUser>()).Where(x => x.Claims.Any(c => c.Type == userClaim.Type && c.Value == userClaim.Value)).ToListAsync(token: cancellationToken);
2021-11-19 16:11:12 +01:00
}
/// <inheritdoc />
public async Task RemoveClaimsAsync(TUser user, IEnumerable<Claim> claims, CancellationToken cancellationToken)
{
IEnumerable<UserClaim> userClaims = claims.Select(c => new UserClaim(c)).ToList();
user.Claims = user.Claims.Except(userClaims, new UserClaimComparer()).ToList();
2021-12-26 01:42:21 +01:00
await Update(user);
2021-11-19 16:11:12 +01:00
}
/// <inheritdoc />
public async Task ReplaceClaimAsync(TUser user, Claim claim, Claim newClaim, CancellationToken cancellationToken)
{
2021-11-24 15:49:25 +01:00
UserClaim userClaim = new(claim);
UserClaim newUserClaim = new(newClaim);
2021-11-19 16:11:12 +01:00
user.Claims.Remove(userClaim);
user.Claims.Add(newUserClaim);
2021-12-26 01:42:21 +01:00
await Update(user);
2021-11-19 16:11:12 +01:00
}
}