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