using System.Security.Cryptography; using System.Text; using Microsoft.EntityFrameworkCore; using timetracker.Shared; namespace timetracker.Data; public class AuthService(IDbContextFactory factory, UserNotificationService notifier) : IAuthService { public async Task LoginAsync(string username, string password) { await using var db = await factory.CreateDbContextAsync(); var user = await db.Users .FirstOrDefaultAsync(u => u.Username == username); if (user == null) return null; return VerifyPassword(password, user.PasswordHash, user.PasswordSalt) ? user : null; } public async Task> GetAllUsersAsync() { await using var db = await factory.CreateDbContextAsync(); return await db.Users .OrderBy(u => u.Username) .ToListAsync(); } public async Task DeleteUserAsync(int userId) { await using var db = await factory.CreateDbContextAsync(); var user = await db.Users.FindAsync(userId); if (user != null) { db.Users.Remove(user); await db.SaveChangesAsync(); await notifier.NotifyUserDeletedAsync(userId); await notifier.NotifyUsersChangedAsync(); } } public async Task RenameUserAsync(int userId, string newUsername) { if (string.IsNullOrWhiteSpace(newUsername) || newUsername.Length < 3) return "Benutzername muss mindestens 3 Zeichen lang sein."; await using var db = await factory.CreateDbContextAsync(); if (await db.Users.AnyAsync(u => u.Username == newUsername && u.Id != userId)) return "Benutzername bereits vergeben."; var user = await db.Users.FindAsync(userId); if (user == null) return "Benutzer nicht gefunden."; user.Username = newUsername; await db.SaveChangesAsync(); await notifier.NotifyUsersChangedAsync(); return null; } public async Task<(User? User, string? Error)> RegisterAsync(string username, string password, string? honeypot = null) { if (string.IsNullOrWhiteSpace(username) || username.Length < 3) return (null, "Benutzername muss mindestens 3 Zeichen lang sein."); if (string.IsNullOrWhiteSpace(password) || password.Length < 6) return (null, "Passwort muss mindestens 6 Zeichen lang sein."); await using var db = await factory.CreateDbContextAsync(); if (await db.Users.AnyAsync(u => u.Username == username)) return (null, "Benutzername bereits vergeben."); var (hash, salt) = HashPassword(password); var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt }; db.Users.Add(user); await db.SaveChangesAsync(); await notifier.NotifyUsersChangedAsync(); return (user, null); } private static (string hash, string salt) HashPassword(string password) { var saltBytes = RandomNumberGenerator.GetBytes(32); var salt = Convert.ToBase64String(saltBytes); var hash = ComputeHash(password, salt); return (hash, salt); } private static bool VerifyPassword(string password, string hash, string salt) => ComputeHash(password, salt) == hash; private static string ComputeHash(string password, string salt) { var hash = Rfc2898DeriveBytes.Pbkdf2( Encoding.UTF8.GetBytes(password), Convert.FromBase64String(salt), iterations: 200_000, HashAlgorithmName.SHA256, outputLength: 32); return Convert.ToBase64String(hash); } }