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).await?;
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
62async fn get_login_method(client: &Client) -> Result<UserLoginMethod, NotAuthenticatedError> {
63 client
64 .internal
65 .get_login_method()
66 .await
67 .ok_or(NotAuthenticatedError)
68}
69
70fn build_secret_verification_request(
72 login_method: &UserLoginMethod,
73 input: &SecretVerificationRequest,
74) -> Result<SecretVerificationRequestModel, UserApiKeyError> {
75 if let UserLoginMethod::Username { email, kdf, .. } = login_method {
76 let master_password_hash = input
77 .master_password
78 .as_ref()
79 .map(|p| {
80 let master_key = MasterKey::derive(p, email, kdf)?;
81
82 Ok::<String, CryptoError>(
83 master_key
84 .derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization)
85 .to_string(),
86 )
87 })
88 .transpose()?;
89 Ok(SecretVerificationRequestModel {
90 master_password_hash,
91 otp: input.otp.as_ref().cloned(),
92 secret: None,
93 auth_request_access_code: None,
94 })
95 } else {
96 Err(UserApiKeyError::UnsupportedLoginMethod)
97 }
98}
99
100#[derive(Serialize, Deserialize, Debug)]
102#[serde(rename_all = "camelCase", deny_unknown_fields)]
103pub struct UserApiKeyResponse {
104 api_key: String,
106}
107
108impl UserApiKeyResponse {
109 pub(crate) fn process_response(
110 response: ApiKeyResponseModel,
111 ) -> Result<UserApiKeyResponse, UserApiKeyError> {
112 let api_key = require!(response.api_key);
113 Ok(UserApiKeyResponse { api_key })
114 }
115}