Add copy trading authorization checks in LiveTradingBotGrain and StartCopyTradingCommandHandler. Integrated IKaigenService to verify user ownership of master strategy keys before allowing copy trading. Enhanced error handling and logging for authorization verification.

This commit is contained in:
2025-11-16 22:11:54 +07:00
parent 2baa2e173c
commit c229212acd
4 changed files with 149 additions and 19 deletions

View File

@@ -19,6 +19,7 @@ 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 OwnedKeysEndpoint { get; set; } = "/api/keys/owned";
public string SecretKey { get; set; } = string.Empty;
}
@@ -169,24 +170,48 @@ public class KaigenService : IKaigenService
}
}
public async Task<OwnedKeysResponse> GetOwnedKeysAsync(User user)
{
// If credits are disabled, return empty response
if (!_creditsEnabled)
{
_logger.LogInformation("Credits disabled - returning empty owned keys for user {UserName}",
user.Name);
return new OwnedKeysResponse { Items = new List<OwnedKeyItem>(), Overall = 0 };
}
try
{
var walletAddress = GetUserWalletAddress(user);
_logger.LogInformation(
"Fetching owned keys for user {UserName} (wallet: {WalletAddress})",
user.Name, walletAddress);
var result = await SendAuthenticatedGetRequestAsync(
$"{_settings.BaseUrl}{_settings.OwnedKeysEndpoint}",
user);
_logger.LogInformation(
"Successfully fetched owned keys for user {UserName} (wallet: {WalletAddress})",
user.Name, walletAddress);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching owned keys for user {UserName}", user.Name);
throw;
}
}
private async Task<KaigenResponse> 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);
request.Headers.Authorization = BuildAuthorizationHeader(user);
var response = await _httpClient.SendAsync(request);
@@ -202,6 +227,40 @@ public class KaigenService : IKaigenService
return result ?? new KaigenResponse { Success = false, Message = "Failed to parse response" };
}
private async Task<OwnedKeysResponse> SendAuthenticatedGetRequestAsync(string url, User user)
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Authorization = BuildAuthorizationHeader(user);
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
_logger.LogError("GET request failed. Status: {StatusCode}, Error: {Error}",
response.StatusCode, errorContent);
throw new Exception($"GET request failed: HTTP {response.StatusCode}: {errorContent}");
}
var result = await response.Content.ReadFromJsonAsync<OwnedKeysResponse>(_jsonOptions);
return result ?? new OwnedKeysResponse { Items = new List<OwnedKeyItem>(), Overall = 0 };
}
private AuthenticationHeaderValue BuildAuthorizationHeader(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));
return new AuthenticationHeaderValue("Basic", base64Auth);
}
private string GetUserWalletAddress(User user)
{
if (user?.Accounts == null || !user.Accounts.Any())