bitwarden_core/platform/
get_user_api_key.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! Get the user's API key.
//!
//! This module provides the functionality to get the user's API key.
//!
//! <div class="warning">
//!
//! This code is currently unused and unmaintained!
//!
//! - Prior to use it should be reviewed and tested.
//! - Code should move to the appropriate code owner.
//! - Secret verification should be extracted as it's a common pattern for multiple requests.
//!
//! </div>

use std::sync::Arc;

use bitwarden_api_api::{
    apis::accounts_api::accounts_api_key_post,
    models::{ApiKeyResponseModel, SecretVerificationRequestModel},
};
use bitwarden_crypto::{HashPurpose, MasterKey};
use log::{debug, info};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;

use super::SecretVerificationRequest;
use crate::{
    client::{LoginMethod, UserLoginMethod},
    require, ApiError, Client, MissingFieldError, NotAuthenticatedError,
};

#[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,
}

/// Get the user's API key.
pub(crate) async fn get_user_api_key(
    client: &Client,
    input: &SecretVerificationRequest,
) -> Result<UserApiKeyResponse, UserApiKeyError> {
    info!("Getting Api Key");
    debug!("{:?}", input);

    let auth_settings = get_login_method(client)?;
    let config = client.internal.get_api_configurations().await;

    let request = build_secret_verification_request(&auth_settings, input)?;
    let response = accounts_api_key_post(&config.api, Some(request))
        .await
        .map_err(ApiError::from)?;
    UserApiKeyResponse::process_response(response)
}

fn get_login_method(client: &Client) -> Result<Arc<LoginMethod>, NotAuthenticatedError> {
    if client.internal.is_authed() {
        client
            .internal
            .get_login_method()
            .ok_or(NotAuthenticatedError)
    } else {
        Err(NotAuthenticatedError)
    }
}

/// Build the secret verification request.
fn build_secret_verification_request(
    login_method: &LoginMethod,
    input: &SecretVerificationRequest,
) -> Result<SecretVerificationRequestModel, UserApiKeyError> {
    if let LoginMethod::User(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)?;

                master_key.derive_master_key_hash(p.as_bytes(), HashPurpose::ServerAuthorization)
            })
            .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, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct UserApiKeyResponse {
    /// The user's API key, which represents the client_secret portion of an oauth request.
    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 })
    }
}