using Microsoft.AspNetCore.Cryptography.KeyDerivation; namespace HyperBooru.Services; public interface IUserService { public UserSessionState UserSessionState { get; } } public class UserService : IUserService { public UserSessionState UserSessionState => globalUserService.GetSessionState(httpContext.Session.Id); private IHttpContextAccessor httpContextAccessor; private IGlobalUserService globalUserService; private HttpContext httpContext => httpContextAccessor.HttpContext!; public UserService( IHttpContextAccessor httpContextAccessor, IGlobalUserService globalUserService) { this.httpContextAccessor = httpContextAccessor; this.globalUserService = globalUserService; // HTTP context session states are discarded if no values // are set. Set a dummy value so that the session state // will not be discarded later when we actually need it. httpContext.Session.SetInt32("Persist", 1); } public static string HashPassword(string password) => Convert.ToBase64String( KeyDerivation.Pbkdf2( password, Array.Empty(), KeyDerivationPrf.HMACSHA512, 100_000, 512 / 8)); } public interface IGlobalUserService { public UserSessionState GetSessionState(string id); } public class GlobalUserService : IGlobalUserService { // TODO: prune this list periodically private Dictionary sessionStates = new(); public UserSessionState GetSessionState(string id) { sessionStates.TryGetValue(id, out var state); if(state is null) { state = new(); sessionStates[id] = state; } return state; } } public record UserSessionState { public event UserSessionStateChange OnStateChange; public bool ShowNsfw { get => showNsfw; set { showNsfw = value; OnStateChange.Invoke(this); } } private bool showNsfw = false; } public delegate void UserSessionStateChange(UserSessionState sessionState);