File: InMemoryUserStore.cs
Web Access
Project: src\src\Identity\samples\IdentitySample.PasskeyUI\IdentitySample.PasskeyUI.csproj (IdentitySample.PasskeyUI)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.Test;
 
namespace IdentitySample.PasskeyUI;
 
public sealed class InMemoryUserStore<TUser> :
    IQueryableUserStore<TUser>,
    IUserPasskeyStore<TUser>
    where TUser : PocoUser
{
    private readonly Dictionary<string, TUser> _users = [];
 
    public IQueryable<TUser> Users => _users.Values.AsQueryable();
 
    public Task<IdentityResult> CreateAsync(TUser user, CancellationToken cancellationToken)
    {
        _users[user.Id] = user;
        return Task.FromResult(IdentityResult.Success);
    }
 
    public Task<IdentityResult> DeleteAsync(TUser user, CancellationToken cancellationToken)
    {
        if (!_users.Remove(user.Id))
        {
            throw new InvalidOperationException($"Unknown user with ID '{user.Id}'.");
        }
        return Task.FromResult(IdentityResult.Success);
    }
 
    public Task<TUser?> FindByIdAsync(string userId, CancellationToken cancellationToken)
        => Task.FromResult(_users.TryGetValue(userId, out var result) ? result : null);
 
    public Task<TUser?> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
        => Task.FromResult(_users.Values.FirstOrDefault(u => string.Equals(u.NormalizedUserName, normalizedUserName, StringComparison.Ordinal)));
 
    public Task<TUser?> FindByPasskeyIdAsync(byte[] credentialId, CancellationToken cancellationToken)
        => Task.FromResult(_users.Values.FirstOrDefault(u => u.Passkeys.Any(p => p.CredentialId.SequenceEqual(credentialId))));
 
    public Task<UserPasskeyInfo?> FindPasskeyAsync(TUser user, byte[] credentialId, CancellationToken cancellationToken)
        => Task.FromResult(ToUserPasskeyInfo(user.Passkeys.FirstOrDefault(p => p.CredentialId.SequenceEqual(credentialId))));
 
    public Task<string?> GetNormalizedUserNameAsync(TUser user, CancellationToken cancellationToken)
        => Task.FromResult<string?>(user.NormalizedUserName);
 
    public Task<string> GetUserIdAsync(TUser user, CancellationToken cancellationToken)
        => Task.FromResult(user.Id);
 
    public Task<string?> GetUserNameAsync(TUser user, CancellationToken cancellationToken)
        => Task.FromResult<string?>(user.UserName);
 
    public Task SetNormalizedUserNameAsync(TUser user, string? normalizedName, CancellationToken cancellationToken)
    {
        user.NormalizedUserName = normalizedName;
        return Task.CompletedTask;
    }
 
    public Task SetUserNameAsync(TUser user, string? userName, CancellationToken cancellationToken)
    {
        user.UserName = userName;
        return Task.CompletedTask;
    }
 
    public Task<IdentityResult> UpdateAsync(TUser user, CancellationToken cancellationToken)
    {
        _users[user.Id] = user;
        return Task.FromResult(IdentityResult.Success);
    }
 
    public Task SetPasskeyAsync(TUser user, UserPasskeyInfo passkey, CancellationToken cancellationToken)
    {
        var passkeyEntity = user.Passkeys.FirstOrDefault(p => p.CredentialId.SequenceEqual(passkey.CredentialId));
        if (passkeyEntity is null)
        {
            user.Passkeys.Add(ToPocoUserPasskey(user, passkey));
        }
        else
        {
            passkeyEntity.Name = passkey.Name;
            passkeyEntity.SignCount = passkey.SignCount;
            passkeyEntity.IsBackedUp = passkey.IsBackedUp;
            passkeyEntity.IsUserVerified = passkey.IsUserVerified;
        }
        return Task.CompletedTask;
    }
 
    public Task<IList<UserPasskeyInfo>> GetPasskeysAsync(TUser user, CancellationToken cancellationToken)
        => Task.FromResult<IList<UserPasskeyInfo>>(user.Passkeys.Select(ToUserPasskeyInfo).ToList()!);
 
    public Task RemovePasskeyAsync(TUser user, byte[] credentialId, CancellationToken cancellationToken)
    {
        var passkey = user.Passkeys.SingleOrDefault(p => p.CredentialId.SequenceEqual(credentialId));
        if (passkey is not null)
        {
            user.Passkeys.Remove(passkey);
        }
 
        return Task.CompletedTask;
    }
 
    [return: NotNullIfNotNull(nameof(p))]
    private static UserPasskeyInfo? ToUserPasskeyInfo(PocoUserPasskey<string>? p)
        => p is null ? null : new(
            p.CredentialId,
            p.PublicKey,
            p.Name,
            p.CreatedAt,
            p.SignCount,
            p.Transports,
            p.IsUserVerified,
            p.IsBackupEligible,
            p.IsBackedUp,
            p.AttestationObject,
            p.ClientDataJson);
 
    [return: NotNullIfNotNull(nameof(p))]
    private static PocoUserPasskey<string>? ToPocoUserPasskey(TUser user, UserPasskeyInfo? p)
        => p is null ? null : new PocoUserPasskey<string>
        {
            UserId = user.Id,
            CredentialId = p.CredentialId,
            PublicKey = p.PublicKey,
            Name = p.Name,
            CreatedAt = p.CreatedAt,
            Transports = p.Transports,
            SignCount = p.SignCount,
            IsUserVerified = p.IsUserVerified,
            IsBackupEligible = p.IsBackupEligible,
            IsBackedUp = p.IsBackedUp,
            AttestationObject = p.AttestationObject,
            ClientDataJson = p.ClientDataJson,
        };
 
    public void Dispose()
    {
    }
}