bw/auth/
login.rs

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