(&self)
| 111 | |
| 112 | impl Register { |
| 113 | async fn execute(&self) -> CliResult<()> { |
| 114 | // 1. Open the identity store. |
| 115 | let store = IdentityStore::open_default().map_err(|e| { |
| 116 | CliError::Internal(anyhow::anyhow!("Failed to open identity store: {e}")) |
| 117 | })?; |
| 118 | |
| 119 | // 2. Load the identity. |
| 120 | let identity = if let Some(name) = &self.identity { |
| 121 | store |
| 122 | .load_by_name(name) |
| 123 | .map_err(|_| CliError::IdentityNotFound(name.clone()))? |
| 124 | } else { |
| 125 | store |
| 126 | .get_default() |
| 127 | .map_err(|e| { |
| 128 | CliError::Internal(anyhow::anyhow!("Failed to load default identity: {e}")) |
| 129 | })? |
| 130 | .ok_or_else(|| { |
| 131 | CliError::Internal(anyhow::anyhow!( |
| 132 | "No default identity set. Create one first:\n \ |
| 133 | atomic identity new <name> --email <email> --set-default" |
| 134 | )) |
| 135 | })? |
| 136 | }; |
| 137 | |
| 138 | // 3. Load the keypair (needs the secret key for signing). |
| 139 | let keypair = store.load_keypair(&identity.id, None).map_err(|e| { |
| 140 | CliError::Internal(anyhow::anyhow!( |
| 141 | "Failed to load keypair for '{}': {e}", |
| 142 | identity.name |
| 143 | )) |
| 144 | })?; |
| 145 | |
| 146 | // 4. Build the registration payload. |
| 147 | let username = &identity.name; |
| 148 | let public_key_b32 = identity.public_key_base32(); |
| 149 | let timestamp = Utc::now(); |
| 150 | let timestamp_str = timestamp.to_rfc3339_opts(chrono::SecondsFormat::Secs, true); |
| 151 | |
| 152 | // 5. Build and sign the canonical payload. |
| 153 | let payload = format!("{SIGNING_DOMAIN}\n{username}\n{public_key_b32}\n{timestamp_str}"); |
| 154 | let signature_bytes = keypair.sign(payload.as_bytes()); |
| 155 | let signature_b32 = BASE32_NOPAD.encode(&signature_bytes); |
| 156 | |
| 157 | // 6. Construct the JSON request body. |
| 158 | let body = serde_json::json!({ |
| 159 | "username": username, |
| 160 | "email": identity.email, |
| 161 | "public_key": public_key_b32, |
| 162 | "timestamp": timestamp_str, |
| 163 | "signature": signature_b32, |
| 164 | }); |
| 165 | |
| 166 | // 7. POST to the server. |
| 167 | let url = format!("{}/register", self.server_url.trim_end_matches('/')); |
| 168 | |
| 169 | let client = reqwest::Client::new(); |
| 170 | let response = client |
no test coverage detected