diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs index 4ee8bb1a..8bec772a 100644 --- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs @@ -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); diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs index e8792b1a..f3e89e75 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs @@ -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; } diff --git a/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs b/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs index 89b2cadd..0eef7262 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs @@ -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; } diff --git a/app/MindWork AI Studio/Settings/IDataSource.cs b/app/MindWork AI Studio/Settings/IDataSource.cs index 39291ad2..7e29123d 100644 --- a/app/MindWork AI Studio/Settings/IDataSource.cs +++ b/app/MindWork AI Studio/Settings/IDataSource.cs @@ -16,21 +16,6 @@ namespace AIStudio.Settings; [JsonDerivedType(typeof(DataSourceERI_V1), nameof(DataSourceType.ERI_V1))] public interface IDataSource : IConfigurationObject { - /// - /// The number of the data source. - /// - public uint Num { get; init; } - - /// - /// The unique identifier of the data source. - /// - public string Id { get; init; } - - /// - /// The name of the data source. - /// - public string Name { get; init; } - /// /// Which type of data source is this? /// @@ -40,16 +25,6 @@ public interface IDataSource : IConfigurationObject /// Which data security policy is applied to this data source? /// public DataSourceSecurity SecurityPolicy { get; init; } - - /// - /// Is this data source an enterprise configuration? - /// - public bool IsEnterpriseConfiguration { get; init; } - - /// - /// The ID of the enterprise configuration plugin. - /// - public Guid EnterpriseConfigurationPluginId { get; init; } /// /// The maximum number of matches to return when retrieving data from the ERI server. diff --git a/app/MindWork AI Studio/Settings/IERIDataSource.cs b/app/MindWork AI Studio/Settings/IERIDataSource.cs index 5744ace8..40dd625d 100644 --- a/app/MindWork AI Studio/Settings/IERIDataSource.cs +++ b/app/MindWork AI Studio/Settings/IERIDataSource.cs @@ -1,4 +1,5 @@ using AIStudio.Assistants.ERI; +using AIStudio.Settings.DataModel; using AIStudio.Tools.ERIClient.DataModel; namespace AIStudio.Settings; diff --git a/app/MindWork AI Studio/Settings/IExternalDataSource.cs b/app/MindWork AI Studio/Settings/IExternalDataSource.cs index 8dd03718..6b75fa56 100644 --- a/app/MindWork AI Studio/Settings/IExternalDataSource.cs +++ b/app/MindWork AI Studio/Settings/IExternalDataSource.cs @@ -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; diff --git a/app/MindWork AI Studio/Tools/ERIClient/ERIClientV1.cs b/app/MindWork AI Studio/Tools/ERIClient/ERIClientV1.cs index 287be619..f00976b9 100644 --- a/app/MindWork AI Studio/Tools/ERIClient/ERIClientV1.cs +++ b/app/MindWork AI Studio/Tools/ERIClient/ERIClientV1.cs @@ -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() diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseApiKey.cs b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseApiKey.cs index 91d8ec04..63b7ebfb 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseApiKey.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseApiKey.cs @@ -13,81 +13,4 @@ public sealed record PendingEnterpriseApiKey( string SecretId, string SecretName, string ApiKey, - SecretStoreType StoreType); - -/// -/// Static container for pending API keys during plugin loading. -/// -public static class PendingEnterpriseApiKeys -{ - private static readonly List PENDING_KEYS = []; - private static readonly Lock LOCK = new(); - - /// - /// Adds a pending API key to the list. - /// - /// The pending API key to add. - public static void Add(PendingEnterpriseApiKey key) - { - lock (LOCK) - PENDING_KEYS.Add(key); - } - - /// - /// Gets and clears all pending API keys. - /// - /// A list of all pending API keys. - public static IReadOnlyList GetAndClear() - { - lock (LOCK) - { - var keys = PENDING_KEYS.ToList(); - PENDING_KEYS.Clear(); - return keys; - } - } -} - -/// -/// Represents a pending enterprise secret that needs to be stored in the OS keyring. -/// -/// The secret ID. -/// The secret name. -/// The decrypted secret data. -public sealed record PendingEnterpriseSecret( - string SecretId, - string SecretName, - string SecretData); - -/// -/// Static container for pending enterprise secrets during plugin loading. -/// -public static class PendingEnterpriseSecrets -{ - private static readonly List PENDING_SECRETS = []; - private static readonly Lock LOCK = new(); - - /// - /// Adds a pending enterprise secret to the list. - /// - /// The pending enterprise secret to add. - public static void Add(PendingEnterpriseSecret secret) - { - lock (LOCK) - PENDING_SECRETS.Add(secret); - } - - /// - /// Gets and clears all pending enterprise secrets. - /// - /// A list of all pending enterprise secrets. - public static IReadOnlyList GetAndClear() - { - lock (LOCK) - { - var secrets = PENDING_SECRETS.ToList(); - PENDING_SECRETS.Clear(); - return secrets; - } - } -} \ No newline at end of file + SecretStoreType StoreType); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseApiKeys.cs b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseApiKeys.cs new file mode 100644 index 00000000..8824424e --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseApiKeys.cs @@ -0,0 +1,34 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Static container for pending API keys during plugin loading. +/// +public static class PendingEnterpriseApiKeys +{ + private static readonly List PENDING_KEYS = []; + private static readonly Lock LOCK = new(); + + /// + /// Adds a pending API key to the list. + /// + /// The pending API key to add. + public static void Add(PendingEnterpriseApiKey key) + { + lock (LOCK) + PENDING_KEYS.Add(key); + } + + /// + /// Gets and clears all pending API keys. + /// + /// A list of all pending API keys. + public static IReadOnlyList GetAndClear() + { + lock (LOCK) + { + var keys = PENDING_KEYS.ToList(); + PENDING_KEYS.Clear(); + return keys; + } + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseSecret.cs b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseSecret.cs new file mode 100644 index 00000000..d4be88e4 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseSecret.cs @@ -0,0 +1,14 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents a pending enterprise secret that needs to be stored in the OS keyring. +/// +/// The secret ID. +/// The secret name. +/// The decrypted secret data. +/// The type of secret store to use. +public sealed record PendingEnterpriseSecret( + string SecretId, + string SecretName, + string SecretData, + SecretStoreType StoreType); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseSecrets.cs b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseSecrets.cs new file mode 100644 index 00000000..1fef45fa --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PendingEnterpriseSecrets.cs @@ -0,0 +1,34 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Static container for pending enterprise secrets during plugin loading. +/// +public static class PendingEnterpriseSecrets +{ + private static readonly List PENDING_SECRETS = []; + private static readonly Lock LOCK = new(); + + /// + /// Adds a pending enterprise secret to the list. + /// + /// The pending enterprise secret to add. + public static void Add(PendingEnterpriseSecret secret) + { + lock (LOCK) + PENDING_SECRETS.Add(secret); + } + + /// + /// Gets and clears all pending enterprise secrets. + /// + /// A list of all pending enterprise secrets. + public static IReadOnlyList GetAndClear() + { + lock (LOCK) + { + var secrets = PENDING_SECRETS.ToList(); + PENDING_SECRETS.Clear(); + return secrets; + } + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs index f5b09f1f..77391601 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs @@ -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."); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs index 8bd7ae0c..26f10e7d 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs @@ -252,6 +252,7 @@ public sealed record PluginConfigurationObject /// A list of currently available plugins. /// A list of all existing configuration objects. /// An optional parameter specifying the type of secret store to use for deleting associated API keys from the OS keyring, if applicable. + /// When true, delete the associated non-API-key secret from the OS keyring. /// Returns true if the configuration was altered during cleanup; otherwise, false. public static async Task CleanLeftOverConfigurationObjects( 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 diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index 0b7147da..d09eaf34 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -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: diff --git a/app/MindWork AI Studio/Tools/SecretStoreType.cs b/app/MindWork AI Studio/Tools/SecretStoreType.cs index 9ebcadcd..5e9182d7 100644 --- a/app/MindWork AI Studio/Tools/SecretStoreType.cs +++ b/app/MindWork AI Studio/Tools/SecretStoreType.cs @@ -1,10 +1,10 @@ namespace AIStudio.Tools; /// -/// Represents the type of secret store used for API keys. +/// Represents the type of secret store used for API keys and other secrets. /// /// -/// 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). /// @@ -29,4 +29,9 @@ public enum SecretStoreType /// Image provider secrets. Uses the "image::" prefix. /// IMAGE_PROVIDER, + + /// + /// Data source secrets. Uses the "data-source::" prefix. + /// + DATA_SOURCE, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs b/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs index d0d4ba9e..5e8ae2f0 100644 --- a/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs +++ b/app/MindWork AI Studio/Tools/SecretStoreTypeExtensions.cs @@ -9,12 +9,14 @@ public static class SecretStoreTypeExtensions /// LLM_PROVIDER uses the legacy "provider" prefix for backward compatibility. /// /// The SecretStoreType enum value. - /// >The corresponding prefix string. + /// The corresponding prefix string. 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", }; diff --git a/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs index cd564e9a..6db55a6c 100644 --- a/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs +++ b/app/MindWork AI Studio/Tools/Services/EnterpriseEnvironmentService.cs @@ -200,7 +200,7 @@ public sealed class EnterpriseEnvironmentService(ILogger(), null, secretTargets); + AddEnterpriseManagedSecretTargets(configurationData.DataSources.OfType(), SecretStoreType.DATA_SOURCE, secretTargets); return secretTargets.ToList(); } private static void AddEnterpriseManagedSecretTargets( IEnumerable secrets, - SecretStoreType? storeType, + SecretStoreType storeType, ISet secretTargets) where TSecret : ISecretId, IConfigurationObject { foreach (var secret in secrets) diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Secrets.cs b/app/MindWork AI Studio/Tools/Services/RustService.Secrets.cs index 49f51a1d..f66801a6 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.Secrets.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.Secrets.cs @@ -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}"; + /// /// Try to get the secret data for the given secret ID. /// /// The secret ID to get the data for. + /// The secret store type. /// Indicates if we are trying to get the data. In that case, we don't log errors. /// The requested secret. - public async Task GetSecret(ISecretId secretId, bool isTrying = false) + public async Task 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(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 /// /// The secret ID to store the data for. /// The data to store. + /// The secret store type. /// The store secret response. - public async Task SetSecret(ISecretId secretId, string secretData) + public async Task 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(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. /// /// The secret ID to delete the data for. + /// The secret store type. /// The delete secret response. - public async Task DeleteSecret(ISecretId secretId) + public async Task 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 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(this.jsonRustSerializerOptions); + } + + private async Task 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(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; }