Refactor secret management to include SecretStoreType for data sources

This commit is contained in:
Thorsten Sommer 2026-05-17 14:28:14 +02:00
parent e9fa4187fc
commit cea7f09653
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
18 changed files with 182 additions and 143 deletions

View File

@ -116,7 +116,7 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
if (this.dataAuthMethod is AuthMethod.TOKEN or AuthMethod.USERNAME_PASSWORD)
{
// Load the secret:
var requestedSecret = await this.RustService.GetSecret(this);
var requestedSecret = await this.RustService.GetSecret(this, SecretStoreType.DATA_SOURCE);
if (requestedSecret.Success)
this.dataSecret = await requestedSecret.Secret.Decrypt(this.encryption);
else
@ -324,7 +324,7 @@ public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
if (!string.IsNullOrWhiteSpace(this.dataSecret))
{
// Store the secret in the OS secure storage:
var storeResponse = await this.RustService.SetSecret(this, this.dataSecret);
var storeResponse = await this.RustService.SetSecret(this, this.dataSecret, SecretStoreType.DATA_SOURCE);
if (!storeResponse.Success)
{
this.dataSecretStorageIssue = string.Format(T("Failed to store the auth. secret in the operating system. The message was: {0}. Please try again."), storeResponse.Issue);

View File

@ -180,7 +180,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase
// All other auth methods require a secret, which we need to delete now:
else
{
var deleteSecretResponse = await this.RustService.DeleteSecret(externalDataSource);
var deleteSecretResponse = await this.RustService.DeleteSecret(externalDataSource, SecretStoreType.DATA_SOURCE);
if (deleteSecretResponse.Success)
applyChanges = true;
}

View File

@ -2,7 +2,6 @@
using AIStudio.Assistants.ERI;
using AIStudio.Chat;
using AIStudio.Tools;
using AIStudio.Tools.ERIClient;
using AIStudio.Tools.ERIClient.DataModel;
using AIStudio.Tools.PluginSystem;
@ -313,7 +312,8 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
PendingEnterpriseSecrets.Add(new(
$"{ISecretId.ENTERPRISE_KEY_PREFIX}::{dataSource.Id}",
dataSource.Name,
decryptedSecret));
decryptedSecret,
SecretStoreType.DATA_SOURCE));
LOGGER.LogDebug($"Successfully decrypted the {secretFieldName} for data source {idx}. It will be stored in the OS keyring. (Plugin ID: {configPluginId})");
return true;
}

View File

