mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2025-04-28 19:39:46 +00:00
Migrated the store secret calls from Tauri JS to the runtime API
This commit is contained in:
parent
97854e7eaa
commit
a224d2ab10
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
@ -104,8 +102,9 @@ public partial class ProviderDialog : ComponentBase
|
||||
private MudForm form = null!;
|
||||
|
||||
private readonly List<Model> 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.";
|
@ -41,25 +41,7 @@ public sealed class SettingsManager(ILogger<SettingsManager> 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);
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for storing a secret response.
|
||||
/// </summary>
|
||||
/// <param name="Success">True, when the secret was successfully stored.</param>
|
||||
/// <param name="Issue">The issue, when the secret could not be stored.</param>
|
||||
public readonly record struct StoreSecretResponse(bool Success, string Issue);
|
||||
|
||||
/// <summary>
|
||||
/// Try to store the API key for the given provider.
|
||||
/// </summary>
|
||||
/// <param name="jsRuntime">The JS runtime to access the Rust code.</param>
|
||||
/// <param name="provider">The provider to store the API key for.</param>
|
||||
/// <param name="key">The API key to store.</param>
|
||||
/// <returns>The store secret response.</returns>
|
||||
public async Task<StoreSecretResponse> SetAPIKey(IJSRuntime jsRuntime, IProvider provider, string key) => await jsRuntime.InvokeAsync<StoreSecretResponse>("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);
|
||||
|
||||
/// <summary>
|
||||
|
@ -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);
|
||||
string UserName
|
||||
);
|
3
app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs
Normal file
3
app/MindWork AI Studio/Tools/Rust/StoreSecretRequest.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
public readonly record struct StoreSecretRequest(string Destination, string UserName, EncryptedText Secret);
|
8
app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs
Normal file
8
app/MindWork AI Studio/Tools/Rust/StoreSecretResponse.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace AIStudio.Tools.Rust;
|
||||
|
||||
/// <summary>
|
||||
/// Data structure for storing a secret response.
|
||||
/// </summary>
|
||||
/// <param name="Success">True, when the secret was successfully stored.</param>
|
||||
/// <param name="Issue">The issue, when the secret could not be stored.</param>
|
||||
public readonly record struct StoreSecretResponse(bool Success, string Issue);
|
@ -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<RustService>? logger;
|
||||
private Encryption? encryptor;
|
||||
@ -164,7 +171,7 @@ public sealed class RustService(string apiPort) : IDisposable
|
||||
public async Task<RequestedSecret> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to store the API key for the given provider.
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider to store the API key for.</param>
|
||||
/// <param name="key">The API key to store.</param>
|
||||
/// <returns>The store secret response.</returns>
|
||||
public async Task<StoreSecretResponse> 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<StoreSecretResponse>();
|
||||
if (!state.Success)
|
||||
this.logger!.LogError($"Failed to store the API key for provider '{provider.Id}': '{state.Issue}'");
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
#region IDisposable
|
||||
|
||||
|
@ -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 = "<request>")]
|
||||
fn store_secret(request: Json<StoreSecret>) -> Json<StoreSecretResponse> {
|
||||
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,
|
||||
|
Loading…
Reference in New Issue
Block a user