Skip to content
This repository was archived by the owner on Oct 6, 2024. It is now read-only.

Commit c6d3cc1

Browse files
author
尼亚
authored
Merge pull request #1 from ch3ck/update-test-event
Fix handler function
2 parents 9fb044d + 6c3729d commit c6d3cc1

File tree

3 files changed

+77
-189
lines changed

3 files changed

+77
-189
lines changed

src/lib.rs

Lines changed: 71 additions & 183 deletions
Original file line numberDiff line numberDiff line change
@@ -27,69 +27,15 @@ use rusoto_core::Region;
2727
use rusoto_ses::{
2828
Body, Content, Destination, Message, SendEmailRequest, Ses, SesClient,
2929
};
30-
use serde::{Deserialize, Serialize};
31-
// use serde_json::Value;
30+
use serde::Serialize;
31+
use serde_json::Value;
3232
use simple_logger::SimpleLogger;
3333
use std::collections::HashMap;
3434
use std::fmt::Debug;
3535
use std::{fmt, process};
3636
use tracing::log::LevelFilter;
3737
use tracing::{error, info, warn};
3838

39-
// LambdaRequest: Represents the incoming Request from AWS Lambda
40-
// This is deserialized into a struct payload
41-
//
42-
#[derive(Debug, Default, Deserialize)]
43-
#[serde(default)]
44-
pub struct LambdaRequest {
45-
/** lambda request body */
46-
records: Vec<LambdaRequestRecord>,
47-
}
48-
49-
#[derive(Debug, Default, Deserialize)]
50-
#[serde(default)]
51-
struct LambdaRequestRecord {
52-
/** event source */
53-
event_source: String,
54-
55-
/** event version */
56-
event_version: String,
57-
58-
/** event subscription arn*/
59-
event_subscription_arn: String,
60-
61-
/** SNS Message body */
62-
sns: SNSMessageBody,
63-
}
64-
65-
#[derive(Debug, Default, Deserialize)]
66-
#[serde(default, rename_all = "camelCase")]
67-
struct SNSMessageBody {
68-
r#type: String,
69-
70-
message_id: String,
71-
72-
topic_arn: String,
73-
74-
subject: String,
75-
76-
/** SES Message request */
77-
message: SesMessageRequest,
78-
79-
timestamp: String,
80-
81-
signature_version: u32,
82-
83-
signature: String,
84-
85-
signing_cert_url: String,
86-
87-
unsubscribe_url: String,
88-
89-
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
90-
message_attributes: HashMap<String, String>,
91-
}
92-
9339
/// LambdaResponse: The Outgoing response being passed by the Lambda
9440
#[derive(Debug, Default, Clone, Serialize)]
9541
#[serde(default, rename_all = "camelCase")]
@@ -135,109 +81,10 @@ impl fmt::Display for LambdaResponse {
13581
}
13682
}
13783

