1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#![allow(unused_imports)]
use tracing::{info, warn, debug, error, trace, instrument, span, Level};
use error_chain::bail;
use ate::prelude::*;
use std::sync::Arc;
use url::Url;
use std::io::stdout;
use std::io::Write;

use crate::prelude::*;
use crate::helper::*;
use crate::error::*;
use crate::request::*;
use crate::opt::*;

pub async fn reset_command(registry: &Registry, email: String, new_password: String, recovery_key: EncryptKey, sudo_code: String, sudo_code_2: String, auth: Url) -> Result<ResetResponse, ResetError>
{
    // Open a command chain
    let chain = registry.open_cmd(&auth).await?;

    // Generate a read-key using the password and some seed data
    // (this read-key will be mixed with entropy on the server side to decrypt the row
    //  which means that neither the client nor the server can get at the data alone)
    let prefix = format!("remote-login:{}:", email);
    let new_secret = password_to_read_key(&prefix, &new_password, 15, KeySize::Bit192);
    
    // Create the query command
    let auth = match auth.domain() {
        Some(a) => a.to_string(),
        None => "ate".to_string(),
    };
    let reset = ResetRequest {
        email,
        auth,
        new_secret,
        recovery_key,
        sudo_code,
        sudo_code_2,
    };

    let response: Result<ResetResponse, ResetFailed> = chain.invoke(reset).await?;
    let result = response?;
    Ok(result)
}

pub async fn main_reset(
    username: Option<String>,
    recovery_code: Option<String>,
    sudo_code: Option<String>,
    sudo_code_2: Option<String>,
    new_password: Option<String>,
    auth: Url
) -> Result<ResetResponse, ResetError>
{
    if recovery_code.is_none() ||
       sudo_code.is_none() {
        eprintln!(r#"# Account Reset Process

You will need *both* of the following to reset your account:
- Your 'recovery code' that you saved during account creation - if not - then
  the recovery code is likely still in your email inbox.
- Two sequential 'authenticator code' response challenges from your mobile app.
"#);
    }

    let username = match username {
        Some(a) => a,
        None => {
            print!("Username: ");
            stdout().lock().flush()?;
            let mut s = String::new();
            std::io::stdin().read_line(&mut s).expect("Did not enter a valid username");
            s.trim().to_string()
        }
    };

    let recovery_code = match recovery_code {
        Some(a) => a,
        None => {
            print!("Recovery Code: ");
            stdout().lock().flush()?;
            let mut s = String::new();
            std::io::stdin().read_line(&mut s).expect("Did not enter a valid recovery code");
            s.trim().to_string()
        }
    };
    let recovery_prefix = format!("recover-login:{}:", username);
    let recovery_key = password_to_read_key(&recovery_prefix, &recovery_code, 15, KeySize::Bit192);

    let new_password = match new_password {
        Some(a) => a,
        None => {
            let ret1 = rpassword_wasi::prompt_password("New Password: ").unwrap();
            let ret2 = rpassword_wasi::prompt_password("New Password Again: ").unwrap();
            if ret1 != ret2 {
                bail!(ResetErrorKind::PasswordMismatch);
            }

            ret2
        }
    };

    let sudo_code = match sudo_code {
        Some(a) => a,
        None => {
            print!("Authenticator Code: ");
            stdout().lock().flush()?;
            let mut s = String::new();
            std::io::stdin().read_line(&mut s).expect("Did not enter a valid authenticator code");
            s.trim().to_string()
        }
    };

    let sudo_code_2 = match sudo_code_2 {
        Some(a) => a,
        None => {
            print!("Next Authenticator Code: ");
            stdout().lock().flush()?;
            let mut s = String::new();
            std::io::stdin().read_line(&mut s).expect("Did not enter a valid authenticator code");
            s = s.trim().to_string();

            if sudo_code == s {
                bail!(ResetErrorKind::AuthenticatorCodeEqual);
            }

            s
        }
    };

    let registry = ate::mesh::Registry::new( &conf_cmd()).await.cement();
    let result = match reset_command(
        &registry,
        username,
        new_password,
        recovery_key,
        sudo_code,
        sudo_code_2,
        auth
    ).await {
        Ok(a) => a,
        Err(err) => {
            bail!(err);
        }
    };

    if is_tty_stdout() {
        println!("Account reset (id={})", result.key);

        // Display the QR code
        println!("");
        if let Some(message_of_the_day) = &result.message_of_the_day {
            println!("{}", message_of_the_day.as_str());
            println!("");
        }
        println!("Below is your new Google Authenticator QR code - scan it on your phone and");
        println!("save it as this code is the only way you can recover the account another time.");
        println!("");
        println!("{}", result.qr_code);
    }

    Ok(result)
}