From a224d2ab10046b6333690f787df66027477c7d7e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 29 Aug 2024 08:56:06 +0200 Subject: [PATCH] Migrated the store secret calls from Tauri JS to the runtime API --- .../ProviderDialog.razor | 0 .../ProviderDialog.razor.cs | 15 ++++--- .../Settings/SettingsManager.cs | 18 -------- .../Tools/Rust/GetSecretRequest.cs | 5 +-- .../Tools/Rust/StoreSecretRequest.cs | 3 ++ .../Tools/Rust/StoreSecretResponse.cs | 8 ++++ app/MindWork AI Studio/Tools/RustService.cs | 34 ++++++++++++++- runtime/src/main.rs | 41 ++++++++++++++----- 8 files changed, 83 insertions(+), 41 deletions(-) rename app/MindWork AI Studio/{Settings => Dialogs}/ProviderDialog.razor (100%) rename app/MindWork AI Studio/{Settings => Dialogs}/ProviderDialog.razor.cs (97%) create mode 100644 app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs create mode 100644 app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor similarity index 100% rename from app/MindWork AI Studio/Settings/ProviderDialog.razor rename to app/MindWork AI Studio/Dialogs/ProviderDialog.razor diff --git a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs similarity index 97% rename from app/MindWork AI Studio/Settings/ProviderDialog.razor.cs rename to app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs index e228368d..0e1a00eb 100644 --- a/app/MindWork AI Studio/Settings/ProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs @@ -1,13 +1,14 @@ using System.Text.RegularExpressions; using AIStudio.Provider; +using AIStudio.Settings; using Microsoft.AspNetCore.Components; using Host = AIStudio.Provider.SelfHosted.Host; using RustService = AIStudio.Tools.RustService; -namespace AIStudio.Settings; +namespace AIStudio.Dialogs; /// /// The provider settings dialog. @@ -82,9 +83,6 @@ public partial class ProviderDialog : ComponentBase [Inject] private RustService RustService { get; init; } = null!; - - [Inject] - private Encryption Encryption { get; init; } = null!; private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); @@ -104,8 +102,9 @@ public partial class ProviderDialog : ComponentBase private MudForm form = null!; private readonly List availableModels = new(); - - private Provider CreateProviderSettings() => new() + private readonly Encryption encryption = Program.ENCRYPTION; + + private Settings.Provider CreateProviderSettings() => new() { Num = this.DataNum, Id = this.DataId, @@ -154,7 +153,7 @@ public partial class ProviderDialog : ComponentBase var requestedSecret = await this.RustService.GetAPIKey(provider); if(requestedSecret.Success) { - this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.Encryption); + this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption); // Now, we try to load the list of available models: await this.ReloadModels(); @@ -200,7 +199,7 @@ public partial class ProviderDialog : ComponentBase var provider = addedProviderSettings.CreateProvider(this.Logger, this.RustService); // Store the API key in the OS secure storage: - var storeResponse = await this.SettingsManager.SetAPIKey(this.JsRuntime, provider, this.dataAPIKey); + var storeResponse = await this.RustService.SetAPIKey(provider, this.dataAPIKey); if (!storeResponse.Success) { this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again."; diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index cfdddccc..871119fe 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -41,25 +41,7 @@ public sealed class SettingsManager(ILogger logger) private bool IsSetUp => !string.IsNullOrWhiteSpace(ConfigDirectory) && !string.IsNullOrWhiteSpace(DataDirectory); #region API Key Handling - - private readonly record struct StoreSecretRequest(string Destination, string UserName, string Secret); - /// - /// Data structure for storing a secret response. - /// - /// True, when the secret was successfully stored. - /// The issue, when the secret could not be stored. - public readonly record struct StoreSecretResponse(bool Success, string Issue); - - /// - /// Try to store the API key for the given provider. - /// - /// The JS runtime to access the Rust code. - /// The provider to store the API key for. - /// The API key to store. - /// The store secret response. - public async Task SetAPIKey(IJSRuntime jsRuntime, IProvider provider, string key) => await jsRuntime.InvokeAsync("window.__TAURI__.invoke", "store_secret", new StoreSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, key)); - private readonly record struct DeleteSecretRequest(string Destination, string UserName); /// diff --git a/app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs b/app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs index 0fd4283d..ddccc322 100644 --- a/app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs +++ b/app/MindWork AI Studio/Tools/Rust/GetSecretRequest.cs @@ -1,7 +1,6 @@ -using System.Text.Json.Serialization; - namespace AIStudio.Tools.Rust; public readonly record struct GetSecretRequest( string Destination, - [property:JsonPropertyName("user_name")] string UserName); \ No newline at end of file + string UserName +); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs b/app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs new file mode 100644 index 00000000..a970c275 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools.Rust; + +public readonly record struct StoreSecretRequest(string Destination, string UserName, EncryptedText Secret); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs b/app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs new file mode 100644 index 00000000..04860469 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs @@ -0,0 +1,8 @@ +namespace AIStudio.Tools.Rust; + +/// +/// Data structure for storing a secret response. +/// +/// True, when the secret was successfully stored. +/// The issue, when the secret could not be stored. +public readonly record struct StoreSecretResponse(bool Success, string Issue); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/RustService.cs b/app/MindWork AI Studio/Tools/RustService.cs index b84e5471..f14abf67 100644 --- a/app/MindWork AI Studio/Tools/RustService.cs +++ b/app/MindWork AI Studio/Tools/RustService.cs @@ -1,3 +1,5 @@ +using System.Text.Json; + using AIStudio.Provider; using AIStudio.Tools.Rust; @@ -14,6 +16,11 @@ public sealed class RustService(string apiPort) : IDisposable { BaseAddress = new Uri($"http://127.0.0.1:{apiPort}"), }; + + private readonly JsonSerializerOptions jsonRustSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + }; private ILogger? logger; private Encryption? encryptor; @@ -164,7 +171,7 @@ public sealed class RustService(string apiPort) : IDisposable public async Task GetAPIKey(IProvider provider) { var secretRequest = new GetSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName); - var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest); + var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions); if (!result.IsSuccessStatusCode) { this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'"); @@ -177,6 +184,31 @@ public sealed class RustService(string apiPort) : IDisposable return secret; } + + /// + /// Try to store the API key for the given provider. + /// + /// The provider to store the API key for. + /// The API key to store. + /// The store secret response. + public async Task SetAPIKey(IProvider provider, string key) + { + var encryptedKey = await this.encryptor!.Encrypt(key); + var request = new StoreSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, encryptedKey); + var result = await this.http.PostAsJsonAsync("/secrets/store", request, this.jsonRustSerializerOptions); + if (!result.IsSuccessStatusCode) + { + this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'"); + return new StoreSecretResponse(false, "Failed to get the API key due to an API issue."); + } + + var state = await result.Content.ReadFromJsonAsync(); + if (!state.Success) + this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}': '{state.Issue}'"); + + return state; + } + #region IDisposable diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 8887089a..73cd684c 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -194,7 +194,7 @@ async fn main() { // tauri::async_runtime::spawn(async move { _ = rocket::custom(figment) - .mount("/", routes![dotnet_port, dotnet_ready, set_clipboard, check_for_update, install_update, get_secret]) + .mount("/", routes![dotnet_port, dotnet_ready, set_clipboard, check_for_update, install_update, get_secret, store_secret]) .ignite().await.unwrap() .launch().await.unwrap(); }); @@ -319,7 +319,7 @@ async fn main() { }) .plugin(tauri_plugin_window_state::Builder::default().build()) .invoke_handler(tauri::generate_handler![ - store_secret, delete_secret + delete_secret ]) .build(tauri::generate_context!()) .expect("Error while running Tauri application"); @@ -746,30 +746,49 @@ async fn install_update() { } } -#[tauri::command] -fn store_secret(destination: String, user_name: String, secret: String) -> StoreSecretResponse { - let service = format!("mindwork-ai-studio::{}", destination); - let entry = Entry::new(service.as_str(), user_name.as_str()).unwrap(); - let result = entry.set_password(secret.as_str()); +#[post("/secrets/store", data = "")] +fn store_secret(request: Json) -> Json { + let user_name = request.user_name.as_str(); + let decrypted_text = match ENCRYPTION.decrypt(&request.secret) { + Ok(text) => text, + Err(e) => { + error!(Source = "Secret Store"; "Failed to decrypt the text: {e}."); + return Json(StoreSecretResponse { + success: false, + issue: format!("Failed to decrypt the text: {e}"), + }) + }, + }; + + let service = format!("mindwork-ai-studio::{}", request.destination); + let entry = Entry::new(service.as_str(), user_name).unwrap(); + let result = entry.set_password(decrypted_text.as_str()); match result { Ok(_) => { info!(Source = "Secret Store"; "Secret for {service} and user {user_name} was stored successfully."); - StoreSecretResponse { + Json(StoreSecretResponse { success: true, issue: String::from(""), - } + }) }, Err(e) => { error!(Source = "Secret Store"; "Failed to store secret for {service} and user {user_name}: {e}."); - StoreSecretResponse { + Json(StoreSecretResponse { success: false, issue: e.to_string(), - } + }) }, } } +#[derive(Deserialize)] +struct StoreSecret { + destination: String, + user_name: String, + secret: EncryptedText, +} + #[derive(Serialize)] struct StoreSecretResponse { success: bool,