bitwarden_generators/username_forwarders/
addyio.rs1use reqwest::{StatusCode, header::CONTENT_TYPE};
2
3use crate::username::UsernameError;
4
5pub(crate) async fn generate(
6 http: &reqwest::Client,
7 api_token: String,
8 domain: String,
9 base_url: String,
10 website: Option<String>,
11) -> Result<String, UsernameError> {
12 let description = super::format_description(&website);
13
14 #[derive(serde::Serialize)]
15 struct Request {
16 domain: String,
17 description: String,
18 }
19
20 let response = http
21 .post(format!("{base_url}/api/v1/aliases"))
22 .header(CONTENT_TYPE, "application/json")
23 .bearer_auth(api_token)
24 .header("X-Requested-With", "XMLHttpRequest")
25 .json(&Request {
26 domain,
27 description,
28 })
29 .send()
30 .await?;
31
32 if response.status() == StatusCode::UNAUTHORIZED {
33 return Err(UsernameError::InvalidApiKey);
34 }
35
36 response.error_for_status_ref()?;
38
39 #[derive(serde::Deserialize)]
40 struct ResponseData {
41 email: String,
42 }
43 #[derive(serde::Deserialize)]
44 struct Response {
45 data: ResponseData,
46 }
47 let response: Response = response.json().await?;
48
49 Ok(response.data.email)
50}
51
52#[cfg(test)]
53mod tests {
54 use bitwarden_api_base::new_http_client;
55 use serde_json::json;
56
57 use crate::username::UsernameError;
58 #[tokio::test]
59 async fn test_mock_server() {
60 use wiremock::{Mock, ResponseTemplate, matchers};
61
62 let server = wiremock::MockServer::start().await;
63
64 server
66 .register(
67 Mock::given(matchers::path("/api/v1/aliases"))
68 .and(matchers::method("POST"))
69 .and(matchers::header("Content-Type", "application/json"))
70 .and(matchers::header("Authorization", "Bearer MY_TOKEN"))
71 .and(matchers::body_json(json!({
72 "domain": "myemail.com",
73 "description": "Website: example.com. Generated by Bitwarden."
74 })))
75 .respond_with(ResponseTemplate::new(201).set_body_json(json!({
76 "data": {
77 "id": "50c9e585-e7f5-41c4-9016-9014c15454bc",
78 "user_id": "ca0a4e09-c266-4f6f-845c-958db5090f09",
79 "local_part": "50c9e585-e7f5-41c4-9016-9014c15454bc",
80 "domain": "myemail.com",
81 "email": "[email protected]",
82 "active": true
83 }
84 })))
85 .expect(1),
86 )
87 .await;
88 server
90 .register(
91 Mock::given(matchers::path("/api/v1/aliases"))
92 .and(matchers::method("POST"))
93 .and(matchers::header("Content-Type", "application/json"))
94 .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN"))
95 .and(matchers::body_json(json!({
96 "domain": "myemail.com",
97 "description": "Website: example.com. Generated by Bitwarden."
98 })))
99 .respond_with(ResponseTemplate::new(401))
100 .expect(1),
101 )
102 .await;
103 server
105 .register(
106 Mock::given(matchers::path("/api/v1/aliases"))
107 .and(matchers::method("POST"))
108 .and(matchers::header("Content-Type", "application/json"))
109 .and(matchers::header("Authorization", "Bearer MY_TOKEN"))
110 .and(matchers::body_json(json!({
111 "domain": "gmail.com",
112 "description": "Website: example.com. Generated by Bitwarden."
113 })))
114 .respond_with(ResponseTemplate::new(403))
115 .expect(1),
116 )
117 .await;
118
119 let address = super::generate(
120 &new_http_client(),
121 "MY_TOKEN".into(),
122 "myemail.com".into(),
123 format!("http://{}", server.address()),
124 Some("example.com".into()),
125 )
126 .await
127 .unwrap();
128
129 let fake_token_error = super::generate(
130 &new_http_client(),
131 "MY_FAKE_TOKEN".into(),
132 "myemail.com".into(),
133 format!("http://{}", server.address()),
134 Some("example.com".into()),
135 )
136 .await
137 .unwrap_err();
138
139 assert_eq!(
140 fake_token_error.to_string(),
141 UsernameError::InvalidApiKey.to_string()
142 );
143
144 let fake_domain_error = super::generate(
145 &new_http_client(),
146 "MY_TOKEN".into(),
147 "gmail.com".into(),
148 format!("http://{}", server.address()),
149 Some("example.com".into()),
150 )
151 .await
152 .unwrap_err();
153
154 assert!(fake_domain_error.to_string().contains("403 Forbidden"));
155
156 server.verify().await;
157 assert_eq!(address, "[email protected]");
158 }
159}