using System.Collections.Concurrent; using System.Collections.Immutable; using System.Security.Cryptography; using Phantom.Common.Data.Web.Users; using Phantom.Controller.Database; using Phantom.Controller.Database.Repositories; namespace Phantom.Controller.Services.Users; sealed class UserLoginManager { private const int SessionIdBytes = 20; private readonly ConcurrentDictionary<Guid, List<ImmutableArray<byte>>> sessionTokensByUserGuid = new (); private readonly UserManager userManager; private readonly PermissionManager permissionManager; private readonly IDbContextProvider dbProvider; public UserLoginManager(UserManager userManager, PermissionManager permissionManager, IDbContextProvider dbProvider) { this.userManager = userManager; this.permissionManager = permissionManager; this.dbProvider = dbProvider; } public async Task<LogInSuccess?> LogIn(string username, string password) { var user = await userManager.GetAuthenticated(username, password); if (user == null) { return null; } var token = ImmutableArray.Create(RandomNumberGenerator.GetBytes(SessionIdBytes)); var sessionTokens = sessionTokensByUserGuid.GetOrAdd(user.UserGuid, static _ => new List<ImmutableArray<byte>>()); lock (sessionTokens) { sessionTokens.Add(token); } await using (var db = dbProvider.Lazy()) { var auditLogWriter = new AuditLogRepository(db).Writer(user.UserGuid); auditLogWriter.UserLoggedIn(user); await db.Ctx.SaveChangesAsync(); } return new LogInSuccess(user.UserGuid, await permissionManager.FetchPermissionsForUserId(user.UserGuid), token); } public async Task LogOut(Guid userGuid, ImmutableArray<byte> sessionToken) { if (!sessionTokensByUserGuid.TryGetValue(userGuid, out var sessionTokens)) { return; } lock (sessionTokens) { if (sessionTokens.RemoveAll(token => token.SequenceEqual(sessionToken)) == 0) { return; } } await using var db = dbProvider.Lazy(); var auditLogWriter = new AuditLogRepository(db).Writer(userGuid); auditLogWriter.UserLoggedOut(userGuid); await db.Ctx.SaveChangesAsync(); } }