bitwarden_generators/username_forwarders/
firefox.rs1use reqwest::{
2 header::{self},
3 StatusCode,
4};
5
6use crate::username::UsernameError;
7
8pub async fn generate(
9 http: &reqwest::Client,
10 api_token: String,
11 website: Option<String>,
12) -> Result<String, UsernameError> {
13 generate_with_api_url(http, api_token, website, "https://relay.firefox.com".into()).await
14}
15
16async fn generate_with_api_url(
17 http: &reqwest::Client,
18 api_token: String,
19 website: Option<String>,
20 api_url: String,
21) -> Result<String, UsernameError> {
22 #[derive(serde::Serialize)]
23 struct Request {
24 enabled: bool,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 generated_for: Option<String>,
27 description: String,
28 }
29
30 let description = super::format_description_ff(&website);
31
32 let response = http
33 .post(format!("{api_url}/api/v1/relayaddresses/"))
34 .header(header::AUTHORIZATION, format!("Token {api_token}"))
35 .json(&Request {
36 enabled: true,
37 generated_for: website,
38 description,
39 })
40 .send()
41 .await?;
42
43 if response.status() == StatusCode::UNAUTHORIZED {
44 return Err(UsernameError::InvalidApiKey);
45 }
46
47 response.error_for_status_ref()?;
49
50 #[derive(serde::Deserialize)]
51 struct Response {
52 full_address: String,
53 }
54 let response: Response = response.json().await?;
55
56 Ok(response.full_address)
57}
58
59#[cfg(test)]
60mod tests {
61 use serde_json::json;
62
63 use crate::username::UsernameError;
64
65 #[tokio::test]
66 async fn test_mock_success() {
67 use wiremock::{matchers, Mock, ResponseTemplate};
68
69 let server = wiremock::MockServer::start().await;
70
71 server
72 .register(
73 Mock::given(matchers::path("/api/v1/relayaddresses/"))
74 .and(matchers::method("POST"))
75 .and(matchers::header("Content-Type", "application/json"))
76 .and(matchers::header("Authorization", "Token MY_TOKEN"))
77 .and(matchers::body_json(json!({
78 "enabled": true,
79 "generated_for": "example.com",
80 "description": "example.com - Generated by Bitwarden."
81 })))
82 .respond_with(ResponseTemplate::new(201).set_body_json(json!({
83 "full_address": "[email protected]"
84 })))
85 .expect(1),
86 )
87 .await;
88
89 let address = super::generate_with_api_url(
90 &reqwest::Client::new(),
91 "MY_TOKEN".into(),
92 Some("example.com".into()),
93 format!("http://{}", server.address()),
94 )
95 .await
96 .unwrap();
97 assert_eq!(address, "[email protected]");
98
99 server.verify().await;
100 }
101
102 #[tokio::test]
103 async fn test_mock_without_website() {
104 use wiremock::{matchers, Mock, ResponseTemplate};
105
106 let server = wiremock::MockServer::start().await;
107
108 server
109 .register(
110 Mock::given(matchers::path("/api/v1/relayaddresses/"))
111 .and(matchers::method("POST"))
112 .and(matchers::header("Content-Type", "application/json"))
113 .and(matchers::header("Authorization", "Token MY_OTHER_TOKEN"))
114 .and(matchers::body_json(json!({
115 "enabled": true,
116 "description": "Generated by Bitwarden."
117 })))
118 .respond_with(ResponseTemplate::new(201).set_body_json(json!({
119 "full_address": "[email protected]"
120 })))
121 .expect(1),
122 )
123 .await;
124
125 let address = super::generate_with_api_url(
126 &reqwest::Client::new(),
127 "MY_OTHER_TOKEN".into(),
128 None,
129 format!("http://{}", server.address()),
130 )
131 .await
132 .unwrap();
133 assert_eq!(address, "[email protected]");
134
135 server.verify().await;
136 }
137
138 #[tokio::test]
139 async fn test_mock_invalid_token() {
140 use wiremock::{matchers, Mock, ResponseTemplate};
141
142 let server = wiremock::MockServer::start().await;
143
144 server
145 .register(
146 Mock::given(matchers::path("/api/v1/relayaddresses/"))
147 .and(matchers::method("POST"))
148 .and(matchers::header("Content-Type", "application/json"))
149 .and(matchers::header("Authorization", "Token MY_FAKE_TOKEN"))
150 .and(matchers::body_json(json!({
151 "enabled": true,
152 "generated_for": "example.com",
153 "description": "example.com - Generated by Bitwarden."
154 })))
155 .respond_with(ResponseTemplate::new(401))
156 .expect(1),
157 )
158 .await;
159
160 let error = super::generate_with_api_url(
161 &reqwest::Client::new(),
162 "MY_FAKE_TOKEN".into(),
163 Some("example.com".into()),
164 format!("http://{}", server.address()),
165 )
166 .await
167 .unwrap_err();
168
169 assert_eq!(error.to_string(), UsernameError::InvalidApiKey.to_string());
170
171 server.verify().await;
172 }
173}