Skip to main content

relay_actions/actions/
send.rs

1use serde::Serialize;
2
3use crate::{
4    actions::Action, cache::CacheManager, sign::sign_request, sinks::progress::ProgressSink,
5    storage::Storage,
6};
7use relay_lib::{
8    core::{Uuid, Version, chrono::Utc},
9    prelude::{Address, MetaEnvelope, PrivateEnvelope, e2e, sign},
10};
11
12#[derive(Serialize)]
13struct SendReq {
14    pub envelope: MetaEnvelope,
15    pub self_envelope: Option<MetaEnvelope>,
16}
17
18pub struct Send {
19    pub address: Address,
20    pub to: Address,
21    pub message: Option<String>,
22    pub subject: Option<String>,
23    pub self_encrypt: bool,
24}
25
26impl Action for Send {
27    type Output = ();
28
29    fn execute(
30        &self,
31        storage: &mut Storage,
32        cache: &mut CacheManager,
33        progress: &mut dyn ProgressSink,
34    ) -> Self::Output {
35        let identity = storage
36            .root
37            .get_identity(&self.address)
38            .unwrap_or_else(|| {
39                progress.abort(&format!("No identity found for address `{}`", self.address));
40            })
41            .clone();
42
43        progress.step("Fetching records", "Fetched records");
44        let agent_record = cache
45            .records
46            .agent(&mut cache.agents, self.address.agent())
47            .unwrap_or_else(|e| {
48                progress.error(&format!("{:#?}", e));
49                progress.abort("Failed to fetch agent record");
50            });
51        let recipient_record = cache
52            .records
53            .user(&mut cache.agents, self.to.agent(), self.to.user())
54            .unwrap_or_else(|e| {
55                progress.error(&format!("{:#?}", e));
56                progress.abort("Failed to fetch recipient record");
57            });
58
59        progress.step("Composing message", "Composed message");
60        let message = match &self.message {
61            Some(msg) => msg.clone(),
62            None => {
63                progress.pause();
64                let edited = edit::edit(
65                    "\n# Please write your message here.\n# Lines starting with '#' will be ignored\n",
66                )
67                .unwrap_or_else(|e| {
68                    progress.error(&format!("{:#?}", e));
69                    progress.abort("Failed to open editor");
70                });
71                progress.resume();
72
73                edited
74                    .lines()
75                    .filter(|line| !line.starts_with('#'))
76                    .collect::<Vec<_>>()
77                    .join("\n")
78            }
79        };
80
81        let private = PrivateEnvelope {
82            content_type: "plain/text".to_string(),
83            message: message.as_bytes().to_vec(),
84            subject: self.subject.as_ref().map(|s| s.trim().as_bytes().to_vec()),
85        };
86
87        progress.step("Encrypting message", "Encrypted message");
88        let payload = serde_json::to_vec(&private).unwrap_or_else(|e| {
89            progress.error(&format!("{:#?}", e));
90            progress.abort("Failed to serialize message payload");
91        });
92
93        let (crypto, ciphertext) = e2e::encrypt(
94            relay_lib::crypto::OsRng,
95            &recipient_record.public_key(),
96            &payload,
97            &[],
98        )
99        .unwrap_or_else(|e| {
100            progress.error(&format!("{:#?}", e));
101            progress.abort("Failed to encrypt message");
102        });
103
104        progress.step("Signing message", "Signed message");
105        let signed = sign::sign(&ciphertext, &identity.signing_key());
106
107        let envelope = MetaEnvelope {
108            gid: Uuid::new_v4(),
109            version: Version::parse("0.0.1").unwrap(),
110            from: self.address.clone(),
111            to: self.to.clone(),
112            timestamp: Utc::now(),
113            crypto,
114            payload: signed,
115            self_encrypted: false,
116        };
117
118        let self_envelope = if self.self_encrypt {
119            let (crypto, ciphertext) = e2e::encrypt(
120                relay_lib::crypto::OsRng,
121                &identity.pub_record.public_key(),
122                &payload,
123                &[],
124            )
125            .unwrap_or_else(|e| {
126                progress.error(&format!("{:#?}", e));
127                progress.abort("Failed to encrypt self-envelope");
128            });
129
130            let signed = sign::sign(&ciphertext, &identity.signing_key());
131
132            Some(MetaEnvelope {
133                gid: Uuid::new_v4(),
134                version: Version::parse("0.0.1").unwrap(),
135                from: self.address.clone(),
136                to: self.address.clone(),
137                timestamp: Utc::now(),
138                crypto,
139                payload: signed,
140                self_encrypted: true,
141            })
142        } else {
143            None
144        };
145
146        progress.step("Sending message", "Sent message");
147        let request = sign_request(
148            &self.address.canonical(),
149            SendReq {
150                envelope,
151                self_envelope,
152            },
153            &agent_record,
154            &identity.signing_key(),
155        )
156        .unwrap_or_else(|e| {
157            progress.error(&format!("{:#?}", e));
158            progress.abort("Failed to sign send request");
159        });
160
161        let resp = reqwest::blocking::Client::new()
162            .post(
163                cache
164                    .agents
165                    .url(self.address.agent())
166                    .unwrap_or_else(|e| {
167                        progress.error(&format!("{:#?}", e));
168                        progress.abort("failed to fetch agent information");
169                    })
170                    .join("/send/user")
171                    .unwrap_or_else(|e| {
172                        progress.error(&format!("{:#?}", e));
173                        progress.abort("Failed to construct send user URL");
174                    }),
175            )
176            .json(&request)
177            .send()
178            .unwrap_or_else(|e| {
179                progress.error(&format!("{:#?}", e));
180                progress.abort("Failed to send message request");
181            });
182
183        if resp.status().as_u16() == 404 {
184            progress.abort("Inbox not found on the Agent");
185        }
186        if !resp.status().is_success() {
187            progress.abort(&format!(
188                "Failed to send message. HTTP {}",
189                resp.status().as_u16()
190            ));
191        }
192
193        progress.done();
194    }
195}