diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f261aa0..2787dce4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# 0.5.0-1 (Hotfix) + +* Fix validation for deleting the last signatory of a company +* Fix plain text identity chain bug when accepting/rejecting an invite +* Fix company logo and file upload to use company key instead of personal key +* Fix switching to personal identity when deleting oneself from company + # 0.5.0 * Stabilise Identity Proof Implementation diff --git a/Cargo.toml b/Cargo.toml index 161dcbca..0776041c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.5.0" +version = "0.5.0-1" edition = "2024" license = "MIT" diff --git a/crates/bcr-ebill-api/src/service/company_service.rs b/crates/bcr-ebill-api/src/service/company_service.rs index 1d38245f..500be44c 100644 --- a/crates/bcr-ebill-api/src/service/company_service.rs +++ b/crates/bcr-ebill-api/src/service/company_service.rs @@ -12,6 +12,7 @@ use bcr_ebill_core::application::company::{ Company, CompanySignatory, CompanySignatoryStatus, CompanyStatus, LocalSignatoryOverrideStatus, }; use bcr_ebill_core::application::contact::Contact; +use bcr_ebill_core::application::identity::ActiveIdentityState; use bcr_ebill_core::application::{ServiceTraitBounds, ValidationError}; use bcr_ebill_core::protocol::Identification; use bcr_ebill_core::protocol::Name; @@ -593,7 +594,7 @@ impl CompanyServiceApi for CompanyService { .process_upload_file( &proof_of_registration_file_upload_id, &id, - &full_identity.key_pair.pub_key(), + &company_keys.pub_key(), nostr_relay, UploadFileType::Document, ) @@ -603,7 +604,7 @@ impl CompanyServiceApi for CompanyService { .process_upload_file( &logo_file_upload_id, &id, - &full_identity.key_pair.pub_key(), + &company_keys.pub_key(), nostr_relay, UploadFileType::Picture, ) @@ -859,7 +860,7 @@ impl CompanyServiceApi for CompanyService { self.process_upload_file( &logo_file_upload_id, id, - &full_identity.key_pair.pub_key(), + &company_keys.pub_key(), nostr_relay, UploadFileType::Picture, ) @@ -876,7 +877,7 @@ impl CompanyServiceApi for CompanyService { self.process_upload_file( &proof_of_registration_file_upload_id, id, - &full_identity.key_pair.pub_key(), + &company_keys.pub_key(), nostr_relay, UploadFileType::Document, ) @@ -1106,7 +1107,19 @@ impl CompanyServiceApi for CompanyService { )); } let company_keys = self.store.get_key_pair(id).await?; - if company.signatories.len() == 1 { + // Only count fully accepted signatories + if company + .signatories + .iter() + .filter(|s| { + matches!( + s.status, + CompanySignatoryStatus::InviteAcceptedIdentityProven { .. } + ) + }) + .count() + == 1 + { return Err(super::Error::Validation( ProtocolValidationError::CantRemoveLastSignatory.into(), )); @@ -1186,6 +1199,25 @@ impl CompanyServiceApi for CompanyService { if let Err(e) = self.company_blockchain_store.remove(id).await { error!("Could not delete local company chain for {id}: {e}"); } + // If the current active identity is the company we're removed from - set to personal identity + if let Ok(Some(active_node_id)) = self + .identity_store + .get_current_identity() + .await + .map(|i| i.company) + && &active_node_id == id + && let Err(e) = self + .identity_store + .set_current_identity(&ActiveIdentityState { + personal: full_identity.identity.node_id, + company: None, + }) + .await + { + error!( + "Couldn't set active identity to personal after removing self from company: {e}" + ); + } } debug!( "removed signatory {} to company with id: {id}", @@ -3287,6 +3319,12 @@ pub mod tests { key_pair: keys_clone_clone.clone(), }) }); + identity_store.expect_get_current_identity().returning(|| { + Ok(ActiveIdentityState { + personal: node_id_test(), + company: None, + }) + }); storage.expect_update().returning(|_, _| Ok(())); storage.expect_remove().returning(|_| Ok(())); identity_chain_store diff --git a/crates/bcr-ebill-api/src/service/transport_service/mod.rs b/crates/bcr-ebill-api/src/service/transport_service/mod.rs index 06a68de6..a7625a82 100644 --- a/crates/bcr-ebill-api/src/service/transport_service/mod.rs +++ b/crates/bcr-ebill-api/src/service/transport_service/mod.rs @@ -12,7 +12,6 @@ pub mod restore; mod transport; pub mod transport_client; -use log::error; use serde::{Deserialize, Serialize}; use thiserror::Error; diff --git a/crates/bcr-ebill-core/src/protocol/blockchain/identity/mod.rs b/crates/bcr-ebill-core/src/protocol/blockchain/identity/mod.rs index 45cf95c4..34a3d1de 100644 --- a/crates/bcr-ebill-core/src/protocol/blockchain/identity/mod.rs +++ b/crates/bcr-ebill-core/src/protocol/blockchain/identity/mod.rs @@ -91,12 +91,12 @@ impl IdentityBlockPlaintextWrapper { IdentityOpCode::InviteSignatory => { borsh_to_json_value::(&self.plaintext_data_bytes)? } - IdentityOpCode::AcceptSignatoryInvite => { - borsh_to_json_value::(&self.plaintext_data_bytes)? - } - IdentityOpCode::RejectSignatoryInvite => { - borsh_to_json_value::(&self.plaintext_data_bytes)? - } + IdentityOpCode::AcceptSignatoryInvite => borsh_to_json_value::< + IdentityAcceptSignatoryInviteBlockData, + >(&self.plaintext_data_bytes)?, + IdentityOpCode::RejectSignatoryInvite => borsh_to_json_value::< + IdentityRejectSignatoryInviteBlockData, + >(&self.plaintext_data_bytes)?, IdentityOpCode::RemoveSignatory => { borsh_to_json_value::(&self.plaintext_data_bytes)? } diff --git a/crates/bcr-ebill-core/src/protocol/blockchain/mod.rs b/crates/bcr-ebill-core/src/protocol/blockchain/mod.rs index 7b7415e3..a2e52408 100644 --- a/crates/bcr-ebill-core/src/protocol/blockchain/mod.rs +++ b/crates/bcr-ebill-core/src/protocol/blockchain/mod.rs @@ -268,8 +268,7 @@ pub trait Blockchain { ) -> Option<&Self::Block> { self.blocks() .iter() - .filter(|block| block.op_code() == &op_code) - .next_back() + .rfind(|block| block.op_code() == &op_code) } /// Checks if there is any block with a given operation code in the current blocks list. diff --git a/crates/bcr-ebill-transport/src/handler/company_chain_event_processor.rs b/crates/bcr-ebill-transport/src/handler/company_chain_event_processor.rs index 3a44f583..983afe6a 100644 --- a/crates/bcr-ebill-transport/src/handler/company_chain_event_processor.rs +++ b/crates/bcr-ebill-transport/src/handler/company_chain_event_processor.rs @@ -11,6 +11,7 @@ use bcr_ebill_api::service::transport_service::transport_client::TransportClient use bcr_ebill_core::{ application::{ company::CompanyStatus, + identity::ActiveIdentityState, notification::{Notification, NotificationType}, }, protocol::{ @@ -410,13 +411,39 @@ impl CompanyChainEventProcessor { .await .map_err(|e| Error::Persistence(e.to_string()))?; } - update @ CompanyBlockPayload::RemoveSignatory(_) => { + CompanyBlockPayload::RemoveSignatory(payload) => { + let removee = payload.removee.clone(); info!("Removing signatory from company {company_id}"); - company.apply_block_data(&update, identity_node_id, block.timestamp()); + company.apply_block_data( + &CompanyBlockPayload::RemoveSignatory(payload), + identity_node_id, + block.timestamp(), + ); self.company_store .update(company_id, company) .await .map_err(|e| Error::Persistence(e.to_string()))?; + + // if we're being removed and current identity is that company - change it + if &removee == identity_node_id + && let Ok(Some(active_node_id)) = self + .identity_store + .get_current_identity() + .await + .map(|i| i.company) + && &active_node_id == company_id + && let Err(e) = self + .identity_store + .set_current_identity(&ActiveIdentityState { + personal: identity_node_id.to_owned(), + company: None, + }) + .await + { + error!( + "Couldn't set active identity to personal after removing self from company: {e}" + ); + } } CompanyBlockPayload::SignBill(payload) => { if let Some(bill_key) = payload.bill_key diff --git a/crates/bcr-ebill-wasm/index.html b/crates/bcr-ebill-wasm/index.html index 0f694b0f..8f369dc8 100644 --- a/crates/bcr-ebill-wasm/index.html +++ b/crates/bcr-ebill-wasm/index.html @@ -103,6 +103,7 @@