138-
#[derive(Debug, Default, Deserialize)]
139-
#[serde(default, rename_all = "camelCase")]
140-
struct MessageHeader {
141-
name: String,
142-
143-
value: String,
144-
}
145-
146-
#[derive(Debug, Default, Deserialize)]
147-
#[serde(default, rename_all = "camelCase")]
148-
struct ReceiptStatus {
149-
status: String,
150-
}
151-
152-
#[derive(Debug, Default, Deserialize)]
153-
#[serde(default, rename_all = "camelCase")]
154-
struct LambdaInfo {
155-
r#type: String,
156-
157-
topic_arn: String,
158-
159-
function_arn: String,
160-
161-
invocation_type: String,
162-
}
163-
164-
#[derive(Debug, Default, Deserialize)]
165-
#[serde(default, rename_all = "camelCase")]
166-
struct ReceiptInfo {
167-
timestamp: String,
168-
169-
processing_time_millis: u32,
170-
171-
recipients: Vec<String>,
172-
173-
spam_verdict: ReceiptStatus,
174-
175-
virus_verdict: ReceiptStatus,
176-
177-
spf_verdict: ReceiptStatus,
178-
dkim_verdict: ReceiptStatus,
179-
dmarc_verdict: ReceiptStatus,
180-
action: LambdaInfo,
181-
}
182-
183-
#[derive(Debug, Default, Deserialize)]
184-
#[serde(default, rename_all = "camelCase")]
185-
struct CommonHeaderReq {
186-
return_path: String,
187-
188-
from: Vec<String>,
189-
190-
date: String,
191-
192-
to: Vec<String>,
193-
194-
cc: Vec<String>,
195-
196-
bcc: Vec<String>,
197-
198-
message_id: String,
199-
200-
subject: String,
201-
}
202-
203-
#[derive(Debug, Default, Deserialize)]
204-
#[serde(default, rename_all = "camelCase")]
205-
struct MailInfoReq {
206-
timestamp: String,
207-
208-
source: String,
209-
210-
message_id: String,
211-
212-
destination: Vec<String>,
213-
214-
headers_truncated: bool,
215-
216-
headers: Vec<MessageHeader>,
217-
common_headers: CommonHeaderReq,
218-
}
219-
220-
/// SesMessageRequest: SES Message
221-
#[derive(Debug, Default, Deserialize)]
222-
#[serde(default, rename_all = "camelCase")]
223-
pub struct SesMessageRequest {
224-
/** notification type */
225-
notification_type: String,
226-
227-
/** receipt metadata **/
228-
receipt: ReceiptInfo,
229-
230-
/** Email content */
231-
content: String,
232-
233-
/** Email metadata */
234-
mail: MailInfoReq,
235-
}
236-
23784
/// PrivatEmail_Handler: processes incoming messages from SNS
23885
/// and forwards to the appropriate recipient email
23986
pub(crate) async fn privatemail_handler(
240-
event: LambdaRequest,
87+
event: Value,
24188
ctx: Context,
24289
) -> Result<LambdaResponse, Error> {
24390
// Enable Cloudwatch error logging at runtime
@@ -251,53 +98,94 @@ pub(crate) async fn privatemail_handler(
25198
let email_config = config::PrivatEmailConfig::new_from_env();
25299

253100
// Fetch request payload
254-
let sns_payload: &LambdaRequestRecord = event.records.first().unwrap();
101+
let sns_payload = event["Records"][0]["Sns"].as_object().unwrap();
255102
info!("Raw Email Info: {:?}", sns_payload);
256103

257-
let email_info = &sns_payload.sns.message;
258-
info!("Email Message: {:?}", email_info);
259-
let new_email_info: &SesMessageRequest = email_info;
260-
info!("Email NotificationType: {:#?}", new_email_info);
261-
262104
// skip spam messages
263-
if new_email_info.receipt.spam_verdict.status == "FAIL"
264-
|| new_email_info.receipt.virus_verdict.status == "FAIL"
105+
if sns_payload["Message"]["receipt"]["spamVerdict"]["status"]
106+
.as_str()
107+
.unwrap()
108+
== "FAIL"
109+
|| sns_payload["Message"]["receipt"]["virusVerdict"]["status"]
110+
.as_str()
111+
.unwrap()
112+
== "FAIL"
265113
{
266114
warn!("Message contains spam or virus, skipping!");
267115
process::exit(200);
268116
// Ok(LambdaResponse(200, "message skipped"))
269117
}
270118

271119
// Rewrite Email From header to contain sender's name with forwarder's email address
120+
let raw_from = sns_payload["Message"]["mail"]["commonHeaders"]
121+
["returnPath"]
122+
.as_str()
123+
.unwrap()
124+
.to_string();
125+
let from: Vec<String> = vec![raw_from];
126+
127+
let to_emails: Option<Vec<String>> =
128+
Some(vec![email_config.to_email.to_string()]);
129+
130+
info!(
131+
"Email Subject: {:#?}",
132+
sns_payload["Message"]["mail"]["commonHeaders"]["subject"]
133+
.as_str()
134+
.unwrap()
135+
.to_string()
136+
);
137+
info!("From Email: {:#?}", from);
138+
info!("To Email: {:#?}", to_emails);
139+
info!(
140+
"Email content: {:#?}",
141+
sns_payload["Message"]["content"].as_str().unwrap().to_string()
142+
);
143+
272144
let ses_email_message = SendEmailRequest {
273145
configuration_set_name: None,
274146
destination: Destination {
275-
bcc_addresses: Some(new_email_info.mail.common_headers.bcc.clone()),
276-
cc_addresses: Some(new_email_info.mail.common_headers.cc.clone()),
277-
to_addresses: Some(vec![email_config.to_email.clone()]),
147+
bcc_addresses: Some(vec!["".to_string()]),
148+
cc_addresses: Some(vec!["".to_string()]),
149+
to_addresses: to_emails,
278150
},
279151
message: Message {
280152
body: Body {
281153
html: Some(Content {
282154
charset: Some(String::from("utf-8")),
283-
data: new_email_info.content.to_string(),
155+
data: sns_payload["Message"]["content"]
156+
.as_str()
157+
.unwrap()
158+
.to_string(),
284159
}),
285160
text: Some(Content {
286161
charset: Some(String::from("utf-8")),
287-
data: new_email_info.content.to_string(),
162+
data: sns_payload["Message"]["content"]
163+
.as_str()
164+
.unwrap()
165+
.to_string(),
288166
}),
289167
},
290168
subject: Content {
291169
charset: Some(String::from("utf-8")),
292-
data: new_email_info.mail.common_headers.subject.clone(),
170+
data: sns_payload["Message"]["mail"]["commonHeaders"]
171+
["subject"]
172+
.as_str()
173+
.unwrap()
174+
.to_string(),
293175
},
294176
},
295-
reply_to_addresses: Some(
296-
new_email_info.mail.common_headers.from.clone(),
177+
reply_to_addresses: Some(from),
178+
return_path: Some(
179+
sns_payload["Message"]["mail"]["source"]
180+
.as_str()
181+
.unwrap()
182+
.to_string(),
297183
),
298-
return_path: Some(new_email_info.mail.source.to_string()),
299184
return_path_arn: None,
300-
source: new_email_info.mail.source.clone(),
185+
source: sns_payload["Message"]["mail"]["source"]
186+
.as_str()
187+
.unwrap()
188+
.to_string(),
301189
source_arn: None,
302190
tags: None,
303191
};
@@ -318,29 +206,29 @@ pub(crate) async fn privatemail_handler(
318206
#[cfg(test)]
319207
mod tests {
320208
use super::*;
321-
use std::fs::File;
322-
use std::io::BufReader;
323209
use std::path::PathBuf;
210+
use std::{env, fs};
324211

325-
fn read_test_event() -> Result<LambdaRequest, Error> {
212+
fn read_test_event() -> Value {
326213
// Open the file in read-only mode with buffer.
327214

328215
let mut srcdir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
329216
srcdir.push("tests/payload/testEvent.json");
330217
println!("Cur Dir: {}", srcdir.display());
331-
let file = File::open(srcdir)?;
332-
let reader = BufReader::new(file);
333218

334-
// Read the JSON contents of the file as an instance of `User`.
335-
let req = serde_json::from_reader(reader)?;
219+
// Read the JSON contents of the file as an instance of `String`.
220+
let input_str = fs::read_to_string(srcdir.as_path()).unwrap();
221+
info!("Input str: {}", input_str);
336222

337-
// Return the `LambdaRequest`.
338-
Ok(req)
223+
// Return the `Value`.
224+
return serde_json::from_str(input_str.as_str()).unwrap();
339225
}
340226

341227
#[tokio::test]
342228
// #[ignore]
343229
async fn handler_handles() {
230+
env::set_var("TO_EMAIL", "[email protected]");
231+
env::set_var("FROM_EMAIL", "[email protected]");
344232
let test_event = read_test_event();
345233
assert_eq!(
346234
privatemail_handler(test_event, Context::default())

src/lib/config.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ impl PrivatEmailConfig {
5858
/// As long as you have the `from_email` and `to_email` environment setup; this should work
5959
pub fn new_from_env() -> Self {
6060
PrivatEmailConfig {
61-
from_email: env::var("FROM_EMAIL").unwrap(),
62-
to_email: env::var("TO_EMAIL").unwrap(),
61+
from_email: env::var("FROM_EMAIL").unwrap_or_default(),
62+
to_email: env::var("TO_EMAIL").unwrap_or_default(),
6363
subject_prefix: Some(String::from("PrivateMail: ")), // not currently used
6464
email_bucket: None,
6565
email_key_prefix: None,
@@ -117,8 +117,8 @@ mod tests {
117117

118118
#[test]
119119
fn test_new_from_env_privatemail_config() {
120-
env::set_var("from_email", "test_from");
121-
env::set_var("to_email", "test_to");
120+
env::set_var("FROM_EMAIL", "test_from");
121+
env::set_var("TO_EMAIL", "test_to");
122122

123123
let new_config = PrivatEmailConfig::new_from_env();
124124
assert_eq!(new_config.from_email.contains("test_from"), true);

tests/payload/testEvent.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
},
8181
{
8282
"name": "Content-Type",
83-
"value": "multipart/alternative; boundary="-- -- = _Part_16661649_1644988291 .1615939463851 ""
83+
"value": "multipart/alternative; boundary='-- -- = _Part_16661649_1644988291 .1615939463851 '"
8484
},
8585
{
8686
"name": "X-EmailType-Id",
@@ -152,7 +152,7 @@
152152
}
153153
},
154154
"content": "Return-Path: <[email protected]>rnReceived: from rn2-txn-msbadger06103.apple.com (rn2-txn-msbadger06103.apple.com [17.111.110.98])rn by inbound-smtp.us-east-1.amazonaws.com with SMTP id 3hrf8kqv0k8oc7t86fadne11md91d90gdpsell01rn for [email protected];rn Wed, 17 Mar 2021 00:04:24 +0000 (UTC)rnX-SES-Spam-Verdict: PASSrnX-SES-Virus-Verdict: PASSrnReceived-SPF: pass (spfCheck: domain of email.apple.com designates 17.111.110.98 as permitted sender) client-ip=17.111.110.98; [email protected]; helo=rn2-txn-msbadger06103.apple.com;rnAuthentication-Results: amazonses.com;rn spf=pass (spfCheck: domain of email.apple.com designates 17.111.110.98 as permitted sender) client-ip=17.111.110.98; [email protected]; helo=rn2-txn-msbadger06103.apple.com;rn dkim=pass [email protected];rn dmarc=pass header.from=email.apple.com;rnX-SES-RECEIPT: AEFBQUFBQUFBQUFIc09Bb2pTTHBLZG1udTJTVk9OSmpyNG4xM0JNMmFPeVEwRlA1emk1TkdGa3FZOXhWNnBSb24zWmk5OGhOVWU1T2ViSmFoYjhGYUl0b09adi8yRTFUTXFTOThyUWZwNy8rSFZ1R2lDb0ZkM1A3N0I5a2FxU3RWZGlSaldZbXNiRjlEM0IrSEQ2VVhmQWVjQ3Z4UHBOMzIrVnpKbTEvcnpCMDRTMDYyKzBOQmw2OVpTZkYwY3Qrd2tGQkl6YW9USkxkV0pxNkhzTWJsWnVoejFtQmJXRm9mU1pZOUs5ZHd2a0dvZndUVTRvdzRmbURLM3FBUzF1L3Zna1NyUDNoandVV2tiZVp6Ymw4b09vK3UvcW16V3VsUmlPV01PV2FEeEJTNWxmNGhhazJ2L2c9PQ==rnX-SES-DKIM-SIGNATURE: a=rsa-sha256; q=dns/txt; b=dIPAKDWZKrlyZ4fALXLZjvhOHDU79YnlTq9c+I0bba3euy4ZFlxRt+acEMWdDlxNVbmWjvmZ60drbALSWfhqdIOWmbvd6XaMyqS6DIV9dEqo3bpFgghi0+OZAqs4Y7oTHFezbYw7bMikCiFpOezL5J68C29tiYgm1w6sZvrXfso=; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1615939465; v=1; bh=lVSsD3MUG3sqOrlw8QZ2JcSqd/lMVBvvpboqic7Fllg=; h=From:To:Cc:Bcc:Subject:Date:Message-ID:MIME-Version:Content-Type:X-SES-RECEIPT;rnDKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=email.apple.com;rnts=email0517; t=1615939463;rntbh=lVSsD3MUG3sqOrlw8QZ2JcSqd/lMVBvvpboqic7Fllg=;rnth=Date:From:To:Message-ID:Subject:Content-Type;rntb=LB+J4ywHV0ECEle/2saC11aFbomhd1IyMu9Ge8U/JXnoxei390LJY/Gm/QszMUoXZrnt r45Du9xMjJ9qMpAAMmxE6xrs5SkMFrmJXEkXVmyMC8bmgIbqOwjMt3XxuJUb7SKd+Irnt Pv/EKoIGPyBwbtb3GxY5uhq8wmL38Ch2kv/PMFNFlUf1aSDqvR+cTzvH1kVQXJeL+vrnt GDFlPLrP+2XzZvaUvY+oPpc1FrPNVwna9aWA8ZidOJw7UmNRucuVg67XNCXs5STc+Frnt c0uKHSvYnpC7JRhxufuhLqtkUrXij3rYcSBgor9GtYEkEQfqM7M+xkNB0k+2xI/uYirnt 4cykUakVC9r0w==rnDate: Wed, 17 Mar 2021 00:04:23 +0000 (GMT)rnFrom: Apple Store <[email protected]>rnTo: [email protected]: <[email protected]>rnSubject: Explore your Apple Watch with a Specialist online.rnMIME-Version: 1.0rnContent-Type: multipart/alternative; rntboundary="
155-
}
155+
},
156156
"Timestamp": "2021-03-16T17:20:34.581Z",
157157
"SignatureVersion": "1",
158158
"Signature": "oWiAwndwB/YtyU3a1JKE7UHSXLsbdXGQIuCHMGoXy3s8JWXTz/OU3v/yezenhLz92k2nYDr8Py2hDxqZiQ+U/w/9UL2mECEbzcn3VKV3XurpX/aw85RPxv2JTqroY5jDsZkh1amx+tD9jD1KzH61UsBadfovYL16Pl6Ipovr4GbumjeDWdYG87nsTCvSCwlwVnfXO8lrdqh+AgC2FaYrrYFmi8ZHD+PZTggd4+0je8FAJmUwe8xCiUuSaiRDvEAIN+X3k50b+uJDTq9W8fvwV0c0DJEwlOyWU9hhWxp2LqM9MhdRLhSxJG62k+RVi7U75CDrLhNt5Agg7JRLyxjaqA==",

0 commit comments

Comments
 (0)