From 05506f49faf5da231ba111ef4a0a654dedee7e6e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 24 Jun 2024 17:34:37 +0200 Subject: [PATCH] migrate AI tokens from the git-configuration to the keystore. All AI related options are stored in the user-level git configuration file. Upon first access, they will be removed from there and placed into the keystore as part of the migration. The UI is provided with functions to store and save secrets which it will use specifically to interact with these keys. It's explicitly out of scope to *not* show the keys in plain-text anymore after entering them. --- app/src/lib/ai/service.ts | 25 +++++++++++++++++++++++-- app/src/lib/backend/gitConfigService.ts | 4 ++++ app/src/routes/settings/ai/+page.svelte | 12 ++++++++++-- crates/gitbutler-tauri/src/app.rs | 5 +++++ crates/gitbutler-tauri/src/commands.rs | 6 ++++++ crates/gitbutler-tauri/src/lib.rs | 2 +- crates/gitbutler-tauri/src/main.rs | 1 + 7 files changed, 50 insertions(+), 5 deletions(-) diff --git a/app/src/lib/ai/service.ts b/app/src/lib/ai/service.ts index 30554ba0b..6c3d1b2a1 100644 --- a/app/src/lib/ai/service.ts +++ b/app/src/lib/ai/service.ts @@ -14,6 +14,7 @@ import { MessageRole, type Prompt } from '$lib/ai/types'; +import { invoke } from '$lib/backend/ipc'; import { buildFailureFromAny, isFailure, ok, type Result } from '$lib/result'; import { splitMessage } from '$lib/utils/commitMessage'; import OpenAI from 'openai'; @@ -90,7 +91,17 @@ export class AIService { } async getOpenAIKey() { - return await this.gitConfig.get(GitAIConfigKey.OpenAIKey); + const secretInConfig = await this.gitConfig.get(GitAIConfigKey.OpenAIKey); + if (secretInConfig !== undefined) { + await invoke('secret_set_global', { + handle: 'aiOpenAIKey', + secret: secretInConfig + }); + await this.gitConfig.remove(GitAIConfigKey.OpenAIKey); + return secretInConfig; + } else { + return await invoke('secret_get_global', { handle: 'aiOpenAIKey' }); + } } async getOpenAIModleName() { @@ -108,7 +119,17 @@ export class AIService { } async getAnthropicKey() { - return await this.gitConfig.get(GitAIConfigKey.AnthropicKey); + const secretInConfig = await this.gitConfig.get(GitAIConfigKey.AnthropicKey); + if (secretInConfig !== undefined) { + await invoke('secret_set_global', { + handle: 'aiAnthropicKey', + secret: secretInConfig + }); + await this.gitConfig.remove(GitAIConfigKey.AnthropicKey); + return secretInConfig; + } else { + return await invoke('secret_get_global', { handle: 'aiAnthropicKey' }); + } } async getAnthropicModelName() { diff --git a/app/src/lib/backend/gitConfigService.ts b/app/src/lib/backend/gitConfigService.ts index 9e1843a1e..2197d132a 100644 --- a/app/src/lib/backend/gitConfigService.ts +++ b/app/src/lib/backend/gitConfigService.ts @@ -5,6 +5,10 @@ export class GitConfigService { return (await invoke('git_get_global_config', { key })) || undefined; } + async remove(key: string): Promise { + return await invoke('git_remove_global_config', { key }); + } + async getWithDefault(key: string, defaultValue: T): Promise { const value = await invoke('git_get_global_config', { key }); return value || defaultValue; diff --git a/app/src/routes/settings/ai/+page.svelte b/app/src/routes/settings/ai/+page.svelte index ebe028904..ed8cf3428 100644 --- a/app/src/routes/settings/ai/+page.svelte +++ b/app/src/routes/settings/ai/+page.svelte @@ -15,6 +15,7 @@ import TextBox from '$lib/shared/TextBox.svelte'; import { UserService } from '$lib/stores/user'; import { getContext } from '$lib/utils/context'; + import { invoke } from '@tauri-apps/api/tauri'; import { onMount, tick } from 'svelte'; const gitConfigService = getContext(GitConfigService); @@ -34,10 +35,17 @@ let ollamaEndpoint: string | undefined; let ollamaModel: string | undefined; - function setConfiguration(key: GitAIConfigKey, value: string | undefined) { + async function setConfiguration(key: GitAIConfigKey, value: string | undefined) { if (!initialized) return; - gitConfigService.set(key, value || ''); + if (key === GitAIConfigKey.OpenAIKey || key === GitAIConfigKey.AnthropicKey) { + await invoke('secret_set_global', { + handle: key.split('.')[1], + secret: value + }); + } else { + gitConfigService.set(key, value || ''); + } } $: setConfiguration(GitAIConfigKey.ModelProvider, modelKind); diff --git a/crates/gitbutler-tauri/src/app.rs b/crates/gitbutler-tauri/src/app.rs index 09121a091..a5390b6f9 100644 --- a/crates/gitbutler-tauri/src/app.rs +++ b/crates/gitbutler-tauri/src/app.rs @@ -79,6 +79,11 @@ impl App { Ok(value.to_string()) } + pub fn git_remove_global_config(key: &str) -> Result<()> { + let mut config = git2::Config::open_default()?; + Ok(config.remove(key)?) + } + pub fn git_get_global_config(key: &str) -> Result> { let config = git2::Config::open_default()?; let value = config.get_string(key); diff --git a/crates/gitbutler-tauri/src/commands.rs b/crates/gitbutler-tauri/src/commands.rs index 85157fc2b..4036c2dce 100644 --- a/crates/gitbutler-tauri/src/commands.rs +++ b/crates/gitbutler-tauri/src/commands.rs @@ -103,6 +103,12 @@ pub async fn git_set_global_config( Ok(result) } +#[tauri::command(async)] +#[instrument(err(Debug))] +pub async fn git_remove_global_config(key: &str) -> Result<(), Error> { + Ok(app::App::git_remove_global_config(key)?) +} + #[tauri::command(async)] #[instrument(skip(_handle), err(Debug))] pub async fn git_get_global_config( diff --git a/crates/gitbutler-tauri/src/lib.rs b/crates/gitbutler-tauri/src/lib.rs index 551357bea..9f92d795d 100644 --- a/crates/gitbutler-tauri/src/lib.rs +++ b/crates/gitbutler-tauri/src/lib.rs @@ -27,9 +27,9 @@ pub mod github; pub mod keys; pub mod projects; pub mod remotes; +pub mod secret; pub mod undo; pub mod users; pub mod virtual_branches; -pub mod secret; pub mod zip; diff --git a/crates/gitbutler-tauri/src/main.rs b/crates/gitbutler-tauri/src/main.rs index 10793d0dc..b5866dae6 100644 --- a/crates/gitbutler-tauri/src/main.rs +++ b/crates/gitbutler-tauri/src/main.rs @@ -168,6 +168,7 @@ fn main() { commands::delete_all_data, commands::mark_resolved, commands::git_set_global_config, + commands::git_remove_global_config, commands::git_get_global_config, commands::git_test_push, commands::git_test_fetch,