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