bitwarden_generators/username_forwarders/
forwardemail.rsuse reqwest::{header::CONTENT_TYPE, StatusCode};
use crate::username::UsernameError;
pub async fn generate(
http: &reqwest::Client,
api_token: String,
domain: String,
website: Option<String>,
) -> Result<String, UsernameError> {
generate_with_api_url(
http,
api_token,
domain,
website,
"https://api.forwardemail.net".into(),
)
.await
}
async fn generate_with_api_url(
http: &reqwest::Client,
api_token: String,
domain: String,
website: Option<String>,
api_url: String,
) -> Result<String, UsernameError> {
let description = super::format_description(&website);
#[derive(serde::Serialize)]
struct Request {
labels: Option<String>,
description: String,
}
let response = http
.post(format!("{api_url}/v1/domains/{domain}/aliases"))
.header(CONTENT_TYPE, "application/json")
.basic_auth(api_token, None::<String>)
.json(&Request {
description,
labels: website,
})
.send()
.await?;
if response.status() == StatusCode::UNAUTHORIZED {
return Err(UsernameError::InvalidApiKey);
}
#[derive(serde::Deserialize)]
struct ResponseDomain {
name: Option<String>,
}
#[derive(serde::Deserialize)]
struct Response {
name: Option<String>,
domain: Option<ResponseDomain>,
message: Option<String>,
error: Option<String>,
}
let status = response.status();
let response: Response = response.json().await?;
if status.is_success() {
if let Some(name) = response.name {
if let Some(response_domain) = response.domain {
return Ok(format!(
"{}@{}",
name,
response_domain.name.unwrap_or(domain)
));
}
}
}
if let Some(message) = response.message {
return Err(UsernameError::ResponseContent { status, message });
}
if let Some(message) = response.error {
return Err(UsernameError::ResponseContent { status, message });
}
Err(UsernameError::Unknown)
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::username::UsernameError;
#[tokio::test]
async fn test_mock_server() {
use wiremock::{matchers, Mock, ResponseTemplate};
let server = wiremock::MockServer::start().await;
server
.register(
Mock::given(matchers::path("/v1/domains/mydomain.com/aliases"))
.and(matchers::method("POST"))
.and(matchers::header("Content-Type", "application/json"))
.and(matchers::header("Authorization", "Basic TVlfVE9LRU46"))
.and(matchers::body_json(json!({
"labels": "example.com",
"description": "Website: example.com. Generated by Bitwarden."
})))
.respond_with(ResponseTemplate::new(201).set_body_json(json!({
"name": "wertg8ad",
"domain": {
"name": "mydomain.com"
}
})))
.expect(1),
)
.await;
server
.register(
Mock::given(matchers::path("/v1/domains/mydomain.com/aliases"))
.and(matchers::method("POST"))
.and(matchers::header("Content-Type", "application/json"))
.and(matchers::header(
"Authorization",
"Basic TVlfRkFLRV9UT0tFTjo=",
))
.and(matchers::body_json(json!({
"labels": "example.com",
"description": "Website: example.com. Generated by Bitwarden."
})))
.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API token."
})))
.expect(1),
)
.await;
server
.register(
Mock::given(matchers::path("/v1/domains/mydomain.com/aliases"))
.and(matchers::method("POST"))
.and(matchers::header("Content-Type", "application/json"))
.and(matchers::header(
"Authorization",
"Basic TVlfRlJFRV9UT0tFTjo=",
))
.and(matchers::body_json(json!({
"labels": "example.com",
"description": "Website: example.com. Generated by Bitwarden."
})))
.respond_with(ResponseTemplate::new(402).set_body_json(json!({
"statusCode": 402,
"error": "Payment required",
"message": "Please upgrade to a paid plan to unlock this feature."
})))
.expect(1),
)
.await;
let address = super::generate_with_api_url(
&reqwest::Client::new(),
"MY_TOKEN".into(),
"mydomain.com".into(),
Some("example.com".into()),
format!("http://{}", server.address()),
)
.await
.unwrap();
assert_eq!(address, "[email protected]");
let invalid_token_error = super::generate_with_api_url(
&reqwest::Client::new(),
"MY_FAKE_TOKEN".into(),
"mydomain.com".into(),
Some("example.com".into()),
format!("http://{}", server.address()),
)
.await
.unwrap_err();
assert_eq!(
invalid_token_error.to_string(),
UsernameError::InvalidApiKey.to_string()
);
let free_token_error = super::generate_with_api_url(
&reqwest::Client::new(),
"MY_FREE_TOKEN".into(),
"mydomain.com".into(),
Some("example.com".into()),
format!("http://{}", server.address()),
)
.await
.unwrap_err();
assert!(free_token_error
.to_string()
.contains("Please upgrade to a paid plan"));
server.verify().await;
}
}