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