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
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}