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.
This commit is contained in:
Sebastian Thiel 2024-06-24 17:34:37 +02:00
parent de00e4f049
commit 05506f49fa
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
7 changed files with 50 additions and 5 deletions

View File

@ -14,6 +14,7 @@ import {
MessageRole, MessageRole,
type Prompt type Prompt
} from '$lib/ai/types'; } from '$lib/ai/types';
import { invoke } from '$lib/backend/ipc';
import { buildFailureFromAny, isFailure, ok, type Result } from '$lib/result'; import { buildFailureFromAny, isFailure, ok, type Result } from '$lib/result';
import { splitMessage } from '$lib/utils/commitMessage'; import { splitMessage } from '$lib/utils/commitMessage';
import OpenAI from 'openai'; import OpenAI from 'openai';
@ -90,7 +91,17 @@ export class AIService {
} }
async getOpenAIKey() { 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() { async getOpenAIModleName() {
@ -108,7 +119,17 @@ export class AIService {
} }
async getAnthropicKey() { 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() { async getAnthropicModelName() {

View File

@ -5,6 +5,10 @@ export class GitConfigService {
return (await invoke<T | undefined>('git_get_global_config', { key })) || undefined; return (await invoke<T | undefined>('git_get_global_config', { key })) || undefined;
} }
async remove(key: string): Promise<undefined> {
return await invoke('git_remove_global_config', { key });
}
async getWithDefault<T extends string>(key: string, defaultValue: T): Promise<T> { async getWithDefault<T extends string>(key: string, defaultValue: T): Promise<T> {
const value = await invoke<T | undefined>('git_get_global_config', { key }); const value = await invoke<T | undefined>('git_get_global_config', { key });
return value || defaultValue; return value || defaultValue;

View File

@ -15,6 +15,7 @@
import TextBox from '$lib/shared/TextBox.svelte'; import TextBox from '$lib/shared/TextBox.svelte';
import { UserService } from '$lib/stores/user'; import { UserService } from '$lib/stores/user';
import { getContext } from '$lib/utils/context'; import { getContext } from '$lib/utils/context';
import { invoke } from '@tauri-apps/api/tauri';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
const gitConfigService = getContext(GitConfigService); const gitConfigService = getContext(GitConfigService);
@ -34,10 +35,17 @@
let ollamaEndpoint: string | undefined; let ollamaEndpoint: string | undefined;
let ollamaModel: string | undefined; let ollamaModel: string | undefined;
function setConfiguration(key: GitAIConfigKey, value: string | undefined) { async function setConfiguration(key: GitAIConfigKey, value: string | undefined) {
if (!initialized) return; 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); $: setConfiguration(GitAIConfigKey.ModelProvider, modelKind);

View File

@ -79,6 +79,11 @@ impl App {
Ok(value.to_string()) 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<Option<String>> { pub fn git_get_global_config(key: &str) -> Result<Option<String>> {
let config = git2::Config::open_default()?; let config = git2::Config::open_default()?;
let value = config.get_string(key); let value = config.get_string(key);

View File

@ -103,6 +103,12 @@ pub async fn git_set_global_config(
Ok(result) 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)] #[tauri::command(async)]
#[instrument(skip(_handle), err(Debug))] #[instrument(skip(_handle), err(Debug))]
pub async fn git_get_global_config( pub async fn git_get_global_config(

View File

@ -27,9 +27,9 @@ pub mod github;
pub mod keys; pub mod keys;
pub mod projects; pub mod projects;
pub mod remotes; pub mod remotes;
pub mod secret;
pub mod undo; pub mod undo;
pub mod users; pub mod users;
pub mod virtual_branches; pub mod virtual_branches;
pub mod secret;
pub mod zip; pub mod zip;

View File

@ -168,6 +168,7 @@ fn main() {
commands::delete_all_data, commands::delete_all_data,
commands::mark_resolved, commands::mark_resolved,
commands::git_set_global_config, commands::git_set_global_config,
commands::git_remove_global_config,
commands::git_get_global_config, commands::git_get_global_config,
commands::git_test_push, commands::git_test_push,
commands::git_test_fetch, commands::git_test_fetch,