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