cli: Give passphrase error on prompt
Example:
✗ Invalid passphrase, please try again
? Passphrase: ***
This commit is contained in:
parent
5040f33025
commit
579ddfe725
|
|
@ -177,10 +177,11 @@ pub fn authenticate(options: Options, profile: &Profile) -> anyhow::Result<()> {
|
||||||
term::format::Identity::new(profile).styled()
|
term::format::Identity::new(profile).styled()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let validator = term::io::PassphraseValidator::new(profile.keystore.clone());
|
||||||
let passphrase = if options.stdin {
|
let passphrase = if options.stdin {
|
||||||
term::passphrase_stdin()?
|
term::passphrase_stdin()?
|
||||||
} else {
|
} else {
|
||||||
term::passphrase(RAD_PASSPHRASE)?
|
term::passphrase(RAD_PASSPHRASE, validator)?
|
||||||
};
|
};
|
||||||
register(&mut agent, profile, passphrase)?;
|
register(&mut agent, profile, passphrase)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ pub fn start(
|
||||||
let envs = if profile.keystore.is_encrypted()? {
|
let envs = if profile.keystore.is_encrypted()? {
|
||||||
// Ask passphrase here, otherwise it'll be a fatal error when running the daemon
|
// Ask passphrase here, otherwise it'll be a fatal error when running the daemon
|
||||||
// without `RAD_PASSPHRASE`.
|
// without `RAD_PASSPHRASE`.
|
||||||
let Ok(passphrase) = term::io::passphrase(profile::env::RAD_PASSPHRASE) else {
|
let validator = term::io::PassphraseValidator::new(profile.keystore.clone());
|
||||||
|
let Ok(passphrase) = term::io::passphrase(profile::env::RAD_PASSPHRASE, validator) else {
|
||||||
anyhow::bail!("your radicle passphrase is required to start your node");
|
anyhow::bail!("your radicle passphrase is required to start your node");
|
||||||
};
|
};
|
||||||
Some((profile::env::RAD_PASSPHRASE, passphrase))
|
Some((profile::env::RAD_PASSPHRASE, passphrase))
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,51 @@
|
||||||
use radicle::cob::issue::Issue;
|
use radicle::cob::issue::Issue;
|
||||||
use radicle::cob::thread::{Comment, CommentId};
|
use radicle::cob::thread::{Comment, CommentId};
|
||||||
use radicle::crypto::ssh::keystore::MemorySigner;
|
use radicle::crypto::ssh::keystore::MemorySigner;
|
||||||
use radicle::crypto::Signer;
|
use radicle::crypto::{ssh::Keystore, Signer};
|
||||||
use radicle::profile::env::RAD_PASSPHRASE;
|
use radicle::profile::env::RAD_PASSPHRASE;
|
||||||
use radicle::profile::Profile;
|
use radicle::profile::Profile;
|
||||||
|
|
||||||
pub use radicle_term::io::*;
|
pub use radicle_term::io::*;
|
||||||
pub use radicle_term::spinner;
|
pub use radicle_term::spinner;
|
||||||
|
|
||||||
|
use inquire::validator;
|
||||||
|
|
||||||
|
/// Validates secret key passphrases.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PassphraseValidator {
|
||||||
|
keystore: Keystore,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PassphraseValidator {
|
||||||
|
/// Create a new validator.
|
||||||
|
pub fn new(keystore: Keystore) -> Self {
|
||||||
|
Self { keystore }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl inquire::validator::StringValidator for PassphraseValidator {
|
||||||
|
fn validate(
|
||||||
|
&self,
|
||||||
|
input: &str,
|
||||||
|
) -> Result<validator::Validation, inquire::error::CustomUserError> {
|
||||||
|
let passphrase = Passphrase::from(input.to_owned());
|
||||||
|
if self.keystore.is_valid_passphrase(&passphrase)? {
|
||||||
|
Ok(validator::Validation::Valid)
|
||||||
|
} else {
|
||||||
|
Ok(validator::Validation::Invalid(
|
||||||
|
validator::ErrorMessage::from("Invalid passphrase, please try again"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user.
|
/// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user.
|
||||||
pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
|
pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
|
||||||
if let Ok(signer) = profile.signer() {
|
if let Ok(signer) = profile.signer() {
|
||||||
return Ok(signer);
|
return Ok(signer);
|
||||||
}
|
}
|
||||||
let passphrase = passphrase(RAD_PASSPHRASE)?;
|
let validator = PassphraseValidator::new(profile.keystore.clone());
|
||||||
|
let passphrase = passphrase(RAD_PASSPHRASE, validator)?;
|
||||||
let spinner = spinner("Unsealing key...");
|
let spinner = spinner("Unsealing key...");
|
||||||
let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;
|
let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,19 @@ impl Keystore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that the passphrase is valid.
|
||||||
|
pub fn is_valid_passphrase(&self, passphrase: &Passphrase) -> Result<bool, Error> {
|
||||||
|
let path = self.path.join("radicle");
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(Error::Io(io::ErrorKind::NotFound.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let secret = ssh_key::PrivateKey::read_openssh_file(&path)?;
|
||||||
|
let valid = secret.decrypt(passphrase).is_ok();
|
||||||
|
|
||||||
|
Ok(valid)
|
||||||
|
}
|
||||||
|
|
||||||
/// Check whether the secret key is encrypted.
|
/// Check whether the secret key is encrypted.
|
||||||
pub fn is_encrypted(&self) -> Result<bool, Error> {
|
pub fn is_encrypted(&self) -> Result<bool, Error> {
|
||||||
let path = self.path.join("radicle");
|
let path = self.path.join("radicle");
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use std::ffi::OsStr;
|
||||||
use std::{env, fmt, io, process};
|
use std::{env, fmt, io, process};
|
||||||
|
|
||||||
use inquire::ui::{ErrorMessageRenderConfig, StyleSheet, Styled};
|
use inquire::ui::{ErrorMessageRenderConfig, StyleSheet, Styled};
|
||||||
|
use inquire::validator;
|
||||||
use inquire::InquireError;
|
use inquire::InquireError;
|
||||||
use inquire::{ui::Color, ui::RenderConfig, Confirm, CustomType, Password};
|
use inquire::{ui::Color, ui::RenderConfig, Confirm, CustomType, Password};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
@ -11,7 +12,7 @@ use crate::command;
|
||||||
use crate::format;
|
use crate::format;
|
||||||
use crate::{style, Paint};
|
use crate::{style, Paint};
|
||||||
|
|
||||||
// TODO: Try not to export this.
|
pub use inquire;
|
||||||
pub use inquire::Select;
|
pub use inquire::Select;
|
||||||
|
|
||||||
pub const ERROR_PREFIX: Paint<&str> = Paint::red("✗");
|
pub const ERROR_PREFIX: Paint<&str> = Paint::red("✗");
|
||||||
|
|
@ -199,7 +200,10 @@ where
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn passphrase<K: AsRef<OsStr>>(var: K) -> Result<Passphrase, inquire::InquireError> {
|
pub fn passphrase<K: AsRef<OsStr>, V: validator::StringValidator + 'static>(
|
||||||
|
var: K,
|
||||||
|
validate: V,
|
||||||
|
) -> Result<Passphrase, inquire::InquireError> {
|
||||||
if let Ok(p) = env::var(var) {
|
if let Ok(p) = env::var(var) {
|
||||||
Ok(Passphrase::from(p))
|
Ok(Passphrase::from(p))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -208,6 +212,7 @@ pub fn passphrase<K: AsRef<OsStr>>(var: K) -> Result<Passphrase, inquire::Inquir
|
||||||
.with_render_config(*CONFIG)
|
.with_render_config(*CONFIG)
|
||||||
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
.with_display_mode(inquire::PasswordDisplayMode::Masked)
|
||||||
.without_confirmation()
|
.without_confirmation()
|
||||||
|
.with_validator(validate)
|
||||||
.prompt()?,
|
.prompt()?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue