bitwarden_core/auth/login/
access_token.rs1use 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 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 let payload: EncString = r.encrypted_payload.parse()?;
58
59 let decrypted_payload: Vec<u8> = payload.decrypt_with_key(&access_token.encryption_key)?;
60
61 #[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 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#[derive(Serialize, Deserialize, Debug, JsonSchema)]
152#[serde(rename_all = "camelCase", deny_unknown_fields)]
153pub struct AccessTokenLoginRequest {
154 pub access_token: String,
156 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 pub reset_master_password: bool,
167 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}