using AuthHex.Models.IOs; using AuthHex.DTOs; using System.Text.Json; using System.Net.Mail; using System.Net; using System.Security.Cryptography; using System.Text; namespace AuthHex.Utility { public class ThirdPartyService { private readonly IConfiguration _config; private readonly HttpClient _httpClient; public ThirdPartyService(IConfiguration config, HttpClient httpClient) { _config = config; _httpClient = httpClient; } public async Task EmailConfiguration(string email, string otpCode, string? subject = null, string? body = null) { try { if (string.IsNullOrWhiteSpace(email)) { return new ApiResponse { StatusCode = 400, Message = "Email is required" }; } if (string.IsNullOrWhiteSpace(otpCode)) { return new ApiResponse { StatusCode = 400, Message = "OTP code is required" }; } var testEmail = email; var result = await SendEmailAsync(testEmail, otpCode, subject, body); return new ApiResponse { StatusCode = result.Success ? 200 : 500, Data = new { testEmail, success = result.Success }, Message = result.Message }; } catch (Exception ex) { return new ApiResponse { StatusCode = 500, Message = ex.Message }; } } public async Task SendSmsAsync(string mobileNumber, string otpCode, string? message = null) { try { if (string.IsNullOrWhiteSpace(mobileNumber)) { return new OtpResponse { StatusCode = 400, Message = "Mobile number is required" }; } if (string.IsNullOrWhiteSpace(otpCode)) { return new OtpResponse { StatusCode = 400, Message = "OTP code is required" }; } string contactNumber = mobileNumber; string apiUrl = _config["Sms:ApiUrl"] ?? "https://backup.introps.com/Sms_common/send_sms"; string apiKey = Environment.GetEnvironmentVariable("SMS_API_KEY") ?? _config["Sms:ApiKey"] ?? "8989898989"; var msgText = string.IsNullOrWhiteSpace(message) ? $"Your otp code is: {otpCode}" : message; var smsRequest = new SmsRequest { api_key = apiKey, phone_number = contactNumber, msg_text = msgText }; var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair("api_key", smsRequest.api_key), new KeyValuePair("phone_number", smsRequest.phone_number), new KeyValuePair("msg_text", smsRequest.msg_text) }); var response = await _httpClient.PostAsync(apiUrl, formContent); var responseContent = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode && responseContent.Contains("agent:d")) { string jsonString = responseContent.Split(new[] { "agent:d" }, StringSplitOptions.None)[1]; var responseJson = JsonSerializer.Deserialize>(jsonString); if (responseJson is not null && responseJson.TryGetValue("stt", out var statusValue) && statusValue?.ToString() == "ok") { Console.WriteLine($"SMS OTP {otpCode} sent to {contactNumber}"); return new OtpResponse { StatusCode = 200, Message = "OTP sent successfully." }; } } Console.WriteLine($"Failed to send SMS OTP to {contactNumber}. Response: {responseContent}"); return new OtpResponse { StatusCode = 500, Message = "Failed to send OTP.", ApiResponse = responseContent }; } catch (Exception ex) { Console.WriteLine($"Exception sending OTP: {ex.Message}"); return new OtpResponse { StatusCode = 500, Message = "Exception occurred while sending OTP.", Error = ex.Message }; } } private async Task<(bool Success, string Message)> SendEmailAsync(string email, string code, string? subject = null, string? body = null) { try { if (string.IsNullOrWhiteSpace(email)) { return (false, "Email address is required"); } string smtpHost = _config["SmtpSettings:SmtpServer"] ?? "smtp.hostinger.com"; int smtpPort = int.Parse(_config["SmtpSettings:SmtpPort"] ?? "587"); string? senderEmail = _config["SmtpSettings:SenderEmail"] ?? Environment.GetEnvironmentVariable("SMTP_USER"); string? senderPassword = _config["SmtpSettings:SmtpPassword"] ?? Environment.GetEnvironmentVariable("SMTP_PASSWORD"); string senderName = _config["SmtpSettings:SenderName"] ?? "Hexa Fitness"; if (string.IsNullOrWhiteSpace(senderEmail) || string.IsNullOrWhiteSpace(senderPassword)) { return (false, "Email configuration is missing"); } using (var smtpClient = new SmtpClient(smtpHost, smtpPort)) { smtpClient.Credentials = new NetworkCredential(senderEmail, senderPassword); smtpClient.EnableSsl = true; smtpClient.Timeout = 10000; var emailSubject = string.IsNullOrWhiteSpace(subject) ? "Your OTP Code" : subject; var emailBody = string.IsNullOrWhiteSpace(body) ? $@"

OTP Verification

Your OTP code is:

{code}

This code will expire in 10 minutes.

If you did not request this code, please ignore this email.

" : body.Replace("{code}", code); var mailMessage = new MailMessage { From = new MailAddress(senderEmail, senderName), Subject = emailSubject, Body = emailBody, IsBodyHtml = true }; mailMessage.To.Add(email); await smtpClient.SendMailAsync(mailMessage); Console.WriteLine($"Email OTP {code} sent to {email}"); return (true, "Email sent successfully"); } } catch (SmtpException ex) { Console.WriteLine($"SMTP Error sending email: {ex.Message}"); return (false, $"Failed to send email: {ex.Message}"); } catch (Exception ex) { Console.WriteLine($"Exception sending email: {ex.Message}"); return (false, $"Exception occurred: {ex.Message}"); } } public string GenerateSecretKey(int? length = null) { var keyLength = length ?? int.Parse(_config["GoogleAuthenticator:SecretKeyLength"] ?? "32"); const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; var random = new Random(); var secret = new StringBuilder(keyLength); for (int i = 0; i < keyLength; i++) { secret.Append(chars[random.Next(chars.Length)]); } return secret.ToString(); } public string GenerateQrCodeData(string userEmail, string secretKey, string? issuer = null) { var appIssuer = issuer ?? _config["GoogleAuthenticator:Issuer"] ?? "AuthHex"; var encodedIssuer = Uri.EscapeDataString(appIssuer); var encodedEmail = Uri.EscapeDataString(userEmail); return $"otpauth://totp/{encodedIssuer}:{encodedEmail}?secret={secretKey}&issuer={encodedIssuer}"; } public string GenerateTotpCode(string secretKey, DateTime? timeStamp = null) { var time = timeStamp ?? DateTime.UtcNow; var unixTime = ((DateTimeOffset)time).ToUnixTimeSeconds(); var timeWindow = int.Parse(_config["GoogleAuthenticator:TimeWindowSeconds"] ?? "30"); var timeStep = unixTime / timeWindow; var secretBytes = Base32Decode(secretKey); var timeBytes = BitConverter.GetBytes(timeStep); if (BitConverter.IsLittleEndian) { Array.Reverse(timeBytes); } using (var hmac = new HMACSHA1(secretBytes)) { var hash = hmac.ComputeHash(timeBytes); var offset = hash[hash.Length - 1] & 0x0F; var code = ((hash[offset] & 0x7F) << 24) | ((hash[offset + 1] & 0xFF) << 16) | ((hash[offset + 2] & 0xFF) << 8) | (hash[offset + 3] & 0xFF); return (code % 1000000).ToString("D6"); } } public bool ValidateTotpCode(string secretKey, string userCode, int? windowSize = null) { if (string.IsNullOrWhiteSpace(secretKey) || string.IsNullOrWhiteSpace(userCode)) { return false; } var validationWindow = windowSize ?? int.Parse(_config["GoogleAuthenticator:ValidationWindowSize"] ?? "1"); var timeWindow = int.Parse(_config["GoogleAuthenticator:TimeWindowSeconds"] ?? "30"); var currentTime = DateTime.UtcNow; for (int i = -validationWindow; i <= validationWindow; i++) { var timeToCheck = currentTime.AddSeconds(i * timeWindow); var generatedCode = GenerateTotpCode(secretKey, timeToCheck); if (generatedCode == userCode) { return true; } } return false; } public async Task InitiateTwoFASetup(string userEmail, string? userName = null) { try { if (string.IsNullOrWhiteSpace(userEmail)) { return new ApiResponse { StatusCode = 400, Message = "Email is required" }; } var secretKey = GenerateSecretKey(); var issuer = _config["GoogleAuthenticator:Issuer"] ?? "AuthHex"; var qrCodeData = GenerateQrCodeData(userEmail, secretKey, issuer); var displayName = string.IsNullOrWhiteSpace(userName) ? userEmail : userName; return new ApiResponse { StatusCode = 200, Data = new { secretKey, qrCodeData, manualEntryKey = FormatSecretForManualEntry(secretKey), userEmail, displayName, issuer, setupInstructions = new { step1 = "Install Google Authenticator app on your mobile device", step2 = "Scan the QR code or enter the manual key", step3 = "Enter the 6-digit code from your app to verify setup" } }, Message = "2FA setup initiated. Please verify with your authenticator app to complete setup." }; } catch (Exception ex) { return new ApiResponse { StatusCode = 500, Message = ex.Message }; } } public async Task CompleteTwoFASetup(string secretKey, string verificationCode, string? userEmail = null) { try { if (string.IsNullOrWhiteSpace(secretKey)) { return new ApiResponse { StatusCode = 400, Message = "Secret key is required" }; } if (string.IsNullOrWhiteSpace(verificationCode)) { return new ApiResponse { StatusCode = 400, Message = "Verification code is required" }; } // Validate the code to ensure user has successfully set up their authenticator var isValid = ValidateTotpCode(secretKey, verificationCode); if (!isValid) { return new ApiResponse { StatusCode = 400, Message = "Invalid verification code. Please check your authenticator app and try again." }; } return new ApiResponse { StatusCode = 200, Data = new { secretKey, isVerified = true, verifiedAt = DateTime.UtcNow, userEmail, backupCodes = GenerateBackupCodes() // Generate backup codes for recovery }, Message = "2FA setup completed successfully. Please save your backup codes in a secure location." }; } catch (Exception ex) { return new ApiResponse { StatusCode = 500, Message = ex.Message }; } } public async Task VerifyTwoFACode(string secretKey, string code) { try { if (string.IsNullOrWhiteSpace(secretKey)) { return new ApiResponse { StatusCode = 400, Message = "Secret key is required" }; } if (string.IsNullOrWhiteSpace(code)) { return new ApiResponse { StatusCode = 400, Message = "Verification code is required" }; } var isValid = ValidateTotpCode(secretKey, code); return new ApiResponse { StatusCode = isValid ? 200 : 400, Data = new { isValid, timestamp = DateTime.UtcNow, remainingTime = GetRemainingTimeInCurrentWindow() }, Message = isValid ? "Code verified successfully" : "Invalid or expired code" }; } catch (Exception ex) { return new ApiResponse { StatusCode = 500, Message = ex.Message }; } } public async Task DisableTwoFA(string secretKey, string verificationCode) { try { if (string.IsNullOrWhiteSpace(secretKey)) { return new ApiResponse { StatusCode = 400, Message = "Secret key is required" }; } if (string.IsNullOrWhiteSpace(verificationCode)) { return new ApiResponse { StatusCode = 400, Message = "Verification code is required to disable 2FA" }; } // Require valid code to disable 2FA for security var isValid = ValidateTotpCode(secretKey, verificationCode); if (!isValid) { return new ApiResponse { StatusCode = 400, Message = "Invalid verification code. Cannot disable 2FA without valid code." }; } return new ApiResponse { StatusCode = 200, Data = new { disabled = true, disabledAt = DateTime.UtcNow }, Message = "2FA has been disabled successfully" }; } catch (Exception ex) { return new ApiResponse { StatusCode = 500, Message = ex.Message }; } } private string[] GenerateBackupCodes(int count = 10) { var codes = new string[count]; var random = new Random(); for (int i = 0; i < count; i++) { codes[i] = random.Next(100000, 999999).ToString(); } return codes; } private int GetRemainingTimeInCurrentWindow() { var timeWindow = int.Parse(_config["GoogleAuthenticator:TimeWindowSeconds"] ?? "30"); var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var remainingSeconds = timeWindow - (currentTime % timeWindow); return (int)remainingSeconds; } private byte[] Base32Decode(string input) { if (string.IsNullOrEmpty(input)) throw new ArgumentException("Input cannot be null or empty"); input = input.TrimEnd('=').ToUpper(); var output = new List(); var bits = 0; var bitsCount = 0; foreach (char c in input) { var value = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".IndexOf(c); if (value < 0) throw new ArgumentException($"Invalid character in Base32 string: {c}"); bits = (bits << 5) | value; bitsCount += 5; if (bitsCount >= 8) { output.Add((byte)(bits >> (bitsCount - 8))); bitsCount -= 8; } } return output.ToArray(); } private string FormatSecretForManualEntry(string secret) { return string.Join(" ", Enumerable.Range(0, secret.Length / 4) .Select(i => secret.Substring(i * 4, 4))); } } }