cli/auth: use Clap
This commit is contained in:
parent
dfd35480be
commit
384c506489
|
|
@ -1,17 +1,23 @@
|
||||||
Note that aliases must not be longer than 32 bytes, or you will get an error.
|
Note that aliases must not be longer than 32 bytes, or you will get an error.
|
||||||
There are other rules as well:
|
There are other rules as well:
|
||||||
|
|
||||||
``` (fail)
|
``` (stderr) (fail)
|
||||||
$ rad auth --alias "5fad63fe6b339fa92c588d926121bea6240773a7"
|
$ rad auth --alias "5fad63fe6b339fa92c588d926121bea6240773a7"
|
||||||
✗ Error: rad auth: alias cannot be greater than 32 bytes
|
error: invalid value '5fad63fe6b339fa92c588d926121bea6240773a7' for '--alias <STRING>': alias cannot be greater than 32 bytes
|
||||||
|
|
||||||
|
For more information, try '--help'.
|
||||||
```
|
```
|
||||||
|
|
||||||
``` (fail)
|
``` (stderr) (fail)
|
||||||
$ rad auth --alias "john doe"
|
$ rad auth --alias "john doe"
|
||||||
✗ Error: rad auth: alias cannot contain whitespace or control characters
|
error: invalid value 'john doe' for '--alias <STRING>': alias cannot contain whitespace or control characters
|
||||||
|
|
||||||
|
For more information, try '--help'.
|
||||||
```
|
```
|
||||||
|
|
||||||
``` (fail)
|
``` (stderr) (fail)
|
||||||
$ rad auth --alias ""
|
$ rad auth --alias ""
|
||||||
✗ Error: rad auth: alias cannot be empty
|
error: invalid value '' for '--alias <STRING>': alias cannot be empty
|
||||||
|
|
||||||
|
For more information, try '--help'.
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(clippy::or_fun_call)]
|
#![allow(clippy::or_fun_call)]
|
||||||
use std::ffi::OsString;
|
mod args;
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
|
|
@ -11,73 +12,18 @@ use radicle::profile::env;
|
||||||
use radicle::{profile, Profile};
|
use radicle::{profile, Profile};
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
use crate::terminal::args::{Args, Error, Help};
|
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub use args::Args;
|
||||||
name: "auth",
|
pub(crate) use args::ABOUT;
|
||||||
description: "Manage identities and profiles",
|
|
||||||
version: env!("RADICLE_VERSION"),
|
|
||||||
usage: r#"
|
|
||||||
Usage
|
|
||||||
|
|
||||||
rad auth [<option>...]
|
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
|
|
||||||
A passphrase may be given via the environment variable `RAD_PASSPHRASE` or
|
|
||||||
via the standard input stream if `--stdin` is used. Using either of these
|
|
||||||
methods disables the passphrase prompt.
|
|
||||||
|
|
||||||
Options
|
|
||||||
|
|
||||||
--alias When initializing an identity, sets the node alias
|
|
||||||
--stdin Read passphrase from stdin (default: false)
|
|
||||||
--help Print help
|
|
||||||
"#,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Options {
|
|
||||||
pub stdin: bool,
|
|
||||||
pub alias: Option<Alias>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Args for Options {
|
|
||||||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
|
||||||
use lexopt::prelude::*;
|
|
||||||
|
|
||||||
let mut stdin = false;
|
|
||||||
let mut alias = None;
|
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
|
||||||
match arg {
|
|
||||||
Long("alias") => {
|
|
||||||
let val = parser.value()?;
|
|
||||||
let val = term::args::alias(&val)?;
|
|
||||||
|
|
||||||
alias = Some(val);
|
|
||||||
}
|
|
||||||
Long("stdin") => {
|
|
||||||
stdin = true;
|
|
||||||
}
|
|
||||||
Long("help") | Short('h') => {
|
|
||||||
return Err(Error::Help.into());
|
|
||||||
}
|
|
||||||
_ => anyhow::bail!(arg.unexpected()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((Options { alias, stdin }, vec![]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|
||||||
match ctx.profile() {
|
match ctx.profile() {
|
||||||
Ok(profile) => authenticate(options, &profile),
|
Ok(profile) => authenticate(args, &profile),
|
||||||
Err(_) => init(options),
|
Err(_) => init(args),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(options: Options) -> anyhow::Result<()> {
|
pub fn init(args: Args) -> anyhow::Result<()> {
|
||||||
term::headline("Initializing your radicle 👾 identity");
|
term::headline("Initializing your radicle 👾 identity");
|
||||||
|
|
||||||
if let Ok(version) = radicle::git::version() {
|
if let Ok(version) = radicle::git::version() {
|
||||||
|
|
@ -92,7 +38,7 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
anyhow::bail!("A Git installation is required for Radicle to run.");
|
anyhow::bail!("A Git installation is required for Radicle to run.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let alias: Alias = if let Some(alias) = options.alias {
|
let alias: Alias = if let Some(alias) = args.alias {
|
||||||
alias
|
alias
|
||||||
} else {
|
} else {
|
||||||
let user = env::var("USER").ok().and_then(|u| Alias::from_str(&u).ok());
|
let user = env::var("USER").ok().and_then(|u| Alias::from_str(&u).ok());
|
||||||
|
|
@ -105,7 +51,7 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
user.ok_or_else(|| anyhow::anyhow!("An alias is required for Radicle to run."))?
|
user.ok_or_else(|| anyhow::anyhow!("An alias is required for Radicle to run."))?
|
||||||
};
|
};
|
||||||
let home = profile::home()?;
|
let home = profile::home()?;
|
||||||
let passphrase = if options.stdin {
|
let passphrase = if args.stdin {
|
||||||
Some(term::passphrase_stdin()?)
|
Some(term::passphrase_stdin()?)
|
||||||
} else {
|
} else {
|
||||||
term::passphrase_confirm("Enter a passphrase:", env::RAD_PASSPHRASE)?
|
term::passphrase_confirm("Enter a passphrase:", env::RAD_PASSPHRASE)?
|
||||||
|
|
@ -165,7 +111,7 @@ pub fn init(options: Options) -> anyhow::Result<()> {
|
||||||
|
|
||||||
/// Try loading the identity's key into SSH Agent, falling back to verifying `RAD_PASSPHRASE` for
|
/// Try loading the identity's key into SSH Agent, falling back to verifying `RAD_PASSPHRASE` for
|
||||||
/// use.
|
/// use.
|
||||||
pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
pub fn authenticate(args: Args, profile: &Profile) -> anyhow::Result<()> {
|
||||||
if !profile.keystore.is_encrypted()? {
|
if !profile.keystore.is_encrypted()? {
|
||||||
term::success!("Authenticated as {}", term::format::tertiary(profile.id()));
|
term::success!("Authenticated as {}", term::format::tertiary(profile.id()));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -186,7 +132,7 @@ pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
let passphrase = if let Some(phrase) = profile::env::passphrase() {
|
let passphrase = if let Some(phrase) = profile::env::passphrase() {
|
||||||
phrase
|
phrase
|
||||||
} else if options.stdin {
|
} else if args.stdin {
|
||||||
term::passphrase_stdin()?
|
term::passphrase_stdin()?
|
||||||
} else if let Some(passphrase) =
|
} else if let Some(passphrase) =
|
||||||
term::io::passphrase(term::io::PassphraseValidator::new(profile.keystore.clone()))?
|
term::io::passphrase(term::io::PassphraseValidator::new(profile.keystore.clone()))?
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
use clap::Parser;
|
||||||
|
use radicle::node::Alias;
|
||||||
|
|
||||||
|
pub(crate) const ABOUT: &str = "Manage identities and profiles";
|
||||||
|
const LONG_ABOUT: &str = r#"
|
||||||
|
A passphrase may be given via the environment variable `RAD_PASSPHRASE` or
|
||||||
|
via the standard input stream if `--stdin` is used. Using either of these
|
||||||
|
methods disables the passphrase prompt.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
#[command(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
|
||||||
|
pub struct Args {
|
||||||
|
/// When initializing an identity, sets the node alias
|
||||||
|
#[arg(long, value_name = "STRING")]
|
||||||
|
pub alias: Option<Alias>,
|
||||||
|
|
||||||
|
/// Read passphrase from stdin
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
pub stdin: bool,
|
||||||
|
}
|
||||||
|
|
@ -37,7 +37,10 @@ impl CommandItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMMANDS: &[CommandItem] = &[
|
const COMMANDS: &[CommandItem] = &[
|
||||||
CommandItem::Lexopt(crate::commands::auth::HELP),
|
CommandItem::Clap {
|
||||||
|
name: "auth",
|
||||||
|
about: crate::commands::auth::ABOUT,
|
||||||
|
},
|
||||||
CommandItem::Clap {
|
CommandItem::Clap {
|
||||||
name: "block",
|
name: "block",
|
||||||
about: crate::commands::block::ABOUT,
|
about: crate::commands::block::ABOUT,
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ struct CliArgs {
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
|
Auth(auth::Args),
|
||||||
Block(block::Args),
|
Block(block::Args),
|
||||||
Clean(clean::Args),
|
Clean(clean::Args),
|
||||||
Clone(clone::Args),
|
Clone(clone::Args),
|
||||||
|
|
@ -183,7 +184,9 @@ fn run(command: Command) -> Result<(), Option<anyhow::Error>> {
|
||||||
pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>> {
|
pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>> {
|
||||||
match exe {
|
match exe {
|
||||||
"auth" => {
|
"auth" => {
|
||||||
term::run_command_args::<auth::Options, _>(auth::HELP, auth::run, args.to_vec());
|
if let Some(Commands::Auth(args)) = CliArgs::parse().command {
|
||||||
|
term::run_command_fn(auth::run, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"block" => {
|
"block" => {
|
||||||
if let Some(Commands::Block(args)) = CliArgs::parse().command {
|
if let Some(Commands::Block(args)) = CliArgs::parse().command {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue