Base template solution with tests, authentication, support for admin accounts, prepared tests for auth, basic error handling system used in auth.

This commit is contained in:
Matěj Kubíček
2026-04-18 17:37:38 +02:00
parent 5a2e76fefd
commit f50bec167f
101 changed files with 83814 additions and 0 deletions
@@ -0,0 +1,135 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
using TemplateWeb.Data;
using TemplateWeb.Interfaces;
using TemplateWeb.Models;
using TemplateWeb.Models.DbModels;
namespace TemplateWeb.Services;
public class AuthService : IAuthService
{
private readonly AppDbContext _context;
private readonly ILogger<AuthService> _logger;
private readonly IHttpContextAccessor _httpContextAccessor;
public AuthService(
AppDbContext context, ILogger<AuthService> logger, IHttpContextAccessor httpContextAccessor)
{
_context = context;
_logger = logger;
_httpContextAccessor = httpContextAccessor;
}
public async Task<ServiceResult<UserModel>> FindUserByEmailOrUsernameAsync(string emailOrUsername)
{
if (string.IsNullOrWhiteSpace(emailOrUsername))
return ServiceResult<UserModel>.Failure("Email or username is required.");
var trimmed = emailOrUsername.Trim().ToLower();
var user = await _context.Users.SingleOrDefaultAsync(u =>
u.Email.Equals(trimmed, StringComparison.CurrentCultureIgnoreCase) ||
u.UserName.Equals(trimmed, StringComparison.CurrentCultureIgnoreCase));
return user == null ? ServiceResult<UserModel>.Failure("User not found.") : ServiceResult<UserModel>.Ok(user);
}
public async Task<ServiceResult> AuthenticateAsync(UserModel user, bool rememberMe)
{
var claims = new List<Claim>
{
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
new(ClaimTypes.Name, user.UserName),
new(ClaimTypes.Email, user.Email),
new(ClaimTypes.Role, user.Role.ToString()),
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
_logger.LogError("HttpContext is null during authentication.");
return ServiceResult.Failure("An internal error occurred.");
}
await httpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties
{
IsPersistent = rememberMe,
ExpiresUtc = rememberMe
? DateTimeOffset.UtcNow.AddDays(30)
: DateTimeOffset.UtcNow.AddHours(8)
});
return ServiceResult.Ok();
}
public async Task<ServiceResult<UserModel>> RegisterAsync(AuthRegisterViewModel viewModel)
{
var userNameTrimmed = viewModel.UserName.Trim();
var emailTrimmed = viewModel.Email.Trim().ToLower();
var existingUser = await _context.Users.AnyAsync(u =>
u.UserName.Equals(userNameTrimmed, StringComparison.CurrentCultureIgnoreCase) ||
u.Email.Equals(emailTrimmed, StringComparison.CurrentCultureIgnoreCase));
if (existingUser)
{
return ServiceResult<UserModel>.Failure("Username or Email already exists.");
}
var user = new UserModel
{
UserName = userNameTrimmed,
Email = emailTrimmed,
Password = BCrypt.Net.BCrypt.HashPassword(viewModel.Password),
Role = UserRole.User
};
try
{
await _context.Users.AddAsync(user);
await _context.SaveChangesAsync();
_logger.LogInformation("User {UserName} registered.", user.UserName);
return ServiceResult<UserModel>.Ok(user);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error registering user {UserName}", user.UserName);
return ServiceResult<UserModel>.Failure("An error occurred while saving the user.");
}
}
public async Task LogoutAsync()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext != null)
{
await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
_logger.LogInformation("User logged out.");
}
}
public async Task<ServiceResult> DeleteUserAsync(UserModel user)
{
try
{
_context.Users.Remove(user);
await _context.SaveChangesAsync();
_logger.LogInformation("User {UserId} deleted.", user.Id);
return ServiceResult.Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting user {UserId}", user.Id);
return ServiceResult.Failure("An error occurred while deleting the user.");
}
}
}