bitwarden_core/auth/login/
api_key.rs1use bitwarden_crypto::{EncString, MasterKey};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 auth::{
7 api::{request::ApiTokenRequest, response::IdentityTokenResponse},
8 login::{response::two_factor::TwoFactorProviders, LoginError, PasswordLoginResponse},
9 JwtToken,
10 },
11 client::{internal::UserKeyState, LoginMethod, UserLoginMethod},
12 require, Client,
13};
14
15pub(crate) async fn login_api_key(
16 client: &Client,
17 input: &ApiKeyLoginRequest,
18) -> Result<ApiKeyLoginResponse, LoginError> {
19 let response = request_api_identity_tokens(client, input).await?;
23
24 if let IdentityTokenResponse::Authenticated(r) = &response {
25 let access_token_obj: JwtToken = r.access_token.parse()?;
26
27 let email = access_token_obj
29 .email
30 .ok_or(LoginError::JwtTokenMissingEmail)?;
31
32 let kdf = client.auth().prelogin(email.clone()).await?;
33
34 client.internal.set_tokens(
35 r.access_token.clone(),
36 r.refresh_token.clone(),
37 r.expires_in,
38 );
39
40 let master_key = MasterKey::derive(&input.password, &email, &kdf)?;
41
42 client
43 .internal
44 .set_login_method(LoginMethod::User(UserLoginMethod::ApiKey {
45 client_id: input.client_id.to_owned(),
46 client_secret: input.client_secret.to_owned(),
47 email,
48 kdf,
49 }));
50
51 let user_key: EncString = require!(r.key.as_deref()).parse()?;
52 let private_key: EncString = require!(r.private_key.as_deref()).parse()?;
53
54 client.internal.initialize_user_crypto_master_key(
55 master_key,
56 user_key,
57 UserKeyState {
58 private_key,
59 signing_key: None,
60 security_state: None,
61 },
62 )?;
63 }
64
65 Ok(ApiKeyLoginResponse::process_response(response))
66}
67
68async fn request_api_identity_tokens(
69 client: &Client,
70 input: &ApiKeyLoginRequest,
71) -> Result<IdentityTokenResponse, LoginError> {
72 let config = client.internal.get_api_configurations().await;
73 ApiTokenRequest::new(&input.client_id, &input.client_secret)
74 .send(&config)
75 .await
76}
77
78#[derive(Serialize, Deserialize, Debug, JsonSchema)]
80#[serde(rename_all = "camelCase", deny_unknown_fields)]
81pub struct ApiKeyLoginRequest {
82 pub client_id: String,
84 pub client_secret: String,
86
87 pub password: String,
89}
90
91#[allow(missing_docs)]
92#[derive(Serialize, Deserialize, Debug, JsonSchema)]
93#[serde(rename_all = "camelCase", deny_unknown_fields)]
94pub struct ApiKeyLoginResponse {
95 pub authenticated: bool,
96 pub reset_master_password: bool,
98 pub force_password_reset: bool,
100 two_factor: Option<TwoFactorProviders>,
101}
102
103impl ApiKeyLoginResponse {
104 pub(crate) fn process_response(response: IdentityTokenResponse) -> ApiKeyLoginResponse {
105 let password_response = PasswordLoginResponse::process_response(response);
106
107 ApiKeyLoginResponse {
108 authenticated: password_response.authenticated,
109 reset_master_password: password_response.reset_master_password,
110 force_password_reset: password_response.force_password_reset,
111 two_factor: password_response.two_factor,
112 }
113 }
114}