using System; using System.Collections.Generic; using System.Configuration; using System.Net.Http; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; namespace TechSec602 { /// /// C# Client Library for 602TechSec API v2 Integration /// Provides easy integration for verification calls in web applications /// public class TechSec602Client : IDisposable { private readonly HttpClient _httpClient; private readonly TechSec602Config _config; public TechSec602Client(TechSec602Config config) { _config = config; _httpClient = new HttpClient() { Timeout = TimeSpan.FromMilliseconds(_config.TimeoutMs) }; if (!string.IsNullOrEmpty(_config.ApiKey)) { _httpClient.DefaultRequestHeaders.Add("X-API-Key", _config.ApiKey); } } public TechSec602Client() : this(TechSec602Config.FromAppSettings()) { } /// /// Primary verification method using v2 API format (URL-based) /// /// The URL to verify /// The client IP address (direct connection) /// Optional X-Forwarded-For IP for proxy scenarios. When provided, BOTH IPs are checked. /// Optional API key override public async Task VerifyRequestAsync(string url, string ipAddress = null, string forwardedForIp = null, string apiKey = null) { try { var requestApiKey = apiKey ?? _config.ApiKey; var clientIp = ipAddress ?? GetClientIpAddress(); var formData = new List> { new KeyValuePair("url", url) }; if (!string.IsNullOrEmpty(requestApiKey)) { formData.Add(new KeyValuePair("apiKey", requestApiKey)); } if (!string.IsNullOrEmpty(clientIp)) { formData.Add(new KeyValuePair("ipAddress", clientIp)); } // Add forwarded IP for proxy/load balancer scenarios // When provided, the API will check BOTH the direct IP and forwarded IP if (!string.IsNullOrEmpty(forwardedForIp)) { formData.Add(new KeyValuePair("forwardedForIp", forwardedForIp)); } var content = new FormUrlEncodedContent(formData); var response = await _httpClient.PostAsync($"{_config.BaseUrl}/api/verify", content); if (response.IsSuccessStatusCode) { var jsonResponse = await response.Content.ReadAsStringAsync(); var apiResult = JsonConvert.DeserializeObject(jsonResponse); return new VerificationResult { IsAllowed = apiResult.IsAllowed, Reason = apiResult.Reason, ProcessingTimeMs = apiResult.ProcessingTimeMs, Success = true }; } else { return new VerificationResult { IsAllowed = _config.FailOpen, Reason = $"HTTP {response.StatusCode}: {response.ReasonPhrase}", Success = false }; } } catch (TaskCanceledException) { // Timeout occurred return new VerificationResult { IsAllowed = _config.FailOpen, Reason = "Request timeout", Success = false }; } catch (Exception ex) { // Network or other error return new VerificationResult { IsAllowed = _config.FailOpen, Reason = ex.Message, Success = false }; } } /// /// Synchronous verification method (not recommended for production) /// /// The URL to verify /// The client IP address (direct connection) /// Optional X-Forwarded-For IP for proxy scenarios. When provided, BOTH IPs are checked. /// Optional API key override public VerificationResult VerifyRequest(string url, string ipAddress = null, string forwardedForIp = null, string apiKey = null) { return VerifyRequestAsync(url, ipAddress, forwardedForIp, apiKey).Result; } /// /// Quick verification for current HTTP context. /// Automatically extracts both the direct IP and X-Forwarded-For IP if present. /// public async Task VerifyCurrentRequestAsync() { if (HttpContext.Current == null) { return new VerificationResult { IsAllowed = _config.FailOpen, Reason = "No HTTP context", Success = false }; } var request = HttpContext.Current.Request; var url = request.Url.ToString(); var ipAddress = request.UserHostAddress; // Extract X-Forwarded-For IP separately for dual-IP verification var forwardedForIp = GetForwardedForIp(); return await VerifyRequestAsync(url, ipAddress, forwardedForIp); } /// /// Gets the X-Forwarded-For IP address from the request headers. /// Returns the first IP in the chain (client IP). /// private string GetForwardedForIp() { if (HttpContext.Current == null) return null; var forwarded = HttpContext.Current.Request.Headers["X-Forwarded-For"]; if (!string.IsNullOrEmpty(forwarded)) { return forwarded.Split(',')[0].Trim(); } // Also check X-Real-IP as an alternative var realIp = HttpContext.Current.Request.Headers["X-Real-IP"]; if (!string.IsNullOrEmpty(realIp)) { return realIp; } return null; } /// /// Health check for the verification service /// public async Task HealthCheckAsync() { try { var response = await _httpClient.GetAsync($"{_config.BaseUrl}/api/verify/health"); return response.IsSuccessStatusCode; } catch { return false; } } /// /// Get API information and capabilities /// public async Task GetApiInfoAsync() { try { var response = await _httpClient.GetAsync($"{_config.BaseUrl}/api/verify/info"); if (response.IsSuccessStatusCode) { var json = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(json); } } catch { // Ignore errors } return null; } /// /// Gets the client IP address - returns direct connection IP (UserHostAddress). /// For proxy scenarios, use GetForwardedForIp() separately. /// private string GetClientIpAddress() { if (HttpContext.Current == null) return null; return HttpContext.Current.Request.UserHostAddress; } public void Dispose() { _httpClient?.Dispose(); } } /// /// Configuration class for TechSec602Client /// public class TechSec602Config { public string BaseUrl { get; set; } = "https://sec.602.tech"; public string ApiKey { get; set; } = ""; public int TimeoutMs { get; set; } = 5000; public bool FailOpen { get; set; } = true; // Allow requests when service is unavailable public bool EnableLogging { get; set; } = false; public bool CacheResults { get; set; } = true; public int CacheTimeoutMinutes { get; set; } = 5; public static TechSec602Config FromAppSettings() { return new TechSec602Config { BaseUrl = ConfigurationManager.AppSettings["TechSec602.BaseUrl"] ?? "https://sec.602.tech", ApiKey = ConfigurationManager.AppSettings["TechSec602.ApiKey"] ?? "", TimeoutMs = int.Parse(ConfigurationManager.AppSettings["TechSec602.TimeoutMs"] ?? "5000"), FailOpen = bool.Parse(ConfigurationManager.AppSettings["TechSec602.FailOpen"] ?? "true"), EnableLogging = bool.Parse(ConfigurationManager.AppSettings["TechSec602.EnableLogging"] ?? "false"), CacheResults = bool.Parse(ConfigurationManager.AppSettings["TechSec602.CacheResults"] ?? "true"), CacheTimeoutMinutes = int.Parse(ConfigurationManager.AppSettings["TechSec602.CacheTimeoutMinutes"] ?? "5") }; } } /// /// Result of verification request /// public class VerificationResult { public bool IsAllowed { get; set; } public string Reason { get; set; } public double? ProcessingTimeMs { get; set; } public bool Success { get; set; } } /// /// API response model /// internal class ApiVerificationResponse { public bool IsAllowed { get; set; } public string Reason { get; set; } public double ProcessingTimeMs { get; set; } } }