@@ -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
|
- Check the nip07 signer in `CliOptions::ensure_signer` - by Awiteb
|
||||||
- Required the `applied_commits` - by Awiteb
|
- Required the `applied_commits` - by Awiteb
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Remove `dbus` C dep - by Awiteb
|
||||||
|
|
||||||
## [0.4.0] - 2025-08-08
|
## [0.4.0] - 2025-08-08
|
||||||
|
|
||||||
### Added
|
### 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"
|
rust-version = "1.88.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-utility = "0.3.1"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
convert_case = "0.8.0"
|
convert_case = "0.8.0"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
@@ -28,6 +29,16 @@ toml = "0.9.4"
|
|||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
|
|
||||||
|
[dependencies.keyring]
|
||||||
|
features = [
|
||||||
|
"apple-native",
|
||||||
|
"windows-native",
|
||||||
|
"async-secret-service",
|
||||||
|
"tokio",
|
||||||
|
"crypto-rust",
|
||||||
|
]
|
||||||
|
version = "3.6.3"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
version = "4.5.42"
|
version = "4.5.42"
|
||||||
@@ -46,10 +57,6 @@ tracing-subscriber = "0.3.19"
|
|||||||
default-features = false
|
default-features = false
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
|
|
||||||
[dependencies.nostr-keyring]
|
|
||||||
default-features = false
|
|
||||||
version = "0.43.0"
|
|
||||||
|
|
||||||
[dependencies.nostr-sdk]
|
[dependencies.nostr-sdk]
|
||||||
default-features = false
|
default-features = false
|
||||||
version = "0.43.0"
|
version = "0.43.0"
|
||||||
|
|||||||
10
flake.nix
10
flake.nix
@@ -23,11 +23,9 @@
|
|||||||
# n34 development environment
|
# n34 development environment
|
||||||
devShells.default = mkShell {
|
devShells.default = mkShell {
|
||||||
packages = [
|
packages = [
|
||||||
dbus
|
|
||||||
git-cliff
|
git-cliff
|
||||||
mdbook
|
mdbook
|
||||||
nushell
|
nushell
|
||||||
pkg-config
|
|
||||||
taplo
|
taplo
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -50,14 +48,6 @@
|
|||||||
cargoLock.lockFile = ./Cargo.lock;
|
cargoLock.lockFile = ./Cargo.lock;
|
||||||
src = lib.cleanSource ./.;
|
src = lib.cleanSource ./.;
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkg-config
|
|
||||||
];
|
|
||||||
|
|
||||||
buildInputs = [
|
|
||||||
dbus
|
|
||||||
];
|
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
inherit (manifest) description homepage;
|
inherit (manifest) description homepage;
|
||||||
license = lib.licenses.gpl3Plus;
|
license = lib.licenses.gpl3Plus;
|
||||||
|
|||||||
@@ -17,8 +17,9 @@
|
|||||||
use clap::{ArgGroup, Args};
|
use clap::{ArgGroup, Args};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::{Cli, CliOptions, traits::CommandRunner},
|
cli::{CliOptions, traits::CommandRunner},
|
||||||
error::N34Result,
|
error::N34Result,
|
||||||
|
nostr_keyring,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
@@ -46,8 +47,6 @@ impl CommandRunner for KeyringArgs {
|
|||||||
const NEED_SIGNER: bool = false;
|
const NEED_SIGNER: bool = false;
|
||||||
|
|
||||||
async fn run(self, mut options: CliOptions) -> N34Result<()> {
|
async fn run(self, mut options: CliOptions) -> N34Result<()> {
|
||||||
let keyring = nostr_keyring::NostrKeyring::new(Cli::N34_KEYRING_SERVICE_NAME);
|
|
||||||
|
|
||||||
if self.enable {
|
if self.enable {
|
||||||
options.config.keyring_secret_key = true;
|
options.config.keyring_secret_key = true;
|
||||||
} else if self.disable {
|
} else if self.disable {
|
||||||
@@ -55,7 +54,7 @@ impl CommandRunner for KeyringArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.reset || self.disable {
|
if self.reset || self.disable {
|
||||||
let _ = keyring.delete(Cli::USER_KEY_PAIR_ENTRY);
|
let _ = nostr_keyring::user::delete().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.config.dump()
|
options.config.dump()
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ impl CliOptions {
|
|||||||
if let Some(ref bunker_url) = self.bunker_url {
|
if let Some(ref bunker_url) = self.bunker_url {
|
||||||
let mut nostrconnect = NostrConnect::new(
|
let mut nostrconnect = NostrConnect::new(
|
||||||
bunker_url.clone(),
|
bunker_url.clone(),
|
||||||
Cli::n34_keypair()?,
|
Cli::n34_keypair().await?,
|
||||||
BUNKER_TIMEOUT,
|
BUNKER_TIMEOUT,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,8 +42,6 @@ use nostr::key::Keys;
|
|||||||
use nostr::key::SecretKey;
|
use nostr::key::SecretKey;
|
||||||
use nostr_browser_signer_proxy::BrowserSignerProxy;
|
use nostr_browser_signer_proxy::BrowserSignerProxy;
|
||||||
use nostr_browser_signer_proxy::BrowserSignerProxyOptions;
|
use nostr_browser_signer_proxy::BrowserSignerProxyOptions;
|
||||||
use nostr_keyring::KeyringError;
|
|
||||||
use nostr_keyring::NostrKeyring;
|
|
||||||
use types::RelayOrSet;
|
use types::RelayOrSet;
|
||||||
|
|
||||||
pub use self::commands::*;
|
pub use self::commands::*;
|
||||||
@@ -52,7 +50,7 @@ use self::traits::CommandRunner;
|
|||||||
use crate::cli::options_state::BROWSER_SIGNER_PROXY_TIMEOUT;
|
use crate::cli::options_state::BROWSER_SIGNER_PROXY_TIMEOUT;
|
||||||
use crate::error::N34Error;
|
use crate::error::N34Error;
|
||||||
use crate::error::N34Result;
|
use crate::error::N34Result;
|
||||||
use crate::nostr_utils::traits::NostrKeyringErrorUtils;
|
use crate::nostr_keyring;
|
||||||
|
|
||||||
/// Header message, used in the help message
|
/// Header message, used in the help message
|
||||||
const HEADER: &str = r#"Copyright (C) 2025 Awiteb <a@4rs.nl>
|
const HEADER: &str = r#"Copyright (C) 2025 Awiteb <a@4rs.nl>
|
||||||
@@ -86,13 +84,6 @@ pub struct Cli {
|
|||||||
|
|
||||||
|
|
||||||
impl 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
|
/// Executes the command
|
||||||
pub async fn run(self) -> N34Result<()> {
|
pub async fn run(self) -> N34Result<()> {
|
||||||
self.command.run(self.options).await
|
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
|
/// Gets the n34 keypair from the keyring or generates and stores a new one
|
||||||
/// if none exists.
|
/// if none exists.
|
||||||
pub fn n34_keypair() -> N34Result<Keys> {
|
pub async fn n34_keypair() -> N34Result<Keys> {
|
||||||
let keyring = NostrKeyring::new(Self::N34_KEYRING_SERVICE_NAME);
|
match nostr_keyring::n34::get().await {
|
||||||
|
|
||||||
match keyring.get(Self::N34_KEY_PAIR_ENTRY) {
|
|
||||||
Ok(keys) => Ok(keys),
|
Ok(keys) => Ok(keys),
|
||||||
Err(nostr_keyring::Error::Keyring(KeyringError::NoEntry)) => {
|
Err(N34Error::Keyring(keyring::Error::NoEntry)) => {
|
||||||
let new_keys = Keys::generate();
|
let new_keys = Keys::generate();
|
||||||
keyring.set(Self::N34_KEY_PAIR_ENTRY, &new_keys)?;
|
nostr_keyring::n34::set(&new_keys).await?;
|
||||||
Ok(new_keys)
|
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
|
/// 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
|
/// is provided, stores and returns it. If no key exists and none is
|
||||||
/// provided, returns an error.
|
/// provided, returns an error.
|
||||||
pub fn user_keypair(secret_key: Option<SecretKey>) -> N34Result<Keys> {
|
pub async fn user_keypair(secret_key: Option<SecretKey>) -> N34Result<Keys> {
|
||||||
let keyring = NostrKeyring::new(Self::N34_KEYRING_SERVICE_NAME);
|
let keyring_key = nostr_keyring::user::get().await;
|
||||||
let keyring_key = keyring.get(Self::USER_KEY_PAIR_ENTRY);
|
|
||||||
|
|
||||||
if let Err(ref err) = keyring_key
|
if let Err(ref err) = keyring_key
|
||||||
&& err.is_keyring_no_entry()
|
&& err.is_keyring_no_entry()
|
||||||
&& let Some(secret_key) = secret_key
|
&& let Some(secret_key) = secret_key
|
||||||
{
|
{
|
||||||
let keypair = Keys::new(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);
|
return Ok(keypair);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +122,7 @@ impl Cli {
|
|||||||
if err.is_keyring_no_entry() {
|
if err.is_keyring_no_entry() {
|
||||||
N34Error::SecretKeyKeyringWithoutEntry
|
N34Error::SecretKeyKeyringWithoutEntry
|
||||||
} else {
|
} else {
|
||||||
N34Error::Keyring(err)
|
err
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -142,7 +130,7 @@ impl Cli {
|
|||||||
|
|
||||||
/// Processes the CLI configuration by applying fallback values from config if
|
/// Processes the CLI configuration by applying fallback values from config if
|
||||||
/// needed. Returns the processed Cli configuration if successful.
|
/// 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);
|
cli.options.pow = cli.options.pow.or(cli.options.config.pow);
|
||||||
|
|
||||||
if cli.options.relays.is_empty()
|
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());
|
cli.options.bunker_url = Some(bunker_url.clone());
|
||||||
} else if cli.options.config.keyring_secret_key {
|
} else if cli.options.config.keyring_secret_key {
|
||||||
cli.options.secret_key = Some(
|
cli.options.secret_key = Some(
|
||||||
Cli::user_keypair(cli.options.secret_key)?
|
Cli::user_keypair(cli.options.secret_key)
|
||||||
|
.await?
|
||||||
.secret_key()
|
.secret_key()
|
||||||
.clone(),
|
.clone(),
|
||||||
);
|
);
|
||||||
|
|||||||
12
src/error.rs
12
src/error.rs
@@ -51,8 +51,12 @@ pub enum N34Error {
|
|||||||
Addr(#[from] AddrParseError),
|
Addr(#[from] AddrParseError),
|
||||||
#[error("Browser Signer Proxy Error: {0}")]
|
#[error("Browser Signer Proxy Error: {0}")]
|
||||||
BrowserSignerProxy(#[from] nostr_browser_signer_proxy::Error),
|
BrowserSignerProxy(#[from] nostr_browser_signer_proxy::Error),
|
||||||
|
#[error("Keys error: {0}")]
|
||||||
|
Keys(#[from] nostr::key::Error),
|
||||||
#[error("Keyring error: {0}")]
|
#[error("Keyring error: {0}")]
|
||||||
Keyring(#[from] nostr_keyring::Error),
|
Keyring(#[from] keyring::Error),
|
||||||
|
#[error("Join error: {0}")]
|
||||||
|
Join(#[from] tokio::task::JoinError),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Config(#[from] ConfigError),
|
Config(#[from] ConfigError),
|
||||||
#[error(
|
#[error(
|
||||||
@@ -187,4 +191,10 @@ impl N34Error {
|
|||||||
_ => ExitCode::FAILURE,
|
_ => 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;
|
pub mod cli;
|
||||||
/// N34 errors
|
/// N34 errors
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
/// Nostr keyring
|
||||||
|
pub mod nostr_keyring;
|
||||||
/// Nostr utils module
|
/// Nostr utils module
|
||||||
pub mod nostr_utils;
|
pub mod nostr_utils;
|
||||||
|
|
||||||
@@ -79,7 +81,7 @@ async fn try_main() -> error::N34Result<()> {
|
|||||||
let guard =
|
let guard =
|
||||||
tracing::subscriber::set_default(tracing_subscriber::registry().with(stderr_log_layer()));
|
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()?;
|
let logs_file = cli::utils::logs_file()?;
|
||||||
|
|
||||||
// Replace the thread-local subscriber with a global default subscriber based on
|
// 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,
|
parser::Token,
|
||||||
types::{RelayUrl, Url},
|
types::{RelayUrl, Url},
|
||||||
};
|
};
|
||||||
use nostr_keyring::KeyringError;
|
|
||||||
|
|
||||||
use crate::cli::issue::ISSUE_ALT_PREFIX;
|
use crate::cli::issue::ISSUE_ALT_PREFIX;
|
||||||
use crate::cli::patch::{
|
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)]
|
#[easy_ext::ext(KindExt)]
|
||||||
impl Kind {
|
impl Kind {
|
||||||
/// Checks if the kind is a root kind, such as GitIssue, GitPatch, GitPr or
|
/// Checks if the kind is a root kind, such as GitIssue, GitPatch, GitPr or
|
||||||
|
|||||||
Reference in New Issue
Block a user