using Manager.Services; using ManagerService.Data; using ManagerService.DTOs; using ManagerService.Helpers; using ManagerService.Service.Services; using ManagerService.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Mqtt.Client.AspNetCore.Services; using NSwag.Annotations; using System; using System.Collections.Generic; using System.Net; using System.Linq; using System.Threading.Tasks; namespace ManagerService.Service.Controllers { /// /// Authentication controller /// [ApiController, Route("api/[controller]")] [Authorize] [OpenApiTag("Authentication", Description = "Authentication management")] public class AuthenticationController : ControllerBase { private readonly ILogger _logger; private readonly TokensService _tokensService; /*private readonly UserDatabaseService _UserDatabaseService; private readonly DeviceDatabaseService _DeviceDatabaseService; private readonly ConfigurationDatabaseService _ConfigurationDatabaseService;*/ private readonly MyInfoMateDbContext _myInfoMateDbContext; private readonly ProfileLogic _profileLogic; private readonly IEmailService _emailService; private readonly IConfiguration _configuration; public AuthenticationController(ILogger logger, TokensService tokensService, MyInfoMateDbContext myInfoMateDbContext, ProfileLogic profileLogic, IEmailService emailService, IConfiguration configuration/*UserDatabaseService UserDatabaseService, DeviceDatabaseService DeviceDatabaseService, ConfigurationDatabaseService ConfigurationDatabaseService*/) { _logger = logger; _tokensService = tokensService; _myInfoMateDbContext = myInfoMateDbContext; _profileLogic = profileLogic; _emailService = emailService; _configuration = configuration; //_UserDatabaseService = UserDatabaseService; //_DeviceDatabaseService = DeviceDatabaseService; //_ConfigurationDatabaseService = ConfigurationDatabaseService; } /// /// Authenticate (business) /// /// user email /// user password /// Token descriptor private ObjectResult Authenticate(string email, string password) { try { #if DEBUG email = "test@email.be"; password = "kljqsdkljqsd"; // password = "kljqsdkljqsd"; // W/7aj4NB60i3YFKJq50pbw== #endif // Set user token ? var user = _myInfoMateDbContext.Users.FirstOrDefault(u => u.Email.ToLower() == email.ToLower()); //var user = _UserDatabaseService.GetByEmail(email.ToLower()); if (user == null) throw new KeyNotFoundException("User not found"); var token = _tokensService.Authenticate(user, password); MqttClientService.SetServices(_myInfoMateDbContext);//_DeviceDatabaseService, _ConfigurationDatabaseService); return new OkObjectResult(token); } catch (UnauthorizedAccessException ex) { _logger?.LogError(ex, $"Authentication error for user '{email}': unauthorized access"); return Unauthorized(ex); } catch (Exception ex) { _logger?.LogError(ex, $"Authenticate error for user '{email}'"); return Problem($"Authenticate error for user '{email}': {ex.Message}"); } } /// /// Authenticate with form parameters (used by Swagger test client) /// /// Swagger token request /// Token descriptor [AllowAnonymous] [HttpPost("Token")] [Consumes("application/x-www-form-urlencoded")] [ProducesResponseType(typeof(TokenDTO), (int) HttpStatusCode.OK)] [ProducesResponseType(typeof(string), (int) HttpStatusCode.Unauthorized)] [ProducesResponseType(typeof(string), (int) HttpStatusCode.InternalServerError)] public ObjectResult AuthenticateWithForm([FromForm] SwaggerTokenRequest tokenRequest) { return Authenticate(tokenRequest.username, tokenRequest.password); } /// /// Authenticate with Json parameters (used by most clients) /// /// Login DTO /// Token descriptor [AllowAnonymous] [HttpPost("Authenticate")] [Consumes("application/json")] [ProducesResponseType(typeof(TokenDTO), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(string), (int)HttpStatusCode.Unauthorized)] [ProducesResponseType(typeof(string), (int)HttpStatusCode.InternalServerError)] public ObjectResult AuthenticateWithJson([FromBody] LoginDTO login) { return Authenticate(login.email.ToLower(), login.password); } /// /// Request a password reset link by email. Always returns 200 (does not reveal /// whether the email exists) to avoid leaking which addresses have an account. /// /// Email of the account [AllowAnonymous] [HttpPost("forgot-password")] [Consumes("application/json")] [ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)] public async Task ForgotPassword([FromBody] ForgotPasswordDTO dto) { try { var email = dto?.email?.Trim().ToLowerInvariant(); var user = string.IsNullOrEmpty(email) ? null : _myInfoMateDbContext.Users.FirstOrDefault(u => u.Email.ToLower() == email); if (user != null) { var rawToken = PasswordTokenHelper.GenerateRawToken(); user.PasswordTokenHash = PasswordTokenHelper.Hash(rawToken); user.PasswordTokenExpiresAt = PasswordTokenHelper.DefaultExpiry(); _myInfoMateDbContext.SaveChanges(); var managerAppUrl = _configuration["AppUrls:ManagerApp"]; var resetUrl = $"{managerAppUrl}/set-password?token={rawToken}"; await _emailService.SendPasswordResetEmailAsync(user.Email, user.FirstName, resetUrl); } return new OkObjectResult("If this email is registered, a reset link has been sent."); } catch (Exception ex) { _logger?.LogError(ex, "Forgot-password error"); return new OkObjectResult("If this email is registered, a reset link has been sent."); } } /// /// Consume a set-password/reset/invite token and set a new password /// /// Token and new password [AllowAnonymous] [HttpPost("set-password")] [Consumes("application/json")] [ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)] [ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)] public ObjectResult SetPassword([FromBody] SetPasswordDTO dto) { try { if (string.IsNullOrEmpty(dto?.token) || string.IsNullOrEmpty(dto?.newPassword) || dto.newPassword.Length < 8) throw new ArgumentException("Token and a password of at least 8 characters are required"); var tokenHash = PasswordTokenHelper.Hash(dto.token); var user = _myInfoMateDbContext.Users.FirstOrDefault(u => u.PasswordTokenHash == tokenHash); if (user == null || user.PasswordTokenExpiresAt == null || user.PasswordTokenExpiresAt < DateTime.UtcNow) throw new ArgumentException("This link is invalid or has expired"); user.Password = _profileLogic.HashPassword(dto.newPassword); user.PasswordTokenHash = null; user.PasswordTokenExpiresAt = null; _myInfoMateDbContext.SaveChanges(); return new OkObjectResult("Password updated"); } catch (ArgumentException ex) { return new BadRequestObjectResult(ex.Message) { }; } catch (Exception ex) { _logger?.LogError(ex, "Set-password error"); return new ObjectResult(ex.Message) { StatusCode = 500 }; } } } }