Skip to main content

bitwarden_generators/username_forwarders/
simplelogin.rs

1use reqwest::{StatusCode, header::CONTENT_TYPE};
2
3use crate::username::UsernameError;
4
5pub(crate) async fn generate(
6    http: &reqwest::Client,
7    api_key: String,
8    base_url: String,
9    website: Option<String>,
10) -> Result<String, UsernameError> {
11    generate_with_api_url(http, api_key, base_url, website).await
12}
13
14async fn generate_with_api_url(
15    http: &reqwest::Client,
16    api_key: String,
17    api_url: String,
18    website: Option<String>,
19) -> Result<String, UsernameError> {
20    let query = website
21        .as_ref()
22        .map(|w| format!("?hostname={w}"))
23        .unwrap_or_default();
24
25    let note = super::format_description(&website);
26
27    #[derive(serde::Serialize)]
28    struct Request {
29        note: String,
30    }
31
32    let response = http
33        .post(format!("{api_url}/api/alias/random/new{query}"))
34        .header(CONTENT_TYPE, "application/json")
35        .header("Authentication", api_key)
36        .json(&Request { note })
37        .send()
38        .await?;
39
40    if response.status() == StatusCode::UNAUTHORIZED {
41        return Err(UsernameError::InvalidApiKey);
42    }
43
44    // Throw any other errors
45    response.error_for_status_ref()?;
46
47    #[derive(serde::Deserialize)]
48    struct Response {
49        alias: String,
50    }
51    let response: Response = response.json().await?;
52
53    Ok(response.alias)
54}
55
56#[cfg(test)]
57mod tests {
58    use bitwarden_api_base::new_http_client;
59    use serde_json::json;
60
61    use crate::username::UsernameError;
62    #[tokio::test]
63    async fn test_mock_server() {
64        use wiremock::{Mock, ResponseTemplate, matchers};
65
66        let server = wiremock::MockServer::start().await;
67
68        // Mock the request to the SimpleLogin API, and verify that the correct request is made
69        server
70            .register(
71                Mock::given(matchers::path("/api/alias/random/new"))
72                    .and(matchers::method("POST"))
73                    .and(matchers::query_param("hostname", "example.com"))
74                    .and(matchers::header("Content-Type", "application/json"))
75                    .and(matchers::header("Authentication", "MY_TOKEN"))
76                    .and(matchers::body_json(json!({
77                        "note": "Website: example.com. Generated by Bitwarden."
78                    })))
79                    .respond_with(ResponseTemplate::new(201).set_body_json(json!({
80                        "alias": "[email protected]",
81                    })))
82                    .expect(1),
83            )
84            .await;
85        // Mock an invalid token request
86        server
87            .register(
88                Mock::given(matchers::path("/api/alias/random/new"))
89                    .and(matchers::method("POST"))
90                    .and(matchers::query_param("hostname", "example.com"))
91                    .and(matchers::header("Content-Type", "application/json"))
92                    .and(matchers::header("Authentication", "MY_FAKE_TOKEN"))
93                    .and(matchers::body_json(json!({
94                        "note": "Website: example.com. Generated by Bitwarden."
95                    })))
96                    .respond_with(ResponseTemplate::new(401))
97                    .expect(1),
98            )
99            .await;
100
101        let address = super::generate_with_api_url(
102            &new_http_client(),
103            "MY_TOKEN".into(),
104            format!("http://{}", server.address()),
105            Some("example.com".into()),
106        )
107        .await
108        .unwrap();
109        assert_eq!(address, "[email protected]");
110
111        let fake_token_error = super::generate_with_api_url(
112            &new_http_client(),
113            "MY_FAKE_TOKEN".into(),
114            format!("http://{}", server.address()),
115            Some("example.com".into()),
116        )
117        .await
118        .unwrap_err();
119
120        assert_eq!(
121            fake_token_error.to_string(),
122            UsernameError::InvalidApiKey.to_string()
123        );
124
125        server.verify().await;
126    }
127}