refactor(blahctl)!: update to new id-desc and schema

This commit is contained in:
oxalica 2024-09-24 21:54:54 -04:00
parent 10dcc64fe9
commit 3cc6017ae0
2 changed files with 39 additions and 55 deletions

View file

@ -11,7 +11,7 @@ hex = "0.4"
humantime = "2" humantime = "2"
rand = "0.8" rand = "0.8"
reqwest = { version = "0.12", features = ["json"] } reqwest = { version = "0.12", features = ["json"] }
rusqlite = "0.32" rusqlite = { version = "0.32", features = ["rusqlite-macros"] }
serde_json = "1" serde_json = "1"
tokio = { version = "1", features = ["rt", "macros"] } tokio = { version = "1", features = ["rt", "macros"] }

View file

@ -10,17 +10,13 @@ use blah_types::{
}; };
use ed25519_dalek::pkcs8::spki::der::pem::LineEnding; use ed25519_dalek::pkcs8::spki::der::pem::LineEnding;
use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey}; use ed25519_dalek::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey};
use ed25519_dalek::{SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH}; use ed25519_dalek::{SigningKey, VerifyingKey};
use humantime::Duration; use humantime::Duration;
use rand::thread_rng; use rand::thread_rng;
use reqwest::Url; use reqwest::Url;
use rusqlite::{named_params, Connection}; use rusqlite::{prepare_and_bind, Connection};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
/// NB. Sync with docs of [`User::url`].
// FIXME: Remove old interface.
const KEY_URL_SUBPATH: &str = "/.well-known/blah/key";
const USER_AGENT: &str = concat!("blahctl/", env!("CARGO_PKG_VERSION")); const USER_AGENT: &str = concat!("blahctl/", env!("CARGO_PKG_VERSION"));
/// Control or manage Blah Chat Server. /// Control or manage Blah Chat Server.
@ -198,7 +194,7 @@ impl IdDescArgs {
enum DbCommand { enum DbCommand {
/// Create and initialize database. /// Create and initialize database.
Init, Init,
/// Set user property, possibly adding new users. /// Set property of an existing user.
SetUser { SetUser {
#[command(flatten)] #[command(flatten)]
user: Box<User>, user: Box<User>,
@ -241,52 +237,44 @@ enum ApiCommand {
// This should be an enum but clap does not support it on `Args` yet. // This should be an enum but clap does not support it on `Args` yet.
// See: https://github.com/clap-rs/clap/issues/2621 // See: https://github.com/clap-rs/clap/issues/2621
#[derive(Debug, clap::Args)] #[derive(Debug, clap::Args)]
#[clap(group = clap::ArgGroup::new("user").required(true).multiple(false))] #[group(required = true, multiple = false)]
struct User { struct User {
/// Hex-encoded public key. /// Hex-encoded public key.
#[arg(long, group = "user", value_parser = userkey_parser)] #[arg(long, group = "user")]
key: Option<VerifyingKey>, key: Option<PubKey>,
/// Path to a user public key. /// Path to a user public key.
#[arg(long, short = 'f', group = "user")] #[arg(long, group = "user")]
public_key_file: Option<PathBuf>, public_key_file: Option<PathBuf>,
/// User's URL where `/.well-known/blah/key` is hosted. /// The identity URL to check.
///
/// It should be a HTTPS domain with a top-level path `/`.
#[arg(long, group = "user")] #[arg(long, group = "user")]
url: Option<Url>, id_url: Option<IdUrl>,
}
fn userkey_parser(s: &str) -> clap::error::Result<VerifyingKey> { /// The identity description JSON path to check.
(|| { #[arg(long, group = "user")]
let mut buf = [0u8; PUBLIC_KEY_LENGTH]; desc_file: Option<PathBuf>,
hex::decode_to_slice(s, &mut buf).ok()?;
VerifyingKey::from_bytes(&buf).ok()
})()
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidValue))
} }
impl User { impl User {
async fn fetch_key(&self) -> Result<PubKey> { fn load(&self, rt: &Runtime) -> Result<PubKey> {
let rawkey = if let Some(key) = &self.key { if let Some(key) = &self.key {
return Ok(key.into()); Ok(key.clone())
} else if let Some(path) = &self.public_key_file { } else if let Some(path) = &self.public_key_file {
fs::read_to_string(path).context("failed to read key file")? let src = fs::read_to_string(path).context("failed to read key file")?;
} else if let Some(url) = &self.url { let key = VerifyingKey::from_public_key_pem(&src)
let url = url.join(KEY_URL_SUBPATH)?; .context("invalid key")?
build_client()? .to_bytes();
.get(url) Ok(PubKey(key))
.send()
.await?
.error_for_status()?
.text()
.await?
} else { } else {
unreachable!() let args = IdDescArgs {
}; id_url: self.id_url.clone(),
let key = VerifyingKey::from_public_key_pem(&rawkey) desc_file: self.desc_file.clone(),
.context("invalid key")? };
.to_bytes(); Ok(args.load(rt)?.id_key)
Ok(PubKey(key)) }
} }
} }
@ -455,21 +443,17 @@ fn main_db(conn: Connection, command: DbCommand) -> Result<()> {
match command { match command {
DbCommand::Init => {} DbCommand::Init => {}
DbCommand::SetUser { user, permission } => { DbCommand::SetUser { user, permission } => {
let userkey = build_rt()?.block_on(user.fetch_key())?; let rt = build_rt()?;
let id_key = user.load(&rt)?;
conn.execute( prepare_and_bind!(
conn,
r" r"
INSERT UPDATE `user`
INTO `user` (`userkey`, `permission`) SET `permission` = :permission
VALUES (:userkey, :permission) WHERE `id_key` = :id_key
ON CONFLICT (`userkey`) DO UPDATE SET "
`permission` = :permission )
", .raw_execute()?;
named_params! {
":userkey": userkey,
":permission": permission,
},
)?;
} }
} }
Ok(()) Ok(())