marco.pms.api/Marco.Pms.Services/Extensions/EncryptResponseAttribute.cs

134 lines
5.3 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Security.Cryptography;
using System.Text;
public class EncryptResponseAttribute : TypeFilterAttribute
{
public EncryptResponseAttribute() : base(typeof(EncryptResponseFilter))
{
}
private class EncryptResponseFilter : IAsyncResultFilter
{
// 32-byte Key
//private readonly string _keyBase64 = "h9J4kL2mN5pQ8rS1tV3wX6yZ0aB7cD9eF1gH3jK5mN6=";
private readonly string _keyBase64 = "u4J7p9Qx2hF5vYtLz8Kq3mN1sG0bRwXyZcD6eH8jFQw=";
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// 1. EXECUTE THE CONTROLLER FIRST
// We let the controller run to populate context.Result
// Note: We are intercepting *before* the response goes to the client.
try
{
if (context.Result is ObjectResult objectResult && objectResult.Value != null)
{
// 2. SERIALIZE (Safe Settings)
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
};
var plainJson = JsonConvert.SerializeObject(objectResult.Value, settings);
// 3. ENCRYPT ASYNC (Prevents Thread Blocking 502)
var encryptedText = await EncryptAsync(plainJson);
// 4. RETURN CONTENT RESULT
// Use ContentResult to send raw text.
// OkObjectResult would try to JSON-serialize the string again (adding quotes).
context.Result = new ContentResult
{
Content = encryptedText,
ContentType = "text/plain",
StatusCode = 200
};
}
}
catch (Exception ex)
{
// FAIL-SAFE LOGGING
Console.WriteLine($"Encryption Crashed: {ex.Message}");
// We do NOT modify context.Result here.
// The original unencrypted ObjectResult will flow through to the client.
// This ensures the user gets DATA, not a 502.
}
await next();
}
private async Task<string> EncryptAsync(string plainText)
{
if (string.IsNullOrEmpty(plainText)) return plainText;
using var aes = Aes.Create();
aes.Key = Convert.FromBase64String(_keyBase64);
aes.GenerateIV();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
// 1. Convert string to bytes directly (Avoids StreamWriter encoding issues)
var plainBytes = Encoding.UTF8.GetBytes(plainText);
using var ms = new MemoryStream();
// 2. Write IV (16 bytes)
ms.Write(aes.IV, 0, aes.IV.Length);
using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
// 3. Write Data
await cs.WriteAsync(plainBytes, 0, plainBytes.Length);
// 4. CRITICAL: Flush the final block (Padding) to the MemoryStream
// Without this, Dart receives incomplete data and throws "Invalid Padding"
cs.FlushFinalBlock();
}
// 5. Convert full stream to Base64
return Convert.ToBase64String(ms.ToArray());
}
//private async Task<string> EncryptAsync(string plainText)
//{
// if (string.IsNullOrEmpty(plainText)) return plainText;
// using var aes = Aes.Create();
// aes.Key = Convert.FromBase64String(_keyBase64);
// aes.GenerateIV();
// aes.Mode = CipherMode.CBC;
// aes.Padding = PaddingMode.PKCS7;
// // We do NOT use 'using' on the MemoryStream here yet,
// // because we need to read from it after the CryptoStream finishes.
// using var ms = new MemoryStream();
// // Write IV first (16 bytes)
// ms.Write(aes.IV, 0, aes.IV.Length);
// using (var encryptor = aes.CreateEncryptor(aes.Key, aes.IV))
// using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
// using (var sw = new StreamWriter(cs))
// {
// // CRITICAL FIX: Use Async Write
// await sw.WriteAsync(plainText);
// // Flush the writer, but do not close the underlying streams yet via 'using' exit
// await sw.FlushAsync();
// }
// // At this point, CryptoStream is closed (disposed by using block),
// // causing the final block to be flushed to MemoryStream.
// // MemoryStream is technically closed, but .NET allows ToArray() on closed MemoryStreams.
// return Convert.ToBase64String(ms.ToArray());
//}
}
}