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