215 lines
6.2 KiB
C#
215 lines
6.2 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using LittleShop.Data;
|
|
using LittleShop.Models;
|
|
using LittleShop.DTOs;
|
|
|
|
namespace LittleShop.Services;
|
|
|
|
public class AuthService : IAuthService
|
|
{
|
|
private readonly LittleShopContext _context;
|
|
private readonly IConfiguration _configuration;
|
|
|
|
public AuthService(LittleShopContext context, IConfiguration configuration)
|
|
{
|
|
_context = context;
|
|
_configuration = configuration;
|
|
}
|
|
|
|
public async Task<AuthResponseDto?> LoginAsync(LoginDto loginDto)
|
|
{
|
|
var user = await _context.Users
|
|
.FirstOrDefaultAsync(u => u.Username == loginDto.Username && u.IsActive);
|
|
|
|
if (user == null || !VerifyPassword(loginDto.Password, user.PasswordHash))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var token = GenerateJwtToken(user);
|
|
return new AuthResponseDto
|
|
{
|
|
Token = token,
|
|
Username = user.Username,
|
|
ExpiresAt = DateTime.UtcNow.AddHours(24)
|
|
};
|
|
}
|
|
|
|
public async Task<bool> SeedDefaultUserAsync()
|
|
{
|
|
if (await _context.Users.AnyAsync())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var defaultUser = new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Username = "admin",
|
|
PasswordHash = HashPassword("admin"),
|
|
CreatedAt = DateTime.UtcNow,
|
|
IsActive = true
|
|
};
|
|
|
|
_context.Users.Add(defaultUser);
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<UserDto?> CreateUserAsync(CreateUserDto createUserDto)
|
|
{
|
|
if (await _context.Users.AnyAsync(u => u.Username == createUserDto.Username))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var user = new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Username = createUserDto.Username,
|
|
PasswordHash = HashPassword(createUserDto.Password),
|
|
CreatedAt = DateTime.UtcNow,
|
|
IsActive = true
|
|
};
|
|
|
|
_context.Users.Add(user);
|
|
await _context.SaveChangesAsync();
|
|
|
|
return new UserDto
|
|
{
|
|
Id = user.Id,
|
|
Username = user.Username,
|
|
CreatedAt = user.CreatedAt,
|
|
IsActive = user.IsActive
|
|
};
|
|
}
|
|
|
|
public async Task<UserDto?> GetUserByIdAsync(Guid id)
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
if (user == null) return null;
|
|
|
|
return new UserDto
|
|
{
|
|
Id = user.Id,
|
|
Username = user.Username,
|
|
CreatedAt = user.CreatedAt,
|
|
IsActive = user.IsActive
|
|
};
|
|
}
|
|
|
|
public async Task<IEnumerable<UserDto>> GetAllUsersAsync()
|
|
{
|
|
return await _context.Users
|
|
.Select(u => new UserDto
|
|
{
|
|
Id = u.Id,
|
|
Username = u.Username,
|
|
CreatedAt = u.CreatedAt,
|
|
IsActive = u.IsActive
|
|
})
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<bool> DeleteUserAsync(Guid id)
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
if (user == null) return false;
|
|
|
|
user.IsActive = false;
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> UpdateUserAsync(Guid id, UpdateUserDto updateUserDto)
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
if (user == null) return false;
|
|
|
|
if (!string.IsNullOrEmpty(updateUserDto.Username))
|
|
{
|
|
if (await _context.Users.AnyAsync(u => u.Username == updateUserDto.Username && u.Id != id))
|
|
{
|
|
return false;
|
|
}
|
|
user.Username = updateUserDto.Username;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(updateUserDto.Password))
|
|
{
|
|
user.PasswordHash = HashPassword(updateUserDto.Password);
|
|
}
|
|
|
|
if (updateUserDto.IsActive.HasValue)
|
|
{
|
|
user.IsActive = updateUserDto.IsActive.Value;
|
|
}
|
|
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
private string GenerateJwtToken(User user)
|
|
{
|
|
var jwtKey = _configuration["Jwt:Key"] ?? "YourSuperSecretKeyThatIsAtLeast32CharactersLong!";
|
|
var jwtIssuer = _configuration["Jwt:Issuer"] ?? "LittleShop";
|
|
var jwtAudience = _configuration["Jwt:Audience"] ?? "LittleShop";
|
|
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
var key = Encoding.ASCII.GetBytes(jwtKey);
|
|
var tokenDescriptor = new SecurityTokenDescriptor
|
|
{
|
|
Subject = new ClaimsIdentity(new[]
|
|
{
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
new Claim(ClaimTypes.Name, user.Username)
|
|
}),
|
|
Expires = DateTime.UtcNow.AddHours(24),
|
|
Issuer = jwtIssuer,
|
|
Audience = jwtAudience,
|
|
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
|
};
|
|
|
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
|
return tokenHandler.WriteToken(token);
|
|
}
|
|
|
|
private static string HashPassword(string password)
|
|
{
|
|
using var rng = RandomNumberGenerator.Create();
|
|
var salt = new byte[16];
|
|
rng.GetBytes(salt);
|
|
|
|
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256);
|
|
var hash = pbkdf2.GetBytes(32);
|
|
|
|
var hashBytes = new byte[48];
|
|
Array.Copy(salt, 0, hashBytes, 0, 16);
|
|
Array.Copy(hash, 0, hashBytes, 16, 32);
|
|
|
|
return Convert.ToBase64String(hashBytes);
|
|
}
|
|
|
|
private static bool VerifyPassword(string password, string hashedPassword)
|
|
{
|
|
var hashBytes = Convert.FromBase64String(hashedPassword);
|
|
var salt = new byte[16];
|
|
Array.Copy(hashBytes, 0, salt, 0, 16);
|
|
|
|
using var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256);
|
|
var hash = pbkdf2.GetBytes(32);
|
|
|
|
for (int i = 0; i < 32; i++)
|
|
{
|
|
if (hashBytes[i + 16] != hash[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
} |