relay_actions/actions/
claim.rs1use 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}