remove keystore
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m46s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m46s
Rust / build (macos-latest, stable) (push) Has been cancelled
Rust / build (windows-latest, stable) (push) Has been cancelled
This commit is contained in:
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -1321,7 +1321,6 @@ dependencies = [
|
|||||||
"gpui_windows",
|
"gpui_windows",
|
||||||
"indexset",
|
"indexset",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"key_store",
|
|
||||||
"log",
|
"log",
|
||||||
"nostr-connect",
|
"nostr-connect",
|
||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
@@ -3509,22 +3508,6 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "key_store"
|
|
||||||
version = "0.3.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"common",
|
|
||||||
"futures",
|
|
||||||
"gpui",
|
|
||||||
"log",
|
|
||||||
"nostr-sdk",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"smallvec",
|
|
||||||
"smol",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "khronos-egl"
|
name = "khronos-egl"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ theme = { path = "../theme" }
|
|||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
state = { path = "../state" }
|
state = { path = "../state" }
|
||||||
device = { path = "../device" }
|
device = { path = "../device" }
|
||||||
key_store = { path = "../key_store" }
|
|
||||||
chat = { path = "../chat" }
|
chat = { path = "../chat" }
|
||||||
chat_ui = { path = "../chat_ui" }
|
chat_ui = { path = "../chat_ui" }
|
||||||
settings = { path = "../settings" }
|
settings = { path = "../settings" }
|
||||||
|
|||||||
@@ -79,9 +79,6 @@ fn main() {
|
|||||||
// Initialize theme registry
|
// Initialize theme registry
|
||||||
theme::init(cx);
|
theme::init(cx);
|
||||||
|
|
||||||
// Initialize backend for keys storage
|
|
||||||
key_store::init(cx);
|
|
||||||
|
|
||||||
// Initialize the nostr client
|
// Initialize the nostr client
|
||||||
state::init(window, cx);
|
state::init(window, cx);
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "key_store"
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
publish.workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
common = { path = "../common" }
|
|
||||||
|
|
||||||
gpui.workspace = true
|
|
||||||
nostr-sdk.workspace = true
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
|
||||||
smallvec.workspace = true
|
|
||||||
smol.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
futures.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use common::config_dir;
|
|
||||||
use futures::FutureExt as _;
|
|
||||||
use gpui::AsyncApp;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct Credential {
|
|
||||||
public_key: PublicKey,
|
|
||||||
secret: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Credential {
|
|
||||||
pub fn new(user: String, secret: Vec<u8>) -> Self {
|
|
||||||
Self {
|
|
||||||
public_key: PublicKey::parse(&user).unwrap(),
|
|
||||||
secret: String::from_utf8(secret).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn public_key(&self) -> PublicKey {
|
|
||||||
self.public_key
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn secret(&self) -> &str {
|
|
||||||
&self.secret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum KeyItem {
|
|
||||||
User,
|
|
||||||
Bunker,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for KeyItem {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::User => write!(f, "coop-user"),
|
|
||||||
Self::Bunker => write!(f, "coop-bunker"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<KeyItem> for String {
|
|
||||||
fn from(item: KeyItem) -> Self {
|
|
||||||
item.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait KeyBackend: Any + Send + Sync {
|
|
||||||
fn name(&self) -> &str;
|
|
||||||
|
|
||||||
/// Reads the credentials from the provider.
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
fn read_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>>;
|
|
||||||
|
|
||||||
/// Writes the credentials to the provider.
|
|
||||||
fn write_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
username: &'a str,
|
|
||||||
password: &'a [u8],
|
|
||||||
cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
|
|
||||||
|
|
||||||
/// Deletes the credentials from the provider.
|
|
||||||
fn delete_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A credentials provider that stores credentials in the system keychain.
|
|
||||||
pub struct KeyringProvider;
|
|
||||||
|
|
||||||
impl KeyBackend for KeyringProvider {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"keyring"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>> {
|
|
||||||
async move { cx.update(|cx| cx.read_credentials(url)).await }.boxed_local()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
username: &'a str,
|
|
||||||
password: &'a [u8],
|
|
||||||
cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
|
||||||
async move {
|
|
||||||
cx.update(move |cx| cx.write_credentials(url, username, password))
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
.boxed_local()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
|
||||||
async move { cx.update(move |cx| cx.delete_credentials(url)).await }.boxed_local()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A credentials provider that stores credentials in a local file.
|
|
||||||
pub struct FileProvider {
|
|
||||||
path: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileProvider {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let path = config_dir().join(".keys");
|
|
||||||
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
let _ = std::fs::create_dir_all(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { path }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_credentials(&self) -> Result<HashMap<String, (String, Vec<u8>)>> {
|
|
||||||
let json = std::fs::read(&self.path)?;
|
|
||||||
let credentials: HashMap<String, (String, Vec<u8>)> = serde_json::from_slice(&json)?;
|
|
||||||
|
|
||||||
Ok(credentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_credentials(&self, credentials: &HashMap<String, (String, Vec<u8>)>) -> Result<()> {
|
|
||||||
let json = serde_json::to_string(credentials)?;
|
|
||||||
std::fs::write(&self.path, json)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for FileProvider {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyBackend for FileProvider {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"file"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
_cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<Option<(String, Vec<u8>)>>> + 'a>> {
|
|
||||||
async move {
|
|
||||||
Ok(self
|
|
||||||
.load_credentials()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.get(url)
|
|
||||||
.cloned())
|
|
||||||
}
|
|
||||||
.boxed_local()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
username: &'a str,
|
|
||||||
password: &'a [u8],
|
|
||||||
_cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
|
||||||
async move {
|
|
||||||
let mut credentials = self.load_credentials().unwrap_or_default();
|
|
||||||
credentials.insert(url.to_string(), (username.to_string(), password.to_vec()));
|
|
||||||
|
|
||||||
self.save_credentials(&credentials)
|
|
||||||
}
|
|
||||||
.boxed_local()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete_credentials<'a>(
|
|
||||||
&'a self,
|
|
||||||
url: &'a str,
|
|
||||||
_cx: &'a AsyncApp,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Result<()>> + 'a>> {
|
|
||||||
async move {
|
|
||||||
let mut credentials = self.load_credentials()?;
|
|
||||||
credentials.remove(url);
|
|
||||||
|
|
||||||
self.save_credentials(&credentials)
|
|
||||||
}
|
|
||||||
.boxed_local()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
use std::sync::{Arc, LazyLock};
|
|
||||||
|
|
||||||
pub use backend::*;
|
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Task};
|
|
||||||
use smallvec::{smallvec, SmallVec};
|
|
||||||
|
|
||||||
mod backend;
|
|
||||||
|
|
||||||
static DISABLE_KEYRING: LazyLock<bool> =
|
|
||||||
LazyLock::new(|| std::env::var("DISABLE_KEYRING").is_ok_and(|value| !value.is_empty()));
|
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
|
||||||
KeyStore::set_global(cx.new(KeyStore::new), cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct GlobalKeyStore(Entity<KeyStore>);
|
|
||||||
|
|
||||||
impl Global for GlobalKeyStore {}
|
|
||||||
|
|
||||||
pub struct KeyStore {
|
|
||||||
/// Key Store for storing credentials
|
|
||||||
pub backend: Arc<dyn KeyBackend>,
|
|
||||||
|
|
||||||
/// Whether the keystore has been initialized
|
|
||||||
pub initialized: bool,
|
|
||||||
|
|
||||||
/// Tasks for asynchronous operations
|
|
||||||
_tasks: SmallVec<[Task<()>; 1]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyStore {
|
|
||||||
/// Retrieve the global keys state
|
|
||||||
pub fn global(cx: &App) -> Entity<Self> {
|
|
||||||
cx.global::<GlobalKeyStore>().0.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the global keys instance
|
|
||||||
pub(crate) fn set_global(state: Entity<Self>, cx: &mut App) {
|
|
||||||
cx.set_global(GlobalKeyStore(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new keys instance
|
|
||||||
pub(crate) fn new(cx: &mut Context<Self>) -> Self {
|
|
||||||
// Use the file system for keystore in development or when the user specifies it
|
|
||||||
let use_file_keystore = cfg!(debug_assertions) || *DISABLE_KEYRING;
|
|
||||||
|
|
||||||
// Construct the key backend
|
|
||||||
let backend: Arc<dyn KeyBackend> = if use_file_keystore {
|
|
||||||
Arc::new(FileProvider::default())
|
|
||||||
} else {
|
|
||||||
Arc::new(KeyringProvider)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only used for testing keyring availability on the user's system
|
|
||||||
let read_credential = cx.read_credentials("Coop");
|
|
||||||
let mut tasks = smallvec![];
|
|
||||||
|
|
||||||
tasks.push(
|
|
||||||
// Verify the keyring availability
|
|
||||||
cx.spawn(async move |this, cx| {
|
|
||||||
let result = read_credential.await;
|
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
if let Err(e) = result {
|
|
||||||
log::error!("Keyring error: {e}");
|
|
||||||
// For Linux:
|
|
||||||
// The user has not installed secret service on their system
|
|
||||||
// Fall back to the file provider
|
|
||||||
this.backend = Arc::new(FileProvider::default());
|
|
||||||
}
|
|
||||||
this.initialized = true;
|
|
||||||
cx.notify();
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
backend,
|
|
||||||
initialized: false,
|
|
||||||
_tasks: tasks,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the key backend.
|
|
||||||
pub fn backend(&self) -> Arc<dyn KeyBackend> {
|
|
||||||
Arc::clone(&self.backend)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the keystore is a file key backend.
|
|
||||||
pub fn is_using_file_keystore(&self) -> bool {
|
|
||||||
self.backend.name() == "file"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user