Add encryption for Kaigen server auth

This commit is contained in:
2025-07-17 16:50:54 +07:00
parent e27b4c4a76
commit f6013b8e9d
6 changed files with 266 additions and 48 deletions

View File

@@ -25,6 +25,7 @@
<ProjectReference Include="..\Managing.ABI.GmxV2\Managing.ABI.GmxV2.csproj"/>
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj" />
<ProjectReference Include="..\Managing.Core\Managing.Core.csproj"/>
<ProjectReference Include="..\Managing.Tools.ABI\Managing.Tools.ABI.csproj"/>
</ItemGroup>

View File

@@ -1,23 +1,25 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Managing.Application.Abstractions.Services;
using Managing.Core;
using Managing.Domain.Users;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Nethereum.Signer;
namespace Managing.Infrastructure.Evm.Services;
/// <summary>
/// Configuration settings for Kaigen credit management service.
/// The PrivateKey should be set via the KAIGEN_PRIVATE_KEY environment variable for security.
/// The SecretKey should be set via the KAIGEN_SECRET_KEY environment variable for security.
/// </summary>
public class KaigenSettings
{
public string BaseUrl { get; set; } = "https://api.kaigen.managing.live";
public string DebitEndpoint { get; set; } = "/api/credits/debit";
public string RefundEndpoint { get; set; } = "/api/credits/refund";
public string PrivateKey { get; set; } = string.Empty;
public string SecretKey { get; set; } = string.Empty;
}
public class KaigenService : IKaigenService
@@ -45,22 +47,22 @@ public class KaigenService : IKaigenService
if (!_creditsEnabled)
{
_logger.LogInformation("Kaigen credits are disabled via KAIGEN_CREDITS_ENABLED environment variable");
return; // Skip private key validation if credits are disabled
return; // Skip secret key validation if credits are disabled
}
// Always read from environment variable for security
var envPrivateKey = Environment.GetEnvironmentVariable("KAIGEN_PRIVATE_KEY");
if (!string.IsNullOrEmpty(envPrivateKey))
var envSecretKey = Environment.GetEnvironmentVariable("KAIGEN_SECRET_KEY");
if (!string.IsNullOrEmpty(envSecretKey))
{
_settings.PrivateKey = envPrivateKey;
_logger.LogInformation("Using KAIGEN_PRIVATE_KEY from environment variable");
_settings.SecretKey = envSecretKey;
_logger.LogInformation("Using KAIGEN_SECRET_KEY from environment variable");
}
// Validate required settings only if credits are enabled
if (string.IsNullOrEmpty(_settings.PrivateKey))
if (string.IsNullOrEmpty(_settings.SecretKey))
{
throw new InvalidOperationException(
"Kaigen PrivateKey is not configured. Please set the KAIGEN_PRIVATE_KEY environment variable.");
"Kaigen SecretKey is not configured. Please set the KAIGEN_SECRET_KEY environment variable.");
}
if (string.IsNullOrEmpty(_settings.BaseUrl))
@@ -86,29 +88,22 @@ public class KaigenService : IKaigenService
var walletAddress = GetUserWalletAddress(user);
var requestId = Guid.NewGuid().ToString();
// Create the message to sign (concatenate the values)
var message = $"{requestId}{walletAddress}{debitAmount}";
// Sign the message
var signature = SignMessage(message, _settings.PrivateKey);
// Create the request payload
var requestPayload = new
{
requestId = requestId,
walletAddress = walletAddress,
debitAmount = debitAmount,
signature = signature
debitAmount = debitAmount
};
_logger.LogInformation(
"Debiting {Amount} credits for user {UserName} (wallet: {WalletAddress}) with request ID {RequestId}",
debitAmount, user.Name, walletAddress, requestId);
var response = await _httpClient.PutAsJsonAsync(
var response = await SendAuthenticatedRequestAsync(
$"{_settings.BaseUrl}{_settings.DebitEndpoint}",
requestPayload,
_jsonOptions);
user);
if (!response.IsSuccessStatusCode)
{
@@ -146,28 +141,21 @@ public class KaigenService : IKaigenService
{
var walletAddress = GetUserWalletAddress(user);
// Create the message to sign (concatenate the values)
var message = $"{requestId}{walletAddress}";
// Sign the message
var signature = SignMessage(message, _settings.PrivateKey);
// Create the request payload
var requestPayload = new
{
requestId = requestId,
walletAddress = walletAddress,
signature = signature
walletAddress = walletAddress
};
_logger.LogInformation(
"Refunding credits for user {UserName} (wallet: {WalletAddress}) with request ID {RequestId}",
user.Name, walletAddress, requestId);
var response = await _httpClient.PutAsJsonAsync(
var response = await SendAuthenticatedRequestAsync(
$"{_settings.BaseUrl}{_settings.RefundEndpoint}",
requestPayload,
_jsonOptions);
user);
if (!response.IsSuccessStatusCode)
{
@@ -188,6 +176,28 @@ public class KaigenService : IKaigenService
}
}
private async Task<HttpResponseMessage> SendAuthenticatedRequestAsync(string url, object payload, User user)
{
// Create the auth token: "walletaddress-username"
var authToken = $"{GetUserWalletAddress(user)}-{user.Name}";
// Encrypt the auth token using AES-256-GCM
var encryptedToken = CryptoHelpers.EncryptAesGcm(authToken, _settings.SecretKey);
// Create Basic Auth header with the encrypted token
var basicAuthString = $"{encryptedToken}:";
var base64Auth = Convert.ToBase64String(Encoding.ASCII.GetBytes(basicAuthString));
// Create a new request with the auth header
var request = new HttpRequestMessage(HttpMethod.Put, url)
{
Content = JsonContent.Create(payload, options: _jsonOptions)
};
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64Auth);
return await _httpClient.SendAsync(request);
}
private string GetUserWalletAddress(User user)
{
if (user?.Accounts == null || !user.Accounts.Any())
@@ -206,13 +216,6 @@ public class KaigenService : IKaigenService
return walletAddress;
}
private string SignMessage(string message, string privateKey)
{
var signer = new EthereumMessageSigner();
var signature = signer.EncodeUTF8AndSign(message, new EthECKey(privateKey));
return signature;
}
private class KaigenResponse
{
public bool Success { get; set; }