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 serde_json::json;
43
44    use crate::username::UsernameError;
45    #[tokio::test]
46    async fn test_mock_server() {
47        use wiremock::{Mock, ResponseTemplate, matchers};
48
49        let server = wiremock::MockServer::start().await;
50
51        // Mock the request to the DDG API, and verify that the correct request is made
52        server
53            .register(
54                Mock::given(matchers::path("/api/email/addresses"))
55                    .and(matchers::method("POST"))
56                    .and(matchers::header("Content-Type", "application/json"))
57                    .and(matchers::header("Authorization", "Bearer MY_TOKEN"))
58                    .respond_with(ResponseTemplate::new(201).set_body_json(json!({
59                        "address": "bw7prt"
60                    })))
61                    .expect(1),
62            )
63            .await;
64        // Mock an invalid token request
65        server
66            .register(
67                Mock::given(matchers::path("/api/email/addresses"))
68                    .and(matchers::method("POST"))
69                    .and(matchers::header("Content-Type", "application/json"))
70                    .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN"))
71                    .respond_with(ResponseTemplate::new(401))
72                    .expect(1),
73            )
74            .await;
75
76        let address = super::generate_with_api_url(
77            &reqwest::Client::new(),
78            "MY_TOKEN".into(),
79            format!("http://{}", server.address()),
80        )
81        .await
82        .unwrap();
83        assert_eq!(address, "[email protected]");
84
85        let fake_token_error = super::generate_with_api_url(
86            &reqwest::Client::new(),
87            "MY_FAKE_TOKEN".into(),
88            format!("http://{}", server.address()),
89        )
90        .await
91        .unwrap_err();
92
93        assert_eq!(
94            fake_token_error.to_string(),
95            UsernameError::InvalidApiKey.to_string()
96        );
97
98        server.verify().await;
99    }
100}