use bitwarden_api_api::models::{ApiKeyResponseModel, SecretVerificationRequestModel};
use bitwarden_crypto::{CryptoError, HashPurpose, MasterKey};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::{debug, info};
use super::SecretVerificationRequest;
use crate::{
ApiError, Client, MissingFieldError, NotAuthenticatedError, client::UserLoginMethod, require,
};
#[allow(missing_docs)]
#[derive(Debug, Error)]
pub enum UserApiKeyError {
#[error(transparent)]
Api(#[from] ApiError),
#[error(transparent)]
Crypto(#[from] bitwarden_crypto::CryptoError),
#[error(transparent)]
NotAuthenticated(#[from] NotAuthenticatedError),
#[error(transparent)]
MissingField(#[from] MissingFieldError),
#[error("Unsupported login method")]
UnsupportedLoginMethod,
}
pub(crate) async fn get_user_api_key(
client: &Client,
input: &SecretVerificationRequest,
) -> Result<UserApiKeyResponse, UserApiKeyError> {
info!("Getting Api Key");
debug!(?input);
let login_method = get_login_method(client).await?;
let config = client.internal.get_api_configurations();
let request = build_secret_verification_request(&login_method, input)?;
let response = config
.api_client
.accounts_api()
.api_key(Some(request))
.await
.map_err(ApiError::from)?;
UserApiKeyResponse::process_response(response)
}
async fn get_login_method(client: &Client) -> Result<UserLoginMethod, NotAuthenticatedError> {
client
.internal
.get_login_method()
.await
.ok_or(NotAuthenticatedError)
}
fn build_secret_verification_request(
login_method: &UserLoginMethod,
input: &SecretVerificationRequest,
) -> Result<SecretVerificationRequestModel, UserApiKeyError> {
if let UserLoginMethod::Username { email, kdf, .. } = login_method {
let master_password_hash = input
.master_password
.as_ref()
.map(|p| {
let master_key = MasterKey::derive(p, email, kdf)?;
Ok::<String, CryptoError>(
master_key
.derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization)
.to_string(),
)
})
.transpose()?;
Ok(SecretVerificationRequestModel {
master_password_hash,
otp: input.otp.as_ref().cloned(),
secret: None,
auth_request_access_code: None,
})
} else {
Err(UserApiKeyError::UnsupportedLoginMethod)
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct UserApiKeyResponse {
api_key: String,
}
impl UserApiKeyResponse {
pub(crate) fn process_response(
response: ApiKeyResponseModel,
) -> Result<UserApiKeyResponse, UserApiKeyError> {
let api_key = require!(response.api_key);
Ok(UserApiKeyResponse { api_key })
}
}