bitwarden_core/platform/
get_user_api_key.rs1use bitwarden_api_api::models::{ApiKeyResponseModel, SecretVerificationRequestModel};
16use bitwarden_crypto::{CryptoError, HashPurpose, MasterKey};
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19use tracing::{debug, info};
20
21use super::SecretVerificationRequest;
22use crate::{
23 ApiError, Client, MissingFieldError, NotAuthenticatedError, client::UserLoginMethod, require,
24};
25
26#[allow(missing_docs)]
27#[derive(Debug, Error)]
28pub enum UserApiKeyError {
29 #[error(transparent)]
30 Api(#[from] ApiError),
31 #[error(transparent)]
32 Crypto(#[from] bitwarden_crypto::CryptoError),
33 #[error(transparent)]
34 NotAuthenticated(#[from] NotAuthenticatedError),
35 #[error(transparent)]
36 MissingField(#[from] MissingFieldError),
37 #[error("Unsupported login method")]
38 UnsupportedLoginMethod,
39}
40
41pub(crate) async fn get_user_api_key(
43 client: &Client,
44 input: &SecretVerificationRequest,
45) -> Result<UserApiKeyResponse, UserApiKeyError> {
46 info!("Getting Api Key");
47 debug!(?input);
48
49 let login_method = get_login_method(client)?;
50 let config = client.internal.get_api_configurations();
51
52 let request = build_secret_verification_request(&login_method, input)?;
53 let response = config
54 .api_client
55 .accounts_api()
56 .api_key(Some(request))
57 .await
58 .map_err(ApiError::from)?;
59 UserApiKeyResponse::process_response(response)
60}
61
62fn get_login_method(client: &Client) -> Result<UserLoginMethod, NotAuthenticatedError> {
63 client
64 .internal
65 .get_login_method()
66 .ok_or(NotAuthenticatedError)
67}
68
69fn build_secret_verification_request(
71 login_method: &UserLoginMethod,
72 input: &SecretVerificationRequest,
73) -> Result<SecretVerificationRequestModel, UserApiKeyError> {
74 if let UserLoginMethod::Username { email, kdf, .. } = login_method {
75 let master_password_hash = input
76 .master_password
77 .as_ref()
78 .map(|p| {
79 let master_key = MasterKey::derive(p, email, kdf)?;
80
81 Ok::<String, CryptoError>(
82 master_key
83 .derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization)
84 .to_string(),
85 )
86 })
87 .transpose()?;
88 Ok(SecretVerificationRequestModel {
89 master_password_hash,
90 otp: input.otp.as_ref().cloned(),
91 secret: None,
92 auth_request_access_code: None,
93 })
94 } else {
95 Err(UserApiKeyError::UnsupportedLoginMethod)
96 }
97}
98
99#[derive(Serialize, Deserialize, Debug)]
101#[serde(rename_all = "camelCase", deny_unknown_fields)]
102pub struct UserApiKeyResponse {
103 api_key: String,
105}
106
107impl UserApiKeyResponse {
108 pub(crate) fn process_response(
109 response: ApiKeyResponseModel,
110 ) -> Result<UserApiKeyResponse, UserApiKeyError> {
111 let api_key = require!(response.api_key);
112 Ok(UserApiKeyResponse { api_key })
113 }
114}