131 lines
5.0 KiB
C#
131 lines
5.0 KiB
C#
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<HasherSettings> 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<string> ToMd5Hash(string input)
|
|
{
|
|
if (string.IsNullOrEmpty(input))
|
|
return Result.Fail<string>("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<bool> VerifyPayfastWebhookSignature(PayfastWebhookPayload payload, string incomingSignature)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrWhiteSpace(incomingSignature))
|
|
return Result.Fail<bool>("Validation failed: Missing signature string parameter.");
|
|
|
|
var parameters = new List<string>();
|
|
|
|
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<bool>(localHashResult.Errors);
|
|
|
|
bool isValid = string.Equals(localHashResult.Value, incomingSignature, StringComparison.OrdinalIgnoreCase);
|
|
|
|
return Result.Ok(isValid);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<bool>(new Error("An error occurred during Payfast MD5 verification.").CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public Result<string> HashEncodeHex(string input) => string.IsNullOrWhiteSpace(input) || !HexHashRegex.IsMatch(input)
|
|
? Result.Fail<string>("Input must be a valid hexadecimal string.")
|
|
: Result.Ok(hasher.EncodeHex(input));
|
|
|
|
public Result<string> HashEncodeIntId(int id) => id < 0
|
|
? Result.Fail<string>("Id cannot be negative.")
|
|
: Result.Ok(hasher.Encode(id));
|
|
|
|
public Result<string> HashEncodeLongId(long id) => id < 0
|
|
? Result.Fail<string>("Id cannot be negative.")
|
|
: Result.Ok(hasher.EncodeLong(id));
|
|
|
|
public Result<int> DecodeIntIdHash(string hash)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(hash)) return Result.Fail<int>("Invalid token layout.");
|
|
|
|
int[] decoded = hasher.Decode(hash);
|
|
|
|
return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail<int>("Invalid or modified Int hash token.");
|
|
}
|
|
|
|
public Result<long> DecodeLongIdHash(string hash)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(hash)) return Result.Fail<long>("Invalid token layout.");
|
|
|
|
long[] decoded = hasher.DecodeLong(hash);
|
|
|
|
return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail<long>("Invalid or modified Long hash token.");
|
|
}
|
|
|
|
public Result<string> DecodeHexHash(string hex)
|
|
{
|
|
try
|
|
{
|
|
string decoded = hasher.DecodeHex(hex);
|
|
|
|
return string.IsNullOrEmpty(decoded)
|
|
? Result.Fail<string>("Invalid or corrupted hex hash.")
|
|
: Result.Ok(decoded);
|
|
}
|
|
catch (FormatException fex)
|
|
{
|
|
return Result.Fail<string>(new Error("Invalid hash structure.").CausedBy(fex));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail<string>(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
} |