bitwarden_generators/username_forwarders/
simplelogin.rs1use 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 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 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 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}