@ -16,21 +16,6 @@ namespace AIStudio.Settings;
[JsonDerivedType(typeof(DataSourceERI_V1), nameof(DataSourceType.ERI_V1))]
public interface IDataSource : IConfigurationObject
{
/// <summary>
/// The number of the data source.
/// </summary>
public uint Num { get; init; }
/// <summary>
/// The unique identifier of the data source.
/// </summary>
public string Id { get; init; }
/// <summary>
/// The name of the data source.
/// </summary>
public string Name { get; init; }
/// <summary>
/// Which type of data source is this?
/// </summary>
@ -41,16 +26,6 @@ public interface IDataSource : IConfigurationObject
/// </summary>
public DataSourceSecurity SecurityPolicy { get; init; }
/// <summary>
/// Is this data source an enterprise configuration?
/// </summary>
public bool IsEnterpriseConfiguration { get; init; }
/// <summary>
/// The ID of the enterprise configuration plugin.
/// </summary>
public Guid EnterpriseConfigurationPluginId { get; init; }
/// <summary>
/// The maximum number of matches to return when retrieving data from the ERI server.
/// </summary>

View File

@ -1,4 +1,5 @@
using AIStudio.Assistants.ERI;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Settings;

View File

@ -7,7 +7,7 @@ public interface IExternalDataSource : IDataSource, ISecretId
#region Implementation of ISecretId
[JsonIgnore]
string ISecretId.SecretId => this.IsEnterpriseConfiguration ? $"{ISecretId.ENTERPRISE_KEY_PREFIX}::{this.Id}" : this.Id;
string ISecretId.SecretId => this.IsEnterpriseConfiguration ? $"{ENTERPRISE_KEY_PREFIX}::{this.Id}" : this.Id;
[JsonIgnore]
string ISecretId.SecretName => this.Name;

View File

@ -119,7 +119,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
string password;
if (string.IsNullOrWhiteSpace(temporarySecret))
{
var passwordResponse = await rustService.GetSecret(this.DataSource);
var passwordResponse = await rustService.GetSecret(this.DataSource, SecretStoreType.DATA_SOURCE);
if (!passwordResponse.Success)
{
return new()
@ -173,7 +173,7 @@ public class ERIClientV1(IERIDataSource dataSource) : ERIClientBase(dataSource),
string token;
if (string.IsNullOrWhiteSpace(temporarySecret))
{
var tokenResponse = await rustService.GetSecret(this.DataSource);
var tokenResponse = await rustService.GetSecret(this.DataSource, SecretStoreType.DATA_SOURCE);
if (!tokenResponse.Success)
{
return new()

View File

@ -14,80 +14,3 @@ public sealed record PendingEnterpriseApiKey(
string SecretName,
string ApiKey,
SecretStoreType StoreType);
/// <summary>
/// Static container for pending API keys during plugin loading.
/// </summary>
public static class PendingEnterpriseApiKeys
{
private static readonly List<PendingEnterpriseApiKey> PENDING_KEYS = [];
private static readonly Lock LOCK = new();
/// <summary>
/// Adds a pending API key to the list.
/// </summary>
/// <param name="key">The pending API key to add.</param>
public static void Add(PendingEnterpriseApiKey key)
{
lock (LOCK)
PENDING_KEYS.Add(key);
}
/// <summary>
/// Gets and clears all pending API keys.
/// </summary>
/// <returns>A list of all pending API keys.</returns>
public static IReadOnlyList<PendingEnterpriseApiKey> GetAndClear()
{
lock (LOCK)
{
var keys = PENDING_KEYS.ToList();
PENDING_KEYS.Clear();
return keys;
}
}
}
/// <summary>
/// Represents a pending enterprise secret that needs to be stored in the OS keyring.
/// </summary>
/// <param name="SecretId">The secret ID.</param>
/// <param name="SecretName">The secret name.</param>
/// <param name="SecretData">The decrypted secret data.</param>
public sealed record PendingEnterpriseSecret(
string SecretId,
string SecretName,
string SecretData);
/// <summary>
/// Static container for pending enterprise secrets during plugin loading.
/// </summary>
public static class PendingEnterpriseSecrets
{
private static readonly List<PendingEnterpriseSecret> PENDING_SECRETS = [];
private static readonly Lock LOCK = new();
/// <summary>
/// Adds a pending enterprise secret to the list.
/// </summary>
/// <param name="secret">The pending enterprise secret to add.</param>
public static void Add(PendingEnterpriseSecret secret)
{
lock (LOCK)
PENDING_SECRETS.Add(secret);
}
/// <summary>
/// Gets and clears all pending enterprise secrets.
/// </summary>
/// <returns>A list of all pending enterprise secrets.</returns>
public static IReadOnlyList<PendingEnterpriseSecret> GetAndClear()
{
lock (LOCK)
{
var secrets = PENDING_SECRETS.ToList();
PENDING_SECRETS.Clear();
return secrets;
}
}
}

View File

@ -0,0 +1,34 @@
namespace AIStudio.Tools.PluginSystem;
/// <summary>
/// Static container for pending API keys during plugin loading.
/// </summary>
public static class PendingEnterpriseApiKeys
{
private static readonly List<PendingEnterpriseApiKey> PENDING_KEYS = [];
private static readonly Lock LOCK = new();
/// <summary>
/// Adds a pending API key to the list.
/// </summary>
/// <param name="key">The pending API key to add.</param>
public static void Add(PendingEnterpriseApiKey key)
{
lock (LOCK)
PENDING_KEYS.Add(key);
}
/// <summary>
/// Gets and clears all pending API keys.
/// </summary>
/// <returns>A list of all pending API keys.</returns>
public static IReadOnlyList<PendingEnterpriseApiKey> GetAndClear()
{
lock (LOCK)
{
var keys = PENDING_KEYS.ToList();
PENDING_KEYS.Clear();
return keys;
}
}
}

View File

@ -0,0 +1,14 @@
namespace AIStudio.Tools.PluginSystem;
/// <summary>
/// Represents a pending enterprise secret that needs to be stored in the OS keyring.
/// </summary>
/// <param name="SecretId">The secret ID.</param>
/// <param name="SecretName">The secret name.</param>
/// <param name="SecretData">The decrypted secret data.</param>
/// <param name="StoreType">The type of secret store to use.</param>
public sealed record PendingEnterpriseSecret(
string SecretId,
string SecretName,
string SecretData,
SecretStoreType StoreType);

View File

@ -0,0 +1,34 @@
namespace AIStudio.Tools.PluginSystem;
/// <summary>
/// Static container for pending enterprise secrets during plugin loading.
/// </summary>
public static class PendingEnterpriseSecrets
{
private static readonly List<PendingEnterpriseSecret> PENDING_SECRETS = [];
private static readonly Lock LOCK = new();
/// <summary>
/// Adds a pending enterprise secret to the list.
/// </summary>
/// <param name="secret">The pending enterprise secret to add.</param>
public static void Add(PendingEnterpriseSecret secret)
{
lock (LOCK)
PENDING_SECRETS.Add(secret);
}
/// <summary>
/// Gets and clears all pending enterprise secrets.
/// </summary>
/// <returns>A list of all pending enterprise secrets.</returns>
public static IReadOnlyList<PendingEnterpriseSecret> GetAndClear()
{
lock (LOCK)
{
var secrets = PENDING_SECRETS.ToList();
PENDING_SECRETS.Clear();
return secrets;
}
}
}

View File

@ -62,7 +62,7 @@ public sealed class PluginConfiguration(bool isInternal, LuaState state, PluginT
try
{
var secretId = new TemporarySecretId(pendingSecret.SecretId, pendingSecret.SecretName);
var result = await rustService.SetSecret(secretId, pendingSecret.SecretData);
var result = await rustService.SetSecret(secretId, pendingSecret.SecretData, pendingSecret.StoreType);
if (result.Success)
LOG.LogDebug($"Successfully stored enterprise secret for '{pendingSecret.SecretName}' in the OS keyring.");

View File

@ -252,6 +252,7 @@ public sealed record PluginConfigurationObject
/// <param name="availablePlugins">A list of currently available plugins.</param>
/// <param name="configObjectList">A list of all existing configuration objects.</param>
/// <param name="secretStoreType">An optional parameter specifying the type of secret store to use for deleting associated API keys from the OS keyring, if applicable.</param>
/// <param name="deleteSecret">When true, delete the associated non-API-key secret from the OS keyring.</param>
/// <returns>Returns true if the configuration was altered during cleanup; otherwise, false.</returns>
public static async Task<bool> CleanLeftOverConfigurationObjects<TClass>(
PluginConfigurationObjectType configObjectType,
@ -304,7 +305,7 @@ public sealed record PluginConfigurationObject
// Delete the API key from the OS keyring if the removed object has one:
if(deleteSecret && item is ISecretId regularSecretId)
{
var deleteResult = await RUST_SERVICE.DeleteSecret(regularSecretId);
var deleteResult = await RUST_SERVICE.DeleteSecret(regularSecretId, secretStoreType ?? SecretStoreType.DATA_SOURCE);
if (deleteResult.Success)
LOG.LogInformation($"Successfully deleted secret for removed enterprise object '{item.Name}' from the OS keyring.");
else

View File

@ -175,7 +175,7 @@ public static partial class PluginFactory
wasConfigurationChanged = true;
// Check data sources:
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DATA_SOURCE, x => x.DataSources, AVAILABLE_PLUGINS, configObjectList, deleteSecret: true))
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DATA_SOURCE, x => x.DataSources, AVAILABLE_PLUGINS, configObjectList, SecretStoreType.DATA_SOURCE, deleteSecret: true))
wasConfigurationChanged = true;
// Check chat templates:

View File

@ -1,10 +1,10 @@
namespace AIStudio.Tools;
/// <summary>
/// Represents the type of secret store used for API keys.
/// Represents the type of secret store used for API keys and other secrets.
/// </summary>
/// <remarks>
/// Different provider types use different prefixes for storing API keys.
/// Different provider and secret types use different prefixes for storing secrets.
/// This prevents collisions when the same instance name is used across
/// different provider types (e.g., LLM, Embedding, Transcription).
/// </remarks>
@ -29,4 +29,9 @@ public enum SecretStoreType
/// Image provider secrets. Uses the "image::" prefix.
/// </summary>
IMAGE_PROVIDER,
/// <summary>
/// Data source secrets. Uses the "data-source::" prefix.
/// </summary>
DATA_SOURCE,
}

View File

@ -9,12 +9,14 @@ public static class SecretStoreTypeExtensions
/// LLM_PROVIDER uses the legacy "provider" prefix for backward compatibility.
/// </remarks>
/// <param name="type">The SecretStoreType enum value.</param>
/// <returns>>The corresponding prefix string.</returns>
/// <returns>The corresponding prefix string.</returns>
public static string Prefix(this SecretStoreType type) => type switch
{
SecretStoreType.LLM_PROVIDER => "provider",
SecretStoreType.EMBEDDING_PROVIDER => "embedding",
SecretStoreType.TRANSCRIPTION_PROVIDER => "transcription",
SecretStoreType.IMAGE_PROVIDER => "image",
SecretStoreType.DATA_SOURCE => "data-source",
_ => "provider",
};

View File

@ -200,7 +200,7 @@ public sealed class EnterpriseEnvironmentService(ILogger<EnterpriseEnvironmentSe
{
logger.LogInformation("The enterprise encryption secret changed. Refreshing the enterprise encryption service and reloading plugins.");
PluginFactory.InitializeEnterpriseEncryption(enterpriseEncryptionSecret);
await this.RemoveEnterpriseManagedApiKeysAsync();
await this.RemoveEnterpriseManagedSecretsAsync();
await PluginFactory.LoadAll();
}
@ -249,36 +249,36 @@ public sealed class EnterpriseEnvironmentService(ILogger<EnterpriseEnvironmentSe
return serverUrl.Trim().TrimEnd('/');
}
private async Task RemoveEnterpriseManagedApiKeysAsync()
private async Task RemoveEnterpriseManagedSecretsAsync()
{
var secretTargets = GetEnterpriseManagedSecretTargets();
if (secretTargets.Count == 0)
{
logger.LogInformation("No enterprise-managed API keys are currently known in the settings. No keyring cleanup is required.");
logger.LogInformation("No enterprise-managed secrets are currently known in the settings. No keyring cleanup is required.");
return;
}
logger.LogInformation("Removing {SecretCount} enterprise-managed API key(s) from the OS keyring after an enterprise encryption secret change.", secretTargets.Count);
logger.LogInformation("Removing {SecretCount} enterprise-managed secret(s) from the OS keyring after an enterprise encryption secret change.", secretTargets.Count);
foreach (var target in secretTargets)
{
try
{
var deleteResult = target.StoreType is { } storeType
? await rustService.DeleteAPIKey(target, storeType)
: await rustService.DeleteSecret(target);
var deleteResult = target.StoreType is SecretStoreType.DATA_SOURCE
? await rustService.DeleteSecret(target, target.StoreType)
: await rustService.DeleteAPIKey(target, target.StoreType);
if (deleteResult.Success)
{
if (deleteResult.WasEntryFound)
logger.LogInformation("Successfully deleted enterprise-managed API key '{SecretName}' from the OS keyring.", target.SecretName);
logger.LogInformation("Successfully deleted enterprise-managed secret '{SecretName}' from the OS keyring.", target.SecretName);
else
logger.LogInformation("Enterprise-managed API key '{SecretName}' was already absent from the OS keyring.", target.SecretName);
logger.LogInformation("Enterprise-managed secret '{SecretName}' was already absent from the OS keyring.", target.SecretName);
}
else
logger.LogWarning("Failed to delete enterprise-managed API key '{SecretName}' from the OS keyring: {Issue}", target.SecretName, deleteResult.Issue);
logger.LogWarning("Failed to delete enterprise-managed secret '{SecretName}' from the OS keyring: {Issue}", target.SecretName, deleteResult.Issue);
}
catch (Exception e)
{
logger.LogWarning(e, "Failed to delete enterprise-managed API key '{SecretName}' from the OS keyring.", target.SecretName);
logger.LogWarning(e, "Failed to delete enterprise-managed secret '{SecretName}' from the OS keyring.", target.SecretName);
}
}
}
@ -291,14 +291,14 @@ public sealed class EnterpriseEnvironmentService(ILogger<EnterpriseEnvironmentSe
AddEnterpriseManagedSecretTargets(configurationData.Providers, SecretStoreType.LLM_PROVIDER, secretTargets);
AddEnterpriseManagedSecretTargets(configurationData.EmbeddingProviders, SecretStoreType.EMBEDDING_PROVIDER, secretTargets);
AddEnterpriseManagedSecretTargets(configurationData.TranscriptionProviders, SecretStoreType.TRANSCRIPTION_PROVIDER, secretTargets);
AddEnterpriseManagedSecretTargets(configurationData.DataSources.OfType<IExternalDataSource>(), null, secretTargets);
AddEnterpriseManagedSecretTargets(configurationData.DataSources.OfType<IExternalDataSource>(), SecretStoreType.DATA_SOURCE, secretTargets);
return secretTargets.ToList();
}
private static void AddEnterpriseManagedSecretTargets<TSecret>(
IEnumerable<TSecret> secrets,
SecretStoreType? storeType,
SecretStoreType storeType,
ISet<EnterpriseSecretTarget> secretTargets) where TSecret : ISecretId, IConfigurationObject
{
foreach (var secret in secrets)

View File

@ -4,26 +4,42 @@ namespace AIStudio.Tools.Services;
public sealed partial class RustService
{
private static string SecretKey(ISecretId secretId, SecretStoreType storeType) => $"{storeType.Prefix()}::{secretId.SecretId}::{secretId.SecretName}";
private static string LegacySecretKey(ISecretId secretId) => $"secret::{secretId.SecretId}::{secretId.SecretName}";
/// <summary>
/// Try to get the secret data for the given secret ID.
/// </summary>
/// <param name="secretId">The secret ID to get the data for.</param>
/// <param name="storeType">The secret store type.</param>
/// <param name="isTrying">Indicates if we are trying to get the data. In that case, we don't log errors.</param>
/// <returns>The requested secret.</returns>
public async Task<RequestedSecret> GetSecret(ISecretId secretId, bool isTrying = false)
public async Task<RequestedSecret> GetSecret(ISecretId secretId, SecretStoreType storeType, bool isTrying = false)
{
var secretRequest = new SelectSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, isTrying);
var secretKey = SecretKey(secretId, storeType);
var secretRequest = new SelectSecretRequest(secretKey, Environment.UserName, isTrying);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
if(!isTrying)
this.logger!.LogError($"Failed to get the secret data for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
this.logger!.LogError($"Failed to get the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the secret data due to an API issue."));
}
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
if (!secret.Success && storeType is SecretStoreType.DATA_SOURCE)
{
var legacySecret = await this.GetLegacySecret(secretId, isTrying: true);
if (legacySecret.Success)
{
this.logger!.LogDebug($"Successfully retrieved the legacy data source secret for '{LegacySecretKey(secretId)}'.");
return legacySecret;
}
}
if (!secret.Success && !isTrying)
this.logger!.LogError($"Failed to get the secret data for secret ID '{secretId.SecretId}': '{secret.Issue}'");
this.logger!.LogError($"Failed to get the secret data for '{secretKey}': '{secret.Issue}'");
return secret;
}
@ -33,21 +49,26 @@ public sealed partial class RustService
/// </summary>
/// <param name="secretId">The secret ID to store the data for.</param>
/// <param name="secretData">The data to store.</param>
/// <param name="storeType">The secret store type.</param>
/// <returns>The store secret response.</returns>
public async Task<StoreSecretResponse> SetSecret(ISecretId secretId, string secretData)
public async Task<StoreSecretResponse> SetSecret(ISecretId secretId, string secretData, SecretStoreType storeType)
{
var secretKey = SecretKey(secretId, storeType);
var encryptedSecret = await this.encryptor!.Encrypt(secretData);
var request = new StoreSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, encryptedSecret);
var request = new StoreSecretRequest(secretKey, Environment.UserName, encryptedSecret);
var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to store the secret data for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
return new StoreSecretResponse(false, TB("Failed to get the secret data due to an API issue."));
this.logger!.LogError($"Failed to store the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new StoreSecretResponse(false, TB("Failed to store the secret data due to an API issue."));
}
var state = await result.Content.ReadFromJsonAsync<StoreSecretResponse>(this.jsonRustSerializerOptions);
if (!state.Success)
this.logger!.LogError($"Failed to store the secret data for secret ID '{secretId.SecretId}': '{state.Issue}'");
this.logger!.LogError($"Failed to store the secret data for '{secretKey}': '{state.Issue}'");
if (state.Success && storeType is SecretStoreType.DATA_SOURCE)
await this.DeleteSecretKey(secretId, LegacySecretKey(secretId));
return state;
}
@ -56,20 +77,49 @@ public sealed partial class RustService
/// Tries to delete the secret data for the given secret ID.
/// </summary>
/// <param name="secretId">The secret ID to delete the data for.</param>
/// <param name="storeType">The secret store type.</param>
/// <returns>The delete secret response.</returns>
public async Task<DeleteSecretResponse> DeleteSecret(ISecretId secretId)
public async Task<DeleteSecretResponse> DeleteSecret(ISecretId secretId, SecretStoreType storeType)
{
var request = new SelectSecretRequest($"secret::{secretId.SecretId}::{secretId.SecretName}", Environment.UserName, false);
var deleteResult = await this.DeleteSecretKey(secretId, SecretKey(secretId, storeType));
if (storeType is not SecretStoreType.DATA_SOURCE || !deleteResult.Success)
return deleteResult;
var legacyDeleteResult = await this.DeleteSecretKey(secretId, LegacySecretKey(secretId));
if (!legacyDeleteResult.Success)
return legacyDeleteResult;
return deleteResult with { WasEntryFound = deleteResult.WasEntryFound || legacyDeleteResult.WasEntryFound };
}
private async Task<RequestedSecret> GetLegacySecret(ISecretId secretId, bool isTrying)
{
var secretKey = LegacySecretKey(secretId);
var secretRequest = new SelectSecretRequest(secretKey, Environment.UserName, isTrying);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
if(!isTrying)
this.logger!.LogError($"Failed to get the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new RequestedSecret(false, new EncryptedText(string.Empty), TB("Failed to get the secret data due to an API issue."));
}
return await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
}
private async Task<DeleteSecretResponse> DeleteSecretKey(ISecretId secretId, string secretKey)
{
var request = new SelectSecretRequest(secretKey, Environment.UserName, false);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to delete the secret data for secret ID '{secretId.SecretId}' due to an API issue: '{result.StatusCode}'");
this.logger!.LogError($"Failed to delete the secret data for '{secretKey}' due to an API issue: '{result.StatusCode}'");
return new DeleteSecretResponse{Success = false, WasEntryFound = false, Issue = TB("Failed to delete the secret data due to an API issue.")};
}
var state = await result.Content.ReadFromJsonAsync<DeleteSecretResponse>(this.jsonRustSerializerOptions);
if (!state.Success)
this.logger!.LogError($"Failed to delete the secret data for secret ID '{secretId.SecretId}': '{state.Issue}'");
this.logger!.LogError($"Failed to delete the secret data for '{secretKey}': '{state.Issue}'");
return state;
}