Skip to content

Commit edac682

Browse files
AaronFeickertagubarev
authored andcommitted
feat: improved passphrase flow (tari-project#5279)
Description --- Improves the flow for setting or changing a passphrase. Closes [issue 5127](tari-project#5127). Motivation and Context --- When setting or [changing](tari-project#5175) a wallet passphrase, the console wallet provides [feedback](tari-project#5111) on the strength of the provided passphrase. In the case of a weak passphrase, it does not prompt the user to choose a better one. This PR implements a better flow for this process, as shown in [this flowchart](tari-project#5127 (comment)). How Has This Been Tested? --- Tested manually. What process can a PR reviewer use to test or verify this change? --- Testing needs to be done manually to assert that the process represented by the linked flowchart is implemented. Manual testing should cover the entire flow for these two operations: - Setting the passphrase for a new wallet - Changing the passphrase for an existing wallet Breaking Changes --- None.
1 parent 5414d58 commit edac682

File tree

1 file changed

+84
-33
lines changed
  • applications/tari_console_wallet/src/init

1 file changed

+84
-33
lines changed

applications/tari_console_wallet/src/init/mod.rs

+84-33
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,69 @@ pub enum WalletBoot {
7878
Recovery,
7979
}
8080

81+
/// Get and confirm a passphrase from the user, with feedback
82+
/// This is intended to be used for new or changed passphrases
83+
///
84+
/// You must provide the initial and confirmation prompts to pass to the user
85+
///
86+
/// We do several things:
87+
/// - Prompt the user for a passphrase
88+
/// - Have the user confirm the passphrase
89+
/// - Score the passphrase
90+
/// - If the passphrase is weak (or empty), give feedback and ask the user what to do:
91+
/// - Proceed with the weak (or empty) passphrase
92+
/// - Choose a better passphrase
93+
/// - Cancel the operation
94+
///
95+
/// If the passphrase and confirmation don't match, or if the user cancels, returns an error
96+
/// Otherwise, returns the passphrase as a `SafePassword`
97+
fn get_new_passphrase(prompt: &str, confirm: &str) -> Result<SafePassword, ExitError> {
98+
// We may need to prompt for a passphrase multiple times
99+
loop {
100+
// Prompt the user for a passphrase and confirm it
101+
let passphrase = prompt_password(prompt)?;
102+
let confirmed = prompt_password(confirm)?;
103+
if passphrase.reveal() != confirmed.reveal() {
104+
return Err(ExitError::new(ExitCode::InputError, "Passphrases don't match!"));
105+
}
106+
107+
// Score the passphrase and provide feedback
108+
let weak = display_password_feedback(&passphrase);
109+
110+
// If the passphrase is weak, see if the user wishes to change it
111+
if weak {
112+
println!("Would you like to choose a different passphrase?");
113+
println!(" y/Y: Yes, choose a different passphrase");
114+
println!(" n/N: No, use this passphrase");
115+
println!(" Enter anything else if you changed your mind and want to cancel");
116+
117+
let mut input = "".to_string();
118+
std::io::stdin().read_line(&mut input);
119+
120+
match input.trim().to_lowercase().as_str() {
121+
// Choose a different passphrase
122+
"y" => {
123+
continue;
124+
},
125+
// Use this passphrase
126+
"n" => {
127+
return Ok(passphrase);
128+
},
129+
// By default, we cancel to be safe
130+
_ => {
131+
return Err(ExitError::new(
132+
ExitCode::InputError,
133+
"Canceling with unchanged passphrase!",
134+
));
135+
},
136+
}
137+
} else {
138+
// The passphrase is fine, so return it
139+
return Ok(passphrase);
140+
}
141+
}
142+
}
143+
81144
/// Get feedback, if available, for a weak passphrase
82145
fn get_password_feedback(passphrase: &SafePassword) -> Option<Vec<String>> {
83146
std::str::from_utf8(passphrase.reveal())
@@ -88,27 +151,35 @@ fn get_password_feedback(passphrase: &SafePassword) -> Option<Vec<String>> {
88151
.map(|suggestion| suggestion.into_iter().map(|item| item.to_string()).collect())
89152
}
90153

91-
// Display password feedback to the user
92-
fn display_password_feedback(passphrase: &SafePassword) {
154+
/// Display passphrase feedback to the user
155+
///
156+
/// Returns `true` if and only if the passphrase is weak
157+
fn display_password_feedback(passphrase: &SafePassword) -> bool {
93158
if passphrase.reveal().is_empty() {
94159
// The passphrase is empty, which the scoring library doesn't handle
95-
println!("However, an empty password puts your wallet at risk against an attacker with access to this device.");
160+
println!();
161+
println!("An empty password puts your wallet at risk against an attacker with access to this device.");
96162
println!("Use this only if you are sure that your device is safe from prying eyes!");
97163
println!();
164+
165+
true
98166
} else if let Some(feedback) = get_password_feedback(passphrase) {
99167
// The scoring library provided feedback
168+
println!();
100169
println!(
101-
"However, the password you chose is weak; a determined attacker with access to your device may be able to \
102-
guess it."
170+
"The password you chose is weak; a determined attacker with access to your device may be able to guess it."
103171
);
104172
println!("You may want to consider changing it to a stronger one.");
105173
println!("Here are some suggestions:");
106174
for suggestion in feedback {
107175
println!("- {}", suggestion);
108176
}
109177
println!();
178+
179+
true
110180
} else {
111-
// There is no feedback to provide
181+
// The Force is strong with this one
182+
false
112183
}
113184
}
114185

@@ -162,17 +233,8 @@ pub async fn change_password(
162233
)
163234
.await?;
164235

165-
let new = prompt_password("New wallet password: ")?;
166-
let confirmed = prompt_password("Confirm new password: ")?;
167-
168-
if new.reveal() != confirmed.reveal() {
169-
return Err(ExitError::new(ExitCode::InputError, "Passwords don't match!"));
170-
}
171-
172-
println!("Passwords match.");
173-
174-
// If the passphrase is weak, let the user know
175-
display_password_feedback(&new);
236+
// Get a new passphrase
237+
let new = get_new_passphrase("New wallet passphrase: ", "Confirm new passphrase: ")?;
176238

177239
// Use the existing and new passphrases to attempt to change the wallet passphrase
178240
wallet.db.change_passphrase(&existing, &new).map_err(|e| match e {
@@ -655,24 +717,13 @@ pub(crate) fn boot_with_password(
655717

656718
let password = match boot_mode {
657719
WalletBoot::New => {
658-
debug!(target: LOG_TARGET, "Prompting for password.");
659-
let password = prompt_password("Create wallet password: ")?;
660-
let confirmed = prompt_password("Confirm wallet password: ")?;
661-
662-
if password.reveal() != confirmed.reveal() {
663-
return Err(ExitError::new(ExitCode::InputError, "Passwords don't match!"));
664-
}
665-
666-
println!("Passwords match.");
667-
668-
// If the passphrase is weak, let the user know
669-
display_password_feedback(&password);
670-
671-
password
720+
// Get a new passphrase
721+
debug!(target: LOG_TARGET, "Prompting for passphrase.");
722+
get_new_passphrase("Create wallet passphrase: ", "Confirm wallet passphrase: ")?
672723
},
673724
WalletBoot::Existing | WalletBoot::Recovery => {
674-
debug!(target: LOG_TARGET, "Prompting for password.");
675-
prompt_password("Enter wallet password: ")?
725+
debug!(target: LOG_TARGET, "Prompting for passphrase.");
726+
prompt_password("Enter wallet passphrase: ")?
676727
},
677728
};
678729

0 commit comments

Comments
 (0)