98 lines
3.6 KiB
C#
98 lines
3.6 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using timetracker.Shared;
|
|
|
|
namespace timetracker.Data;
|
|
|
|
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory, UserNotificationService notifier) : IAuthService
|
|
{
|
|
public async Task<User?> 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<List<User>> 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<string?> 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) {
|
|
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);
|
|
}
|
|
}
|