littleshop/LittleShop/Services/AuthService.cs
SysAdmin 622bdcf111 🔒 SECURITY: Emergency fixes and hardening
EMERGENCY FIXES:
 DELETE MockSilverPayService.cs - removed fake payment system
 REMOVE mock service registration - no fake payments possible
 GENERATE new JWT secret - replaced hardcoded key
 FIX HttpClient disposal - proper resource management

SECURITY HARDENING:
 ADD production guards - prevent mock services in production
 CREATE environment configs - separate dev/prod settings
 ADD config validation - fail fast on misconfiguration

IMPACT:
- Mock payment system completely eliminated
- JWT authentication now uses secure keys
- Production deployment now validated on startup
- Resource leaks fixed in TeleBot currency API

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-22 05:45:49 +01:00

230 lines
6.7 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<UserDto?> GetUserByUsernameAsync(string username)
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Username == username && u.IsActive);
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"] ?? throw new InvalidOperationException("JWT Key not configured. Set Jwt:Key in appsettings.json");
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;
}
}