Skip to main content

relay_actions/actions/
claim.rs

1use serde::Serialize;
2
3use crate::{
4    actions::Action,
5    cache::CacheManager,
6    sinks::progress::ProgressSink,
7    storage::{Identity, Storage},
8};
9use relay_lib::{
10    core::chrono::{DateTime, Utc},
11    crypto::{PublicKey, SigningKey, StaticSecret, hex},
12    prelude::{Address, KeyRecord},
13};
14
15#[derive(Serialize)]
16struct ClaimUserReq {
17    pub address: String,
18    #[serde(with = "hex::serde")]
19    pub ed25519: [u8; 32],
20    #[serde(with = "hex::serde")]
21    pub x25519: [u8; 32],
22    pub expires_at: Option<DateTime<Utc>>,
23    pub invitation: Option<String>,
24}
25
26pub struct Claim {
27    pub address: Address,
28    pub replace: bool,
29    pub invitation: Option<String>,
30}
31
32impl Action for Claim {
33    type Output = Address;
34
35    fn execute(
36        &self,
37        storage: &mut Storage,
38        cache: &mut CacheManager,
39        progress: &mut dyn ProgressSink,
40    ) -> Self::Output {
41        if self.address.inbox().is_some() {
42            progress.error("Cannot claim identity with an inbox");
43            progress.warn("Please provide an address without an inbox to claim an identity, or use `claim-inbox` to claim an inbox.");
44            progress.abort("Claim command only works for addresses without inboxes");
45        }
46        let mut address = self.address.clone();
47        address.inbox = None;
48
49        progress.step("Checking existing identity", "Checked existing identity");
50        if storage.root.get_identity(&address).is_some() {
51            if !self.replace {
52                progress.error(&format!(
53                    "Identity for address `{}` already exists locally.",
54                    address,
55                ));
56                progress.arrow("Use --replace to overwrite.");
57                progress.warn("THIS WILL DELETE THE EXISTING IDENTITY AND ITS PRIVATE KEYS!");
58                progress.warn("THIS CAN NOT BE UNDONE!");
59                progress.abort("Identity already exists locally");
60            } else {
61                progress.warn(&format!(
62                    "Replacing existing identity for address `{}`",
63                    address,
64                ));
65            }
66        }
67
68        progress.step("Generating new identity", "Generated new identity");
69        let identity = Self::generate_identity(&address);
70
71        progress.step("Requesting identity claim", "Identity claim requested");
72        let resp = reqwest::blocking::Client::new()
73            .post(
74                cache
75                    .agents
76                    .url(address.agent())
77                    .unwrap_or_else(|e| {
78                        progress.error(&format!("{:#?}", e));
79                        progress.abort("Failed to fetch Agent information");
80                    })
81                    .join("/user/claim/user")
82                    .unwrap_or_else(|e| {
83                        progress.error(&format!("{:#?}", e));
84                        progress.abort("Failed to construct identity claim URL");
85                    }),
86            )
87            .json(&ClaimUserReq {
88                address: address.canonical(),
89                ed25519: identity.pub_record.ed25519,
90                x25519: identity.pub_record.x25519,
91                expires_at: identity.pub_record.expires_at,
92                invitation: self.invitation.clone(),
93            })
94            .send()
95            .unwrap_or_else(|e| {
96                progress.error(&format!("{:#?}", e));
97                progress.abort("Failed to send identity claim request");
98            });
99
100        if resp.status().as_u16() == 409 {
101            progress.abort("Identity already claimed on the Agent");
102        }
103        if !resp.status().is_success() {
104            progress.abort(&format!(
105                "Failed to claim identity. HTTP {}",
106                resp.status().as_u16()
107            ));
108        }
109
110        progress.step("Storing identity locally", "Identity stored locally");
111        storage.root.identities.insert(address.clone(), identity);
112
113        progress.done();
114        address
115    }
116}
117
118impl Claim {
119    fn generate_identity(address: &Address) -> Identity {
120        let mut rng = relay_lib::crypto::OsRng;
121
122        let signing_key = SigningKey::generate(&mut rng);
123        let static_secret = StaticSecret::random_from_rng(rng);
124        let public_key = PublicKey::from(&static_secret);
125
126        let pub_record = KeyRecord {
127            id: address.canonical().to_string(),
128            version: 1,
129            ed25519: signing_key.verifying_key().to_bytes(),
130            x25519: public_key.to_bytes(),
131            created_at: Utc::now(),
132            expires_at: None,
133        };
134
135        Identity {
136            pub_record,
137            inboxes: vec![],
138            signing_key: signing_key.to_bytes(),
139            static_secret: static_secret.to_bytes(),
140        }
141    }
142}