bitwarden_core/auth/login/
access_token.rsuse std::path::{Path, PathBuf};
use base64::{engine::general_purpose::STANDARD, Engine};
use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey};
use chrono::Utc;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::LoginError;
use crate::{
auth::{
api::{request::AccessTokenRequest, response::IdentityTokenResponse},
login::{response::two_factor::TwoFactorProviders, PasswordLoginResponse},
AccessToken, JwtToken,
},
client::{LoginMethod, ServiceAccountLoginMethod},
require,
secrets_manager::state::{self, ClientState},
Client,
};
pub(crate) async fn login_access_token(
client: &Client,
input: &AccessTokenLoginRequest,
) -> Result<AccessTokenLoginResponse, LoginError> {
let access_token: AccessToken = input.access_token.parse()?;
if let Some(state_file) = &input.state_file {
if let Ok(organization_id) = load_tokens_from_state(client, state_file, &access_token) {
client
.internal
.set_login_method(LoginMethod::ServiceAccount(
ServiceAccountLoginMethod::AccessToken {
access_token,
organization_id,
state_file: Some(state_file.to_path_buf()),
},
));
return Ok(AccessTokenLoginResponse {
authenticated: true,
reset_master_password: false,
force_password_reset: false,
two_factor: None,
});
}
}
let response = request_access_token(client, &access_token).await?;
if let IdentityTokenResponse::Payload(r) = &response {
let payload: EncString = r.encrypted_payload.parse()?;
let decrypted_payload: Vec<u8> = payload.decrypt_with_key(&access_token.encryption_key)?;
#[derive(serde::Deserialize)]
struct Payload {
#[serde(rename = "encryptionKey")]
encryption_key: String,
}
let payload: Payload = serde_json::from_slice(&decrypted_payload)?;
let encryption_key = STANDARD.decode(&payload.encryption_key)?;
let encryption_key = SymmetricCryptoKey::try_from(encryption_key)?;
let access_token_obj: JwtToken = r.access_token.parse()?;
let organization_id = require!(access_token_obj.organization)
.parse()
.map_err(|_| LoginError::InvalidResponse)?;
if let Some(state_file) = &input.state_file {
let state = ClientState::new(r.access_token.clone(), payload.encryption_key);
_ = state::set(state_file, &access_token, state);
}
client.internal.set_tokens(
r.access_token.clone(),
r.refresh_token.clone(),
r.expires_in,
);
client
.internal
.set_login_method(LoginMethod::ServiceAccount(
ServiceAccountLoginMethod::AccessToken {
access_token,
organization_id,
state_file: input.state_file.clone(),
},
));
client.internal.initialize_crypto_single_key(encryption_key);
}
AccessTokenLoginResponse::process_response(response)
}
async fn request_access_token(
client: &Client,
input: &AccessToken,
) -> Result<IdentityTokenResponse, LoginError> {
let config = client.internal.get_api_configurations().await;
AccessTokenRequest::new(input.access_token_id, &input.client_secret)
.send(&config)
.await
}
fn load_tokens_from_state(
client: &Client,
state_file: &Path,
access_token: &AccessToken,
) -> Result<Uuid, LoginError> {
let client_state = state::get(state_file, access_token)?;
let token: JwtToken = client_state.token.parse()?;
if let Some(organization_id) = token.organization {
let time_till_expiration = (token.exp as i64) - Utc::now().timestamp();
if time_till_expiration > 0 {
let organization_id: Uuid = organization_id
.parse()
.map_err(|_| LoginError::InvalidOrganizationId)?;
let encryption_key = SymmetricCryptoKey::try_from(client_state.encryption_key)?;
client
.internal
.set_tokens(client_state.token, None, time_till_expiration as u64);
client.internal.initialize_crypto_single_key(encryption_key);
return Ok(organization_id);
}
}
Err(LoginError::InvalidStateFile)
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AccessTokenLoginRequest {
pub access_token: String,
pub state_file: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AccessTokenLoginResponse {
pub authenticated: bool,
pub reset_master_password: bool,
pub force_password_reset: bool,
two_factor: Option<TwoFactorProviders>,
}
impl AccessTokenLoginResponse {
pub(crate) fn process_response(
response: IdentityTokenResponse,
) -> Result<AccessTokenLoginResponse, LoginError> {
let password_response = PasswordLoginResponse::process_response(response);
Ok(AccessTokenLoginResponse {
authenticated: password_response.authenticated,
reset_master_password: password_response.reset_master_password,
force_password_reset: password_response.force_password_reset,
two_factor: password_response.two_factor,
})
}
}