diff --git a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs index 8d153f39..b8306d58 100644 --- a/app/MindWork AI Studio/Settings/EmbeddingProvider.cs +++ b/app/MindWork AI Studio/Settings/EmbeddingProvider.cs @@ -20,7 +20,8 @@ public sealed record EmbeddingProvider( bool IsEnterpriseConfiguration = false, Guid EnterpriseConfigurationPluginId = default, string Hostname = "http://localhost:1234", - Host Host = Host.NONE) : ConfigurationBaseObject, ISecretId + Host Host = Host.NONE, + bool AllowUserProvidedAPIKey = false) : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey { private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); @@ -97,6 +98,10 @@ public sealed record EmbeddingProvider( return false; } + var allowUserProvidedApiKey = false; + if (table.TryGetValue("AllowUserProvidedAPIKey", out var allowUserProvidedApiKeyValue) && allowUserProvidedApiKeyValue.TryRead(out var allowUserProvidedApiKeyBool)) + allowUserProvidedApiKey = allowUserProvidedApiKeyBool; + provider = new EmbeddingProvider { Num = 0, // will be set later by the PluginConfigurationObject @@ -109,10 +114,18 @@ public sealed record EmbeddingProvider( EnterpriseConfigurationPluginId = configPluginId, Hostname = hostname, Host = host, + AllowUserProvidedAPIKey = allowUserProvidedApiKey, }; - // Handle encrypted API key if present: - if (table.TryGetValue("APIKey", out var apiKeyValue) && apiKeyValue.TryRead(out var apiKeyText) && !string.IsNullOrWhiteSpace(apiKeyText)) + // Handle an encrypted API key if present. When the user manages their own key for this + // embedding provider, we must never enqueue an embedded key: doing so would overwrite the + // user's key in the OS keyring on every configuration reload. + if (allowUserProvidedApiKey) + { + if (table.TryGetValue("APIKey", out var ignoredApiKeyValue) && ignoredApiKeyValue.TryRead(out var ignoredApiKeyText) && !string.IsNullOrWhiteSpace(ignoredApiKeyText)) + LOGGER.LogWarning($"The configured embedding provider {idx} sets both AllowUserProvidedAPIKey and an embedded APIKey. Ignoring the embedded key: the user manages their own key for this provider. (Plugin ID: {configPluginId})"); + } + else if (table.TryGetValue("APIKey", out var apiKeyValue) && apiKeyValue.TryRead(out var apiKeyText) && !string.IsNullOrWhiteSpace(apiKeyText)) { if (!EnterpriseEncryption.IsEncrypted(apiKeyText)) LOGGER.LogWarning($"The configured embedding provider {idx} contains a plaintext API key. Only encrypted API keys (starting with 'ENC:v1:') are supported. (Plugin ID: {configPluginId})"); diff --git a/app/MindWork AI Studio/Settings/TranscriptionProvider.cs b/app/MindWork AI Studio/Settings/TranscriptionProvider.cs index 973cd138..5afae679 100644 --- a/app/MindWork AI Studio/Settings/TranscriptionProvider.cs +++ b/app/MindWork AI Studio/Settings/TranscriptionProvider.cs @@ -20,7 +20,8 @@ public sealed record TranscriptionProvider( bool IsEnterpriseConfiguration = false, Guid EnterpriseConfigurationPluginId = default, string Hostname = "http://localhost:1234", - Host Host = Host.NONE) : ConfigurationBaseObject, ISecretId + Host Host = Host.NONE, + bool AllowUserProvidedAPIKey = false) : ConfigurationBaseObject, ISecretId, IUserProvidedAPIKey { private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); @@ -97,6 +98,10 @@ public sealed record TranscriptionProvider( return false; } + var allowUserProvidedApiKey = false; + if (table.TryGetValue("AllowUserProvidedAPIKey", out var allowUserProvidedApiKeyValue) && allowUserProvidedApiKeyValue.TryRead(out var allowUserProvidedApiKeyBool)) + allowUserProvidedApiKey = allowUserProvidedApiKeyBool; + provider = new TranscriptionProvider { Num = 0, // will be set later by the PluginConfigurationObject @@ -109,10 +114,18 @@ public sealed record TranscriptionProvider( EnterpriseConfigurationPluginId = configPluginId, Hostname = hostname, Host = host, + AllowUserProvidedAPIKey = allowUserProvidedApiKey, }; - // Handle encrypted API key if present: - if (table.TryGetValue("APIKey", out var apiKeyValue) && apiKeyValue.TryRead(out var apiKeyText) && !string.IsNullOrWhiteSpace(apiKeyText)) + // Handle an encrypted API key if present. When the user manages their own key for this + // transcription provider, we must never enqueue an embedded key: doing so would overwrite + // the user's key in the OS keyring on every configuration reload. + if (allowUserProvidedApiKey) + { + if (table.TryGetValue("APIKey", out var ignoredApiKeyValue) && ignoredApiKeyValue.TryRead(out var ignoredApiKeyText) && !string.IsNullOrWhiteSpace(ignoredApiKeyText)) + LOGGER.LogWarning($"The configured transcription provider {idx} sets both AllowUserProvidedAPIKey and an embedded APIKey. Ignoring the embedded key: the user manages their own key for this provider. (Plugin ID: {configPluginId})"); + } + else if (table.TryGetValue("APIKey", out var apiKeyValue) && apiKeyValue.TryRead(out var apiKeyText) && !string.IsNullOrWhiteSpace(apiKeyText)) { if (!EnterpriseEncryption.IsEncrypted(apiKeyText)) LOGGER.LogWarning($"The configured transcription provider {idx} contains a plaintext API key. Only encrypted API keys (starting with 'ENC:v1:') are supported. (Plugin ID: {configPluginId})");