bitwarden_generators/username_forwarders/
addyio.rs1use reqwest::{header::CONTENT_TYPE, StatusCode};
2
3use crate::username::UsernameError;
4
5pub 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 serde_json::json;
55
56 use crate::username::UsernameError;
57 #[tokio::test]
58 async fn test_mock_server() {
59 use wiremock::{matchers, Mock, ResponseTemplate};
60
61 let server = wiremock::MockServer::start().await;
62
63 server
65 .register(
66 Mock::given(matchers::path("/api/v1/aliases"))
67 .and(matchers::method("POST"))
68 .and(matchers::header("Content-Type", "application/json"))
69 .and(matchers::header("Authorization", "Bearer MY_TOKEN"))
70 .and(matchers::body_json(json!({
71 "domain": "myemail.com",
72 "description": "Website: example.com. Generated by Bitwarden."
73 })))
74 .respond_with(ResponseTemplate::new(201).set_body_json(json!({
75 "data": {
76 "id": "50c9e585-e7f5-41c4-9016-9014c15454bc",
77 "user_id": "ca0a4e09-c266-4f6f-845c-958db5090f09",
78 "local_part": "50c9e585-e7f5-41c4-9016-9014c15454bc",
79 "domain": "myemail.com",
80 "email": "[email protected]",
81 "active": true
82 }
83 })))
84 .expect(1),
85 )
86 .await;
87 server
89 .register(
90 Mock::given(matchers::path("/api/v1/aliases"))
91 .and(matchers::method("POST"))
92 .and(matchers::header("Content-Type", "application/json"))
93 .and(matchers::header("Authorization", "Bearer MY_FAKE_TOKEN"))
94 .and(matchers::body_json(json!({
95 "domain": "myemail.com",
96 "description": "Website: example.com. Generated by Bitwarden."
97 })))
98 .respond_with(ResponseTemplate::new(401))
99 .expect(1),
100 )
101 .await;
102 server
104 .register(
105 Mock::given(matchers::path("/api/v1/aliases"))
106 .and(matchers::method("POST"))
107 .and(matchers::header("Content-Type", "application/json"))
108 .and(matchers::header("Authorization", "Bearer MY_TOKEN"))
109 .and(matchers::body_json(json!({
110 "domain": "gmail.com",
111 "description": "Website: example.com. Generated by Bitwarden."
112 })))
113 .respond_with(ResponseTemplate::new(403))
114 .expect(1),
115 )
116 .await;
117
118 let address = super::generate(
119 &reqwest::Client::new(),
120 "MY_TOKEN".into(),
121 "myemail.com".into(),
122 format!("http://{}", server.address()),
123 Some("example.com".into()),
124 )
125 .await
126 .unwrap();
127
128 let fake_token_error = super::generate(
129 &reqwest::Client::new(),
130 "MY_FAKE_TOKEN".into(),
131 "myemail.com".into(),
132 format!("http://{}", server.address()),
133 Some("example.com".into()),
134 )
135 .await
136 .unwrap_err();
137
138 assert_eq!(
139 fake_token_error.to_string(),
140 UsernameError::InvalidApiKey.to_string()
141 );
142
143 let fake_domain_error = super::generate(
144 &reqwest::Client::new(),
145 "MY_TOKEN".into(),
146 "gmail.com".into(),
147 format!("http://{}", server.address()),
148 Some("example.com".into()),
149 )
150 .await
151 .unwrap_err();
152
153 assert!(fake_domain_error.to_string().contains("403 Forbidden"));
154
155 server.verify().await;
156 assert_eq!(address, "[email protected]");
157 }
158}