bitwarden_core/auth/login/
access_token.rs

1use std::path::{Path, PathBuf};
2
3use base64::{engine::general_purpose::STANDARD, Engine};
4use bitwarden_crypto::{BitwardenLegacyKeyBytes, EncString, KeyDecryptable, SymmetricCryptoKey};
5use chrono::Utc;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use super::LoginError;
11use crate::{
12    auth::{
13        api::{request::AccessTokenRequest, response::IdentityTokenResponse},
14        login::{response::two_factor::TwoFactorProviders, PasswordLoginResponse},
15        AccessToken, JwtToken,
16    },
17    client::{LoginMethod, ServiceAccountLoginMethod},
18    require,
19    secrets_manager::state::{self, ClientState},
20    Client,
21};
22
23pub(crate) async fn login_access_token(
24    client: &Client,
25    input: &AccessTokenLoginRequest,
26) -> Result<AccessTokenLoginResponse, LoginError> {
27    //info!("api key logging in");
28    //debug!("{:#?}, {:#?}", client, input);
29
30    let access_token: AccessToken = input.access_token.parse()?;
31
32    if let Some(state_file) = &input.state_file {
33        if let Ok(organization_id) = load_tokens_from_state(client, state_file, &access_token) {
34            client
35                .internal
36                .set_login_method(LoginMethod::ServiceAccount(
37                    ServiceAccountLoginMethod::AccessToken {
38                        access_token,
39                        organization_id,
40                        state_file: Some(state_file.to_path_buf()),
41                    },
42                ));
43
44            return Ok(AccessTokenLoginResponse {
45                authenticated: true,
46                reset_master_password: false,
47                force_password_reset: false,
48                two_factor: None,
49            });
50        }
51    }
52
53    let response = request_access_token(client, &access_token).await?;
54
55    if let IdentityTokenResponse::Payload(r) = &response {
56        // Extract the encrypted payload and use the access token encryption key to decrypt it
57        let payload: EncString = r.encrypted_payload.parse()?;
58
59        let decrypted_payload: Vec<u8> = payload.decrypt_with_key(&access_token.encryption_key)?;
60
61        // Once decrypted, we have to JSON decode to extract the organization encryption key
62        #[derive(serde::Deserialize)]
63        struct Payload {
64            #[serde(rename = "encryptionKey")]
65            encryption_key: String,
66        }
67
68        let payload: Payload = serde_json::from_slice(&decrypted_payload)?;
69        let encryption_key = STANDARD.decode(&payload.encryption_key)?;
70        let encryption_key = BitwardenLegacyKeyBytes::from(encryption_key);
71        let encryption_key = SymmetricCryptoKey::try_from(&encryption_key)?;
72
73        let access_token_obj: JwtToken = r.access_token.parse()?;
74
75        // This should always be Some() when logging in with an access token
76        let organization_id = require!(access_token_obj.organization)
77            .parse()
78            .map_err(|_| LoginError::InvalidResponse)?;
79
80        if let Some(state_file) = &input.state_file {
81            let state = ClientState::new(r.access_token.clone(), payload.encryption_key);
82            _ = state::set(state_file, &access_token, state);
83        }
84
85        client.internal.set_tokens(
86            r.access_token.clone(),
87            r.refresh_token.clone(),
88            r.expires_in,
89        );
90        client
91            .internal
92            .set_login_method(LoginMethod::ServiceAccount(
93                ServiceAccountLoginMethod::AccessToken {
94                    access_token,
95                    organization_id,
96                    state_file: input.state_file.clone(),
97                },
98            ));
99
100        client
101            .internal
102            .initialize_crypto_single_org_key(organization_id, encryption_key);
103    }
104
105    AccessTokenLoginResponse::process_response(response)
106}
107
108async fn request_access_token(
109    client: &Client,
110    input: &AccessToken,
111) -> Result<IdentityTokenResponse, LoginError> {
112    let config = client.internal.get_api_configurations().await;
113    AccessTokenRequest::new(input.access_token_id, &input.client_secret)
114        .send(&config)
115        .await
116}
117
118fn load_tokens_from_state(
119    client: &Client,
120    state_file: &Path,
121    access_token: &AccessToken,
122) -> Result<Uuid, LoginError> {
123    let client_state = state::get(state_file, access_token)?;
124
125    let token: JwtToken = client_state.token.parse()?;
126
127    if let Some(organization_id) = token.organization {
128        let time_till_expiration = (token.exp as i64) - Utc::now().timestamp();
129
130        if time_till_expiration > 0 {
131            let organization_id: Uuid = organization_id
132                .parse()
133                .map_err(|_| LoginError::InvalidOrganizationId)?;
134            let encryption_key = SymmetricCryptoKey::try_from(client_state.encryption_key)?;
135
136            client
137                .internal
138                .set_tokens(client_state.token, None, time_till_expiration as u64);
139            client
140                .internal
141                .initialize_crypto_single_org_key(organization_id, encryption_key);
142
143            return Ok(organization_id);
144        }
145    }
146
147    Err(LoginError::InvalidStateFile)
148}
149
150/// Login to Bitwarden with access token
151#[derive(Serialize, Deserialize, Debug, JsonSchema)]
152#[serde(rename_all = "camelCase", deny_unknown_fields)]
153pub struct AccessTokenLoginRequest {
154    /// Bitwarden service API access token
155    pub access_token: String,
156    /// Path to the state file
157    pub state_file: Option<PathBuf>,
158}
159
160#[allow(missing_docs)]
161#[derive(Serialize, Deserialize, Debug, JsonSchema)]
162#[serde(rename_all = "camelCase", deny_unknown_fields)]
163pub struct AccessTokenLoginResponse {
164    pub authenticated: bool,
165    /// TODO: What does this do?
166    pub reset_master_password: bool,
167    /// Whether or not the user is required to update their master password
168    pub force_password_reset: bool,
169    two_factor: Option<TwoFactorProviders>,
170}
171
172impl AccessTokenLoginResponse {
173    pub(crate) fn process_response(
174        response: IdentityTokenResponse,
175    ) -> Result<AccessTokenLoginResponse, LoginError> {
176        let password_response = PasswordLoginResponse::process_response(response);
177
178        Ok(AccessTokenLoginResponse {
179            authenticated: password_response.authenticated,
180            reset_master_password: password_response.reset_master_password,
181            force_password_reset: password_response.force_password_reset,
182            two_factor: password_response.two_factor,
183        })
184    }
185}