diff --git a/extensions/warp-ipfs/examples/identity-interface.rs b/extensions/warp-ipfs/examples/identity-interface.rs index 575102cb8..7d5c76390 100644 --- a/extensions/warp-ipfs/examples/identity-interface.rs +++ b/extensions/warp-ipfs/examples/identity-interface.rs @@ -765,15 +765,20 @@ async fn main() -> anyhow::Result<()> { } }; let mut table = Table::new(); - table.set_header(vec!["Username", "Public Key", "Status Message", "Banner", "Picture", "Platform", "Status"]); + table.set_header(vec!["Username", "Public Key", "Created", "Last Updated", "Status Message", "Banner", "Picture", "Platform", "Status"]); for identity in idents { let status = account.identity_status(&identity.did_key()).await.unwrap_or(IdentityStatus::Offline); let platform = account.identity_platform(&identity.did_key()).await.unwrap_or_default(); let profile_picture = account.identity_picture(&identity.did_key()).await.unwrap_or_default(); let profile_banner = account.identity_banner(&identity.did_key()).await.unwrap_or_default(); + let created = identity.created(); + let modified = identity.modified(); + table.add_row(vec![ identity.username(), identity.did_key().to_string(), + created.to_string(), + modified.to_string(), identity.status_message().unwrap_or_default(), (!profile_banner.is_empty()).to_string(), (!profile_picture.is_empty()).to_string(), diff --git a/extensions/warp-ipfs/src/store/document.rs b/extensions/warp-ipfs/src/store/document.rs index 32fee1a0c..19f98e14f 100644 --- a/extensions/warp-ipfs/src/store/document.rs +++ b/extensions/warp-ipfs/src/store/document.rs @@ -1,8 +1,9 @@ -pub mod identity; -pub mod utils; pub mod cache; +pub mod identity; pub mod root; +pub mod utils; +use chrono::{DateTime, Utc}; use futures::TryFutureExt; use ipfs::{Ipfs, IpfsPath}; use libipld::{ @@ -76,6 +77,8 @@ where #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] pub struct ExtractedRootDocument { pub identity: Identity, + pub created: DateTime, + pub modified: DateTime, pub friends: Vec, pub block_list: Vec, pub block_by_list: Vec, @@ -101,6 +104,13 @@ impl ExtractedRootDocument { pub struct RootDocument { /// Own Identity pub identity: Cid, + + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub modified: Option>, + /// array of friends (DID) #[serde(skip_serializing_if = "Option::is_none")] pub friends: Option, @@ -127,6 +137,11 @@ impl RootDocument { let mut root_document = self.clone(); //In case there is a signature already exist root_document.signature = None; + if root_document.created.is_none() { + root_document.created = Some(Utc::now()); + } + root_document.modified = Some(Utc::now()); + let bytes = serde_json::to_vec(&root_document)?; let signature = did.sign(&bytes); self.signature = Some(bs58::encode(signature).into_string()); @@ -159,7 +174,18 @@ impl RootDocument { pub async fn resolve( &self, ipfs: &Ipfs, - ) -> Result<(Identity, Vec, Vec, Vec, Vec), Error> { + ) -> Result< + ( + Identity, + Option>, + Option>, + Vec, + Vec, + Vec, + Vec, + ), + Error, + > { let document: IdentityDocument = self .identity .get_local_dag(ipfs) @@ -188,7 +214,15 @@ impl RootDocument { .await .unwrap_or_default(); - Ok((identity, friends, block_list, block_by_list, request)) + Ok(( + identity, + self.created, + self.modified, + friends, + block_list, + block_by_list, + request, + )) } pub async fn import(ipfs: &Ipfs, data: ExtractedRootDocument) -> Result { @@ -222,6 +256,8 @@ impl RootDocument { let mut root_document = RootDocument { identity, + created: Some(data.created), + modified: Some(data.modified), friends, blocks, block_by, @@ -235,10 +271,13 @@ impl RootDocument { } pub async fn export(&self, ipfs: &Ipfs) -> Result { - let (identity, friends, block_list, block_by_list, request) = self.resolve(ipfs).await?; + let (identity, created, modified, friends, block_list, block_by_list, request) = + self.resolve(ipfs).await?; let mut exported = ExtractedRootDocument { identity, + created: created.unwrap_or_default(), + modified: modified.unwrap_or_default(), friends, block_list, block_by_list, diff --git a/extensions/warp-ipfs/src/store/document/identity.rs b/extensions/warp-ipfs/src/store/document/identity.rs index 0ba819af7..445faf7dc 100644 --- a/extensions/warp-ipfs/src/store/document/identity.rs +++ b/extensions/warp-ipfs/src/store/document/identity.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use futures::{StreamExt, TryStreamExt}; use libipld::Cid; use rust_ipfs::{Ipfs, IpfsPath}; @@ -17,6 +18,12 @@ pub struct IdentityDocument { pub did: DID, + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub modified: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub status_message: Option, @@ -42,11 +49,16 @@ impl From for IdentityDocument { let did = identity.did_key(); let short_id = *identity.short_id(); let status_message = identity.status_message(); + let created = Some(identity.created()); + let modified = Some(identity.modified()); + IdentityDocument { username, short_id, did, status_message, + created, + modified, profile_picture: None, profile_banner: None, platform: None, @@ -62,6 +74,8 @@ impl From for IdentityDocument { username: document.username, did: document.did, short_id: document.short_id, + created: document.created, + modified: document.modified, status_message: document.status_message, profile_picture: document.profile_picture, profile_banner: document.profile_banner, @@ -78,6 +92,8 @@ impl From for shuttle::identity::document::IdentityDocument { username: document.username, did: document.did, short_id: document.short_id, + created: document.created, + modified: document.modified, status_message: document.status_message, profile_picture: document.profile_picture, profile_banner: document.profile_banner, @@ -90,11 +106,19 @@ impl From for shuttle::identity::document::IdentityDocument { impl From for Identity { fn from(document: IdentityDocument) -> Self { + Self::from(&document) + } +} + +impl From<&IdentityDocument> for Identity { + fn from(document: &IdentityDocument) -> Self { let mut identity = Identity::default(); - identity.set_did_key(document.did); + identity.set_did_key(document.did.clone()); identity.set_short_id(document.short_id); - identity.set_status_message(document.status_message); + identity.set_status_message(document.status_message.clone()); identity.set_username(&document.username); + identity.set_created(document.created.unwrap_or(Utc::now())); + identity.set_modified(document.modified.unwrap_or(Utc::now())); identity } } @@ -136,16 +160,15 @@ impl IdentityDocument { impl IdentityDocument { pub fn resolve(&self) -> Result { self.verify()?; - let mut identity = Identity::default(); - identity.set_username(&self.username); - identity.set_did_key(self.did.clone()); - identity.set_short_id(self.short_id); - identity.set_status_message(self.status_message.clone()); - Ok(identity) + Ok(self.into()) } pub fn sign(mut self, did: &DID) -> Result { self.signature = None; + if self.created.is_none() { + self.created = Some(Utc::now()); + } + self.modified = Some(Utc::now()); let bytes = serde_json::to_vec(&self)?; let signature = bs58::encode(did.sign(&bytes)).into_string(); self.signature = Some(signature); @@ -185,12 +208,12 @@ impl IdentityDocument { } if let Some(status) = &payload.status_message { - if status.len() > 256 { + if status.len() > 512 { return Err(Error::InvalidLength { context: "identity status message".into(), current: status.len(), minimum: None, - maximum: Some(256), + maximum: Some(512), }); } } diff --git a/extensions/warp-ipfs/src/store/identity.rs b/extensions/warp-ipfs/src/store/identity.rs index 544402509..e3225ffb2 100644 --- a/extensions/warp-ipfs/src/store/identity.rs +++ b/extensions/warp-ipfs/src/store/identity.rs @@ -6,6 +6,8 @@ use crate::{ config::{self, Discovery as DiscoveryConfig, DiscoveryType, UpdateEvents}, store::{did_to_libp2p_pub, discovery::Discovery, DidExt, PeerIdExt, PeerTopic}, }; +use chrono::Utc; + use futures::{ channel::oneshot::{self, Canceled}, stream::BoxStream, @@ -16,6 +18,7 @@ use ipfs::{ p2p::MultiaddrExt, Ipfs, IpfsPath, Keypair, }; + use libipld::Cid; use rust_ipfs as ipfs; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -1309,12 +1312,16 @@ impl IdentityStore { let fingerprint = public_key.fingerprint(); let bytes = fingerprint.as_bytes(); + let time = Utc::now(); + let identity = IdentityDocument { username, short_id: bytes[bytes.len() - SHORT_ID_SIZE..] .try_into() .map_err(anyhow::Error::from)?, did: public_key.into(), + created: Some(time), + modified: Some(time), status_message: None, profile_banner: None, profile_picture: None, diff --git a/tools/shuttle/src/identity/document.rs b/tools/shuttle/src/identity/document.rs index c1026c64b..5a7b376f1 100644 --- a/tools/shuttle/src/identity/document.rs +++ b/tools/shuttle/src/identity/document.rs @@ -1,10 +1,11 @@ +use chrono::{DateTime, Utc}; use libipld::Cid; use serde::{Deserialize, Serialize}; use warp::crypto::did_key::CoreSign; use warp::crypto::DID; use warp::multipass::identity::{IdentityStatus, Platform, SHORT_ID_SIZE}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Eq)] pub struct IdentityDocument { pub username: String, @@ -12,6 +13,12 @@ pub struct IdentityDocument { pub did: DID, + #[serde(skip_serializing_if = "Option::is_none")] + pub created: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub modified: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub status_message: Option, diff --git a/warp/src/multipass/identity.rs b/warp/src/multipass/identity.rs index 4a8917314..190e172e4 100644 --- a/warp/src/multipass/identity.rs +++ b/warp/src/multipass/identity.rs @@ -1,6 +1,7 @@ use std::fmt::Display; use crate::{crypto::DID, error::Error}; +use chrono::{DateTime, Utc}; use derive_more::Display; use serde::{Deserialize, Serialize}; use warp_derive::FFIFree; @@ -171,6 +172,12 @@ pub struct Identity { /// Public key for the identity did_key: DID, + /// Timestamp when the identity was created + created: DateTime, + + /// Timestamp when the identity was last modified or updated + modified: DateTime, + /// Status message status_message: Option, } @@ -191,6 +198,14 @@ impl Identity { pub fn set_status_message(&mut self, message: Option) { self.status_message = message } + + pub fn set_created(&mut self, time: DateTime) { + self.created = time; + } + + pub fn set_modified(&mut self, time: DateTime) { + self.modified = time; + } } impl Identity { @@ -209,6 +224,14 @@ impl Identity { pub fn status_message(&self) -> Option { self.status_message.clone() } + + pub fn created(&self) -> DateTime { + self.created + } + + pub fn modified(&self) -> DateTime { + self.modified + } } #[derive(Debug, Clone, FFIFree)]