bitwarden_core/auth/login/
access_token.rs1use 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 auth::{
12 api::{request::AccessTokenRequest, response::IdentityTokenResponse},
13 login::{response::two_factor::TwoFactorProviders, PasswordLoginResponse},
14 AccessToken, JwtToken,
15 },
16 client::{LoginMethod, ServiceAccountLoginMethod},
17 require,
18 secrets_manager::state::{self, ClientState},
19 Client, OrganizationId,
20};
21
22pub(crate) async fn login_access_token(
23 client: &Client,
24 input: &AccessTokenLoginRequest,
25) -> Result<AccessTokenLoginResponse, LoginError> {
26 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 let payload: EncString = r.encrypted_payload.parse()?;
57
58 let decrypted_payload: Vec<u8> = payload.decrypt_with_key(&access_token.encryption_key)?;
59
60 #[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 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 client
89 .internal
90 .set_login_method(LoginMethod::ServiceAccount(
91 ServiceAccountLoginMethod::AccessToken {
92 access_token,
93 organization_id,
94 state_file: input.state_file.clone(),
95 },
96 ));
97
98 client
99 .internal
100 .initialize_crypto_single_org_key(organization_id, encryption_key);
101 }
102
103 AccessTokenLoginResponse::process_response(response)
104}
105
106async fn request_access_token(
107 client: &Client,
108 input: &AccessToken,
109) -> Result<IdentityTokenResponse, LoginError> {
110 let config = client.internal.get_api_configurations().await;
111 AccessTokenRequest::new(input.access_token_id, &input.client_secret)
112 .send(&config)
113 .await
114}
115
116fn load_tokens_from_state(
117 client: &Client,
118 state_file: &Path,
119 access_token: &AccessToken,
120) -> Result<OrganizationId, LoginError> {
121 let client_state = state::get(state_file, access_token)?;
122
123 let token: JwtToken = client_state.token.parse()?;
124
125 if let Some(organization_id) = token.organization {
126 let time_till_expiration = (token.exp as i64) - Utc::now().timestamp();
127
128 if time_till_expiration > 0 {
129 let organization_id: OrganizationId = organization_id
130 .parse()
131 .map_err(|_| LoginError::InvalidOrganizationId)?;
132 let encryption_key = SymmetricCryptoKey::try_from(client_state.encryption_key)?;
133
134 client
135 .internal
136 .set_tokens(client_state.token, None, time_till_expiration as u64);
137 client
138 .internal
139 .initialize_crypto_single_org_key(organization_id, encryption_key);
140
141 return Ok(organization_id);
142 }
143 }
144
145 Err(LoginError::InvalidStateFile)
146}
147
148#[derive(Serialize, Deserialize, Debug, JsonSchema)]
150#[serde(rename_all = "camelCase", deny_unknown_fields)]
151pub struct AccessTokenLoginRequest {
152 pub access_token: String,
154 pub state_file: Option<PathBuf>,
156}
157
158#[allow(missing_docs)]
159#[derive(Serialize, Deserialize, Debug, JsonSchema)]
160#[serde(rename_all = "camelCase", deny_unknown_fields)]
161pub struct AccessTokenLoginResponse {
162 pub authenticated: bool,
163 pub reset_master_password: bool,
165 pub force_password_reset: bool,
167 two_factor: Option<TwoFactorProviders>,
168}
169
170impl AccessTokenLoginResponse {
171 pub(crate) fn process_response(
172 response: IdentityTokenResponse,
173 ) -> Result<AccessTokenLoginResponse, LoginError> {
174 let password_response = PasswordLoginResponse::process_response(response);
175
176 Ok(AccessTokenLoginResponse {
177 authenticated: password_response.authenticated,
178 reset_master_password: password_response.reset_master_password,
179 force_password_reset: password_response.force_password_reset,
180 two_factor: password_response.two_factor,
181 })
182 }
183}