inquire/prompts/
password.rs

1use crate::{
2    config::get_configuration,
3    error::{InquireError, InquireResult},
4    formatter::StringFormatter,
5    input::Input,
6    terminal::get_default_terminal,
7    ui::{Backend, Key, KeyModifiers, PasswordBackend, RenderConfig},
8    validator::{ErrorMessage, StringValidator, Validation},
9};
10
11/// Display modes of the text input of a password prompt.
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub enum PasswordDisplayMode {
14    /// Password text input is not rendered at all, no indication of input.
15    Hidden,
16
17    /// Characters of the password text input are rendered marked as different
18    /// characters, such as asterisks. These characters are configured in the
19    /// render config.
20    Masked,
21
22    /// Password text input is fully rendered as a normal input, just like
23    /// [Text](crate::Text) prompts.
24    Full,
25}
26
27// Helper type for representing the password confirmation flow.
28struct PasswordConfirmation<'a> {
29    // The message of the prompt.
30    message: &'a str,
31
32    // The error message of the prompt.
33    error_message: &'a str,
34
35    // The input to confirm.
36    input: Input,
37}
38
39/// Prompt meant for secretive text inputs.
40///
41/// By default, the password prompt behaves like a standard one you'd see in common CLI applications: the user has no UI indicators about the state of the current input. They do not know how many characters they typed, or which character they typed, with no option to display the current text input.
42///
43/// However, you can still customize these and other behaviors if you wish:
44/// - **Standard display mode**: Set the display mode of the text input among hidden, masked and full via the `PasswordDisplayMode` enum.
45///   - Hidden: default behavior, no UI indicators.
46///   - Masked: behaves like a normal text input, except that all characters of the input are masked to a special character, which is `'*'` by default but can be customized via `RenderConfig`.
47///   - Full: behaves like a normal text input, no modifications.
48/// - **Toggle display mode**: When enabling this feature by calling the `with_display_toggle_enabled()` method, you allow the user to toggle between the standard display mode set and the full display mode.
49///   - If you have set the standard display mode to hidden (which is also the default) or masked, the user can press `Ctrl+R` to change the display mode to `Full`, and `Ctrl+R` again to change it back to the standard one.
50///   - Obviously, if you have set the standard display mode to `Full`, pressing `Ctrl+R` won't cause any changes.
51/// - **Confirmation**: By default, the password will have a confirmation flow where the user will be asked for the input twice and the two responses will be compared. If they differ, an error message is shown and the user is prompted again.
52///   - By default, a "Confirmation:" message is shown for the confirmation prompts, but this can be modified by setting a custom confirmation message only shown the second time, using the `with_custom_confirmation_message()` method.
53///   - If confirmation is not desired, it can be turned off using the `without_confirmation()` method.
54/// - **Help message**: Message displayed at the line below the prompt.
55/// - **Formatter**: Custom formatter in case you need to pre-process the user input before showing it as the final answer.
56///   - By default, it prints eight asterisk characters: `********`.
57/// - **Validators**: Custom validators to make sure a given submitted input pass the specified requirements, e.g. not allowing empty inputs or requiring special characters.
58///   - No validators are on by default.
59///
60/// Remember that for CLI applications it is standard to not allow use any display modes other than `Hidden` and to not allow the user to see the text input in any way. _Use the customization options at your discretion_.
61///
62/// # Example
63///
64/// ```no_run
65///  use inquire::{validator::{StringValidator, Validation}, Password, PasswordDisplayMode};
66///
67///  let validator = |input: &str| if input.chars().count() < 10 {
68///      Ok(Validation::Invalid("Keys must have at least 10 characters.".into()))
69///  } else {
70///      Ok(Validation::Valid)
71///  };
72///
73///  let name = Password::new("Encryption Key:")
74///      .with_display_toggle_enabled()
75///      .with_display_mode(PasswordDisplayMode::Hidden)
76///      .with_custom_confirmation_message("Encryption Key (confirm):")
77///      .with_custom_confirmation_error_message("The keys don't match.")
78///      .with_validator(validator)
79///      .with_formatter(&|_| String::from("Input received"))
80///      .with_help_message("It is recommended to generate a new one only for this purpose")
81///      .prompt();
82///
83///  match name {
84///      Ok(_) => println!("This doesn't look like a key."),
85///      Err(_) => println!("An error happened when asking for your key, try again later."),
86///  }
87/// ```
88#[derive(Clone)]
89pub struct Password<'a> {
90    /// Message to be presented to the user.
91    pub message: &'a str,
92
93    /// Message to be presented to the user when confirming the input.
94    pub custom_confirmation_message: Option<&'a str>,
95
96    /// Error to be presented to the user when password confirmation fails.
97    pub custom_confirmation_error_message: Option<&'a str>,
98
99    /// Help message to be presented to the user.
100    pub help_message: Option<&'a str>,
101
102    /// Function that formats the user input and presents it to the user as the final rendering of the prompt.
103    pub formatter: StringFormatter<'a>,
104
105    /// How the password input is displayed to the user.
106    pub display_mode: PasswordDisplayMode,
107
108    /// Whether to allow the user to toggle the display of the current password input by pressing the Ctrl+R hotkey.
109    pub enable_display_toggle: bool,
110
111    /// Whether to ask for input twice to see if the provided passwords are the same.
112    pub enable_confirmation: bool,
113
114    /// Collection of validators to apply to the user input.
115    ///
116    /// Validators are executed in the order they are stored, stopping at and displaying to the user
117    /// only the first validation error that might appear.
118    ///
119    /// The possible error is displayed to the user one line above the prompt.
120    pub validators: Vec<Box<dyn StringValidator>>,
121
122    /// RenderConfig to apply to the rendered interface.
123    ///
124    /// Note: The default render config considers if the NO_COLOR environment variable
125    /// is set to decide whether to render the colored config or the empty one.
126    ///
127    /// When overriding the config in a prompt, NO_COLOR is no longer considered and your
128    /// config is treated as the only source of truth. If you want to customize colors
129    /// and still suport NO_COLOR, you will have to do this on your end.
130    pub render_config: RenderConfig,
131}
132
133impl<'a> Password<'a> {
134    /// Default formatter, set to always display `"********"` regardless of input length.
135    pub const DEFAULT_FORMATTER: StringFormatter<'a> = &|_| String::from("********");
136
137    /// Default validators added to the [Password] prompt, none.
138    pub const DEFAULT_VALIDATORS: Vec<Box<dyn StringValidator>> = vec![];
139
140    /// Default help message.
141    pub const DEFAULT_HELP_MESSAGE: Option<&'a str> = None;
142
143    /// Default value for the allow display toggle variable.
144    pub const DEFAULT_ENABLE_DISPLAY_TOGGLE: bool = false;
145
146    /// Default value for the enable confirmation variable.
147    pub const DEFAULT_ENABLE_CONFIRMATION: bool = true;
148
149    /// Default password display mode.
150    pub const DEFAULT_DISPLAY_MODE: PasswordDisplayMode = PasswordDisplayMode::Hidden;
151
152    /// Creates a [Password] with the provided message and default options.
153    pub fn new(message: &'a str) -> Self {
154        Self {
155            message,
156            custom_confirmation_message: None,
157            custom_confirmation_error_message: None,
158            enable_confirmation: Self::DEFAULT_ENABLE_CONFIRMATION,
159            enable_display_toggle: Self::DEFAULT_ENABLE_DISPLAY_TOGGLE,
160            display_mode: Self::DEFAULT_DISPLAY_MODE,
161            help_message: Self::DEFAULT_HELP_MESSAGE,
162            formatter: Self::DEFAULT_FORMATTER,
163            validators: Self::DEFAULT_VALIDATORS,
164            render_config: get_configuration(),
165        }
166    }
167
168    /// Sets the help message of the prompt.
169    pub fn with_help_message(mut self, message: &'a str) -> Self {
170        self.help_message = Some(message);
171        self
172    }
173
174    /// Sets the flag to enable display toggling.
175    pub fn with_display_toggle_enabled(mut self) -> Self {
176        self.enable_display_toggle = true;
177        self
178    }
179
180    /// Disables the confirmation step of the prompt.
181    pub fn without_confirmation(mut self) -> Self {
182        self.enable_confirmation = false;
183        self
184    }
185
186    /// Sets the prompt message when asking for the password confirmation.
187    pub fn with_custom_confirmation_message(mut self, message: &'a str) -> Self {
188        self.custom_confirmation_message.replace(message);
189        self
190    }
191
192    /// Sets the prompt error message when password confirmation fails.
193    pub fn with_custom_confirmation_error_message(mut self, message: &'a str) -> Self {
194        self.custom_confirmation_error_message.replace(message);
195        self
196    }
197
198    /// Sets the standard display mode for the prompt.
199    pub fn with_display_mode(mut self, mode: PasswordDisplayMode) -> Self {
200        self.display_mode = mode;
201        self
202    }
203
204    /// Sets the formatter.
205    pub fn with_formatter(mut self, formatter: StringFormatter<'a>) -> Self {
206        self.formatter = formatter;
207        self
208    }
209
210    /// Adds a validator to the collection of validators. You might want to use this feature
211    /// in case you need to limit the user to specific choices, such as requiring
212    /// special characters in the password.
213    ///
214    /// Validators are executed in the order they are stored, stopping at and displaying to the user
215    /// only the first validation error that might appear.
216    ///
217    /// The possible error is displayed to the user one line above the prompt.
218    pub fn with_validator<V>(mut self, validator: V) -> Self
219    where
220        V: StringValidator + 'static,
221    {
222        // Directly make space for at least 5 elements, so we won't to re-allocate too often when
223        // calling this function repeatedly.
224        if self.validators.capacity() == 0 {
225            self.validators.reserve(5);
226        }
227
228        self.validators.push(Box::new(validator));
229        self
230    }
231
232    /// Adds the validators to the collection of validators in the order they are given.
233    ///
234    /// Validators are executed in the order they are stored, stopping at and displaying to the user
235    /// only the first validation error that might appear.
236    ///
237    /// The possible error is displayed to the user one line above the prompt.
238    pub fn with_validators(mut self, validators: &[Box<dyn StringValidator>]) -> Self {
239        for validator in validators {
240            #[allow(clippy::clone_double_ref)]
241            self.validators.push(validator.clone());
242        }
243        self
244    }
245
246    /// Sets the provided color theme to this prompt.
247    ///
248    /// Note: The default render config considers if the NO_COLOR environment variable
249    /// is set to decide whether to render the colored config or the empty one.
250    ///
251    /// When overriding the config in a prompt, NO_COLOR is no longer considered and your
252    /// config is treated as the only source of truth. If you want to customize colors
253    /// and still suport NO_COLOR, you will have to do this on your end.
254    pub fn with_render_config(mut self, render_config: RenderConfig) -> Self {
255        self.render_config = render_config;
256        self
257    }
258
259    /// Parses the provided behavioral and rendering options and prompts
260    /// the CLI user for input according to the defined rules.
261    ///
262    /// This method is intended for flows where the user skipping/cancelling
263    /// the prompt - by pressing ESC - is considered normal behavior. In this case,
264    /// it does not return `Err(InquireError::OperationCanceled)`, but `Ok(None)`.
265    ///
266    /// Meanwhile, if the user does submit an answer, the method wraps the return
267    /// type with `Some`.
268    pub fn prompt_skippable(self) -> InquireResult<Option<String>> {
269        match self.prompt() {
270            Ok(answer) => Ok(Some(answer)),
271            Err(InquireError::OperationCanceled) => Ok(None),
272            Err(err) => Err(err),
273        }
274    }
275
276    /// Parses the provided behavioral and rendering options and prompts
277    /// the CLI user for input according to the defined rules.
278    pub fn prompt(self) -> InquireResult<String> {
279        let terminal = get_default_terminal()?;
280        let mut backend = Backend::new(terminal, self.render_config)?;
281        self.prompt_with_backend(&mut backend)
282    }
283
284    pub(crate) fn prompt_with_backend<B: PasswordBackend>(
285        self,
286        backend: &mut B,
287    ) -> InquireResult<String> {
288        PasswordPrompt::from(self).prompt(backend)
289    }
290}
291
292struct PasswordPrompt<'a> {
293    message: &'a str,
294    help_message: Option<&'a str>,
295    input: Input,
296    standard_display_mode: PasswordDisplayMode,
297    display_mode: PasswordDisplayMode,
298    enable_display_toggle: bool,
299    confirmation: Option<PasswordConfirmation<'a>>, // if `None`, confirmation is disabled, `Some(_)` confirmation is enabled
300    confirmation_stage: bool,
301    formatter: StringFormatter<'a>,
302    validators: Vec<Box<dyn StringValidator>>,
303    error: Option<ErrorMessage>,
304}
305
306impl<'a> From<Password<'a>> for PasswordPrompt<'a> {
307    fn from(so: Password<'a>) -> Self {
308        let confirmation = match so.enable_confirmation {
309            true => Some(PasswordConfirmation {
310                message: so.custom_confirmation_message.unwrap_or("Confirmation:"),
311                error_message: so
312                    .custom_confirmation_error_message
313                    .unwrap_or("The answers don't match."),
314                input: Input::new(),
315            }),
316            false => None,
317        };
318
319        Self {
320            message: so.message,
321            help_message: so.help_message,
322            standard_display_mode: so.display_mode,
323            display_mode: so.display_mode,
324            enable_display_toggle: so.enable_display_toggle,
325            confirmation,
326            confirmation_stage: false,
327            formatter: so.formatter,
328            validators: so.validators,
329            input: Input::new(),
330            error: None,
331        }
332    }
333}
334
335impl<'a> From<&'a str> for Password<'a> {
336    fn from(val: &'a str) -> Self {
337        Password::new(val)
338    }
339}
340
341impl<'a> PasswordPrompt<'a> {
342    fn active_input(&self) -> &Input {
343        match &self.confirmation {
344            Some(confirmation) if self.confirmation_stage => &confirmation.input,
345            _ => &self.input,
346        }
347    }
348
349    fn active_input_mut(&mut self) -> &mut Input {
350        match &mut self.confirmation {
351            Some(confirmation) if self.confirmation_stage => &mut confirmation.input,
352            _ => &mut self.input,
353        }
354    }
355
356    fn on_change(&mut self, key: Key) {
357        match key {
358            Key::Char('r', m) | Key::Char('R', m)
359                if m.contains(KeyModifiers::CONTROL) && self.enable_display_toggle =>
360            {
361                self.toggle_display_mode();
362            }
363            _ => {
364                self.active_input_mut().handle_key(key);
365            }
366        };
367    }
368
369    fn toggle_display_mode(&mut self) {
370        self.display_mode = match self.display_mode {
371            PasswordDisplayMode::Hidden => PasswordDisplayMode::Full,
372            PasswordDisplayMode::Masked => PasswordDisplayMode::Full,
373            PasswordDisplayMode::Full => self.standard_display_mode,
374        }
375    }
376
377    fn handle_cancel(&mut self) -> bool {
378        if self.confirmation_stage && self.confirmation.is_some() {
379            if self.display_mode == PasswordDisplayMode::Hidden {
380                self.input.clear();
381            }
382
383            self.error = None;
384            self.confirmation_stage = false;
385
386            true
387        } else {
388            false
389        }
390    }
391
392    fn handle_submit(&mut self) -> InquireResult<Option<String>> {
393        let answer = match self.validate_current_answer()? {
394            Validation::Valid => self.confirm_current_answer(),
395            Validation::Invalid(msg) => {
396                self.error = Some(msg);
397                None
398            }
399        };
400
401        Ok(answer)
402    }
403
404    fn confirm_current_answer(&mut self) -> Option<String> {
405        let cur_answer = self.cur_answer();
406        match &mut self.confirmation {
407            None => Some(cur_answer),
408            Some(confirmation) => {
409                if !self.confirmation_stage {
410                    if self.display_mode == PasswordDisplayMode::Hidden {
411                        confirmation.input.clear();
412                    }
413
414                    self.error = None;
415                    self.confirmation_stage = true;
416
417                    None
418                } else if self.input.content() == cur_answer {
419                    Some(confirmation.input.content().into())
420                } else {
421                    confirmation.input.clear();
422
423                    self.error = Some(confirmation.error_message.into());
424                    self.confirmation_stage = false;
425
426                    None
427                }
428            }
429        }
430    }
431
432    fn validate_current_answer(&self) -> InquireResult<Validation> {
433        for validator in &self.validators {
434            match validator.validate(self.active_input().content()) {
435                Ok(Validation::Valid) => {}
436                Ok(Validation::Invalid(msg)) => return Ok(Validation::Invalid(msg)),
437                Err(err) => return Err(InquireError::Custom(err)),
438            }
439        }
440
441        Ok(Validation::Valid)
442    }
443
444    fn cur_answer(&self) -> String {
445        self.active_input().content().into()
446    }
447
448    fn render<B: PasswordBackend>(&mut self, backend: &mut B) -> InquireResult<()> {
449        backend.frame_setup()?;
450
451        if let Some(err) = &self.error {
452            backend.render_error_message(err)?;
453        }
454
455        match self.display_mode {
456            PasswordDisplayMode::Hidden => {
457                backend.render_prompt(self.message)?;
458
459                match &self.confirmation {
460                    Some(confirmation) if self.confirmation_stage => {
461                        backend.render_prompt(confirmation.message)?
462                    }
463                    _ => {}
464                }
465            }
466            PasswordDisplayMode::Masked => {
467                backend.render_prompt_with_masked_input(self.message, &self.input)?;
468
469                match &self.confirmation {
470                    Some(confirmation) if self.confirmation_stage => {
471                        backend.render_prompt_with_masked_input(
472                            confirmation.message,
473                            &confirmation.input,
474                        )?;
475                    }
476                    _ => {}
477                }
478            }
479            PasswordDisplayMode::Full => {
480                backend.render_prompt_with_full_input(self.message, &self.input)?;
481
482                match &self.confirmation {
483                    Some(confirmation) if self.confirmation_stage => {
484                        backend.render_prompt_with_full_input(
485                            confirmation.message,
486                            &confirmation.input,
487                        )?;
488                    }
489                    _ => {}
490                }
491            }
492        }
493
494        if let Some(message) = self.help_message {
495            backend.render_help_message(message)?;
496        }
497
498        backend.frame_finish()?;
499
500        Ok(())
501    }
502
503    fn prompt<B: PasswordBackend>(mut self, backend: &mut B) -> InquireResult<String> {
504        let final_answer = loop {
505            self.render(backend)?;
506
507            let key = backend.read_key()?;
508
509            match key {
510                Key::Interrupt => interrupt_prompt!(),
511                Key::Cancel => {
512                    if !self.handle_cancel() {
513                        cancel_prompt!(backend, self.message);
514                    }
515                }
516                Key::Submit => {
517                    if let Some(answer) = self.handle_submit()? {
518                        break answer;
519                    }
520                }
521                key => self.on_change(key),
522            }
523        };
524
525        let formatted = (self.formatter)(&final_answer);
526
527        finish_prompt_with_answer!(backend, self.message, &formatted, final_answer);
528    }
529}
530
531#[cfg(test)]
532#[cfg(feature = "crossterm")]
533mod test {
534    use super::Password;
535    use crate::{
536        terminal::crossterm::CrosstermTerminal,
537        ui::{Backend, RenderConfig},
538        validator::{ErrorMessage, Validation},
539    };
540    use crossterm::event::{KeyCode, KeyEvent};
541
542    macro_rules! text_to_events {
543        ($text:expr) => {{
544            $text.chars().map(KeyCode::Char)
545        }};
546    }
547
548    macro_rules! password_test {
549        ($(#[$meta:meta])? $name:ident,$input:expr,$output:expr,$prompt:expr) => {
550            #[test]
551            $(#[$meta])?
552            fn $name() {
553                let read: Vec<KeyEvent> = $input.into_iter().map(KeyEvent::from).collect();
554                let mut read = read.iter();
555
556                let mut write: Vec<u8> = Vec::new();
557                let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read);
558                let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap();
559
560                let ans = $prompt.prompt_with_backend(&mut backend).unwrap();
561
562                assert_eq!($output, ans);
563            }
564        };
565    }
566
567    password_test!(
568        empty,
569        vec![KeyCode::Enter],
570        "",
571        Password::new("").without_confirmation()
572    );
573
574    password_test!(
575        single_letter,
576        vec![KeyCode::Char('b'), KeyCode::Enter],
577        "b",
578        Password::new("").without_confirmation()
579    );
580
581    password_test!(
582        letters_and_enter,
583        text_to_events!("normal input\n"),
584        "normal input",
585        Password::new("").without_confirmation()
586    );
587
588    password_test!(
589        letters_and_enter_with_emoji,
590        text_to_events!("with emoji 🧘🏻‍♂️, 🌍, 🍞, 🚗, 📞\n"),
591        "with emoji 🧘🏻‍♂️, 🌍, 🍞, 🚗, 📞",
592        Password::new("").without_confirmation()
593    );
594
595    password_test!(
596        input_and_correction,
597        {
598            let mut events = vec![];
599            events.append(&mut text_to_events!("anor").collect());
600            events.push(KeyCode::Backspace);
601            events.push(KeyCode::Backspace);
602            events.push(KeyCode::Backspace);
603            events.push(KeyCode::Backspace);
604            events.append(&mut text_to_events!("normal input").collect());
605            events.push(KeyCode::Enter);
606            events
607        },
608        "normal input",
609        Password::new("").without_confirmation()
610    );
611
612    password_test!(
613        input_and_excessive_correction,
614        {
615            let mut events = vec![];
616            events.append(&mut text_to_events!("anor").collect());
617            events.push(KeyCode::Backspace);
618            events.push(KeyCode::Backspace);
619            events.push(KeyCode::Backspace);
620            events.push(KeyCode::Backspace);
621            events.push(KeyCode::Backspace);
622            events.push(KeyCode::Backspace);
623            events.push(KeyCode::Backspace);
624            events.push(KeyCode::Backspace);
625            events.push(KeyCode::Backspace);
626            events.push(KeyCode::Backspace);
627            events.append(&mut text_to_events!("normal input").collect());
628            events.push(KeyCode::Enter);
629            events
630        },
631        "normal input",
632        Password::new("").without_confirmation()
633    );
634
635    password_test!(
636        input_correction_after_validation,
637        {
638            let mut events = vec![];
639            events.append(&mut text_to_events!("1234567890").collect());
640            events.push(KeyCode::Enter);
641            events.push(KeyCode::Backspace);
642            events.push(KeyCode::Backspace);
643            events.push(KeyCode::Backspace);
644            events.push(KeyCode::Backspace);
645            events.push(KeyCode::Backspace);
646            events.append(&mut text_to_events!("yes").collect());
647            events.push(KeyCode::Enter);
648            events
649        },
650        "12345yes",
651        Password::new("")
652            .without_confirmation()
653            .with_validator(|ans: &str| match ans.len() {
654                len if len > 5 && len < 10 => Ok(Validation::Valid),
655                _ => Ok(Validation::Invalid(ErrorMessage::Default)),
656            })
657    );
658
659    password_test!(
660        input_confirmation_same,
661        {
662            let mut events = vec![];
663            events.append(&mut text_to_events!("1234567890").collect());
664            events.push(KeyCode::Enter);
665            events.append(&mut text_to_events!("1234567890").collect());
666            events.push(KeyCode::Enter);
667            events
668        },
669        "1234567890",
670        Password::new("")
671    );
672
673    password_test!(
674        #[should_panic(expected = "Custom stream of characters has ended")]
675        input_confirmation_different,
676        {
677            let mut events = vec![];
678            events.append(&mut text_to_events!("1234567890").collect());
679            events.push(KeyCode::Enter);
680            events.append(&mut text_to_events!("abcdefghij").collect());
681            events.push(KeyCode::Enter);
682            events
683        },
684        "",
685        Password::new("")
686    );
687}