bw/auth/
login.rs

1use bitwarden_cli::text_prompt_when_none;
2use bitwarden_core::{
3    auth::login::{
4        ApiKeyLoginRequest, PasswordLoginRequest, TwoFactorEmailRequest, TwoFactorProvider,
5        TwoFactorRequest,
6    },
7    Client,
8};
9use bitwarden_vault::{SyncRequest, VaultClientExt};
10use color_eyre::eyre::{bail, Result};
11use inquire::{Password, Text};
12use log::{debug, error, info};
13
14pub(crate) async fn login_password(client: Client, email: Option<String>) -> Result<()> {
15    let email = text_prompt_when_none("Email", email)?;
16
17    let password = Password::new("Password").without_confirmation().prompt()?;
18
19    let kdf = client.auth().prelogin(email.clone()).await?;
20
21    let result = client
22        .auth()
23        .login_password(&PasswordLoginRequest {
24            email: email.clone(),
25            password: password.clone(),
26            two_factor: None,
27            kdf: kdf.clone(),
28        })
29        .await?;
30
31    if let Some(two_factor) = result.two_factor {
32        error!("{two_factor:?}");
33
34        let two_factor = if let Some(tf) = two_factor.authenticator {
35            debug!("{tf:?}");
36
37            let token = Text::new("Authenticator code").prompt()?;
38
39            Some(TwoFactorRequest {
40                token,
41                provider: TwoFactorProvider::Authenticator,
42                remember: false,
43            })
44        } else if let Some(tf) = two_factor.email {
45            // Send token
46            client
47                .auth()
48                .send_two_factor_email(&TwoFactorEmailRequest {
49                    email: email.clone(),
50                    password: password.clone(),
51                })
52                .await?;
53
54            info!("Two factor code sent to {tf:?}");
55            let token = Text::new("Two factor code").prompt()?;
56
57            Some(TwoFactorRequest {
58                token,
59                provider: TwoFactorProvider::Email,
60                remember: false,
61            })
62        } else {
63            bail!("Not supported: {:?}", two_factor);
64        };
65
66        let result = client
67            .auth()
68            .login_password(&PasswordLoginRequest {
69                email,
70                password,
71                two_factor,
72                kdf,
73            })
74            .await?;
75
76        debug!("{result:?}");
77    } else {
78        debug!("{result:?}");
79    }
80
81    let res = client
82        .vault()
83        .sync(&SyncRequest {
84            exclude_subdomains: Some(true),
85        })
86        .await?;
87    info!("{res:#?}");
88
89    Ok(())
90}
91
92pub(crate) async fn login_api_key(
93    client: Client,
94    client_id: Option<String>,
95    client_secret: Option<String>,
96) -> Result<()> {
97    let client_id = text_prompt_when_none("Client ID", client_id)?;
98    let client_secret = text_prompt_when_none("Client Secret", client_secret)?;
99
100    let password = Password::new("Password").without_confirmation().prompt()?;
101
102    let result = client
103        .auth()
104        .login_api_key(&ApiKeyLoginRequest {
105            client_id,
106            client_secret,
107            password,
108        })
109        .await?;
110
111    debug!("{result:?}");
112
113    Ok(())
114}
115
116pub(crate) async fn login_device(
117    client: Client,
118    email: Option<String>,
119    device_identifier: Option<String>,
120) -> Result<()> {
121    let email = text_prompt_when_none("Email", email)?;
122    let device_identifier = text_prompt_when_none("Device Identifier", device_identifier)?;
123
124    let auth = client.auth().login_device(email, device_identifier).await?;
125
126    println!("Fingerprint: {}", auth.fingerprint);
127
128    Text::new("Press enter once approved").prompt()?;
129
130    client.auth().login_device_complete(auth).await?;
131
132    Ok(())
133}