Company Testing

+

Company Identity Proof

Company Signatory Email: diff --git a/crates/bcr-ebill-wasm/main.js b/crates/bcr-ebill-wasm/main.js index 4bf60098..f040a9d3 100644 --- a/crates/bcr-ebill-wasm/main.js +++ b/crates/bcr-ebill-wasm/main.js @@ -94,6 +94,7 @@ document.getElementById("get_company_invites").addEventListener("click", getComp document.getElementById("company_accept_invite").addEventListener("click", acceptCompanyInvite); document.getElementById("company_reject_invite").addEventListener("click", rejectCompanyInvite); document.getElementById("locally_hide_signatory").addEventListener("click", locallyHideRemovedSignatory); +document.getElementById("get_proof_file").addEventListener("click", fetchCompanyFile); // restore account, backup seed phrase document.getElementById("get_seed_phrase").addEventListener("click", getSeedPhrase); @@ -274,6 +275,7 @@ async function createCompany() { address: "street 1", }, creator_email: company_email, + proof_of_registration_file_upload_id: document.getElementById("file_upload_id").value || undefined, })); console.log("company: ", company); } @@ -742,6 +744,15 @@ async function syncCompanyChain() { await measured(); } +async function fetchCompanyFile() { + let node_id = document.getElementById("company_id").value; + let detail = success_or_fail(await window.companyApi.detail(node_id)); + let file_name = detail.proof_of_registration_file.name; + let file = success_or_fail(await window.companyApi.file_base64(node_id, file_name)); + + document.getElementById("bill_attached_file").src = `data:${file.content_type};base64,${file.data}`; +} + async function companyDetail() { let node_id = document.getElementById("company_id").value; console.log("companyDetail", node_id); diff --git a/crates/bcr-ebill-wasm/src/api/company.rs b/crates/bcr-ebill-wasm/src/api/company.rs index 0d8cecad..f5d1c63d 100644 --- a/crates/bcr-ebill-wasm/src/api/company.rs +++ b/crates/bcr-ebill-wasm/src/api/company.rs @@ -38,16 +38,11 @@ use crate::{ async fn get_file(id: &str, file_name: &Name) -> Result<(Vec, String)> { let parsed_id = NodeId::from_str(id).map_err(ProtocolValidationError::from)?; - let company = get_ctx() + let (company, keys) = get_ctx() .company_service - .get_company_by_id(&parsed_id) + .get_company_and_keys_by_id(&parsed_id) .await?; // check if company exists - let private_key = get_ctx() - .identity_service - .get_full_identity() - .await? - .key_pair - .get_private_key(); + let private_key = keys.get_private_key(); let file_bytes = get_ctx() .company_service