Use general Did type intsead of DidPkh in identity proofs
This commit is contained in:
parent
4e53a5c4e4
commit
dae6e9437b
12 changed files with 135 additions and 64 deletions
|
@ -11,7 +11,7 @@ use crate::ethereum::identity::{
|
|||
verify_eip191_identity_proof,
|
||||
};
|
||||
use crate::frontend::get_subscription_page_url;
|
||||
use crate::identity::did_pkh::DidPkh;
|
||||
use crate::identity::did::Did;
|
||||
use crate::models::profiles::types::{
|
||||
ExtraField,
|
||||
IdentityProof,
|
||||
|
@ -45,13 +45,14 @@ pub fn parse_identity_proof(
|
|||
if proof_type != ETHEREUM_EIP191_PROOF {
|
||||
return Err(ValidationError("unknown proof type"));
|
||||
};
|
||||
let did = attachment.name.parse::<DidPkh>()
|
||||
let did = attachment.name.parse::<Did>()
|
||||
.map_err(|_| ValidationError("invalid did"))?;
|
||||
let Did::Pkh(ref did_pkh) = did;
|
||||
let signature = attachment.signature_value.as_ref()
|
||||
.ok_or(ValidationError("missing signature"))?;
|
||||
verify_eip191_identity_proof(
|
||||
actor_id,
|
||||
&did,
|
||||
did_pkh,
|
||||
signature,
|
||||
).map_err(|_| ValidationError("invalid identity proof"))?;
|
||||
let proof = IdentityProof {
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::http_signatures::verify::{
|
|||
verify_http_signature,
|
||||
HttpSignatureVerificationError as HttpSignatureError,
|
||||
};
|
||||
use crate::identity::did::Did;
|
||||
use crate::json_signatures::verify::{
|
||||
get_json_signature,
|
||||
verify_jcs_rsa_signature,
|
||||
|
@ -136,8 +137,9 @@ pub async fn verify_signed_activity(
|
|||
verify_jcs_rsa_signature(&signature_data, &public_key)?;
|
||||
actor_profile
|
||||
},
|
||||
JsonSigner::DidPkh(ref signer) => {
|
||||
let mut profiles: Vec<_> = search_profiles_by_did_only(db_client, signer)
|
||||
JsonSigner::DidPkh(did_pkh) => {
|
||||
let did = Did::Pkh(did_pkh.clone());
|
||||
let mut profiles: Vec<_> = search_profiles_by_did_only(db_client, &did)
|
||||
.await?.into_iter()
|
||||
// Exclude local profiles
|
||||
.filter(|profile| !profile.is_local())
|
||||
|
@ -150,7 +152,7 @@ pub async fn verify_signed_activity(
|
|||
};
|
||||
if let Some(profile) = profiles.pop() {
|
||||
verify_jcs_eip191_signature(
|
||||
signer,
|
||||
&did_pkh,
|
||||
&signature_data.message,
|
||||
&signature_data.signature,
|
||||
)?;
|
||||
|
|
79
src/identity/did.rs
Normal file
79
src/identity/did.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
/// https://www.w3.org/TR/did-core/
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
use regex::Regex;
|
||||
use serde::{
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
de::Error as DeserializerError,
|
||||
};
|
||||
|
||||
use super::did_pkh::DidPkh;
|
||||
|
||||
const DID_RE: &str = r"did:(?P<method>\w+):.+";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Did {
|
||||
Pkh(DidPkh),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("DID parse error")]
|
||||
pub struct DidParseError;
|
||||
|
||||
impl FromStr for Did {
|
||||
type Err = DidParseError;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
let did_re = Regex::new(DID_RE).unwrap();
|
||||
let caps = did_re.captures(value).ok_or(DidParseError)?;
|
||||
let did = match &caps["method"] {
|
||||
"pkh" => {
|
||||
let did_pkh = DidPkh::from_str(value)?;
|
||||
Self::Pkh(did_pkh)
|
||||
},
|
||||
_ => return Err(DidParseError),
|
||||
};
|
||||
Ok(did)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Did {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let did_str = match self {
|
||||
Self::Pkh(did_pkh) => did_pkh.to_string(),
|
||||
};
|
||||
write!(formatter, "{}", did_str)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Did {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
let did_str: String = Deserialize::deserialize(deserializer)?;
|
||||
did_str.parse().map_err(DeserializerError::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Did {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
let did_str = self.to_string();
|
||||
serializer.serialize_str(&did_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_did_string_conversion() {
|
||||
let did_str = "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a";
|
||||
let did: Did = did_str.parse().unwrap();
|
||||
assert!(matches!(did, Did::Pkh(_)));
|
||||
assert_eq!(did.to_string(), did_str);
|
||||
}
|
||||
}
|
|
@ -3,13 +3,10 @@ use std::convert::TryInto;
|
|||
use std::str::FromStr;
|
||||
|
||||
use regex::Regex;
|
||||
use serde::{
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
de::Error as DeserializerError,
|
||||
};
|
||||
|
||||
use crate::utils::caip2::ChainId;
|
||||
use crate::utils::currencies::Currency;
|
||||
use super::did::DidParseError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct DidPkh {
|
||||
|
@ -43,10 +40,6 @@ impl ToString for DidPkh {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("DID parse error")]
|
||||
pub struct DidParseError;
|
||||
|
||||
// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md#syntax
|
||||
const DID_PKH_RE: &str = r"did:pkh:(?P<network>[-a-z0-9]{3,8}):(?P<chain>[-a-zA-Z0-9]{1,32}):(?P<address>[a-zA-Z0-9]{1,64})";
|
||||
|
||||
|
@ -67,30 +60,12 @@ impl FromStr for DidPkh {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for DidPkh {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
let did_str = self.to_string();
|
||||
serializer.serialize_str(&did_str)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DidPkh {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where D: Deserializer<'de>
|
||||
{
|
||||
let did_str: String = Deserialize::deserialize(deserializer)?;
|
||||
did_str.parse().map_err(DeserializerError::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_did_string_conversion() {
|
||||
fn test_did_pkh_string_conversion() {
|
||||
let address = "0xB9C5714089478a327F09197987f16f9E5d936E8a";
|
||||
let ethereum = Currency::Ethereum;
|
||||
let did = DidPkh::from_address(ðereum, address);
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod did;
|
||||
pub mod did_pkh;
|
||||
|
|
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::errors::ValidationError;
|
||||
use crate::identity::did::Did;
|
||||
use crate::mastodon_api::pagination::PageSize;
|
||||
use crate::mastodon_api::uploads::{UploadError, save_validated_b64_file};
|
||||
use crate::models::profiles::types::{
|
||||
|
@ -78,12 +79,13 @@ impl Account {
|
|||
|
||||
let mut identity_proofs = vec![];
|
||||
for proof in profile.identity_proofs.clone().into_inner() {
|
||||
let Did::Pkh(did_pkh) = proof.issuer;
|
||||
// Skip proof if it doesn't map to field name
|
||||
if let Some(currency) = proof.issuer.currency() {
|
||||
if let Some(currency) = did_pkh.currency() {
|
||||
let field_name = currency.field_name();
|
||||
let field = AccountField {
|
||||
name: field_name,
|
||||
value: proof.issuer.address,
|
||||
value: did_pkh.address,
|
||||
// Use current time because DID proofs are always valid
|
||||
verified_at: Some(Utc::now()),
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ use crate::ethereum::identity::{
|
|||
create_identity_claim,
|
||||
verify_eip191_identity_proof,
|
||||
};
|
||||
use crate::identity::did_pkh::DidPkh;
|
||||
use crate::identity::did::Did;
|
||||
use crate::json_signatures::{
|
||||
canonicalization::canonicalize_object,
|
||||
create::{add_integrity_proof, IntegrityProof},
|
||||
|
@ -249,7 +249,7 @@ async fn send_signed_update(
|
|||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let signer = data.signer.parse::<DidPkh>()
|
||||
let signer = data.signer.parse::<Did>()
|
||||
.map_err(|_| ValidationError("invalid DID"))?;
|
||||
if !current_user.profile.identity_proofs.any(&signer) {
|
||||
return Err(ValidationError("unknown signer").into());
|
||||
|
@ -261,6 +261,7 @@ async fn send_signed_update(
|
|||
).map_err(|_| HttpError::InternalError)?;
|
||||
let canonical_json = canonicalize_object(&activity)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
let Did::Pkh(signer) = signer;
|
||||
verify_jcs_eip191_signature(&signer, &canonical_json, &data.signature)
|
||||
.map_err(|_| ValidationError("invalid signature"))?;
|
||||
let proof = IntegrityProof::jcs_eip191(&signer, &data.signature);
|
||||
|
@ -290,7 +291,7 @@ async fn get_identity_claim(
|
|||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let actor_id = current_user.profile.actor_id(&config.instance_url());
|
||||
let did = query_params.did.parse::<DidPkh>()
|
||||
let Did::Pkh(did) = query_params.did.parse::<Did>()
|
||||
.map_err(|_| ValidationError("invalid DID"))?;
|
||||
let claim = create_identity_claim(&actor_id, &did)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
|
@ -308,9 +309,10 @@ async fn create_identity_proof(
|
|||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
let mut current_user = get_current_user(db_client, auth.token()).await?;
|
||||
let actor_id = current_user.profile.actor_id(&config.instance_url());
|
||||
let did = proof_data.did.parse::<DidPkh>()
|
||||
let did = proof_data.did.parse::<Did>()
|
||||
.map_err(|_| ValidationError("invalid DID"))?;
|
||||
if did.chain_id != ChainId::ethereum_mainnet() {
|
||||
let Did::Pkh(ref did_pkh) = did;
|
||||
if did_pkh.chain_id != ChainId::ethereum_mainnet() {
|
||||
// DID must point to Ethereum Mainnet because it is a valid
|
||||
// identifier on any Ethereum chain
|
||||
return Err(ValidationError("unsupported chain ID").into());
|
||||
|
@ -319,7 +321,7 @@ async fn create_identity_proof(
|
|||
current_user.public_wallet_address(&Currency::Ethereum);
|
||||
if let Some(address) = maybe_public_address {
|
||||
// Do not allow to add more than one address proof
|
||||
if did.address != address {
|
||||
if did_pkh.address != address {
|
||||
return Err(ValidationError("DID doesn't match current identity").into());
|
||||
};
|
||||
};
|
||||
|
@ -336,7 +338,7 @@ async fn create_identity_proof(
|
|||
};
|
||||
verify_eip191_identity_proof(
|
||||
&actor_id,
|
||||
&did,
|
||||
did_pkh,
|
||||
&proof_data.signature,
|
||||
)?;
|
||||
let proof = IdentityProof {
|
||||
|
@ -399,7 +401,7 @@ async fn search_by_did(
|
|||
query_params: web::Query<SearchDidQueryParams>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
let did: DidPkh = query_params.did.parse()
|
||||
let did: Did = query_params.did.parse()
|
||||
.map_err(|_| ValidationError("invalid DID"))?;
|
||||
let profiles = search_profiles_by_did(db_client, &did, false).await?;
|
||||
let accounts: Vec<Account> = profiles.into_iter()
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::activitypub::fetcher::helpers::{
|
|||
};
|
||||
use crate::config::Config;
|
||||
use crate::errors::{ValidationError, HttpError};
|
||||
use crate::identity::did_pkh::DidPkh;
|
||||
use crate::identity::did::Did;
|
||||
use crate::mastodon_api::accounts::types::Account;
|
||||
use crate::mastodon_api::statuses::helpers::build_status_list;
|
||||
use crate::mastodon_api::statuses::types::Tag;
|
||||
|
@ -33,7 +33,7 @@ enum SearchQuery {
|
|||
TagQuery(String),
|
||||
Url(String),
|
||||
WalletAddress(String),
|
||||
Did(DidPkh),
|
||||
Did(Did),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ fn parse_tag_query(query: &str) -> Result<String, ValidationError> {
|
|||
fn parse_search_query(search_query: &str) -> SearchQuery {
|
||||
let search_query = search_query.trim();
|
||||
// DID is a valid URI so it should be tried before Url::parse
|
||||
if let Ok(did) = DidPkh::from_str(search_query) {
|
||||
if let Ok(did) = Did::from_str(search_query) {
|
||||
return SearchQuery::Did(did);
|
||||
};
|
||||
if Url::parse(search_query).is_ok() {
|
||||
|
|
|
@ -4,7 +4,7 @@ use uuid::Uuid;
|
|||
use crate::database::catch_unique_violation;
|
||||
use crate::database::query_macro::query;
|
||||
use crate::errors::DatabaseError;
|
||||
use crate::identity::did_pkh::DidPkh;
|
||||
use crate::identity::{did::Did, did_pkh::DidPkh};
|
||||
use crate::models::cleanup::{
|
||||
find_orphaned_files,
|
||||
find_orphaned_ipfs_objects,
|
||||
|
@ -402,7 +402,7 @@ pub async fn search_profiles(
|
|||
|
||||
pub async fn search_profiles_by_did_only(
|
||||
db_client: &impl GenericClient,
|
||||
did: &DidPkh,
|
||||
did: &Did,
|
||||
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||
let rows = db_client.query(
|
||||
"
|
||||
|
@ -425,12 +425,17 @@ pub async fn search_profiles_by_did_only(
|
|||
|
||||
pub async fn search_profiles_by_did(
|
||||
db_client: &impl GenericClient,
|
||||
did: &DidPkh,
|
||||
did: &Did,
|
||||
prefer_verified: bool,
|
||||
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||
let did_str = did.to_string();
|
||||
let verified = search_profiles_by_did_only(db_client, did).await?;
|
||||
let unverified = if let Some(currency) = did.currency() {
|
||||
let maybe_currency_address = match did {
|
||||
Did::Pkh(did_pkh) => {
|
||||
did_pkh.currency()
|
||||
.map(|currency| (currency, did_pkh.address.clone()))
|
||||
},
|
||||
};
|
||||
let unverified = if let Some((currency, address)) = maybe_currency_address {
|
||||
// If currency is Ethereum,
|
||||
// search over extra fields must be case insensitive.
|
||||
let value_op = match currency {
|
||||
|
@ -457,9 +462,8 @@ pub async fn search_profiles_by_did(
|
|||
let field_name = currency.field_name();
|
||||
let query = query!(
|
||||
&statement,
|
||||
did=did_str,
|
||||
field_name=field_name,
|
||||
field_value=did.address,
|
||||
field_value=address,
|
||||
)?;
|
||||
let rows = db_client.query(query.sql(), query.parameters()).await?;
|
||||
let unverified = rows.iter()
|
||||
|
@ -487,7 +491,8 @@ pub async fn search_profiles_by_wallet_address(
|
|||
wallet_address: &str,
|
||||
prefer_verified: bool,
|
||||
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||
let did = DidPkh::from_address(currency, wallet_address);
|
||||
let did_pkh = DidPkh::from_address(currency, wallet_address);
|
||||
let did = Did::Pkh(did_pkh);
|
||||
search_profiles_by_did(db_client, &did, prefer_verified).await
|
||||
}
|
||||
|
||||
|
@ -723,7 +728,7 @@ mod tests {
|
|||
async fn test_search_profiles_by_wallet_address_identity_proof() {
|
||||
let db_client = &mut create_test_database().await;
|
||||
let identity_proof = IdentityProof {
|
||||
issuer: DidPkh::from_address(ÐEREUM, "0x1234abcd"),
|
||||
issuer: Did::Pkh(DidPkh::from_address(ÐEREUM, "0x1234abcd")),
|
||||
proof_type: "ethereum".to_string(),
|
||||
value: "13590013185bdea963".to_string(),
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ use crate::activitypub::actors::types::{Actor, ActorAddress};
|
|||
use crate::activitypub::identifiers::local_actor_id;
|
||||
use crate::database::json_macro::{json_from_sql, json_to_sql};
|
||||
use crate::errors::{ConversionError, ValidationError};
|
||||
use crate::identity::did_pkh::DidPkh;
|
||||
use crate::identity::did::Did;
|
||||
use crate::utils::caip2::ChainId;
|
||||
use super::validators::{
|
||||
validate_username,
|
||||
|
@ -25,7 +25,7 @@ use super::validators::{
|
|||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct IdentityProof {
|
||||
pub issuer: DidPkh,
|
||||
pub issuer: Did,
|
||||
pub proof_type: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ impl IdentityProofs {
|
|||
|
||||
/// Returns true if identity proof list contains at least one proof
|
||||
/// created by a given DID.
|
||||
pub fn any(&self, issuer: &DidPkh) -> bool {
|
||||
pub fn any(&self, issuer: &Did) -> bool {
|
||||
let Self(identity_proofs) = self;
|
||||
identity_proofs.iter().any(|proof| proof.issuer == *issuer)
|
||||
}
|
||||
|
@ -430,7 +430,8 @@ mod tests {
|
|||
fn test_identity_proof_serialization() {
|
||||
let json_data = r#"{"issuer":"did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a","proof_type":"ethereum-eip191-00","value":"dbfe"}"#;
|
||||
let proof: IdentityProof = serde_json::from_str(json_data).unwrap();
|
||||
assert_eq!(proof.issuer.address, "0xb9c5714089478a327f09197987f16f9e5d936e8a");
|
||||
let Did::Pkh(ref did_pkh) = proof.issuer;
|
||||
assert_eq!(did_pkh.address, "0xb9c5714089478a327f09197987f16f9e5d936e8a");
|
||||
let serialized = serde_json::to_string(&proof).unwrap();
|
||||
assert_eq!(serialized, json_data);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use uuid::Uuid;
|
|||
|
||||
use crate::database::catch_unique_violation;
|
||||
use crate::errors::DatabaseError;
|
||||
use crate::identity::did_pkh::DidPkh;
|
||||
use crate::identity::{did::Did, did_pkh::DidPkh};
|
||||
use crate::models::profiles::queries::create_profile;
|
||||
use crate::models::profiles::types::{DbActorProfile, ProfileCreateData};
|
||||
use crate::utils::currencies::Currency;
|
||||
|
@ -184,7 +184,7 @@ pub async fn get_user_by_login_address(
|
|||
|
||||
pub async fn get_user_by_did(
|
||||
db_client: &impl GenericClient,
|
||||
did: &DidPkh,
|
||||
did: &Did,
|
||||
) -> Result<User, DatabaseError> {
|
||||
// DIDs must be locally unique
|
||||
let maybe_row = db_client.query_opt(
|
||||
|
@ -212,7 +212,8 @@ pub async fn get_user_by_public_wallet_address(
|
|||
currency: &Currency,
|
||||
wallet_address: &str,
|
||||
) -> Result<User, DatabaseError> {
|
||||
let did = DidPkh::from_address(currency, wallet_address);
|
||||
let did_pkh = DidPkh::from_address(currency, wallet_address);
|
||||
let did = Did::Pkh(did_pkh);
|
||||
get_user_by_did(db_client, &did).await
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use regex::Regex;
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::errors::ValidationError;
|
||||
use crate::identity::did::Did;
|
||||
use crate::models::profiles::types::DbActorProfile;
|
||||
use crate::utils::currencies::Currency;
|
||||
|
||||
|
@ -48,11 +49,12 @@ impl User {
|
|||
/// Returns wallet address if it is verified
|
||||
pub fn public_wallet_address(&self, currency: &Currency) -> Option<String> {
|
||||
for proof in self.profile.identity_proofs.clone().into_inner() {
|
||||
let Did::Pkh(did_pkh) = proof.issuer;
|
||||
// Return the first matching address, because only
|
||||
// one proof per currency is allowed.
|
||||
if let Some(ref address_currency) = proof.issuer.currency() {
|
||||
if let Some(ref address_currency) = did_pkh.currency() {
|
||||
if address_currency == currency {
|
||||
return Some(proof.issuer.address);
|
||||
return Some(did_pkh.address);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue