bitwarden_generators/username_forwarders/
simplelogin.rs

1use reqwest::{header::CONTENT_TYPE, StatusCode};
2
3use crate::username::UsernameError;
4
5pub 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 serde_json::json;
59
60    use crate::username::UsernameError;
61    #[tokio::test]
62    async fn test_mock_server() {
63        use wiremock::{matchers, Mock, ResponseTemplate};
64
65        let server = wiremock::MockServer::start().await;
66
67        // Mock the request to the SimpleLogin API, and verify that the correct request is made
68        server
69            .register(
70                Mock::given(matchers::path("/api/alias/random/new"))
71                    .and(matchers::method("POST"))
72                    .and(matchers::query_param("hostname", "example.com"))
73                    .and(matchers::header("Content-Type", "application/json"))
74                    .and(matchers::header("Authentication", "MY_TOKEN"))
75                    .and(matchers::body_json(json!({
76                        "note": "Website: example.com. Generated by Bitwarden."
77                    })))
78                    .respond_with(ResponseTemplate::new(201).set_body_json(json!({
79                        "alias": "[email protected]",
80                    })))
81                    .expect(1),
82            )
83            .await;
84        // Mock an invalid token request
85        server
86            .register(
87                Mock::given(matchers::path("/api/alias/random/new"))
88                    .and(matchers::method("POST"))
89                    .and(matchers::query_param("hostname", "example.com"))
90                    .and(matchers::header("Content-Type", "application/json"))
91                    .and(matchers::header("Authentication", "MY_FAKE_TOKEN"))
92                    .and(matchers::body_json(json!({
93                        "note": "Website: example.com. Generated by Bitwarden."
94                    })))
95                    .respond_with(ResponseTemplate::new(401))
96                    .expect(1),
97            )
98            .await;
99
100        let address = super::generate_with_api_url(
101            &reqwest::Client::new(),
102            "MY_TOKEN".into(),
103            format!("http://{}", server.address()),
104            Some("example.com".into()),
105        )
106        .await
107        .unwrap();
108        assert_eq!(address, "[email protected]");
109
110        let fake_token_error = super::generate_with_api_url(
111            &reqwest::Client::new(),
112            "MY_FAKE_TOKEN".into(),
113            format!("http://{}", server.address()),
114            Some("example.com".into()),
115        )
116        .await
117        .unwrap_err();
118
119        assert_eq!(
120            fake_token_error.to_string(),
121            UsernameError::InvalidApiKey.to_string()
122        );
123
124        server.verify().await;
125    }
126}