|
1 | 1 | use crate::account_manager::helpers::account::AvailabilityFlags; |
2 | 2 | use crate::account_manager::AccountManager; |
3 | | -use crate::apis::com::atproto::server::get_keys_from_private_key_str; |
4 | 3 | use crate::apis::ApiError; |
5 | 4 | use crate::auth_verifier::AccessStandard; |
6 | 5 | use crate::config::ServerConfig; |
7 | 6 | use crate::plc::types::{OpOrTombstone, Operation}; |
8 | 7 | use crate::{plc, SharedIdResolver, SharedSequencer}; |
9 | 8 | use rocket::serde::json::Json; |
10 | 9 | use rocket::State; |
11 | | -use rsky_common::env::env_str; |
12 | 10 | use rsky_crypto::utils::encode_did_key; |
| 11 | +use rsky_lexicon::com::atproto::identity::SubmitPlcOperationRequest; |
| 12 | +use secp256k1::{Keypair, Secp256k1, SecretKey}; |
13 | 13 | use std::env; |
14 | 14 |
|
15 | | -async fn validate_submit_plc_operation_request( |
| 15 | +#[tracing::instrument(skip_all)] |
| 16 | +fn get_requester_did(auth: &AccessStandard) -> Result<String, ApiError> { |
| 17 | + match &auth.access.credentials { |
| 18 | + None => { |
| 19 | + tracing::error!("Failed to find access credentials"); |
| 20 | + Err(ApiError::RuntimeError) |
| 21 | + } |
| 22 | + Some(res) => match &res.did { |
| 23 | + None => { |
| 24 | + tracing::error!("Failed to find did"); |
| 25 | + Err(ApiError::RuntimeError) |
| 26 | + } |
| 27 | + Some(did) => Ok(did.clone()), |
| 28 | + }, |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +#[tracing::instrument(skip_all)] |
| 33 | +fn get_public_rotation_key() -> Result<String, ApiError> { |
| 34 | + let secp = Secp256k1::new(); |
| 35 | + let private_rotation_key = match env::var("PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX") { |
| 36 | + Ok(res) => res, |
| 37 | + Err(error) => { |
| 38 | + tracing::error!("Error geting rotation private key\n{error}"); |
| 39 | + return Err(ApiError::RuntimeError); |
| 40 | + } |
| 41 | + }; |
| 42 | + match hex::decode(private_rotation_key.as_bytes()) { |
| 43 | + Ok(bytes) => match SecretKey::from_slice(&bytes) { |
| 44 | + Ok(secret_key) => { |
| 45 | + let rotation_keypair = Keypair::from_secret_key(&secp, &secret_key); |
| 46 | + Ok(encode_did_key(&rotation_keypair.public_key())) |
| 47 | + } |
| 48 | + Err(error) => { |
| 49 | + tracing::error!("Error geting rotation secret key from bytes\n{error}"); |
| 50 | + Err(ApiError::RuntimeError) |
| 51 | + } |
| 52 | + }, |
| 53 | + Err(error) => { |
| 54 | + tracing::error!("Unable to hex decode rotation key\n{error}"); |
| 55 | + Err(ApiError::RuntimeError) |
| 56 | + } |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +#[tracing::instrument(skip_all)] |
| 61 | +fn get_public_signing_key() -> Result<String, ApiError> { |
| 62 | + let secp = Secp256k1::new(); |
| 63 | + let private_signing_key = match env::var("PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX") { |
| 64 | + Ok(res) => res, |
| 65 | + Err(error) => { |
| 66 | + tracing::error!("Error geting signing private key\n{error}"); |
| 67 | + return Err(ApiError::RuntimeError); |
| 68 | + } |
| 69 | + }; |
| 70 | + match hex::decode(private_signing_key.as_bytes()) { |
| 71 | + Ok(bytes) => match SecretKey::from_slice(&bytes) { |
| 72 | + Ok(secret_key) => { |
| 73 | + let signing_keypair = Keypair::from_secret_key(&secp, &secret_key); |
| 74 | + Ok(encode_did_key(&signing_keypair.public_key())) |
| 75 | + } |
| 76 | + Err(error) => { |
| 77 | + tracing::error!("Error geting signing secret key from bytes\n{error}"); |
| 78 | + Err(ApiError::RuntimeError) |
| 79 | + } |
| 80 | + }, |
| 81 | + Err(error) => { |
| 82 | + tracing::error!("Unable to hex decode signing key\n{error}"); |
| 83 | + Err(ApiError::RuntimeError) |
| 84 | + } |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +#[tracing::instrument(skip_all)] |
| 89 | +async fn validate_plc_request( |
16 | 90 | did: &str, |
17 | 91 | op: &Operation, |
18 | 92 | public_endpoint: &str, |
19 | 93 | ) -> Result<(), ApiError> { |
20 | | - let private_key = env::var("PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX").unwrap(); |
21 | | - let (_, public_key) = get_keys_from_private_key_str(private_key)?; |
22 | | - let plc_rotation_key = encode_did_key(&public_key); |
23 | | - if !op.rotation_keys.contains(&plc_rotation_key) { |
| 94 | + let public_rotation_key = get_public_signing_key()?; |
| 95 | + if !op.rotation_keys.contains(&public_rotation_key) { |
24 | 96 | return Err(ApiError::InvalidRequest( |
25 | 97 | "Rotation keys do not include server's rotation key".to_string(), |
26 | 98 | )); |
27 | 99 | } |
28 | 100 |
|
29 | | - let private_key = env::var("PDS_REPO_SIGNING_KEY_K256_PRIVATE_KEY_HEX").unwrap(); |
30 | | - let (_, public_key) = get_keys_from_private_key_str(private_key)?; |
31 | | - let signing_rotation_key = encode_did_key(&public_key); |
| 101 | + let public_signing_key = get_public_signing_key()?; |
32 | 102 | match op.verification_methods.get("atproto") { |
33 | 103 | None => { |
34 | 104 | return Err(ApiError::InvalidRequest( |
35 | 105 | "Incorrect signing key".to_string(), |
36 | 106 | )) |
37 | 107 | } |
38 | 108 | Some(res) => { |
39 | | - if res.clone() != signing_rotation_key { |
| 109 | + if res.clone() != public_signing_key { |
40 | 110 | return Err(ApiError::InvalidRequest( |
41 | 111 | "Incorrect signing key".to_string(), |
42 | 112 | )); |
@@ -102,46 +172,71 @@ async fn validate_submit_plc_operation_request( |
102 | 172 | Ok(()) |
103 | 173 | } |
104 | 174 |
|
| 175 | +#[tracing::instrument(skip_all)] |
| 176 | +async fn do_plc_operation(plc_url: &str, did: &str, op: Operation) -> Result<(), ApiError> { |
| 177 | + let plc_client = plc::Client::new(plc_url.to_string()); |
| 178 | + match plc_client |
| 179 | + .send_operation(&did.to_string(), &OpOrTombstone::Operation(op)) |
| 180 | + .await |
| 181 | + { |
| 182 | + Ok(_res) => { |
| 183 | + tracing::info!("Successfully sent PLC Update Operation"); |
| 184 | + Ok(()) |
| 185 | + } |
| 186 | + Err(error) => { |
| 187 | + tracing::error!("Failed to update did:plc\n{error}"); |
| 188 | + Err(ApiError::RuntimeError) |
| 189 | + } |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +#[tracing::instrument(skip_all)] |
| 194 | +fn validate_operation_body(request: SubmitPlcOperationRequest) -> Result<Operation, ApiError> { |
| 195 | + match serde_json::from_value::<Operation>(request.operation) { |
| 196 | + Ok(op) => { |
| 197 | + tracing::debug!("Sucessfully parsed operation body"); |
| 198 | + Ok(op) |
| 199 | + } |
| 200 | + Err(error) => { |
| 201 | + tracing::error!("Error parsing operation body\n{error}"); |
| 202 | + Err(ApiError::InvalidRequest("Invalid operation".to_string())) |
| 203 | + } |
| 204 | + } |
| 205 | +} |
| 206 | + |
105 | 207 | #[rocket::post( |
106 | 208 | "/xrpc/com.atproto.identity.submitPlcOperation", |
107 | 209 | format = "json", |
108 | 210 | data = "<body>" |
109 | 211 | )] |
110 | 212 | #[tracing::instrument(skip_all)] |
111 | 213 | pub async fn submit_plc_operation( |
112 | | - body: Json<Operation>, |
| 214 | + body: Json<SubmitPlcOperationRequest>, |
113 | 215 | auth: AccessStandard, |
114 | 216 | sequencer: &State<SharedSequencer>, |
115 | 217 | id_resolver: &State<SharedIdResolver>, |
116 | 218 | server_config: &State<ServerConfig>, |
117 | 219 | ) -> Result<(), ApiError> { |
118 | | - let did = auth.access.credentials.unwrap().did.unwrap(); |
119 | | - let op = body.into_inner(); |
120 | | - let public_endpoint = server_config.service.public_url.as_str(); |
| 220 | + let did = get_requester_did(&auth)?; |
121 | 221 |
|
122 | | - validate_submit_plc_operation_request(did.as_str(), &op, public_endpoint).await?; |
| 222 | + //Validate and transform request |
| 223 | + let op = validate_operation_body(body.into_inner())?; |
123 | 224 |
|
124 | | - let plc_url = env_str("PDS_DID_PLC_URL").unwrap_or("https://plc.directory".to_owned()); |
125 | | - let plc_client = plc::Client::new(plc_url); |
126 | | - match plc_client |
127 | | - .send_operation(&did, &OpOrTombstone::Operation(op)) |
128 | | - .await |
129 | | - { |
130 | | - Ok(_) => { |
131 | | - tracing::info!("Successfully sent PLC Update Operation"); |
132 | | - } |
133 | | - Err(_) => { |
134 | | - tracing::error!("Failed to update did:plc"); |
135 | | - return Err(ApiError::RuntimeError); |
136 | | - } |
137 | | - } |
138 | | - let mut sequence_lock = sequencer.sequencer.write().await; |
139 | | - sequence_lock |
140 | | - .sequence_identity_evt(did.clone(), None) |
141 | | - .await?; |
| 225 | + //Validate PLC Operation is valid |
| 226 | + validate_plc_request(did.as_str(), &op, server_config.service.public_url.as_str()).await?; |
| 227 | + |
| 228 | + //Send PLC Operation to PLC Service |
| 229 | + do_plc_operation(server_config.identity.plc_url.as_str(), did.as_str(), op).await?; |
| 230 | + |
| 231 | + //Update Sequencer |
| 232 | + let mut seq_lock = sequencer.sequencer.write().await; |
| 233 | + seq_lock.sequence_identity_evt(did.clone(), None).await?; |
| 234 | + |
| 235 | + //Refresh DID after PLC update |
142 | 236 | let mut id_lock = id_resolver.id_resolver.write().await; |
143 | 237 | if let Err(error) = id_lock.did.ensure_resolve(&did, None).await { |
144 | 238 | tracing::error!("Failed to fresh did after plc update\n{error}") |
145 | 239 | }; |
| 240 | + |
146 | 241 | Ok(()) |
147 | 242 | } |
0 commit comments