Add views for signing Update(Person) activity

This commit is contained in:
silverpill 2022-10-29 20:19:39 +00:00
parent dec9b1f3a4
commit 1ec8cb4ddd
6 changed files with 137 additions and 8 deletions

View file

@ -98,13 +98,61 @@ paths:
$ref: '#/components/schemas/Account'
400:
description: Invalid user data
/api/v1/accounts/signed_update:
get:
summary: Build Update(Person) activity for signing (experimental).
responses:
200:
description: Successful operation
content:
application/json:
schema:
type: object
properties:
internal_activity_id:
description: Internal activity ID.
type: string
format: uuid
activity:
description: Canonical representation of activity.
type: string
example: '{"type":"Update"}'
post:
summary: Send signed activity (experimental).
requestBody:
content:
application/json:
schema:
type: object
properties:
internal_activity_id:
description: Internal activity ID.
type: string
format: uuid
signer:
description: Signer's identifier (DID)
type: string
example: 'did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a'
signature:
description: Signature value.
type: string
example: '3312dacd...'
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
400:
description: Invalid signature data.
/api/v1/accounts/identity_proof:
get:
summary: Get unsigned data for identity proof.
parameters:
- name: did
in: query
description: Identifier (DID)
description: Identifier (DID).
required: true
schema:
type: string
@ -132,7 +180,7 @@ paths:
type: object
properties:
did:
description: Identifier (DID)
description: Signer (DID).
type: string
example: 'did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a'
signature:

View file

@ -15,13 +15,16 @@ use crate::models::relationships::queries::get_followers;
use crate::models::users::types::User;
use crate::utils::id::new_uuid;
fn build_update_person(
pub fn build_update_person(
instance_url: &str,
user: &User,
maybe_internal_activity_id: Option<Uuid>,
) -> Result<Activity, ActorKeyError> {
let actor = get_local_actor(user, instance_url)?;
// Update(Person) is idempotent so its ID can be random
let activity_id = local_object_id(instance_url, &new_uuid());
let internal_activity_id =
maybe_internal_activity_id.unwrap_or(new_uuid());
let activity_id = local_object_id(instance_url, &internal_activity_id);
let activity = create_activity(
instance_url,
&user.profile.username,
@ -56,7 +59,7 @@ pub async fn prepare_update_person(
instance: &Instance,
user: &User,
) -> Result<OutgoingActivity<Activity>, DatabaseError> {
let activity = build_update_person(&instance.url(), user)
let activity = build_update_person(&instance.url(), user, None)
.map_err(|_| ConversionError)?;
let recipients = get_update_person_recipients(db_client, &user.id).await?;
Ok(OutgoingActivity {

View file

@ -1,3 +1,3 @@
mod canonicalization;
pub mod canonicalization;
pub mod create;
pub mod verify;

View file

@ -254,6 +254,19 @@ impl AccountUpdateData {
}
}
#[derive(Serialize)]
pub struct UnsignedUpdate {
pub internal_activity_id: Uuid,
pub activity: String, // canonical representation
}
#[derive(Deserialize)]
pub struct SignedUpdate {
pub internal_activity_id: Uuid,
pub signer: String,
pub signature: String,
}
#[derive(Deserialize)]
pub struct IdentityClaimQueryParams {
pub did: String,

View file

@ -8,7 +8,10 @@ use uuid::Uuid;
use crate::activitypub::builders::{
follow::prepare_follow,
undo_follow::prepare_undo_follow,
update_person::prepare_update_person,
update_person::{
build_update_person,
prepare_update_person,
},
};
use crate::config::Config;
use crate::database::{Pool, get_database_client};
@ -22,6 +25,7 @@ use crate::ethereum::identity::{
create_identity_claim,
verify_identity_proof,
};
use crate::json_signatures::canonicalization::canonicalize_object;
use crate::mastodon_api::oauth::auth::get_current_user;
use crate::mastodon_api::pagination::get_paginated_response;
use crate::mastodon_api::search::helpers::search_profiles_only;
@ -62,11 +66,13 @@ use crate::utils::crypto::{
serialize_private_key,
};
use crate::utils::currencies::Currency;
use crate::utils::id::new_uuid;
use super::helpers::get_relationship;
use super::types::{
Account,
AccountCreateData,
AccountUpdateData,
ApiSubscription,
FollowData,
FollowListQueryParams,
IdentityClaim,
@ -75,8 +81,9 @@ use super::types::{
RelationshipQueryParams,
SearchAcctQueryParams,
SearchDidQueryParams,
SignedUpdate,
StatusListQueryParams,
ApiSubscription,
UnsignedUpdate,
};
#[post("")]
@ -205,6 +212,52 @@ async fn update_credentials(
Ok(HttpResponse::Ok().json(account))
}
#[get("/signed_update")]
async fn get_unsigned_update(
auth: BearerAuth,
config: web::Data<Config>,
db_pool: web::Data<Pool>,
) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?;
let current_user = get_current_user(db_client, auth.token()).await?;
let internal_activity_id = new_uuid();
let activity = build_update_person(
&config.instance_url(),
&current_user,
Some(internal_activity_id),
).map_err(|_| HttpError::InternalError)?;
let canonical_json = canonicalize_object(&activity)
.map_err(|_| HttpError::InternalError)?;
let data = UnsignedUpdate {
internal_activity_id,
activity: canonical_json,
};
Ok(HttpResponse::Ok().json(data))
}
#[post("/signed_update")]
async fn send_signed_update(
auth: BearerAuth,
config: web::Data<Config>,
db_pool: web::Data<Pool>,
data: web::Json<SignedUpdate>,
) -> 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>()
.map_err(|_| ValidationError("invalid DID"))?;
if !current_user.profile.identity_proofs.any(&signer) {
return Err(ValidationError("unknown signer").into());
};
let _activity = build_update_person(
&config.instance_url(),
&current_user,
Some(data.internal_activity_id),
).map_err(|_| HttpError::InternalError)?;
let account = Account::from_user(current_user, &config.instance_url());
Ok(HttpResponse::Ok().json(account))
}
#[get("/identity_proof")]
async fn get_identity_claim(
auth: BearerAuth,
@ -243,10 +296,13 @@ async fn create_identity_proof(
let maybe_public_address =
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 {
return Err(ValidationError("DID doesn't match current identity").into());
};
};
// Reject proof if there's another local user with the same DID.
// This is needed for matching ethereum subscriptions
match get_user_by_did(db_client, &did).await {
Ok(user) => {
if user.id != current_user.id {
@ -575,6 +631,8 @@ pub fn account_api_scope() -> Scope {
.service(create_account)
.service(verify_credentials)
.service(update_credentials)
.service(get_unsigned_update)
.service(send_signed_update)
.service(get_identity_claim)
.service(create_identity_proof)
.service(get_relationships_view)

View file

@ -38,6 +38,13 @@ impl IdentityProofs {
let Self(identity_proofs) = self;
identity_proofs
}
/// Returns true if identity proof list contains at least one proof
/// created by a given DID.
pub fn any(&self, issuer: &DidPkh) -> bool {
let Self(identity_proofs) = self;
identity_proofs.iter().any(|proof| proof.issuer == *issuer)
}
}
json_from_sql!(IdentityProofs);