Skip to main content

bitwarden_generators/username_forwarders/
duckduckgo.rs

1use reqwest::{StatusCode, header::CONTENT_TYPE};
2
3use crate::username::UsernameError;
4
5pub(crate) async fn generate(
6    http: &reqwest::Client,
7    token: String,
8) -> Result<String, UsernameError> {
9    generate_with_api_url(http, token, "https://quack.duckduckgo.com".into()).await
10}
11
12async fn generate_with_api_url(
13    http: &reqwest::Client,
14    token: String,
15    api_url: String,
16) -> Result<String, UsernameError> {
17    let response = http
18        .post(format!("{api_url}/api/email/addresses"))
19        .header(CONTENT_TYPE, "application/json")
20        .bearer_auth(token)
21        .send()
22        .await?;
23
24    if response.status() == StatusCode::UNAUTHORIZED {
25        return Err(UsernameError::InvalidApiKey);
26    }
27
28    // Throw any other errors
29    response.error_for_status_ref()?;
30
31    #[derive(serde::Deserialize)]
32    struct Response {
33        address: String,
34    }
35    let response: Response = response.json().await?;
36
37    Ok(format!("{}@duck.com", response.address))
38}
39
40#[cfg(test)]
41mod tests {
42    use bitwarden_api_base::new_http_client;
43    use serde_json::json;
44
45    use crate::username::UsernameError;
46    #[tokio::test]
47    async fn test_mock_server() {
48        use wiremock::{Mock, ResponseTemplate, matchers};
49
50        let server = wiremock::MockServer::start().await;
51
52        // Mock the request to the DDG API, and verify that the correct request is made
53        server
54            .register(
55                Mock::given(matchers::path("/api/email/addresses"))
56                    .and(matchers::method("POST"))
57                    .and(matchers::header("Content-Type", "application/json"))
58                    .and(matchers::header("Authorization", "Bearer MY_TOKEN"))
59                    .respond_with(ResponseTemplate::new(201).set_body_json(json!({
60                        "address": "bw7prt"
61                    })))
62                    .expect(1),
63            )
64            .await;
65        // Mock an invalid token request
66        server
67            .register(
68                Mock::given(matchers::path("/api/email/addresses"))
69                    .and(matchers::method("POST"))
70                    .and(matchers::header("Content-Type", "application/json"))
71                    .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN"))
72                    .respond_with(ResponseTemplate::new(401))
73                    .expect(1),
74            )
75            .await;
76
77        let address = super::generate_with_api_url(
78            &new_http_client(),
79            "MY_TOKEN".into(),
80            format!("http://{}", server.address()),
81        )
82        .await
83        .unwrap();
84        assert_eq!(address, "[email protected]");
85
86        let fake_token_error = super::generate_with_api_url(
87            &new_http_client(),
88            "MY_FAKE_TOKEN".into(),
89            format!("http://{}", server.address()),
90        )
91        .await
92        .unwrap_err();
93
94        assert_eq!(
95            fake_token_error.to_string(),
96            UsernameError::InvalidApiKey.to_string()
97        );
98
99        server.verify().await;
100    }
101}