@@ -34,6 +34,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Check the nip07 signer in `CliOptions::ensure_signer` - by Awiteb
|
||||
- Required the `applied_commits` - by Awiteb
|
||||
|
||||
### Refactor
|
||||
|
||||
- Remove `dbus` C dep - by Awiteb
|
||||
|
||||
## [0.4.0] - 2025-08-08
|
||||
|
||||
### Added
|
||||
|
||||
1263
Cargo.lock
generated
1263
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@@ -14,6 +14,7 @@ categories = ["command-line-utilities"]
|
||||
rust-version = "1.88.0"
|
||||
|
||||
[dependencies]
|
||||
async-utility = "0.3.1"
|
||||
chrono = "0.4.41"
|
||||
convert_case = "0.8.0"
|
||||
dirs = "6.0.0"
|
||||
@@ -28,6 +29,16 @@ toml = "0.9.4"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
|
||||
[dependencies.keyring]
|
||||
features = [
|
||||
"apple-native",
|
||||
"windows-native",
|
||||
"async-secret-service",
|
||||
"tokio",
|
||||
"crypto-rust",
|
||||
]
|
||||
version = "3.6.3"
|
||||
|
||||
[dependencies.clap]
|
||||
features = ["derive"]
|
||||
version = "4.5.42"
|
||||
@@ -46,10 +57,6 @@ tracing-subscriber = "0.3.19"
|
||||
default-features = false
|
||||
version = "0.43.0"
|
||||
|
||||
[dependencies.nostr-keyring]
|
||||
default-features = false
|
||||
version = "0.43.0"
|
||||
|
||||
[dependencies.nostr-sdk]
|
||||
default-features = false
|
||||
version = "0.43.0"
|
||||
|
||||
10
flake.nix
10
flake.nix
@@ -23,11 +23,9 @@
|
||||
# n34 development environment
|
||||
devShells.default = mkShell {
|
||||
packages = [
|
||||
dbus
|
||||
git-cliff
|
||||
mdbook
|
||||
nushell
|
||||
pkg-config
|
||||
taplo
|
||||
];
|
||||
|
||||
@@ -50,14 +48,6 @@
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
src = lib.cleanSource ./.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
dbus
|
||||
];
|
||||
|
||||
meta = {
|
||||
inherit (manifest) description homepage;
|
||||
license = lib.licenses.gpl3Plus;
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
use clap::{ArgGroup, Args};
|
||||
|
||||
use crate::{
|
||||
cli::{Cli, CliOptions, traits::CommandRunner},
|
||||
cli::{CliOptions, traits::CommandRunner},
|
||||
error::N34Result,
|
||||
nostr_keyring,
|
||||
};
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
@@ -46,8 +47,6 @@ impl CommandRunner for KeyringArgs {
|
||||
const NEED_SIGNER: bool = false;
|
||||
|
||||
async fn run(self, mut options: CliOptions) -> N34Result<()> {
|
||||
let keyring = nostr_keyring::NostrKeyring::new(Cli::N34_KEYRING_SERVICE_NAME);
|
||||
|
||||
if self.enable {
|
||||
options.config.keyring_secret_key = true;
|
||||
} else if self.disable {
|
||||
@@ -55,7 +54,7 @@ impl CommandRunner for KeyringArgs {
|
||||
}
|
||||
|
||||
if self.reset || self.disable {
|
||||
let _ = keyring.delete(Cli::USER_KEY_PAIR_ENTRY);
|
||||
let _ = nostr_keyring::user::delete().await;
|
||||
}
|
||||
|
||||
options.config.dump()
|
||||
|
||||
@@ -158,7 +158,7 @@ impl CliOptions {
|
||||
if let Some(ref bunker_url) = self.bunker_url {
|
||||
let mut nostrconnect = NostrConnect::new(
|
||||
bunker_url.clone(),
|
||||
Cli::n34_keypair()?,
|
||||
Cli::n34_keypair().await?,
|
||||
BUNKER_TIMEOUT,
|
||||
None,
|
||||
)
|
||||
|
||||
@@ -42,8 +42,6 @@ use nostr::key::Keys;
|
||||
use nostr::key::SecretKey;
|
||||
use nostr_browser_signer_proxy::BrowserSignerProxy;
|
||||
use nostr_browser_signer_proxy::BrowserSignerProxyOptions;
|
||||
use nostr_keyring::KeyringError;
|
||||
use nostr_keyring::NostrKeyring;
|
||||
use types::RelayOrSet;
|
||||
|
||||
pub use self::commands::*;
|
||||
@@ -52,7 +50,7 @@ use self::traits::CommandRunner;
|
||||
use crate::cli::options_state::BROWSER_SIGNER_PROXY_TIMEOUT;
|
||||
use crate::error::N34Error;
|
||||
use crate::error::N34Result;
|
||||
use crate::nostr_utils::traits::NostrKeyringErrorUtils;
|
||||
use crate::nostr_keyring;
|
||||
|
||||
/// Header message, used in the help message
|
||||
const HEADER: &str = r#"Copyright (C) 2025 Awiteb <a@4rs.nl>
|
||||
@@ -86,13 +84,6 @@ pub struct Cli {
|
||||
|
||||
|
||||
impl Cli {
|
||||
/// Keyring service name of n34
|
||||
pub const N34_KEYRING_SERVICE_NAME: &str = "n34";
|
||||
/// Keyring entry name of the n34 keypair
|
||||
pub const N34_KEY_PAIR_ENTRY: &str = "n34_keypair";
|
||||
/// Keyring entry name of the user secret key
|
||||
pub const USER_KEY_PAIR_ENTRY: &str = "user_keypair";
|
||||
|
||||
/// Executes the command
|
||||
pub async fn run(self) -> N34Result<()> {
|
||||
self.command.run(self.options).await
|
||||
@@ -100,33 +91,30 @@ impl Cli {
|
||||
|
||||
/// Gets the n34 keypair from the keyring or generates and stores a new one
|
||||
/// if none exists.
|
||||
pub fn n34_keypair() -> N34Result<Keys> {
|
||||
let keyring = NostrKeyring::new(Self::N34_KEYRING_SERVICE_NAME);
|
||||
|
||||
match keyring.get(Self::N34_KEY_PAIR_ENTRY) {
|
||||
pub async fn n34_keypair() -> N34Result<Keys> {
|
||||
match nostr_keyring::n34::get().await {
|
||||
Ok(keys) => Ok(keys),
|
||||
Err(nostr_keyring::Error::Keyring(KeyringError::NoEntry)) => {
|
||||
Err(N34Error::Keyring(keyring::Error::NoEntry)) => {
|
||||
let new_keys = Keys::generate();
|
||||
keyring.set(Self::N34_KEY_PAIR_ENTRY, &new_keys)?;
|
||||
nostr_keyring::n34::set(&new_keys).await?;
|
||||
Ok(new_keys)
|
||||
}
|
||||
Err(err) => Err(N34Error::Keyring(err)),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the user's keypair from the keyring. If no key exists and one
|
||||
/// is provided, stores and returns it. If no key exists and none is
|
||||
/// provided, returns an error.
|
||||
pub fn user_keypair(secret_key: Option<SecretKey>) -> N34Result<Keys> {
|
||||
let keyring = NostrKeyring::new(Self::N34_KEYRING_SERVICE_NAME);
|
||||
let keyring_key = keyring.get(Self::USER_KEY_PAIR_ENTRY);
|
||||
pub async fn user_keypair(secret_key: Option<SecretKey>) -> N34Result<Keys> {
|
||||
let keyring_key = nostr_keyring::user::get().await;
|
||||
|
||||
if let Err(ref err) = keyring_key
|
||||
&& err.is_keyring_no_entry()
|
||||
&& let Some(secret_key) = secret_key
|
||||
{
|
||||
let keypair = Keys::new(secret_key);
|
||||
keyring.set(Self::USER_KEY_PAIR_ENTRY, &keypair)?;
|
||||
nostr_keyring::user::set(&keypair).await?;
|
||||
return Ok(keypair);
|
||||
}
|
||||
|
||||
@@ -134,7 +122,7 @@ impl Cli {
|
||||
if err.is_keyring_no_entry() {
|
||||
N34Error::SecretKeyKeyringWithoutEntry
|
||||
} else {
|
||||
N34Error::Keyring(err)
|
||||
err
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -142,7 +130,7 @@ impl Cli {
|
||||
|
||||
/// Processes the CLI configuration by applying fallback values from config if
|
||||
/// needed. Returns the processed Cli configuration if successful.
|
||||
pub fn post_cli(mut cli: Cli) -> N34Result<Cli> {
|
||||
pub async fn post_cli(mut cli: Cli) -> N34Result<Cli> {
|
||||
cli.options.pow = cli.options.pow.or(cli.options.config.pow);
|
||||
|
||||
if cli.options.relays.is_empty()
|
||||
@@ -169,7 +157,8 @@ pub fn post_cli(mut cli: Cli) -> N34Result<Cli> {
|
||||
cli.options.bunker_url = Some(bunker_url.clone());
|
||||
} else if cli.options.config.keyring_secret_key {
|
||||
cli.options.secret_key = Some(
|
||||
Cli::user_keypair(cli.options.secret_key)?
|
||||
Cli::user_keypair(cli.options.secret_key)
|
||||
.await?
|
||||
.secret_key()
|
||||
.clone(),
|
||||
);
|
||||
|
||||
12
src/error.rs
12
src/error.rs
@@ -51,8 +51,12 @@ pub enum N34Error {
|
||||
Addr(#[from] AddrParseError),
|
||||
#[error("Browser Signer Proxy Error: {0}")]
|
||||
BrowserSignerProxy(#[from] nostr_browser_signer_proxy::Error),
|
||||
#[error("Keys error: {0}")]
|
||||
Keys(#[from] nostr::key::Error),
|
||||
#[error("Keyring error: {0}")]
|
||||
Keyring(#[from] nostr_keyring::Error),
|
||||
Keyring(#[from] keyring::Error),
|
||||
#[error("Join error: {0}")]
|
||||
Join(#[from] tokio::task::JoinError),
|
||||
#[error("{0}")]
|
||||
Config(#[from] ConfigError),
|
||||
#[error(
|
||||
@@ -187,4 +191,10 @@ impl N34Error {
|
||||
_ => ExitCode::FAILURE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the error indicates a missing keyring entry.
|
||||
#[inline]
|
||||
pub fn is_keyring_no_entry(&self) -> bool {
|
||||
matches!(self, Self::Keyring(keyring::Error::NoEntry))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
pub mod cli;
|
||||
/// N34 errors
|
||||
pub mod error;
|
||||
/// Nostr keyring
|
||||
pub mod nostr_keyring;
|
||||
/// Nostr utils module
|
||||
pub mod nostr_utils;
|
||||
|
||||
@@ -79,7 +81,7 @@ async fn try_main() -> error::N34Result<()> {
|
||||
let guard =
|
||||
tracing::subscriber::set_default(tracing_subscriber::registry().with(stderr_log_layer()));
|
||||
|
||||
let cli = cli::post_cli(Cli::parse())?;
|
||||
let cli = cli::post_cli(Cli::parse()).await?;
|
||||
let logs_file = cli::utils::logs_file()?;
|
||||
|
||||
// Replace the thread-local subscriber with a global default subscriber based on
|
||||
|
||||
98
src/nostr_keyring.rs
Normal file
98
src/nostr_keyring.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
// n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr
|
||||
// Copyright (C) 2025 Awiteb <a@4rs.nl>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
|
||||
|
||||
use async_utility::task;
|
||||
use keyring::Entry;
|
||||
use nostr::key::{Keys, SecretKey};
|
||||
|
||||
use crate::error::{N34Error, N34Result};
|
||||
|
||||
/// Keyring service name of n34
|
||||
const N34_KEYRING_SERVICE_NAME: &str = "n34";
|
||||
/// Keyring entry name of the n34 keypair
|
||||
const N34_KEY_PAIR_ENTRY: &str = "n34_keypair";
|
||||
/// Keyring entry name of the user secret key
|
||||
const USER_KEY_PAIR_ENTRY: &str = "user_keypair";
|
||||
|
||||
/// Retrieves the keypair entry for `n34`.
|
||||
#[inline]
|
||||
fn n34_entry() -> keyring::Result<Entry> {
|
||||
Entry::new(N34_KEYRING_SERVICE_NAME, N34_KEY_PAIR_ENTRY)
|
||||
}
|
||||
|
||||
/// Retrieves the keypair entry for the user.
|
||||
#[inline]
|
||||
fn user_entry() -> keyring::Result<Entry> {
|
||||
Entry::new(N34_KEYRING_SERVICE_NAME, USER_KEY_PAIR_ENTRY)
|
||||
}
|
||||
|
||||
pub mod user {
|
||||
use super::*;
|
||||
|
||||
/// Stores the user's keypair in the system.
|
||||
pub async fn set(key: &Keys) -> N34Result<()> {
|
||||
let private_key = key.secret_key().to_secret_bytes();
|
||||
|
||||
task::spawn_blocking(move || {
|
||||
user_entry()?
|
||||
.set_secret(&private_key)
|
||||
.map_err(N34Error::from)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Retrieves the user's keypair from the system.
|
||||
pub async fn get() -> N34Result<Keys> {
|
||||
task::spawn_blocking(move || {
|
||||
Ok(Keys::new(SecretKey::from_slice(
|
||||
&user_entry()?.get_secret()?,
|
||||
)?))
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Delete the user's keypair from the system.
|
||||
pub async fn delete() -> N34Result<()> {
|
||||
task::spawn_blocking(move || user_entry()?.delete_credential().map_err(N34Error::from))
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
pub mod n34 {
|
||||
use super::*;
|
||||
|
||||
/// Stores the `n34` client's keypair in the system.
|
||||
pub async fn set(key: &Keys) -> N34Result<()> {
|
||||
let private_key = key.secret_key().to_secret_bytes();
|
||||
|
||||
task::spawn_blocking(move || {
|
||||
n34_entry()?
|
||||
.set_secret(&private_key)
|
||||
.map_err(N34Error::from)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Retrieves the `n34` client's keypair from the system.
|
||||
pub async fn get() -> N34Result<Keys> {
|
||||
task::spawn_blocking(move || {
|
||||
Ok(Keys::new(SecretKey::from_slice(
|
||||
&n34_entry()?.get_secret()?,
|
||||
)?))
|
||||
})
|
||||
.await?
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ use nostr::{
|
||||
parser::Token,
|
||||
types::{RelayUrl, Url},
|
||||
};
|
||||
use nostr_keyring::KeyringError;
|
||||
|
||||
use crate::cli::issue::ISSUE_ALT_PREFIX;
|
||||
use crate::cli::patch::{
|
||||
@@ -378,15 +377,6 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
#[easy_ext::ext(NostrKeyringErrorUtils)]
|
||||
impl nostr_keyring::Error {
|
||||
/// Checks if the error indicates a missing keyring entry.
|
||||
#[inline]
|
||||
pub fn is_keyring_no_entry(&self) -> bool {
|
||||
matches!(self, nostr_keyring::Error::Keyring(KeyringError::NoEntry))
|
||||
}
|
||||
}
|
||||
|
||||
#[easy_ext::ext(KindExt)]
|
||||
impl Kind {
|
||||
/// Checks if the kind is a root kind, such as GitIssue, GitPatch, GitPr or
|
||||
|
||||
Reference in New Issue
Block a user