Files
Dhananjaya99 de7b40a147 ini
2026-07-08 11:11:50 +05:30

568 lines
20 KiB
C#

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<ApiResponse> 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<OtpResponse> 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<string, string>("api_key", smsRequest.api_key),
new KeyValuePair<string, string>("phone_number", smsRequest.phone_number),
new KeyValuePair<string, string>("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<Dictionary<string, object>>(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) ? $@"
<html>
<body style='font-family: Arial, sans-serif;'>
<h2>OTP Verification</h2>
<p>Your OTP code is:</p>
<h1 style='color: #007bff; letter-spacing: 2px;'>{code}</h1>
<p>This code will expire in 10 minutes.</p>
<p>If you did not request this code, please ignore this email.</p>
</body>
</html>" : 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<ApiResponse> 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<ApiResponse> 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<ApiResponse> 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<ApiResponse> 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<byte>();
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)));
}
}
}