bw/auth/
login.rs

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
use bitwarden::{
    auth::login::{
        ApiKeyLoginRequest, PasswordLoginRequest, TwoFactorEmailRequest, TwoFactorProvider,
        TwoFactorRequest,
    },
    vault::{ClientVaultExt, SyncRequest},
    Client,
};
use bitwarden_cli::text_prompt_when_none;
use color_eyre::eyre::{bail, Result};
use inquire::{Password, Text};
use log::{debug, error, info};

pub(crate) async fn login_password(client: Client, email: Option<String>) -> Result<()> {
    let email = text_prompt_when_none("Email", email)?;

    let password = Password::new("Password").without_confirmation().prompt()?;

    let kdf = client.auth().prelogin(email.clone()).await?;

    let result = client
        .auth()
        .login_password(&PasswordLoginRequest {
            email: email.clone(),
            password: password.clone(),
            two_factor: None,
            kdf: kdf.clone(),
        })
        .await?;

    if result.captcha.is_some() {
        // TODO: We should build a web captcha solution
        error!("Captcha required");
    } else if let Some(two_factor) = result.two_factor {
        error!("{:?}", two_factor);

        let two_factor = if let Some(tf) = two_factor.authenticator {
            debug!("{:?}", tf);

            let token = Text::new("Authenticator code").prompt()?;

            Some(TwoFactorRequest {
                token,
                provider: TwoFactorProvider::Authenticator,
                remember: false,
            })
        } else if let Some(tf) = two_factor.email {
            // Send token
            client
                .auth()
                .send_two_factor_email(&TwoFactorEmailRequest {
                    email: email.clone(),
                    password: password.clone(),
                })
                .await?;

            info!("Two factor code sent to {:?}", tf);
            let token = Text::new("Two factor code").prompt()?;

            Some(TwoFactorRequest {
                token,
                provider: TwoFactorProvider::Email,
                remember: false,
            })
        } else {
            bail!("Not supported: {:?}", two_factor);
        };

        let result = client
            .auth()
            .login_password(&PasswordLoginRequest {
                email,
                password,
                two_factor,
                kdf,
            })
            .await?;

        debug!("{:?}", result);
    } else {
        debug!("{:?}", result);
    }

    let res = client
        .vault()
        .sync(&SyncRequest {
            exclude_subdomains: Some(true),
        })
        .await?;
    info!("{:#?}", res);

    Ok(())
}

pub(crate) async fn login_api_key(
    client: Client,
    client_id: Option<String>,
    client_secret: Option<String>,
) -> Result<()> {
    let client_id = text_prompt_when_none("Client ID", client_id)?;
    let client_secret = text_prompt_when_none("Client Secret", client_secret)?;

    let password = Password::new("Password").without_confirmation().prompt()?;

    let result = client
        .auth()
        .login_api_key(&ApiKeyLoginRequest {
            client_id,
            client_secret,
            password,
        })
        .await?;

    debug!("{:?}", result);

    Ok(())
}

pub(crate) async fn login_device(
    client: Client,
    email: Option<String>,
    device_identifier: Option<String>,
) -> Result<()> {
    let email = text_prompt_when_none("Email", email)?;
    let device_identifier = text_prompt_when_none("Device Identifier", device_identifier)?;

    let auth = client.auth().login_device(email, device_identifier).await?;

    println!("Fingerprint: {}", auth.fingerprint);

    Text::new("Press enter once approved").prompt()?;

    client.auth().login_device_complete(auth).await?;

    Ok(())
}