using LiteCharms.Features.Abstractions; using LiteCharms.Features.Hasher.Configuration; using LiteCharms.Features.Models; namespace LiteCharms.Features.Hasher; public sealed partial class HashService(IHashids hasher, IOptions options) : IService { private readonly HasherSettings settings = options.Value; [GeneratedRegex(@"\A\b[0-9a-fA-F]+\b\Z")] private static partial Regex HexHashRegex { get; } [GeneratedRegex(@"\A[0-9a-fA-F]{32}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)] private static partial Regex Md5Regex { get; } [GeneratedRegex(@"\A[0-9a-fA-F]{64}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)] private static partial Regex Sha256Regex { get; } public static bool IsMd5Hash(string? value) => !string.IsNullOrWhiteSpace(value) && Md5Regex.IsMatch(value); public static bool IsSha256Hash(string? value) => !string.IsNullOrWhiteSpace(value) && Sha256Regex.IsMatch(value); public static string? StringToSha256Hash(string? input) => string.IsNullOrEmpty(input) ? null : Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input))); public static string? StreamToSha256Hash(Stream stream) => stream is null ? null : Convert.ToHexString(SHA256.HashData(stream)); public static string? BytesToSha256Hash(byte[] bytes) => bytes is null ? null : Convert.ToHexString(SHA256.HashData(bytes)); public static Result ToMd5Hash(string input) { if (string.IsNullOrEmpty(input)) return Result.Fail("Input content cannot be null or empty for MD5 processing."); byte[] bytes = MD5.HashData(Encoding.UTF8.GetBytes(input)); return Result.Ok(Convert.ToHexString(bytes).ToLowerInvariant()); } public Result VerifyPayfastWebhookSignature(PayfastWebhookPayload payload, string incomingSignature) { try { if (string.IsNullOrWhiteSpace(incomingSignature)) return Result.Fail("Validation failed: Missing signature string parameter."); var parameters = new List(); if (!string.IsNullOrWhiteSpace(payload.Amount)) parameters.Add($"amount={WebUtility.UrlEncode(payload.Amount)}"); if (!string.IsNullOrWhiteSpace(payload.ItemName)) parameters.Add($"item_name={WebUtility.UrlEncode(payload.ItemName)}"); if (!string.IsNullOrWhiteSpace(payload.MPaymentId)) parameters.Add($"m_payment_id={WebUtility.UrlEncode(payload.MPaymentId)}"); string signatureString = string.Join("&", parameters); if (!string.IsNullOrWhiteSpace(settings.PayfastPassphrase)) signatureString += $"&passphrase={WebUtility.UrlEncode(settings.PayfastPassphrase)}"; var localHashResult = ToMd5Hash(signatureString); if (!localHashResult.IsSuccess) return Result.Fail(localHashResult.Errors); bool isValid = string.Equals(localHashResult.Value, incomingSignature, StringComparison.OrdinalIgnoreCase); return Result.Ok(isValid); } catch (Exception ex) { return Result.Fail(new Error("An error occurred during Payfast MD5 verification.").CausedBy(ex)); } } public Result HashEncodeHex(string input) => string.IsNullOrWhiteSpace(input) || !HexHashRegex.IsMatch(input) ? Result.Fail("Input must be a valid hexadecimal string.") : Result.Ok(hasher.EncodeHex(input)); public Result HashEncodeIntId(int id) => id < 0 ? Result.Fail("Id cannot be negative.") : Result.Ok(hasher.Encode(id)); public Result HashEncodeLongId(long id) => id < 0 ? Result.Fail("Id cannot be negative.") : Result.Ok(hasher.EncodeLong(id)); public Result DecodeIntIdHash(string hash) { if (string.IsNullOrWhiteSpace(hash)) return Result.Fail("Invalid token layout."); int[] decoded = hasher.Decode(hash); return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail("Invalid or modified Int hash token."); } public Result DecodeLongIdHash(string hash) { if (string.IsNullOrWhiteSpace(hash)) return Result.Fail("Invalid token layout."); long[] decoded = hasher.DecodeLong(hash); return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail("Invalid or modified Long hash token."); } public Result DecodeHexHash(string hex) { try { string decoded = hasher.DecodeHex(hex); return string.IsNullOrEmpty(decoded) ? Result.Fail("Invalid or corrupted hex hash.") : Result.Ok(decoded); } catch (FormatException fex) { return Result.Fail(new Error("Invalid hash structure.").CausedBy(fex)); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } }