181 lines
5.2 KiB
C#

namespace AAIntegration.SimmonsBank.API.Services;
using AutoMapper;
using BCrypt.Net;
using AAIntegration.SimmonsBank.API.Entities;
using AAIntegration.SimmonsBank.API.Config;
using AAIntegration.SimmonsBank.API.Models.Users;
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.Extensions.Options;
public interface IUserService
{
AuthenticateResponse Authenticate(AuthenticateRequest model);
void Register(RegisterRequest model);
IEnumerable<User> GetAll();
User GetById(int id);
void Update(int id, UserUpdateRequest model);
void Delete(int id);
Dictionary<string, int> GetAllApiKeys();
string GetUserApiKey(int id);
void InvalidateApiKey(string apiKey);
string CreateUserApiKey(int id);
}
public class UserService : IUserService
{
private DataContext _context;
private readonly IMapper _mapper;
private readonly IApiKeyService _apiKeyService;
private readonly IOptions<AppSettings> _appSettings;
public UserService(
DataContext context,
IMapper mapper,
IApiKeyService apiKeyService,
IOptions<AppSettings> appSettings)
{
_apiKeyService = apiKeyService;
_context = context;
_mapper = mapper;
_appSettings = appSettings;
}
public AuthenticateResponse Authenticate(AuthenticateRequest model)
{
var user = _context.Users.SingleOrDefault(x => x.Username == model.Username);
// validate
if (user == null || !BCrypt.Verify(model.Password, user.PasswordHash))
throw new AppException("Username or password is incorrect");
// Authentication successful
// Create Claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
};
// Create JWT Token
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Value.Secret));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256Signature);
var tokeOptions = new JwtSecurityToken(
issuer: _appSettings.Value.APIUrl,
audience: _appSettings.Value.APIUrl,
claims: claims,
expires: DateTime.Now.AddDays(7),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
var response = _mapper.Map<AuthenticateResponse>(user);
response.Token = tokenString;
return response;
}
public void Register(RegisterRequest model)
{
// validate username
if (_context.Users.Any(x => x.Username == model.Username))
throw new AppException("Username '" + model.Username + "' is already taken");
// validate email
if (_context.Users.Any(x => x.Email == model.Email))
throw new AppException("Email '" + model.Email + "' is already taken");
// map model to new user object
var user = _mapper.Map<User>(model);
// hash password
user.PasswordHash = BCrypt.HashPassword(model.Password);
// save user
_context.Users.Add(user);
_context.SaveChanges();
}
public IEnumerable<User> GetAll()
{
return _context.Users;
}
public User GetById(int id)
{
return getUser(id);
}
public void Update(int id, UserUpdateRequest model)
{
var user = getUser(id);
// validate
if (model.Email != user.Email && _context.Users.Any(x => x.Email == model.Email))
throw new AppException("User with the email '" + model.Email + "' already exists");
// hash password if it was entered
if (!string.IsNullOrEmpty(model.Password))
user.PasswordHash = BCrypt.HashPassword(model.Password);
// copy model to user and save
_mapper.Map(model, user);
_context.Users.Update(user);
_context.SaveChanges();
}
public void Delete(int id)
{
var user = getUser(id);
_context.Users.Remove(user);
_context.SaveChanges();
}
public string CreateUserApiKey(int id)
{
var user = getUser(id);
user.ApiKey = _apiKeyService.GenerateApiKey();
_context.Users.Update(user);
_context.SaveChanges();
return user.ApiKey;
}
public Dictionary<string, int> GetAllApiKeys()
{
return _context.Users
.Where(u => u.ApiKey != null)
.ToDictionary(u => u.ApiKey, u => u.Id);
}
public string GetUserApiKey(int id)
{
return this.getUser(id).ApiKey;
}
public void InvalidateApiKey(string apiKey)
{
User? user = _context.Users.FirstOrDefault(u => u.ApiKey == apiKey);
if (user is not null)
user.ApiKey = null;
}
// helper methods
private User getUser(int id)
{
var user = _context.Users.Find(id);
if (user == null) throw new KeyNotFoundException("User not found");
return user;
}
}