Allow muting accounts #1
25 changed files with 250 additions and 341 deletions
30
.woodpecker.yml
Normal file
30
.woodpecker.yml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
matrix:
|
||||||
|
RUST: [stable]
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
|
||||||
|
check-style:
|
||||||
|
image: rust
|
||||||
|
when:
|
||||||
|
branch: [ master ]
|
||||||
|
path:
|
||||||
|
include:
|
||||||
|
- src/**/*.rs
|
||||||
|
- fedimovies-*/**/*.rs
|
||||||
|
commands:
|
||||||
|
- rustup default $RUST
|
||||||
|
- cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
run-tests:
|
||||||
|
image: rust
|
||||||
|
when:
|
||||||
|
branch: [ master ]
|
||||||
|
path:
|
||||||
|
include:
|
||||||
|
- src/**/*.rs
|
||||||
|
- fedimovies-*/**/*.rs
|
||||||
|
environment:
|
||||||
|
- CARGO_TERM_COLOR=always
|
||||||
|
commands:
|
||||||
|
- rustup default $RUST
|
||||||
|
- cargo test --all -- --nocapture
|
53
README.md
53
README.md
|
@ -1,6 +1,7 @@
|
||||||
# Reef
|
# FediMovies
|
||||||
|
[![status-badge](https://ci.caric.io/api/badges/FediMovies/fedimovies/status.svg)](https://ci.caric.io/FediMovies/fedimovies)
|
||||||
|
|
||||||
Lively federated micro-blogging platform.
|
Lively federated movies reviews platform.
|
||||||
|
|
||||||
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
Built on [ActivityPub](https://www.w3.org/TR/activitypub/) protocol, self-hosted, lightweight. Part of the [Fediverse](https://en.wikipedia.org/wiki/Fediverse).
|
||||||
|
|
||||||
|
@ -13,8 +14,8 @@ Features:
|
||||||
|
|
||||||
## Instances
|
## Instances
|
||||||
|
|
||||||
- [FediList](http://demo.fedilist.com/instance?software=reef)
|
- [FediList](http://demo.fedilist.com/instance?software=fedimovies)
|
||||||
- [Fediverse Observer](https://reef.fediverse.observer/list)
|
- [Fediverse Observer](https://fedimovies.fediverse.observer/list)
|
||||||
|
|
||||||
Demo instance: https://nullpointer.social/ ([invite-only](https://nullpointer.social/about))
|
Demo instance: https://nullpointer.social/ ([invite-only](https://nullpointer.social/about))
|
||||||
|
|
||||||
|
@ -44,53 +45,53 @@ Run:
|
||||||
cargo build --release --features production
|
cargo build --release --features production
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will produce two binaries in `target/release` directory, `mitra` and `mitractl`.
|
This command will produce two binaries in `target/release` directory, `fedimovies` and `fedimoviesctl`.
|
||||||
|
|
||||||
Install PostgreSQL and create the database:
|
Install PostgreSQL and create the database:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||||
CREATE DATABASE mitra OWNER mitra;
|
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||||
```
|
```
|
||||||
|
|
||||||
Create configuration file by copying `contrib/mitra_config.yaml` and configure the instance. Default config file path is `/etc/mitra/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
|
Create configuration file by copying `contrib/fedimovies_config.yaml` and configure the instance. Default config file path is `/etc/fedimovies/config.yaml`, but it can be changed using `CONFIG_PATH` environment variable.
|
||||||
|
|
||||||
Put any static files into the directory specified in configuration file. Building instructions for `mitra-web` frontend can be found at https://codeberg.org/silverpill/mitra-web#project-setup.
|
Put any static files into the directory specified in configuration file. Building instructions for `fedimovies-web` frontend can be found at https://code.caric.io/FediMovies/fedimovies#project-setup.
|
||||||
|
|
||||||
Start Mitra:
|
Start Fedimovies:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./mitra
|
./fedimovies
|
||||||
```
|
```
|
||||||
|
|
||||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/mitra.nginx).
|
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
|
||||||
|
|
||||||
To run Mitra as a systemd service, check out the [systemd unit file example](./contrib/mitra.service).
|
To run Fedimovies as a systemd service, check out the [systemd unit file example](./contrib/fedimovies.service).
|
||||||
|
|
||||||
### Debian package
|
### Debian package
|
||||||
|
|
||||||
Download and install Mitra package:
|
Download and install Fedimovies package:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
dpkg -i mitra.deb
|
dpkg -i fedimovies.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
Install PostgreSQL and create the database:
|
Install PostgreSQL and create the database:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE USER mitra WITH PASSWORD 'mitra';
|
CREATE USER fedimovies WITH PASSWORD 'fedimovies';
|
||||||
CREATE DATABASE mitra OWNER mitra;
|
CREATE DATABASE fedimovies OWNER fedimovies;
|
||||||
```
|
```
|
||||||
|
|
||||||
Open configuration file `/etc/mitra/config.yaml` and configure the instance.
|
Open configuration file `/etc/fedimovies/config.yaml` and configure the instance.
|
||||||
|
|
||||||
Start Mitra:
|
Start Fedimovies:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
systemctl start mitra
|
systemctl start fedimovies
|
||||||
```
|
```
|
||||||
|
|
||||||
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/mitra.nginx).
|
An HTTP server will be needed to handle HTTPS requests. See the example of [nginx configuration file](./contrib/fedimovies.nginx).
|
||||||
|
|
||||||
### Tor federation
|
### Tor federation
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ docker-compose up -d
|
||||||
Test connection:
|
Test connection:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
psql -h localhost -p 55432 -U mitra mitra
|
psql -h localhost -p 55432 -U fedimovies fedimovies
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run web service
|
### Run web service
|
||||||
|
@ -129,7 +130,7 @@ cargo run
|
||||||
### Run CLI
|
### Run CLI
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run --bin mitractl
|
cargo run --bin fedimoviesctl
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run linter
|
### Run linter
|
||||||
|
@ -150,15 +151,15 @@ See [FEDERATION.md](./FEDERATION.md)
|
||||||
|
|
||||||
## Client API
|
## Client API
|
||||||
|
|
||||||
Most methods are similar to Mastodon API, but Mitra is not fully compatible.
|
Most methods are similar to Mastodon API, but Fedimovies is not fully compatible.
|
||||||
|
|
||||||
[OpenAPI spec](./docs/openapi.yaml)
|
[OpenAPI spec](./docs/openapi.yaml)
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
`mitractl` is a command-line tool for performing instance maintenance.
|
`fedimoviesctl` is a command-line tool for performing instance maintenance.
|
||||||
|
|
||||||
[Documentation](./docs/mitractl.md)
|
[Documentation](./docs/fedimoviesctl.md)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use deadpool_postgres::SslMode;
|
|
||||||
use openssl::ssl::{SslConnector, SslMethod};
|
use openssl::ssl::{SslConnector, SslMethod};
|
||||||
use postgres_openssl::MakeTlsConnector;
|
use postgres_openssl::MakeTlsConnector;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
|
@ -11,6 +11,7 @@ use crate::notifications::queries::{
|
||||||
create_mention_notification, create_reply_notification, create_repost_notification,
|
create_mention_notification, create_reply_notification, create_repost_notification,
|
||||||
};
|
};
|
||||||
use crate::profiles::{queries::update_post_count, types::DbActorProfile};
|
use crate::profiles::{queries::update_post_count, types::DbActorProfile};
|
||||||
|
use crate::relationships::queries::is_muted;
|
||||||
use crate::relationships::types::RelationshipType;
|
use crate::relationships::types::RelationshipType;
|
||||||
|
|
||||||
use super::types::{DbPost, Post, PostCreateData, PostUpdateData, Visibility};
|
use super::types::{DbPost, Post, PostCreateData, PostUpdateData, Visibility};
|
||||||
|
@ -249,20 +250,32 @@ pub async fn create_post(
|
||||||
notified_users.push(in_reply_to_author.id);
|
notified_users.push(in_reply_to_author.id);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Notify reposted
|
||||||
if let Some(repost_of_id) = &db_post.repost_of_id {
|
if let Some(repost_of_id) = &db_post.repost_of_id {
|
||||||
update_repost_count(&transaction, repost_of_id, 1).await?;
|
update_repost_count(&transaction, repost_of_id, 1).await?;
|
||||||
let repost_of_author = get_post_author(&transaction, repost_of_id).await?;
|
let repost_of_author = get_post_author(&transaction, repost_of_id).await?;
|
||||||
if repost_of_author.is_local()
|
if repost_of_author.is_local()
|
||||||
&& repost_of_author.id != db_post.author_id
|
|
||||||
&& !notified_users.contains(&repost_of_author.id)
|
&& !notified_users.contains(&repost_of_author.id)
|
||||||
|
// Don't notify themselves that they reported their post
|
||||||
|
&& repost_of_author.id != db_post.author_id
|
||||||
{
|
{
|
||||||
create_repost_notification(
|
// Don't create mention notification if the author is muted
|
||||||
&transaction,
|
if is_muted(&transaction, &repost_of_author.id, &db_post.author_id).await? {
|
||||||
&db_post.author_id,
|
log::warn!(
|
||||||
&repost_of_author.id,
|
"User {} mentioned by muted author id {} on post id {}, ignoring mention..",
|
||||||
repost_of_id,
|
repost_of_author.username,
|
||||||
)
|
db_post.author_id,
|
||||||
.await?;
|
db_post.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
create_repost_notification(
|
||||||
|
&transaction,
|
||||||
|
&db_post.author_id,
|
||||||
|
&repost_of_author.id,
|
||||||
|
repost_of_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
notified_users.push(repost_of_author.id);
|
notified_users.push(repost_of_author.id);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -274,8 +287,23 @@ pub async fn create_post(
|
||||||
// or to the author of reposted post
|
// or to the author of reposted post
|
||||||
!notified_users.contains(&profile.id)
|
!notified_users.contains(&profile.id)
|
||||||
{
|
{
|
||||||
create_mention_notification(&transaction, &db_post.author_id, &profile.id, &db_post.id)
|
// Don't create mention notification if the author is muted
|
||||||
|
if is_muted(&transaction, &profile.id, &db_post.author_id).await? {
|
||||||
|
log::warn!(
|
||||||
|
"User {} mentioned by muted author {} in post id {}, ignoring mention..",
|
||||||
|
profile.username,
|
||||||
|
db_post.author_id,
|
||||||
|
db_post.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
create_mention_notification(
|
||||||
|
&transaction,
|
||||||
|
&db_post.author_id,
|
||||||
|
&profile.id,
|
||||||
|
&db_post.id,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Construct post object
|
// Construct post object
|
||||||
|
@ -437,6 +465,7 @@ pub async fn get_home_timeline(
|
||||||
(
|
(
|
||||||
post.author_id = $current_user_id
|
post.author_id = $current_user_id
|
||||||
OR (
|
OR (
|
||||||
|
-- is following or subscribed the post author
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1 FROM relationship
|
SELECT 1 FROM relationship
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -487,6 +516,14 @@ pub async fn get_home_timeline(
|
||||||
WHERE post_id = post.id AND profile_id = $current_user_id
|
WHERE post_id = post.id AND profile_id = $current_user_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
-- author is not muted
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM relationship
|
||||||
|
WHERE
|
||||||
|
source_id = $current_user_id
|
||||||
|
AND target_id = post.author_id
|
||||||
|
AND relationship_type = {relationship_mute}
|
||||||
|
)
|
||||||
AND {visibility_filter}
|
AND {visibility_filter}
|
||||||
AND ($max_post_id::uuid IS NULL OR post.id < $max_post_id)
|
AND ($max_post_id::uuid IS NULL OR post.id < $max_post_id)
|
||||||
ORDER BY post.id DESC
|
ORDER BY post.id DESC
|
||||||
|
@ -501,6 +538,7 @@ pub async fn get_home_timeline(
|
||||||
relationship_subscription=i16::from(&RelationshipType::Subscription),
|
relationship_subscription=i16::from(&RelationshipType::Subscription),
|
||||||
relationship_hide_reposts=i16::from(&RelationshipType::HideReposts),
|
relationship_hide_reposts=i16::from(&RelationshipType::HideReposts),
|
||||||
relationship_hide_replies=i16::from(&RelationshipType::HideReplies),
|
relationship_hide_replies=i16::from(&RelationshipType::HideReplies),
|
||||||
|
relationship_mute=i16::from(&RelationshipType::Mute),
|
||||||
visibility_filter=build_visibility_filter(),
|
visibility_filter=build_visibility_filter(),
|
||||||
);
|
);
|
||||||
let limit: i64 = limit.into();
|
let limit: i64 = limit.into();
|
||||||
|
|
|
@ -614,6 +614,64 @@ pub async fn show_replies(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn mute_posts(
|
||||||
|
db_client: &impl DatabaseClient,
|
||||||
|
source_id: &Uuid,
|
||||||
|
target_id: &Uuid,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
db_client
|
||||||
|
.execute(
|
||||||
|
"
|
||||||
|
INSERT INTO relationship (source_id, target_id, relationship_type)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (source_id, target_id, relationship_type) DO NOTHING
|
||||||
|
",
|
||||||
|
&[&source_id, &target_id, &RelationshipType::Mute],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn unmute_posts(
|
||||||
|
db_client: &impl DatabaseClient,
|
||||||
|
source_id: &Uuid,
|
||||||
|
target_id: &Uuid,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
// Does not return NotFound error
|
||||||
|
db_client
|
||||||
|
.execute(
|
||||||
|
"
|
||||||
|
DELETE FROM relationship
|
||||||
|
WHERE
|
||||||
|
source_id = $1 AND target_id = $2
|
||||||
|
AND relationship_type = $3
|
||||||
|
",
|
||||||
|
&[&source_id, &target_id, &RelationshipType::Mute],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn is_muted(
|
||||||
|
db_client: &impl DatabaseClient,
|
||||||
|
source_id: &Uuid,
|
||||||
|
target_id: &Uuid,
|
||||||
|
) -> Result<bool, DatabaseError> {
|
||||||
|
let rows = db_client
|
||||||
|
.query(
|
||||||
|
"
|
||||||
|
SELECT 1
|
||||||
|
FROM relationship
|
||||||
|
WHERE
|
||||||
|
source_id = $1 AND target_id = $2
|
||||||
|
AND relationship_type = $3
|
||||||
|
",
|
||||||
|
&[&source_id, &target_id, &RelationshipType::Mute],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(rows.len() > 0)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub enum RelationshipType {
|
||||||
Subscription,
|
Subscription,
|
||||||
HideReposts,
|
HideReposts,
|
||||||
HideReplies,
|
HideReplies,
|
||||||
|
Mute,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&RelationshipType> for i16 {
|
impl From<&RelationshipType> for i16 {
|
||||||
|
@ -27,6 +28,7 @@ impl From<&RelationshipType> for i16 {
|
||||||
RelationshipType::Subscription => 3,
|
RelationshipType::Subscription => 3,
|
||||||
RelationshipType::HideReposts => 4,
|
RelationshipType::HideReposts => 4,
|
||||||
RelationshipType::HideReplies => 5,
|
RelationshipType::HideReplies => 5,
|
||||||
|
RelationshipType::Mute => 6,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,7 @@ impl TryFrom<i16> for RelationshipType {
|
||||||
3 => Self::Subscription,
|
3 => Self::Subscription,
|
||||||
4 => Self::HideReposts,
|
4 => Self::HideReposts,
|
||||||
5 => Self::HideReplies,
|
5 => Self::HideReplies,
|
||||||
|
6 => Self::Mute,
|
||||||
_ => return Err(DatabaseTypeError),
|
_ => return Err(DatabaseTypeError),
|
||||||
};
|
};
|
||||||
Ok(relationship_type)
|
Ok(relationship_type)
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
use fedimovies_models::profiles::types::{
|
use fedimovies_models::profiles::types::{
|
||||||
ExtraField, IdentityProof, IdentityProofType, PaymentLink, PaymentOption,
|
ExtraField, IdentityProof, IdentityProofType, PaymentLink, PaymentOption,
|
||||||
};
|
};
|
||||||
use fedimovies_utils::did::Did;
|
|
||||||
|
|
||||||
use crate::activitypub::vocabulary::{IDENTITY_PROOF, LINK, PROPERTY_VALUE};
|
use crate::activitypub::vocabulary::{IDENTITY_PROOF, LINK, PROPERTY_VALUE};
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::identity::{
|
|
||||||
claims::create_identity_claim,
|
|
||||||
minisign::{parse_minisign_signature, verify_minisign_signature},
|
|
||||||
};
|
|
||||||
use crate::json_signatures::proofs::{PROOF_TYPE_ID_EIP191, PROOF_TYPE_ID_MINISIGN};
|
use crate::json_signatures::proofs::{PROOF_TYPE_ID_EIP191, PROOF_TYPE_ID_MINISIGN};
|
||||||
use crate::web_client::urls::get_subscription_page_url;
|
use crate::web_client::urls::get_subscription_page_url;
|
||||||
|
|
||||||
|
@ -30,51 +25,10 @@ pub fn attach_identity_proof(proof: IdentityProof) -> ActorAttachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_identity_proof(
|
pub fn parse_identity_proof(
|
||||||
actor_id: &str,
|
_actor_id: &str,
|
||||||
attachment: &ActorAttachment,
|
_attachment: &ActorAttachment,
|
||||||
) -> Result<IdentityProof, ValidationError> {
|
) -> Result<IdentityProof, ValidationError> {
|
||||||
if attachment.object_type != IDENTITY_PROOF {
|
return Err(ValidationError("incorrect proof type".to_string()));
|
||||||
return Err(ValidationError("invalid attachment type".to_string()));
|
|
||||||
};
|
|
||||||
let proof_type_str = attachment
|
|
||||||
.signature_algorithm
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ValidationError("missing proof type".to_string()))?;
|
|
||||||
let proof_type = match proof_type_str.as_str() {
|
|
||||||
PROOF_TYPE_ID_EIP191 => IdentityProofType::LegacyEip191IdentityProof,
|
|
||||||
PROOF_TYPE_ID_MINISIGN => IdentityProofType::LegacyMinisignIdentityProof,
|
|
||||||
_ => return Err(ValidationError("unsupported proof type".to_string())),
|
|
||||||
};
|
|
||||||
let did = attachment
|
|
||||||
.name
|
|
||||||
.parse::<Did>()
|
|
||||||
.map_err(|_| ValidationError("invalid DID".to_string()))?;
|
|
||||||
let message = create_identity_claim(actor_id, &did)
|
|
||||||
.map_err(|_| ValidationError("invalid claim".to_string()))?;
|
|
||||||
let signature = attachment
|
|
||||||
.signature_value
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ValidationError("missing signature".to_string()))?;
|
|
||||||
match did {
|
|
||||||
Did::Key(ref did_key) => {
|
|
||||||
if !matches!(proof_type, IdentityProofType::LegacyMinisignIdentityProof) {
|
|
||||||
return Err(ValidationError("incorrect proof type".to_string()));
|
|
||||||
};
|
|
||||||
let signature_bin = parse_minisign_signature(signature)
|
|
||||||
.map_err(|_| ValidationError("invalid signature encoding".to_string()))?;
|
|
||||||
verify_minisign_signature(did_key, &message, &signature_bin)
|
|
||||||
.map_err(|_| ValidationError("invalid identity proof".to_string()))?;
|
|
||||||
}
|
|
||||||
Did::Pkh(ref _did_pkh) => {
|
|
||||||
return Err(ValidationError("incorrect proof type".to_string()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let proof = IdentityProof {
|
|
||||||
issuer: did,
|
|
||||||
proof_type: proof_type,
|
|
||||||
value: signature.to_string(),
|
|
||||||
};
|
|
||||||
Ok(proof)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_payment_option(
|
pub fn attach_payment_option(
|
||||||
|
|
|
@ -1,23 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use super::attachments::{
|
||||||
|
attach_extra_field, attach_identity_proof, attach_payment_option, parse_extra_field,
|
||||||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
parse_identity_proof, parse_payment_option,
|
||||||
use serde_json::{json, Value};
|
|
||||||
|
|
||||||
use fedimovies_config::Instance;
|
|
||||||
use fedimovies_models::{
|
|
||||||
profiles::types::{DbActor, DbActorPublicKey, ExtraField, IdentityProof, PaymentOption},
|
|
||||||
users::types::User,
|
|
||||||
};
|
};
|
||||||
use fedimovies_utils::{
|
|
||||||
crypto_rsa::{deserialize_private_key, get_public_key_pem},
|
|
||||||
urls::get_hostname,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::activitypub::types::build_default_context;
|
use crate::activitypub::types::build_default_context;
|
||||||
use crate::activitypub::{
|
use crate::activitypub::{
|
||||||
constants::{
|
|
||||||
AP_CONTEXT, MASTODON_CONTEXT, MITRA_CONTEXT, SCHEMA_ORG_CONTEXT, W3ID_SECURITY_CONTEXT,
|
|
||||||
},
|
|
||||||
identifiers::{
|
identifiers::{
|
||||||
local_actor_id, local_actor_key_id, local_instance_actor_id, LocalActorCollection,
|
local_actor_id, local_actor_key_id, local_instance_actor_id, LocalActorCollection,
|
||||||
},
|
},
|
||||||
|
@ -27,11 +13,17 @@ use crate::activitypub::{
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::media::get_file_url;
|
use crate::media::get_file_url;
|
||||||
use crate::webfinger::types::ActorAddress;
|
use crate::webfinger::types::ActorAddress;
|
||||||
|
use fedimovies_config::Instance;
|
||||||
use super::attachments::{
|
use fedimovies_models::{
|
||||||
attach_extra_field, attach_identity_proof, attach_payment_option, parse_extra_field,
|
profiles::types::{DbActor, DbActorPublicKey, ExtraField, IdentityProof, PaymentOption},
|
||||||
parse_identity_proof, parse_payment_option,
|
users::types::User,
|
||||||
};
|
};
|
||||||
|
use fedimovies_utils::{
|
||||||
|
crypto_rsa::{deserialize_private_key, get_public_key_pem},
|
||||||
|
urls::get_hostname,
|
||||||
|
};
|
||||||
|
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[cfg_attr(test, derive(Default))]
|
#[cfg_attr(test, derive(Default))]
|
||||||
|
@ -245,27 +237,6 @@ impl Actor {
|
||||||
|
|
||||||
pub type ActorKeyError = rsa::pkcs8::Error;
|
pub type ActorKeyError = rsa::pkcs8::Error;
|
||||||
|
|
||||||
fn build_actor_context() -> (
|
|
||||||
&'static str,
|
|
||||||
&'static str,
|
|
||||||
HashMap<&'static str, &'static str>,
|
|
||||||
) {
|
|
||||||
(
|
|
||||||
AP_CONTEXT,
|
|
||||||
W3ID_SECURITY_CONTEXT,
|
|
||||||
HashMap::from([
|
|
||||||
("manuallyApprovesFollowers", "as:manuallyApprovesFollowers"),
|
|
||||||
("schema", SCHEMA_ORG_CONTEXT),
|
|
||||||
("PropertyValue", "schema:PropertyValue"),
|
|
||||||
("value", "schema:value"),
|
|
||||||
("toot", MASTODON_CONTEXT),
|
|
||||||
("IdentityProof", "toot:IdentityProof"),
|
|
||||||
("fedimovies", MITRA_CONTEXT),
|
|
||||||
("subscribers", "fedimovies:subscribers"),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_actor(user: &User, instance_url: &str) -> Result<Actor, ActorKeyError> {
|
pub fn get_local_actor(user: &User, instance_url: &str) -> Result<Actor, ActorKeyError> {
|
||||||
let username = &user.profile.username;
|
let username = &user.profile.username;
|
||||||
let actor_id = local_actor_id(instance_url, username);
|
let actor_id = local_actor_id(instance_url, username);
|
||||||
|
|
|
@ -165,7 +165,7 @@ pub async fn verify_signed_activity(
|
||||||
let signer_key = deserialize_public_key(&signer_actor.public_key.public_key_pem)?;
|
let signer_key = deserialize_public_key(&signer_actor.public_key.public_key_pem)?;
|
||||||
verify_rsa_json_signature(&signature_data, &signer_key)?;
|
verify_rsa_json_signature(&signature_data, &signer_key)?;
|
||||||
}
|
}
|
||||||
JsonSigner::Did(did) => {
|
JsonSigner::Did(_did) => {
|
||||||
return Err(AuthenticationError::InvalidJsonSignatureType);
|
return Err(AuthenticationError::InvalidJsonSignatureType);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,12 +4,11 @@ pub const AP_MEDIA_TYPE: &str =
|
||||||
pub const AS_MEDIA_TYPE: &str = "application/activity+json";
|
pub const AS_MEDIA_TYPE: &str = "application/activity+json";
|
||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
pub const AP_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";
|
pub const AS_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";
|
||||||
pub const W3ID_SECURITY_CONTEXT: &str = "https://w3id.org/security/v1";
|
pub const W3ID_SECURITY_CONTEXT: &str = "https://w3id.org/security/v1";
|
||||||
pub const W3ID_DATA_INTEGRITY_CONTEXT: &str = "https://w3id.org/security/data-integrity/v1";
|
pub const W3ID_DATA_INTEGRITY_CONTEXT: &str = "https://w3id.org/security/data-integrity/v1";
|
||||||
pub const SCHEMA_ORG_CONTEXT: &str = "http://schema.org/";
|
pub const SCHEMA_ORG_CONTEXT: &str = "http://schema.org/#";
|
||||||
pub const MASTODON_CONTEXT: &str = "http://joinmastodon.org/ns#";
|
pub const MASTODON_CONTEXT: &str = "http://joinmastodon.org/ns#";
|
||||||
pub const MITRA_CONTEXT: &str = "http://jsonld.fedimovies.social#";
|
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
pub const AP_PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public";
|
pub const AP_PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public";
|
||||||
|
|
|
@ -9,7 +9,7 @@ use fedimovies_utils::{files::sniff_media_type, urls::guess_protocol};
|
||||||
|
|
||||||
use crate::activitypub::{
|
use crate::activitypub::{
|
||||||
actors::types::Actor,
|
actors::types::Actor,
|
||||||
constants::{AP_CONTEXT, AP_MEDIA_TYPE},
|
constants::{AP_MEDIA_TYPE, AS_CONTEXT},
|
||||||
http_client::{build_federation_client, get_network_type},
|
http_client::{build_federation_client, get_network_type},
|
||||||
identifiers::{local_actor_key_id, local_instance_actor_id},
|
identifiers::{local_actor_key_id, local_instance_actor_id},
|
||||||
types::Object,
|
types::Object,
|
||||||
|
@ -153,7 +153,7 @@ pub async fn perform_webfinger_query(
|
||||||
let jrd: JsonResourceDescriptor = serde_json::from_str(&webfinger_data)?;
|
let jrd: JsonResourceDescriptor = serde_json::from_str(&webfinger_data)?;
|
||||||
// Lemmy servers can have Group and Person actors with the same name
|
// Lemmy servers can have Group and Person actors with the same name
|
||||||
// https://github.com/LemmyNet/lemmy/issues/2037
|
// https://github.com/LemmyNet/lemmy/issues/2037
|
||||||
let ap_type_property = format!("{}#type", AP_CONTEXT);
|
let ap_type_property = format!("{}#type", AS_CONTEXT);
|
||||||
let group_link = jrd.links.iter().find(|link| {
|
let group_link = jrd.links.iter().find(|link| {
|
||||||
link.rel == "self"
|
link.rel == "self"
|
||||||
&& link
|
&& link
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use log::warn;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use crate::activitypub::constants::{
|
||||||
|
AS_CONTEXT, MASTODON_CONTEXT, SCHEMA_ORG_CONTEXT, W3ID_SECURITY_CONTEXT,
|
||||||
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
@ -124,11 +127,11 @@ pub type Context = Value;
|
||||||
|
|
||||||
pub fn build_default_context() -> Context {
|
pub fn build_default_context() -> Context {
|
||||||
json!([
|
json!([
|
||||||
"https://www.w3.org/ns/activitystreams",
|
AS_CONTEXT,
|
||||||
"https://w3id.org/security/v1",
|
W3ID_SECURITY_CONTEXT,
|
||||||
{
|
{
|
||||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
"toot": "http://joinmastodon.org/ns#",
|
"toot": MASTODON_CONTEXT,
|
||||||
"featured": {
|
"featured": {
|
||||||
"@id": "toot:featured",
|
"@id": "toot:featured",
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
|
@ -145,7 +148,7 @@ pub fn build_default_context() -> Context {
|
||||||
"@id": "as:movedTo",
|
"@id": "as:movedTo",
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
},
|
},
|
||||||
"schema": "http://schema.org#",
|
"schema": SCHEMA_ORG_CONTEXT,
|
||||||
"PropertyValue": "schema:PropertyValue",
|
"PropertyValue": "schema:PropertyValue",
|
||||||
"value": "schema:value",
|
"value": "schema:value",
|
||||||
"IdentityProof": "toot:IdentityProof",
|
"IdentityProof": "toot:IdentityProof",
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use fedimovies_utils::{
|
|
||||||
canonicalization::{canonicalize_object, CanonicalizationError},
|
|
||||||
did::Did,
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://www.w3.org/TR/vc-data-model/#credential-subject
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Claim {
|
|
||||||
id: String, // actor ID
|
|
||||||
owner_of: Did,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates key ownership claim and prepares it for signing
|
|
||||||
pub fn create_identity_claim(actor_id: &str, did: &Did) -> Result<String, CanonicalizationError> {
|
|
||||||
let claim = Claim {
|
|
||||||
id: actor_id.to_string(),
|
|
||||||
owner_of: did.clone(),
|
|
||||||
};
|
|
||||||
let message = canonicalize_object(&claim)?;
|
|
||||||
Ok(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use fedimovies_utils::{currencies::Currency, did_pkh::DidPkh};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_identity_claim() {
|
|
||||||
let actor_id = "https://example.org/users/test";
|
|
||||||
let ethereum_address = "0xB9C5714089478a327F09197987f16f9E5d936E8a";
|
|
||||||
let did = Did::Pkh(DidPkh::from_address(&Currency::Ethereum, ethereum_address));
|
|
||||||
let claim = create_identity_claim(actor_id, &did).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
claim,
|
|
||||||
r#"{"id":"https://example.org/users/test","ownerOf":"did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a"}"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
/// https://jedisct1.github.io/minisign/
|
|
||||||
use blake2::{Blake2b512, Digest};
|
|
||||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
|
||||||
|
|
||||||
use fedimovies_utils::did_key::{DidKey, MulticodecError};
|
|
||||||
|
|
||||||
const MINISIGN_SIGNATURE_CODE: [u8; 2] = *b"Ed";
|
|
||||||
const MINISIGN_SIGNATURE_HASHED_CODE: [u8; 2] = *b"ED";
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum ParseError {
|
|
||||||
#[error("invalid encoding")]
|
|
||||||
InvalidEncoding(#[from] base64::DecodeError),
|
|
||||||
|
|
||||||
#[error("invalid key length")]
|
|
||||||
InvalidKeyLength,
|
|
||||||
|
|
||||||
#[error("invalid signature length")]
|
|
||||||
InvalidSignatureLength,
|
|
||||||
|
|
||||||
#[error("invalid signature type")]
|
|
||||||
InvalidSignatureType,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public key format:
|
|
||||||
// base64(<signature_algorithm> || <key_id> || <public_key>)
|
|
||||||
fn parse_minisign_public_key(key_b64: &str) -> Result<[u8; 32], ParseError> {
|
|
||||||
let key_bin = base64::decode(key_b64)?;
|
|
||||||
if key_bin.len() != 42 {
|
|
||||||
return Err(ParseError::InvalidKeyLength);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut signature_algorithm = [0; 2];
|
|
||||||
let mut _key_id = [0; 8];
|
|
||||||
let mut key = [0; 32];
|
|
||||||
signature_algorithm.copy_from_slice(&key_bin[0..2]);
|
|
||||||
_key_id.copy_from_slice(&key_bin[2..10]);
|
|
||||||
key.copy_from_slice(&key_bin[10..42]);
|
|
||||||
|
|
||||||
if signature_algorithm.as_ref() != MINISIGN_SIGNATURE_CODE {
|
|
||||||
return Err(ParseError::InvalidSignatureType);
|
|
||||||
};
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn minisign_key_to_did(key_b64: &str) -> Result<DidKey, ParseError> {
|
|
||||||
let key = parse_minisign_public_key(key_b64)?;
|
|
||||||
let did_key = DidKey::from_ed25519_key(key);
|
|
||||||
Ok(did_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signature format:
|
|
||||||
// base64(<signature_algorithm> || <key_id> || <signature>)
|
|
||||||
pub fn parse_minisign_signature(signature_b64: &str) -> Result<[u8; 64], ParseError> {
|
|
||||||
let signature_bin = base64::decode(signature_b64)?;
|
|
||||||
if signature_bin.len() != 74 {
|
|
||||||
return Err(ParseError::InvalidSignatureLength);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut signature_algorithm = [0; 2];
|
|
||||||
let mut _key_id = [0; 8];
|
|
||||||
let mut signature = [0; 64];
|
|
||||||
signature_algorithm.copy_from_slice(&signature_bin[0..2]);
|
|
||||||
_key_id.copy_from_slice(&signature_bin[2..10]);
|
|
||||||
signature.copy_from_slice(&signature_bin[10..74]);
|
|
||||||
|
|
||||||
if signature_algorithm.as_ref() != MINISIGN_SIGNATURE_HASHED_CODE {
|
|
||||||
return Err(ParseError::InvalidSignatureType);
|
|
||||||
};
|
|
||||||
Ok(signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _verify_ed25519_signature(
|
|
||||||
message: &str,
|
|
||||||
signer: [u8; 32],
|
|
||||||
signature: [u8; 64],
|
|
||||||
) -> Result<(), SignatureError> {
|
|
||||||
let signature = Signature::from_bytes(&signature)?;
|
|
||||||
let public_key = PublicKey::from_bytes(&signer)?;
|
|
||||||
let mut hasher = Blake2b512::new();
|
|
||||||
hasher.update(message);
|
|
||||||
let hash = hasher.finalize();
|
|
||||||
public_key.verify(&hash, &signature)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum VerificationError {
|
|
||||||
#[error(transparent)]
|
|
||||||
InvalidKey(#[from] MulticodecError),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
ParseError(#[from] ParseError),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
SignatureError(#[from] SignatureError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_minisign_signature(
|
|
||||||
signer: &DidKey,
|
|
||||||
message: &str,
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Result<(), VerificationError> {
|
|
||||||
let ed25519_key = signer.try_ed25519_key()?;
|
|
||||||
let ed25519_signature = signature
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| ParseError::InvalidSignatureLength)?;
|
|
||||||
let message = format!("{}\n", message);
|
|
||||||
_verify_ed25519_signature(&message, ed25519_key, ed25519_signature)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_verify_minisign_signature() {
|
|
||||||
let minisign_key = "RWSA58rRENpGFYwAjRjbdST7VHFoIuH9JBHfO2u6i5JgANPIoQhABAF/";
|
|
||||||
let message = "test";
|
|
||||||
let minisign_signature =
|
|
||||||
"RUSA58rRENpGFVKxdZGMG1WdIJ+dlyP83qOqw6GP0H/Li6Brug2A3mFKLtleIRLi6IIG0smzOlX5CEsisNnc897OUHIOSNLsQQs=";
|
|
||||||
let signer = minisign_key_to_did(minisign_key).unwrap();
|
|
||||||
let signature_bin = parse_minisign_signature(minisign_signature).unwrap();
|
|
||||||
let result = verify_minisign_signature(&signer, message, &signature_bin);
|
|
||||||
assert_eq!(result.is_ok(), true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod claims;
|
|
||||||
pub mod minisign;
|
|
|
@ -114,7 +114,7 @@ pub async fn handle_movies_mentions(
|
||||||
let mut repost =
|
let mut repost =
|
||||||
match create_post(&mut transaction, ¤t_user.id, repost_data).await {
|
match create_post(&mut transaction, ¤t_user.id, repost_data).await {
|
||||||
Ok(repost) => repost,
|
Ok(repost) => repost,
|
||||||
Err(DatabaseError::AlreadyExists(err)) => {
|
Err(DatabaseError::AlreadyExists(_err)) => {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Review as Mention of {} already reposted the post with id {}",
|
"Review as Mention of {} already reposted the post with id {}",
|
||||||
current_user.profile.username,
|
current_user.profile.username,
|
||||||
|
|
|
@ -7,9 +7,6 @@ use crate::json_signatures::proofs::PROOF_TYPE_JCS_RSA;
|
||||||
use fedimovies_utils::crypto_rsa::create_rsa_signature;
|
use fedimovies_utils::crypto_rsa::create_rsa_signature;
|
||||||
use fedimovies_utils::{
|
use fedimovies_utils::{
|
||||||
canonicalization::{canonicalize_object, CanonicalizationError},
|
canonicalization::{canonicalize_object, CanonicalizationError},
|
||||||
crypto_rsa::create_rsa_sha256_signature,
|
|
||||||
did_key::DidKey,
|
|
||||||
did_pkh::DidPkh,
|
|
||||||
multibase::encode_multibase_base58btc,
|
multibase::encode_multibase_base58btc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ pub const PROOF_TYPE_ID_MINISIGN: &str = "MitraMinisignSignature2022A";
|
||||||
// - Digest algorithm: SHA-256
|
// - Digest algorithm: SHA-256
|
||||||
// - Signature algorithm: RSASSA-PKCS1-v1_5
|
// - Signature algorithm: RSASSA-PKCS1-v1_5
|
||||||
pub const PROOF_TYPE_JCS_RSA: &str = "MitraJcsRsaSignature2022";
|
pub const PROOF_TYPE_JCS_RSA: &str = "MitraJcsRsaSignature2022";
|
||||||
pub const PROOF_TYPE_JCS_RSA_LEGACY: &str = "JcsRsaSignature2022";
|
|
||||||
|
|
||||||
// https://w3c.github.io/vc-data-integrity/#dataintegrityproof
|
// https://w3c.github.io/vc-data-integrity/#dataintegrityproof
|
||||||
pub const DATA_INTEGRITY_PROOF: &str = "DataIntegrityProof";
|
pub const DATA_INTEGRITY_PROOF: &str = "DataIntegrityProof";
|
||||||
|
@ -41,7 +40,7 @@ impl FromStr for ProofType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProofType {
|
impl ProofType {
|
||||||
pub fn from_cryptosuite(value: &str) -> Result<Self, ConversionError> {
|
pub fn from_cryptosuite(_value: &str) -> Result<Self, ConversionError> {
|
||||||
Err(ConversionError)
|
Err(ConversionError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,11 @@ use fedimovies_utils::{
|
||||||
canonicalization::{canonicalize_object, CanonicalizationError},
|
canonicalization::{canonicalize_object, CanonicalizationError},
|
||||||
crypto_rsa::verify_rsa_sha256_signature,
|
crypto_rsa::verify_rsa_sha256_signature,
|
||||||
did::Did,
|
did::Did,
|
||||||
did_key::DidKey,
|
|
||||||
did_pkh::DidPkh,
|
|
||||||
multibase::{decode_multibase_base58btc, MultibaseError},
|
multibase::{decode_multibase_base58btc, MultibaseError},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::create::{IntegrityProof, PROOF_KEY, PROOF_PURPOSE};
|
use super::create::{IntegrityProof, PROOF_KEY, PROOF_PURPOSE};
|
||||||
use super::proofs::{ProofType, DATA_INTEGRITY_PROOF};
|
use super::proofs::{ProofType, DATA_INTEGRITY_PROOF};
|
||||||
use crate::identity::minisign::verify_minisign_signature;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum JsonSigner {
|
pub enum JsonSigner {
|
||||||
|
|
|
@ -4,7 +4,6 @@ pub mod atom;
|
||||||
mod errors;
|
mod errors;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
mod http_signatures;
|
mod http_signatures;
|
||||||
mod identity;
|
|
||||||
mod ipfs;
|
mod ipfs;
|
||||||
pub mod job_queue;
|
pub mod job_queue;
|
||||||
mod json_signatures;
|
mod json_signatures;
|
||||||
|
|
|
@ -52,6 +52,11 @@ pub async fn get_relationship(
|
||||||
relationship_map.showing_replies = false;
|
relationship_map.showing_replies = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
RelationshipType::Mute => {
|
||||||
|
if relationship.is_direct(source_id, target_id)? {
|
||||||
|
relationship_map.muting = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(relationship_map)
|
Ok(relationship_map)
|
||||||
|
|
|
@ -402,6 +402,7 @@ pub struct RelationshipMap {
|
||||||
pub subscription_from: bool,
|
pub subscription_from: bool,
|
||||||
pub showing_reblogs: bool,
|
pub showing_reblogs: bool,
|
||||||
pub showing_replies: bool,
|
pub showing_replies: bool,
|
||||||
|
pub muting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_showing_reblogs() -> bool {
|
fn default_showing_reblogs() -> bool {
|
||||||
|
@ -423,6 +424,7 @@ impl Default for RelationshipMap {
|
||||||
subscription_from: false,
|
subscription_from: false,
|
||||||
showing_reblogs: default_showing_reblogs(),
|
showing_reblogs: default_showing_reblogs(),
|
||||||
showing_replies: default_showing_replies(),
|
showing_replies: default_showing_replies(),
|
||||||
|
muting: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use fedimovies_config::{Config, DefaultRole, RegistrationType};
|
use fedimovies_config::{Config, DefaultRole, RegistrationType};
|
||||||
|
use fedimovies_models::relationships::queries::{mute_posts, unmute_posts};
|
||||||
use fedimovies_models::{
|
use fedimovies_models::{
|
||||||
database::{get_database_client, DatabaseError, DbPool},
|
database::{get_database_client, DatabaseError, DbPool},
|
||||||
posts::queries::get_posts_by_author,
|
posts::queries::get_posts_by_author,
|
||||||
|
@ -10,22 +11,18 @@ use fedimovies_models::{
|
||||||
profiles::queries::{
|
profiles::queries::{
|
||||||
get_profile_by_acct, get_profile_by_id, search_profiles_by_did, update_profile,
|
get_profile_by_acct, get_profile_by_id, search_profiles_by_did, update_profile,
|
||||||
},
|
},
|
||||||
profiles::types::{IdentityProof, IdentityProofType, ProfileUpdateData},
|
|
||||||
relationships::queries::{
|
relationships::queries::{
|
||||||
get_followers_paginated, get_following_paginated, hide_replies, hide_reposts, show_replies,
|
get_followers_paginated, get_following_paginated, hide_replies, hide_reposts, show_replies,
|
||||||
show_reposts, unfollow,
|
show_reposts, unfollow,
|
||||||
},
|
},
|
||||||
subscriptions::queries::get_incoming_subscriptions,
|
subscriptions::queries::get_incoming_subscriptions,
|
||||||
users::queries::{create_user, get_user_by_did, is_valid_invite_code},
|
users::queries::{create_user, is_valid_invite_code},
|
||||||
users::types::{Role, UserCreateData},
|
users::types::{Role, UserCreateData},
|
||||||
};
|
};
|
||||||
use fedimovies_utils::{
|
use fedimovies_utils::{
|
||||||
caip2::ChainId,
|
|
||||||
canonicalization::canonicalize_object,
|
canonicalization::canonicalize_object,
|
||||||
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||||
currencies::Currency,
|
|
||||||
did::Did,
|
did::Did,
|
||||||
did_pkh::DidPkh,
|
|
||||||
id::generate_ulid,
|
id::generate_ulid,
|
||||||
passwords::hash_password,
|
passwords::hash_password,
|
||||||
};
|
};
|
||||||
|
@ -33,25 +30,16 @@ use fedimovies_utils::{
|
||||||
use super::helpers::{get_aliases, get_relationship};
|
use super::helpers::{get_aliases, get_relationship};
|
||||||
use super::types::{
|
use super::types::{
|
||||||
Account, AccountCreateData, AccountUpdateData, ActivityParams, ApiSubscription, FollowData,
|
Account, AccountCreateData, AccountUpdateData, ActivityParams, ApiSubscription, FollowData,
|
||||||
FollowListQueryParams, IdentityClaim, IdentityClaimQueryParams, IdentityProofData,
|
FollowListQueryParams, LookupAcctQueryParams, RelationshipQueryParams, SearchAcctQueryParams,
|
||||||
LookupAcctQueryParams, RelationshipQueryParams, SearchAcctQueryParams, SearchDidQueryParams,
|
SearchDidQueryParams, StatusListQueryParams, UnsignedActivity,
|
||||||
SignedActivity, StatusListQueryParams, UnsignedActivity,
|
|
||||||
};
|
};
|
||||||
use crate::activitypub::{
|
use crate::activitypub::builders::{
|
||||||
builders::{
|
follow::follow_or_create_request,
|
||||||
follow::follow_or_create_request,
|
undo_follow::prepare_undo_follow,
|
||||||
undo_follow::prepare_undo_follow,
|
update_person::{build_update_person, prepare_update_person},
|
||||||
update_person::{build_update_person, prepare_update_person},
|
|
||||||
},
|
|
||||||
identifiers::local_actor_id,
|
|
||||||
};
|
};
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::http::{get_request_base_url, FormOrJson};
|
use crate::http::{get_request_base_url, FormOrJson};
|
||||||
use crate::identity::{
|
|
||||||
claims::create_identity_claim,
|
|
||||||
minisign::{minisign_key_to_did, parse_minisign_signature, verify_minisign_signature},
|
|
||||||
};
|
|
||||||
use crate::json_signatures::create::IntegrityProof;
|
|
||||||
use crate::mastodon_api::{
|
use crate::mastodon_api::{
|
||||||
errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response,
|
errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response,
|
||||||
search::helpers::search_profiles_only, statuses::helpers::build_status_list,
|
search::helpers::search_profiles_only, statuses::helpers::build_status_list,
|
||||||
|
@ -367,6 +355,41 @@ async fn unfollow_account(
|
||||||
Ok(HttpResponse::Ok().json(relationship))
|
Ok(HttpResponse::Ok().json(relationship))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/{account_id}/mute")]
|
||||||
|
async fn mute_account(
|
||||||
|
auth: BearerAuth,
|
||||||
|
db_pool: web::Data<DbPool>,
|
||||||
|
account_id: web::Path<Uuid>,
|
||||||
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
|
let target = get_profile_by_id(db_client, &account_id).await?;
|
||||||
|
|
||||||
|
mute_posts(db_client, ¤t_user.id, &target.id).await?;
|
||||||
|
|
||||||
|
let relationship = get_relationship(db_client, ¤t_user.id, &target.id).await?;
|
||||||
|
Ok(HttpResponse::Ok().json(relationship))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/{account_id}/unmute")]
|
||||||
|
async fn unmute_account(
|
||||||
|
auth: BearerAuth,
|
||||||
|
db_pool: web::Data<DbPool>,
|
||||||
|
account_id: web::Path<Uuid>,
|
||||||
|
) -> Result<HttpResponse, MastodonError> {
|
||||||
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
|
let target = get_profile_by_id(db_client, &account_id).await?;
|
||||||
|
match unmute_posts(db_client, ¤t_user.id, &target.id).await {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(DatabaseError::NotFound(_)) => (), // not following
|
||||||
|
Err(other_error) => return Err(other_error.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let relationship = get_relationship(db_client, ¤t_user.id, &target.id).await?;
|
||||||
|
Ok(HttpResponse::Ok().json(relationship))
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/{account_id}/statuses")]
|
#[get("/{account_id}/statuses")]
|
||||||
async fn get_account_statuses(
|
async fn get_account_statuses(
|
||||||
auth: Option<BearerAuth>,
|
auth: Option<BearerAuth>,
|
||||||
|
@ -566,6 +589,8 @@ pub fn account_api_scope() -> Scope {
|
||||||
.service(get_account)
|
.service(get_account)
|
||||||
.service(follow_account)
|
.service(follow_account)
|
||||||
.service(unfollow_account)
|
.service(unfollow_account)
|
||||||
|
.service(mute_account)
|
||||||
|
.service(unmute_account)
|
||||||
.service(get_account_statuses)
|
.service(get_account_statuses)
|
||||||
.service(get_account_followers)
|
.service(get_account_followers)
|
||||||
.service(get_account_following)
|
.service(get_account_following)
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct InstanceStats {
|
||||||
struct InstanceStatusLimits {
|
struct InstanceStatusLimits {
|
||||||
max_characters: usize,
|
max_characters: usize,
|
||||||
max_media_attachments: usize,
|
max_media_attachments: usize,
|
||||||
|
characters_reserved_per_url: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -105,6 +106,7 @@ impl InstanceInfo {
|
||||||
statuses: InstanceStatusLimits {
|
statuses: InstanceStatusLimits {
|
||||||
max_characters: config.limits.posts.character_limit,
|
max_characters: config.limits.posts.character_limit,
|
||||||
max_media_attachments: ATTACHMENT_LIMIT,
|
max_media_attachments: ATTACHMENT_LIMIT,
|
||||||
|
characters_reserved_per_url: 32, // not real, but for compatibility
|
||||||
},
|
},
|
||||||
media_attachments: InstanceMediaLimits {
|
media_attachments: InstanceMediaLimits {
|
||||||
supported_mime_types: SUPPORTED_MEDIA_TYPES
|
supported_mime_types: SUPPORTED_MEDIA_TYPES
|
||||||
|
@ -117,7 +119,7 @@ impl InstanceInfo {
|
||||||
},
|
},
|
||||||
thumbnail: None,
|
thumbnail: None,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
languages: vec![],
|
languages: vec!["en".to_string()],
|
||||||
rules: vec![],
|
rules: vec![],
|
||||||
urls: None,
|
urls: None,
|
||||||
login_message: config.login_message.clone(),
|
login_message: config.login_message.clone(),
|
||||||
|
|
Loading…
Reference in a new issue