Skip to main content

bitwarden_core/auth/login/
access_token.rs

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