Merge branch 'main' into proper_scaling_for_blank_user_avatar
This commit is contained in:
commit
2adbb6f74c
@ -63,7 +63,7 @@ commands:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
steps:
|
steps:
|
||||||
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings
|
- run: cargo clippy <<^parameters.no_feature>>--no-default-features --features="${FEATURES}"<</parameters.no_feature>> --release -p <<parameters.package>> -- -D warnings -A clippy::needless_borrow
|
||||||
|
|
||||||
run_with_coverage:
|
run_with_coverage:
|
||||||
description: run command with environment for coverage
|
description: run command with environment for coverage
|
||||||
|
@ -11,7 +11,7 @@ RUN apt update &&\
|
|||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
#install and configure rust
|
#install and configure rust
|
||||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-01-15 -y &&\
|
RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2021-11-27 -y &&\
|
||||||
rustup component add rustfmt clippy &&\
|
rustup component add rustfmt clippy &&\
|
||||||
rustup component add rust-std --target wasm32-unknown-unknown
|
rustup component add rust-std --target wasm32-unknown-unknown
|
||||||
|
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
|
|
||||||
- Upgrade some dependent crates (#858)
|
- Upgrade some dependent crates (#858)
|
||||||
- Use tracing crate (#868)
|
- Use tracing crate (#868)
|
||||||
- Update Rust version to nightly-2021-01-15 (#878)
|
- Update Rust version to nightly-2021-11-27 (#961)
|
||||||
- Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878)
|
- Upgrade Tantivy to 0.13.3 and lindera-tantivy to 0.7.1 (#878)
|
||||||
- Run searcher on actor system (#870)
|
- Run searcher on actor system (#870)
|
||||||
- Use article title as its slug instead of capitalizing and inserting hyphens (#920)
|
- Use article title as its slug instead of capitalizing and inserting hyphens (#920)
|
||||||
|
- Sign GET requests to other instances (#957)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
436
Cargo.lock
generated
436
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,13 +18,13 @@ guid-create = "0.1"
|
|||||||
lettre = "0.9.2"
|
lettre = "0.9.2"
|
||||||
lettre_email = "0.9.2"
|
lettre_email = "0.9.2"
|
||||||
num_cpus = "1.10"
|
num_cpus = "1.10"
|
||||||
rocket = "=0.4.6"
|
rocket = "0.4.6"
|
||||||
rocket_contrib = { version = "=0.4.5", features = ["json"] }
|
rocket_contrib = { version = "0.4.5", features = ["json"] }
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||||
rpassword = "4.0"
|
rpassword = "4.0"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "< 1.0.70"
|
serde_json = "1.0.70"
|
||||||
shrinkwraprs = "0.2.1"
|
shrinkwraprs = "0.2.1"
|
||||||
validator = "0.8"
|
validator = "0.8"
|
||||||
validator_derive = "0.8"
|
validator_derive = "0.8"
|
||||||
|
@ -229,6 +229,10 @@ p.error {
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
@ -236,9 +240,9 @@ p.error {
|
|||||||
font-family: $playfair;
|
font-family: $playfair;
|
||||||
font-size: 1.75em;
|
font-size: 1.75em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
line-height: 1.75;
|
line-height: 1.10;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: relative;
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
transition: color 0.1s ease-in;
|
transition: color 0.1s ease-in;
|
||||||
@ -249,6 +253,7 @@ p.error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
flex-shrink: 0;
|
||||||
text-align: end;
|
text-align: end;
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
10
build.rs
10
build.rs
@ -41,9 +41,9 @@ fn main() {
|
|||||||
.expect("compile templates");
|
.expect("compile templates");
|
||||||
|
|
||||||
compile_themes().expect("Theme compilation error");
|
compile_themes().expect("Theme compilation error");
|
||||||
recursive_copy(&Path::new("assets").join("icons"), &Path::new("static"))
|
recursive_copy(&Path::new("assets").join("icons"), Path::new("static"))
|
||||||
.expect("Couldn't copy icons");
|
.expect("Couldn't copy icons");
|
||||||
recursive_copy(&Path::new("assets").join("images"), &Path::new("static"))
|
recursive_copy(&Path::new("assets").join("images"), Path::new("static"))
|
||||||
.expect("Couldn't copy images");
|
.expect("Couldn't copy images");
|
||||||
create_dir_all(&Path::new("static").join("media")).expect("Couldn't init media directory");
|
create_dir_all(&Path::new("static").join("media")).expect("Couldn't init media directory");
|
||||||
|
|
||||||
@ -97,12 +97,12 @@ fn compile_theme(path: &Path, out_dir: &Path) -> std::io::Result<()> {
|
|||||||
.components()
|
.components()
|
||||||
.skip_while(|c| *c != Component::Normal(OsStr::new("themes")))
|
.skip_while(|c| *c != Component::Normal(OsStr::new("themes")))
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.filter_map(|c| {
|
.map(|c| {
|
||||||
c.as_os_str()
|
c.as_os_str()
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.splitn(2, '.')
|
.split_once('.')
|
||||||
.next()
|
.map_or(c.as_os_str().to_str().unwrap_or_default(), |x| x.0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("-");
|
.join("-");
|
||||||
|
@ -68,4 +68,6 @@ fn new<'a>(args: &ArgMatches<'a>, conn: &Connection) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("Couldn't save instance");
|
.expect("Couldn't save instance");
|
||||||
|
Instance::cache_local(conn);
|
||||||
|
Instance::create_local_instance_user(conn).expect("Couldn't save local instance user");
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ fn main() {
|
|||||||
e => e.map(|_| ()).unwrap(),
|
e => e.map(|_| ()).unwrap(),
|
||||||
}
|
}
|
||||||
let conn = Conn::establish(CONFIG.database_url.as_str());
|
let conn = Conn::establish(CONFIG.database_url.as_str());
|
||||||
let _ = conn.as_ref().map(|conn| Instance::cache_local(conn));
|
let _ = conn.as_ref().map(Instance::cache_local);
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
("instance", Some(args)) => {
|
("instance", Some(args)) => {
|
||||||
|
@ -14,11 +14,11 @@ heck = "0.3.0"
|
|||||||
hex = "0.3"
|
hex = "0.3"
|
||||||
hyper = "0.12.33"
|
hyper = "0.12.33"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "=0.4.6"
|
rocket = "0.4.6"
|
||||||
reqwest = { version = "0.9", features = ["socks"] }
|
reqwest = { version = "0.9", features = ["socks"] }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "< 1.0.70"
|
serde_json = "1.0.70"
|
||||||
shrinkwraprs = "0.3.0"
|
shrinkwraprs = "0.3.0"
|
||||||
syntect = "4.5.0"
|
syntect = "4.5.0"
|
||||||
tokio = "0.1.22"
|
tokio = "0.1.22"
|
||||||
@ -33,3 +33,6 @@ version = "0.4"
|
|||||||
default-features = false
|
default-features = false
|
||||||
git = "https://git.joinplu.me/Plume/pulldown-cmark"
|
git = "https://git.joinplu.me/Plume/pulldown-cmark"
|
||||||
branch = "bidi-plume"
|
branch = "bidi-plume"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
once_cell = "1.5.2"
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use reqwest::header::{HeaderValue, ACCEPT};
|
use reqwest;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use super::{request, sign::Signer};
|
||||||
|
|
||||||
/// Represents an ActivityPub inbox.
|
/// Represents an ActivityPub inbox.
|
||||||
///
|
///
|
||||||
/// It routes an incoming Activity through the registered handlers.
|
/// It routes an incoming Activity through the registered handlers.
|
||||||
@ -10,7 +12,50 @@ use std::fmt::Debug;
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate activitypub;
|
/// # extern crate activitypub;
|
||||||
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note};
|
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note};
|
||||||
|
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
/// # use once_cell::sync::Lazy;
|
||||||
/// # use plume_common::activity_pub::inbox::*;
|
/// # use plume_common::activity_pub::inbox::*;
|
||||||
|
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
|
||||||
|
/// #
|
||||||
|
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
||||||
|
/// #
|
||||||
|
/// # struct MySigner {
|
||||||
|
/// # public_key: String,
|
||||||
|
/// # private_key: String,
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl MySigner {
|
||||||
|
/// # fn new() -> Self {
|
||||||
|
/// # let (pub_key, priv_key) = gen_keypair();
|
||||||
|
/// # Self {
|
||||||
|
/// # public_key: String::from_utf8(pub_key).unwrap(),
|
||||||
|
/// # private_key: String::from_utf8(priv_key).unwrap(),
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl Signer for MySigner {
|
||||||
|
/// # fn get_key_id(&self) -> String {
|
||||||
|
/// # "mysigner".into()
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
||||||
|
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
|
/// # .unwrap();
|
||||||
|
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
/// # signer.update(to_sign.as_bytes()).unwrap();
|
||||||
|
/// # signer.sign_to_vec().map_err(|_| SignError())
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
||||||
|
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
|
/// # .unwrap();
|
||||||
|
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
/// # verifier.update(data.as_bytes()).unwrap();
|
||||||
|
/// # verifier.verify(&signature).map_err(|_| SignError())
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
/// # struct User;
|
/// # struct User;
|
||||||
/// # impl FromId<()> for User {
|
/// # impl FromId<()> for User {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@ -23,6 +68,10 @@ use std::fmt::Debug;
|
|||||||
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(User)
|
/// # Ok(User)
|
||||||
/// # }
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// # &*MY_SIGNER
|
||||||
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsActor<&()> for User {
|
/// # impl AsActor<&()> for User {
|
||||||
/// # fn get_inbox_url(&self) -> String {
|
/// # fn get_inbox_url(&self) -> String {
|
||||||
@ -42,6 +91,10 @@ use std::fmt::Debug;
|
|||||||
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(Message)
|
/// # Ok(Message)
|
||||||
/// # }
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// # &*MY_SIGNER
|
||||||
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsObject<User, Create, &()> for Message {
|
/// # impl AsObject<User, Create, &()> for Message {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@ -207,7 +260,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle the activity
|
// Handle the activity
|
||||||
match obj.activity(ctx, actor, &act_id) {
|
match obj.activity(ctx, actor, act_id) {
|
||||||
Ok(res) => Inbox::Handled(res.into()),
|
Ok(res) => Inbox::Handled(res.into()),
|
||||||
Err(e) => Inbox::Failed(e),
|
Err(e) => Inbox::Failed(e),
|
||||||
}
|
}
|
||||||
@ -311,35 +364,16 @@ pub trait FromId<C>: Sized {
|
|||||||
id: &str,
|
id: &str,
|
||||||
proxy: Option<reqwest::Proxy>,
|
proxy: Option<reqwest::Proxy>,
|
||||||
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
|
||||||
if let Some(proxy) = proxy {
|
request::get(id, Self::get_sender(), proxy)
|
||||||
reqwest::ClientBuilder::new().proxy(proxy)
|
.map_err(|_| (None, InboxError::DerefError))
|
||||||
} else {
|
.and_then(|mut r| {
|
||||||
reqwest::ClientBuilder::new()
|
let json: serde_json::Value = r
|
||||||
}
|
.json()
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
||||||
.build()
|
serde_json::from_value(json.clone())
|
||||||
.map_err(|_| (None, InboxError::DerefError.into()))?
|
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
|
||||||
.get(id)
|
})
|
||||||
.header(
|
.map_err(|(json, e)| (json, e.into()))
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&super::ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
.map_err(|_| (None, InboxError::DerefError.into()))?,
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.map_err(|_| (None, InboxError::DerefError))
|
|
||||||
.and_then(|mut r| {
|
|
||||||
let json: serde_json::Value = r
|
|
||||||
.json()
|
|
||||||
.map_err(|_| (None, InboxError::InvalidObject(None)))?;
|
|
||||||
serde_json::from_value(json.clone())
|
|
||||||
.map_err(|_| (Some(json), InboxError::InvalidObject(None)))
|
|
||||||
})
|
|
||||||
.map_err(|(json, e)| (json, e.into()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a `Self` from its ActivityPub representation
|
/// Builds a `Self` from its ActivityPub representation
|
||||||
@ -347,6 +381,8 @@ pub trait FromId<C>: Sized {
|
|||||||
|
|
||||||
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
|
/// Tries to find a `Self` with a given ID (`id`), using `ctx` (a database)
|
||||||
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
|
fn from_db(ctx: &C, id: &str) -> Result<Self, Self::Error>;
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should be implemented by anything representing an ActivityPub actor.
|
/// Should be implemented by anything representing an ActivityPub actor.
|
||||||
@ -385,6 +421,49 @@ pub trait AsActor<C> {
|
|||||||
/// # extern crate activitypub;
|
/// # extern crate activitypub;
|
||||||
/// # use activitypub::{activity::Create, actor::Person, object::Note};
|
/// # use activitypub::{activity::Create, actor::Person, object::Note};
|
||||||
/// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId};
|
/// # use plume_common::activity_pub::inbox::{AsActor, AsObject, FromId};
|
||||||
|
/// # use plume_common::activity_pub::sign::{gen_keypair, Error as SignError, Result as SignResult, Signer};
|
||||||
|
/// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
/// # use once_cell::sync::Lazy;
|
||||||
|
/// #
|
||||||
|
/// # static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
||||||
|
/// #
|
||||||
|
/// # struct MySigner {
|
||||||
|
/// # public_key: String,
|
||||||
|
/// # private_key: String,
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl MySigner {
|
||||||
|
/// # fn new() -> Self {
|
||||||
|
/// # let (pub_key, priv_key) = gen_keypair();
|
||||||
|
/// # Self {
|
||||||
|
/// # public_key: String::from_utf8(pub_key).unwrap(),
|
||||||
|
/// # private_key: String::from_utf8(priv_key).unwrap(),
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # impl Signer for MySigner {
|
||||||
|
/// # fn get_key_id(&self) -> String {
|
||||||
|
/// # "mysigner".into()
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
||||||
|
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
|
/// # .unwrap();
|
||||||
|
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
/// # signer.update(to_sign.as_bytes()).unwrap();
|
||||||
|
/// # signer.sign_to_vec().map_err(|_| SignError())
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
||||||
|
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
|
/// # .unwrap();
|
||||||
|
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
/// # verifier.update(data.as_bytes()).unwrap();
|
||||||
|
/// # verifier.verify(&signature).map_err(|_| SignError())
|
||||||
|
/// # }
|
||||||
|
/// # }
|
||||||
|
/// #
|
||||||
/// # struct Account;
|
/// # struct Account;
|
||||||
/// # impl FromId<()> for Account {
|
/// # impl FromId<()> for Account {
|
||||||
/// # type Error = ();
|
/// # type Error = ();
|
||||||
@ -397,6 +476,10 @@ pub trait AsActor<C> {
|
|||||||
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
/// # fn from_activity(_: &(), obj: Person) -> Result<Self, Self::Error> {
|
||||||
/// # Ok(Account)
|
/// # Ok(Account)
|
||||||
/// # }
|
/// # }
|
||||||
|
/// #
|
||||||
|
/// # fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// # &*MY_SIGNER
|
||||||
|
/// # }
|
||||||
/// # }
|
/// # }
|
||||||
/// # impl AsActor<()> for Account {
|
/// # impl AsActor<()> for Account {
|
||||||
/// # fn get_inbox_url(&self) -> String {
|
/// # fn get_inbox_url(&self) -> String {
|
||||||
@ -420,6 +503,10 @@ pub trait AsActor<C> {
|
|||||||
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
/// fn from_activity(_: &(), obj: Note) -> Result<Self, Self::Error> {
|
||||||
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? })
|
/// Ok(Message { text: obj.object_props.content_string().map_err(|_| ())? })
|
||||||
/// }
|
/// }
|
||||||
|
///
|
||||||
|
/// fn get_sender() -> &'static dyn Signer {
|
||||||
|
/// &*MY_SIGNER
|
||||||
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// impl AsObject<Account, Create, ()> for Message {
|
/// impl AsObject<Account, Create, ()> for Message {
|
||||||
@ -459,7 +546,51 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::activity_pub::sign::{
|
||||||
|
gen_keypair, Error as SignError, Result as SignResult, Signer,
|
||||||
|
};
|
||||||
use activitypub::{activity::*, actor::Person, object::Note};
|
use activitypub::{activity::*, actor::Person, object::Note};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
|
|
||||||
|
static MY_SIGNER: Lazy<MySigner> = Lazy::new(|| MySigner::new());
|
||||||
|
|
||||||
|
struct MySigner {
|
||||||
|
public_key: String,
|
||||||
|
private_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MySigner {
|
||||||
|
fn new() -> Self {
|
||||||
|
let (pub_key, priv_key) = gen_keypair();
|
||||||
|
Self {
|
||||||
|
public_key: String::from_utf8(pub_key).unwrap(),
|
||||||
|
private_key: String::from_utf8(priv_key).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signer for MySigner {
|
||||||
|
fn get_key_id(&self) -> String {
|
||||||
|
"mysigner".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
||||||
|
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
signer.update(to_sign.as_bytes()).unwrap();
|
||||||
|
signer.sign_to_vec().map_err(|_| SignError())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
||||||
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
|
.unwrap();
|
||||||
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
verifier.update(data.as_bytes()).unwrap();
|
||||||
|
verifier.verify(&signature).map_err(|_| SignError())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct MyActor;
|
struct MyActor;
|
||||||
impl FromId<()> for MyActor {
|
impl FromId<()> for MyActor {
|
||||||
@ -473,6 +604,10 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
||||||
Ok(MyActor)
|
Ok(MyActor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
&*MY_SIGNER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&()> for MyActor {
|
impl AsActor<&()> for MyActor {
|
||||||
@ -497,6 +632,10 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Note) -> Result<Self, Self::Error> {
|
||||||
Ok(MyObject)
|
Ok(MyObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
&*MY_SIGNER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl AsObject<MyActor, Create, &()> for MyObject {
|
impl AsObject<MyActor, Create, &()> for MyObject {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
@ -601,6 +740,10 @@ mod tests {
|
|||||||
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
fn from_activity(_: &(), _obj: Person) -> Result<Self, Self::Error> {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
&*MY_SIGNER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl AsActor<&()> for FailingActor {
|
impl AsActor<&()> for FailingActor {
|
||||||
fn get_inbox_url(&self) -> String {
|
fn get_inbox_url(&self) -> String {
|
||||||
|
@ -145,7 +145,7 @@ where
|
|||||||
warn!("Inbox doesn't have host: {:?}", &inbox);
|
warn!("Inbox doesn't have host: {:?}", &inbox);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable"));
|
let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"));
|
||||||
if host_header_value.is_err() {
|
if host_header_value.is_err() {
|
||||||
warn!("Header value is invalid: {:?}", url.host_str());
|
warn!("Header value is invalid: {:?}", url.host_str());
|
||||||
continue;
|
continue;
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
use chrono::{offset::Utc, DateTime};
|
use chrono::{offset::Utc, DateTime};
|
||||||
use openssl::hash::{Hasher, MessageDigest};
|
use openssl::hash::{Hasher, MessageDigest};
|
||||||
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
|
use reqwest::{
|
||||||
|
header::{
|
||||||
|
HeaderMap, HeaderValue, InvalidHeaderValue, ACCEPT, CONTENT_TYPE, DATE, HOST, USER_AGENT,
|
||||||
|
},
|
||||||
|
ClientBuilder, Proxy, Response, Url, UrlError,
|
||||||
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
@ -13,6 +18,24 @@ const PLUME_USER_AGENT: &str = concat!("Plume/", env!("CARGO_PKG_VERSION"));
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error();
|
pub struct Error();
|
||||||
|
|
||||||
|
impl From<UrlError> for Error {
|
||||||
|
fn from(_err: UrlError) -> Self {
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InvalidHeaderValue> for Error {
|
||||||
|
fn from(_err: InvalidHeaderValue) -> Self {
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
fn from(_err: reqwest::Error) -> Self {
|
||||||
|
Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Digest(String);
|
pub struct Digest(String);
|
||||||
|
|
||||||
impl Digest {
|
impl Digest {
|
||||||
@ -118,8 +141,8 @@ type Path<'a> = &'a str;
|
|||||||
type Query<'a> = &'a str;
|
type Query<'a> = &'a str;
|
||||||
type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>);
|
type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>);
|
||||||
|
|
||||||
pub fn signature<S: Signer>(
|
pub fn signature(
|
||||||
signer: &S,
|
signer: &dyn Signer,
|
||||||
headers: &HeaderMap,
|
headers: &HeaderMap,
|
||||||
request_target: RequestTarget,
|
request_target: RequestTarget,
|
||||||
) -> Result<HeaderValue, Error> {
|
) -> Result<HeaderValue, Error> {
|
||||||
@ -164,10 +187,35 @@ pub fn signature<S: Signer>(
|
|||||||
)).map_err(|_| Error())
|
)).map_err(|_| Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get(url_str: &str, sender: &dyn Signer, proxy: Option<Proxy>) -> Result<Response, Error> {
|
||||||
|
let mut headers = headers();
|
||||||
|
let url = Url::parse(url_str)?;
|
||||||
|
if !url.has_host() {
|
||||||
|
return Err(Error());
|
||||||
|
}
|
||||||
|
let host_header_value = HeaderValue::from_str(url.host_str().expect("Unreachable"))?;
|
||||||
|
headers.insert(HOST, host_header_value);
|
||||||
|
if let Some(proxy) = proxy {
|
||||||
|
ClientBuilder::new().proxy(proxy)
|
||||||
|
} else {
|
||||||
|
ClientBuilder::new()
|
||||||
|
}
|
||||||
|
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
||||||
|
.build()?
|
||||||
|
.get(url_str)
|
||||||
|
.headers(headers.clone())
|
||||||
|
.header(
|
||||||
|
"Signature",
|
||||||
|
signature(sender, &headers, ("get", url.path(), url.query()))?,
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.map_err(|_| Error())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{signature, Error};
|
use super::signature;
|
||||||
use crate::activity_pub::sign::{gen_keypair, Signer};
|
use crate::activity_pub::sign::{gen_keypair, Error, Result, Signer};
|
||||||
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
|
||||||
use reqwest::header::HeaderMap;
|
use reqwest::header::HeaderMap;
|
||||||
|
|
||||||
@ -187,13 +235,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signer for MySigner {
|
impl Signer for MySigner {
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String {
|
fn get_key_id(&self) -> String {
|
||||||
"mysigner".into()
|
"mysigner".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error> {
|
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
||||||
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
@ -201,7 +247,7 @@ mod tests {
|
|||||||
signer.sign_to_vec().map_err(|_| Error())
|
signer.sign_to_vec().map_err(|_| Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error> {
|
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
|
||||||
|
@ -19,20 +19,25 @@ pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error();
|
pub struct Error();
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
impl From<openssl::error::ErrorStack> for Error {
|
||||||
|
fn from(_: openssl::error::ErrorStack) -> Self {
|
||||||
|
Self()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Signer {
|
pub trait Signer {
|
||||||
type Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String;
|
fn get_key_id(&self) -> String;
|
||||||
|
|
||||||
/// Sign some data with the signer keypair
|
/// Sign some data with the signer keypair
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error>;
|
fn sign(&self, to_sign: &str) -> Result<Vec<u8>>;
|
||||||
/// Verify if the signature is valid
|
/// Verify if the signature is valid
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error>;
|
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Signable {
|
pub trait Signable {
|
||||||
fn sign<T>(&mut self, creator: &T) -> Result<&mut Self, Error>
|
fn sign<T>(&mut self, creator: &T) -> Result<&mut Self>
|
||||||
where
|
where
|
||||||
T: Signer;
|
T: Signer;
|
||||||
fn verify<T>(self, creator: &T) -> bool
|
fn verify<T>(self, creator: &T) -> bool
|
||||||
@ -46,7 +51,7 @@ pub trait Signable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signable for serde_json::Value {
|
impl Signable for serde_json::Value {
|
||||||
fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value, Error> {
|
fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value> {
|
||||||
let creation_date = Utc::now().to_rfc3339();
|
let creation_date = Utc::now().to_rfc3339();
|
||||||
let mut options = json!({
|
let mut options = json!({
|
||||||
"type": "RsaSignature2017",
|
"type": "RsaSignature2017",
|
||||||
@ -182,7 +187,7 @@ pub fn verify_http_headers<S: Signer + ::std::fmt::Debug>(
|
|||||||
}
|
}
|
||||||
let digest = all_headers.get_one("digest").unwrap_or("");
|
let digest = all_headers.get_one("digest").unwrap_or("");
|
||||||
let digest = request::Digest::from_header(digest);
|
let digest = request::Digest::from_header(digest);
|
||||||
if !digest.map(|d| d.verify_header(&data)).unwrap_or(false) {
|
if !digest.map(|d| d.verify_header(data)).unwrap_or(false) {
|
||||||
// signature was valid, but body content does not match its digest
|
// signature was valid, but body content does not match its digest
|
||||||
return SignatureValidity::Invalid;
|
return SignatureValidity::Invalid;
|
||||||
}
|
}
|
||||||
|
@ -141,13 +141,13 @@ fn highlight_code<'a>(
|
|||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
let syntax_set = SyntaxSet::load_defaults_newlines();
|
let syntax_set = SyntaxSet::load_defaults_newlines();
|
||||||
let syntax = syntax_set.find_syntax_by_token(&lang).unwrap_or_else(|| {
|
let syntax = syntax_set.find_syntax_by_token(lang).unwrap_or_else(|| {
|
||||||
syntax_set
|
syntax_set
|
||||||
.find_syntax_by_name(&lang)
|
.find_syntax_by_name(lang)
|
||||||
.unwrap_or_else(|| syntax_set.find_syntax_plain_text())
|
.unwrap_or_else(|| syntax_set.find_syntax_plain_text())
|
||||||
});
|
});
|
||||||
let mut html = ClassedHTMLGenerator::new_with_class_style(
|
let mut html = ClassedHTMLGenerator::new_with_class_style(
|
||||||
&syntax,
|
syntax,
|
||||||
&syntax_set,
|
&syntax_set,
|
||||||
ClassStyle::Spaced,
|
ClassStyle::Spaced,
|
||||||
);
|
);
|
||||||
@ -334,16 +334,15 @@ pub fn md_to_html<'a>(
|
|||||||
text_acc.push(c)
|
text_acc.push(c)
|
||||||
}
|
}
|
||||||
let mention = text_acc;
|
let mention = text_acc;
|
||||||
let short_mention = mention.splitn(1, '@').next().unwrap_or("");
|
|
||||||
let link = Tag::Link(
|
let link = Tag::Link(
|
||||||
LinkType::Inline,
|
LinkType::Inline,
|
||||||
format!("{}@/{}/", base_url, &mention).into(),
|
format!("{}@/{}/", base_url, &mention).into(),
|
||||||
short_mention.to_owned().into(),
|
mention.clone().into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
mentions.push(mention.clone());
|
mentions.push(mention.clone());
|
||||||
events.push(Event::Start(link.clone()));
|
events.push(Event::Start(link.clone()));
|
||||||
events.push(Event::Text(format!("@{}", &short_mention).into()));
|
events.push(Event::Text(format!("@{}", &mention).into()));
|
||||||
events.push(Event::End(link));
|
events.push(Event::End(link));
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -54,11 +54,6 @@ pub enum EditorError {
|
|||||||
DOMError,
|
DOMError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::option::NoneError> for EditorError {
|
|
||||||
fn from(_: std::option::NoneError) -> Self {
|
|
||||||
EditorError::NoneError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000;
|
const AUTOSAVE_DEBOUNCE_TIME: i32 = 5000;
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct AutosaveInformation {
|
struct AutosaveInformation {
|
||||||
@ -198,7 +193,7 @@ fn clear_autosave() {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.remove_item(&get_autosave_id())
|
.remove_item(&get_autosave_id())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
console::log_1(&&format!("Saved to {}", &get_autosave_id()).into());
|
console::log_1(&format!("Saved to {}", &get_autosave_id()).into());
|
||||||
}
|
}
|
||||||
type TimeoutHandle = i32;
|
type TimeoutHandle = i32;
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
@ -366,7 +361,9 @@ fn init_editor() -> Result<(), EditorError> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let old_ed = old_ed.unwrap();
|
let old_ed = old_ed.unwrap();
|
||||||
let old_title = document().get_element_by_id("plume-editor-title")?;
|
let old_title = document()
|
||||||
|
.get_element_by_id("plume-editor-title")
|
||||||
|
.ok_or(EditorError::NoneError)?;
|
||||||
old_ed
|
old_ed
|
||||||
.dyn_ref::<HtmlElement>()
|
.dyn_ref::<HtmlElement>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -434,7 +431,8 @@ fn init_editor() -> Result<(), EditorError> {
|
|||||||
bg.class_list().add_1("show").unwrap();
|
bg.class_list().add_1("show").unwrap();
|
||||||
})) as Box<dyn FnMut(MouseEvent)>);
|
})) as Box<dyn FnMut(MouseEvent)>);
|
||||||
document()
|
document()
|
||||||
.get_element_by_id("publish")?
|
.get_element_by_id("publish")
|
||||||
|
.ok_or(EditorError::NoneError)?
|
||||||
.add_event_listener_with_callback("click", show_popup.as_ref().unchecked_ref())
|
.add_event_listener_with_callback("click", show_popup.as_ref().unchecked_ref())
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
show_popup.forget();
|
show_popup.forget();
|
||||||
@ -528,8 +526,14 @@ fn init_popup(
|
|||||||
cover_label
|
cover_label
|
||||||
.set_attribute("for", "cover")
|
.set_attribute("for", "cover")
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
let cover = document.get_element_by_id("cover")?;
|
let cover = document
|
||||||
cover.parent_element()?.remove_child(&cover).ok();
|
.get_element_by_id("cover")
|
||||||
|
.ok_or(EditorError::NoneError)?;
|
||||||
|
cover
|
||||||
|
.parent_element()
|
||||||
|
.ok_or(EditorError::NoneError)?
|
||||||
|
.remove_child(&cover)
|
||||||
|
.ok();
|
||||||
popup
|
popup
|
||||||
.append_child(&cover_label)
|
.append_child(&cover_label)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
@ -554,7 +558,7 @@ fn init_popup(
|
|||||||
draft.set_checked(draft_checkbox.checked());
|
draft.set_checked(draft_checkbox.checked());
|
||||||
|
|
||||||
draft_label
|
draft_label
|
||||||
.append_child(&draft)
|
.append_child(draft)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
draft_label
|
draft_label
|
||||||
.append_child(&document.create_text_node(&i18n!(CATALOG, "This is a draft")))
|
.append_child(&document.create_text_node(&i18n!(CATALOG, "This is a draft")))
|
||||||
@ -620,11 +624,12 @@ fn init_popup(
|
|||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
callback.forget();
|
callback.forget();
|
||||||
popup
|
popup
|
||||||
.append_child(&button)
|
.append_child(button)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
|
|
||||||
document
|
document
|
||||||
.body()?
|
.body()
|
||||||
|
.ok_or(EditorError::NoneError)?
|
||||||
.append_child(&popup)
|
.append_child(&popup)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
Ok(popup)
|
Ok(popup)
|
||||||
@ -641,7 +646,8 @@ fn init_popup_bg() -> Result<Element, EditorError> {
|
|||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
|
|
||||||
document()
|
document()
|
||||||
.body()?
|
.body()
|
||||||
|
.ok_or(EditorError::NoneError)?
|
||||||
.append_child(&bg)
|
.append_child(&bg)
|
||||||
.map_err(|_| EditorError::DOMError)?;
|
.map_err(|_| EditorError::DOMError)?;
|
||||||
let callback = Closure::wrap(Box::new(|_| close_popup()) as Box<dyn FnMut(MouseEvent)>);
|
let callback = Closure::wrap(Box::new(|_| close_popup()) as Box<dyn FnMut(MouseEvent)>);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![recursion_limit = "128"]
|
#![recursion_limit = "128"]
|
||||||
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
|
#![feature(decl_macro, proc_macro_hygiene)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate gettext_macros;
|
extern crate gettext_macros;
|
||||||
@ -61,7 +61,7 @@ lazy_static! {
|
|||||||
static ref CATALOG: gettext::Catalog = {
|
static ref CATALOG: gettext::Catalog = {
|
||||||
let catalogs = include_i18n!();
|
let catalogs = include_i18n!();
|
||||||
let lang = window().unwrap().navigator().language().unwrap();
|
let lang = window().unwrap().navigator().language().unwrap();
|
||||||
let lang = lang.splitn(2, '-').next().unwrap_or("en");
|
let lang = lang.split_once('-').map_or("en", |x| x.0);
|
||||||
|
|
||||||
let english_position = catalogs
|
let english_position = catalogs
|
||||||
.iter()
|
.iter()
|
||||||
@ -85,7 +85,7 @@ pub fn main() -> Result<(), JsValue> {
|
|||||||
menu();
|
menu();
|
||||||
search();
|
search();
|
||||||
editor::init()
|
editor::init()
|
||||||
.map_err(|e| console::error_1(&&format!("Editor error: {:?}", e).into()))
|
.map_err(|e| console::error_1(&format!("Editor error: {:?}", e).into()))
|
||||||
.ok();
|
.ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ pub fn import_migrations(input: TokenStream) -> TokenStream {
|
|||||||
(name, up_sql, down_sql)
|
(name, up_sql, down_sql)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let migrations_name = migrations.iter().map(|m| &m.0).collect::<Vec<_>>();
|
let migrations_name = migrations.iter().map(|m| &m.0);
|
||||||
let migrations_up = migrations
|
let migrations_up = migrations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| m.1.as_str())
|
.map(|m| m.1.as_str())
|
||||||
@ -103,7 +103,7 @@ fn file_to_migration(file: &str) -> TokenStream2 {
|
|||||||
acc.push('\n');
|
acc.push('\n');
|
||||||
}
|
}
|
||||||
} else if let Some(acc_str) = line.strip_prefix("--#!") {
|
} else if let Some(acc_str) = line.strip_prefix("--#!") {
|
||||||
acc.push_str(&acc_str);
|
acc.push_str(acc_str);
|
||||||
acc.push('\n');
|
acc.push('\n');
|
||||||
} else if line.starts_with("--") {
|
} else if line.starts_with("--") {
|
||||||
continue;
|
continue;
|
||||||
|
@ -15,13 +15,13 @@ lazy_static = "1.0"
|
|||||||
ldap3 = "0.7.1"
|
ldap3 = "0.7.1"
|
||||||
migrations_internals= "1.4.0"
|
migrations_internals= "1.4.0"
|
||||||
openssl = "0.10.22"
|
openssl = "0.10.22"
|
||||||
rocket = "=0.4.6"
|
rocket = "0.4.6"
|
||||||
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
rocket_i18n = { git = "https://github.com/Plume-org/rocket_i18n", rev = "e922afa7c366038b3433278c03b1456b346074f2" }
|
||||||
reqwest = "0.9"
|
reqwest = "0.9"
|
||||||
scheduled-thread-pool = "0.2.2"
|
scheduled-thread-pool = "0.2.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "< 1.0.70"
|
serde_json = "1.0.70"
|
||||||
tantivy = "0.13.3"
|
tantivy = "0.13.3"
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
|
@ -86,14 +86,18 @@ impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut parsed_header = headers[0].split(' ');
|
let mut parsed_header = headers[0].split(' ');
|
||||||
let auth_type = parsed_header.next().map_or_else(
|
let auth_type = parsed_header
|
||||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)),
|
.next()
|
||||||
Outcome::Success,
|
.map_or_else::<rocket::Outcome<&str, _, ()>, _, _>(
|
||||||
)?;
|
|| Outcome::Failure((Status::BadRequest, TokenError::NoType)),
|
||||||
let val = parsed_header.next().map_or_else(
|
Outcome::Success,
|
||||||
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)),
|
)?;
|
||||||
Outcome::Success,
|
let val = parsed_header
|
||||||
)?;
|
.next()
|
||||||
|
.map_or_else::<rocket::Outcome<&str, _, ()>, _, _>(
|
||||||
|
|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)),
|
||||||
|
Outcome::Success,
|
||||||
|
)?;
|
||||||
|
|
||||||
if auth_type == "Bearer" {
|
if auth_type == "Bearer" {
|
||||||
let conn = request
|
let conn = request
|
||||||
|
@ -28,7 +28,7 @@ impl BlocklistedEmail {
|
|||||||
pub fn delete_entries(conn: &Connection, ids: Vec<i32>) -> Result<bool> {
|
pub fn delete_entries(conn: &Connection, ids: Vec<i32>) -> Result<bool> {
|
||||||
use diesel::delete;
|
use diesel::delete;
|
||||||
for i in ids {
|
for i in ids {
|
||||||
let be: BlocklistedEmail = BlocklistedEmail::find_by_id(&conn, i)?;
|
let be: BlocklistedEmail = BlocklistedEmail::find_by_id(conn, i)?;
|
||||||
delete(&be).execute(conn)?;
|
delete(&be).execute(conn)?;
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
@ -149,7 +149,15 @@ impl Blog {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||||
.ok_or(Error::Webfinger)
|
.ok_or(Error::Webfinger)
|
||||||
.and_then(|l| Blog::from_id(conn, &l.href?, None, CONFIG.proxy()).map_err(|(_, e)| e))
|
.and_then(|l| {
|
||||||
|
Blog::from_id(
|
||||||
|
conn,
|
||||||
|
&l.href.ok_or(Error::MissingApProperty)?,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.map_err(|(_, e)| e)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
|
||||||
@ -236,7 +244,7 @@ impl Blog {
|
|||||||
(min, max): (i32, i32),
|
(min, max): (i32, i32),
|
||||||
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
||||||
let mut coll = OrderedCollectionPage::default();
|
let mut coll = OrderedCollectionPage::default();
|
||||||
let acts = self.get_activity_page(&conn, (min, max));
|
let acts = self.get_activity_page(conn, (min, max));
|
||||||
//This still doesn't do anything because the outbox
|
//This still doesn't do anything because the outbox
|
||||||
//doesn't do anything yet
|
//doesn't do anything yet
|
||||||
coll.collection_page_props.set_next_link(Id::new(&format!(
|
coll.collection_page_props.set_next_link(Id::new(&format!(
|
||||||
@ -265,7 +273,10 @@ impl Blog {
|
|||||||
|
|
||||||
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
||||||
PKey::from_rsa(Rsa::private_key_from_pem(
|
PKey::from_rsa(Rsa::private_key_from_pem(
|
||||||
self.private_key.clone()?.as_ref(),
|
self.private_key
|
||||||
|
.clone()
|
||||||
|
.ok_or(Error::MissingApProperty)?
|
||||||
|
.as_ref(),
|
||||||
)?)
|
)?)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
@ -318,7 +329,7 @@ impl Blog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||||
for post in Post::get_for_blog(conn, &self)? {
|
for post in Post::get_for_blog(conn, self)? {
|
||||||
post.delete(conn)?;
|
post.delete(conn)?;
|
||||||
}
|
}
|
||||||
diesel::delete(self)
|
diesel::delete(self)
|
||||||
@ -339,12 +350,12 @@ impl FromId<DbConn> for Blog {
|
|||||||
type Object = CustomGroup;
|
type Object = CustomGroup;
|
||||||
|
|
||||||
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
|
||||||
Self::find_by_ap_url(&conn, id)
|
Self::find_by_ap_url(conn, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> {
|
fn from_activity(conn: &DbConn, acct: CustomGroup) -> Result<Self> {
|
||||||
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
||||||
let inst = url.host_str()?;
|
let inst = url.host_str().ok_or(Error::Url)?;
|
||||||
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
||||||
Instance::insert(
|
Instance::insert(
|
||||||
conn,
|
conn,
|
||||||
@ -432,6 +443,10 @@ impl FromId<DbConn> for Blog {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn sign::Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&PlumeRocket> for Blog {
|
impl AsActor<&PlumeRocket> for Blog {
|
||||||
@ -451,24 +466,22 @@ impl AsActor<&PlumeRocket> for Blog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl sign::Signer for Blog {
|
impl sign::Signer for Blog {
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String {
|
fn get_key_id(&self) -> String {
|
||||||
format!("{}#main-key", self.ap_url)
|
format!("{}#main-key", self.ap_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
fn sign(&self, to_sign: &str) -> sign::Result<Vec<u8>> {
|
||||||
let key = self.get_keypair()?;
|
let key = self.get_keypair().map_err(|_| sign::Error())?;
|
||||||
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
|
let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
signer.update(to_sign.as_bytes())?;
|
signer.update(to_sign.as_bytes())?;
|
||||||
signer.sign_to_vec().map_err(Error::from)
|
signer.sign_to_vec().map_err(sign::Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> sign::Result<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
||||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
|
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
verifier.update(data.as_bytes())?;
|
verifier.update(data.as_bytes())?;
|
||||||
verifier.verify(&signature).map_err(Error::from)
|
verifier.verify(signature).map_err(sign::Error::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
|||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
@ -141,18 +142,20 @@ impl Comment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> {
|
pub fn create_activity(&self, conn: &DbConn) -> Result<Create> {
|
||||||
let author = User::get(&conn, self.author_id)?;
|
let author = User::get(conn, self.author_id)?;
|
||||||
|
|
||||||
let note = self.to_activity(conn)?;
|
let note = self.to_activity(conn)?;
|
||||||
let mut act = Create::default();
|
let mut act = Create::default();
|
||||||
act.create_props.set_actor_link(author.into_id())?;
|
act.create_props.set_actor_link(author.into_id())?;
|
||||||
act.create_props.set_object_object(note.clone())?;
|
act.create_props.set_object_object(note.clone())?;
|
||||||
act.object_props
|
act.object_props.set_id_string(format!(
|
||||||
.set_id_string(format!("{}/activity", self.ap_url.clone()?,))?;
|
"{}/activity",
|
||||||
|
self.ap_url.clone().ok_or(Error::MissingApProperty)?,
|
||||||
|
))?;
|
||||||
act.object_props
|
act.object_props
|
||||||
.set_to_link_vec(note.object_props.to_link_vec::<Id>()?)?;
|
.set_to_link_vec(note.object_props.to_link_vec::<Id>()?)?;
|
||||||
act.object_props
|
act.object_props
|
||||||
.set_cc_link_vec(vec![Id::new(self.get_author(&conn)?.followers_endpoint)])?;
|
.set_cc_link_vec(vec![Id::new(self.get_author(conn)?.followers_endpoint)])?;
|
||||||
Ok(act)
|
Ok(act)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +185,9 @@ impl Comment {
|
|||||||
.set_actor_link(self.get_author(conn)?.into_id())?;
|
.set_actor_link(self.get_author(conn)?.into_id())?;
|
||||||
|
|
||||||
let mut tombstone = Tombstone::default();
|
let mut tombstone = Tombstone::default();
|
||||||
tombstone.object_props.set_id_string(self.ap_url.clone()?)?;
|
tombstone
|
||||||
|
.object_props
|
||||||
|
.set_id_string(self.ap_url.clone().ok_or(Error::MissingApProperty)?)?;
|
||||||
act.delete_props.set_object_object(tombstone)?;
|
act.delete_props.set_object_object(tombstone)?;
|
||||||
|
|
||||||
act.object_props
|
act.object_props
|
||||||
@ -204,7 +209,13 @@ impl FromId<DbConn> for Comment {
|
|||||||
|
|
||||||
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
|
fn from_activity(conn: &DbConn, note: Note) -> Result<Self> {
|
||||||
let comm = {
|
let comm = {
|
||||||
let previous_url = note.object_props.in_reply_to.as_ref()?.as_str()?;
|
let previous_url = note
|
||||||
|
.object_props
|
||||||
|
.in_reply_to
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(Error::MissingApProperty)?
|
||||||
|
.as_str()
|
||||||
|
.ok_or(Error::MissingApProperty)?;
|
||||||
let previous_comment = Comment::find_by_ap_url(conn, previous_url);
|
let previous_comment = Comment::find_by_ap_url(conn, previous_url);
|
||||||
|
|
||||||
let is_public = |v: &Option<serde_json::Value>| match v
|
let is_public = |v: &Option<serde_json::Value>| match v
|
||||||
@ -318,6 +329,10 @@ impl FromId<DbConn> for Comment {
|
|||||||
comm.notify(conn)?;
|
comm.notify(conn)?;
|
||||||
Ok(comm)
|
Ok(comm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Comment {
|
impl AsObject<User, Create, &DbConn> for Comment {
|
||||||
@ -346,7 +361,7 @@ impl AsObject<User, Delete, &DbConn> for Comment {
|
|||||||
m.delete(conn)?;
|
m.delete(conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for n in Notification::find_for_comment(&conn, &self)? {
|
for n in Notification::find_for_comment(conn, &self)? {
|
||||||
n.delete(&**conn)?;
|
n.delete(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ pub struct Config {
|
|||||||
pub db_min_idle: Option<u32>,
|
pub db_min_idle: Option<u32>,
|
||||||
pub search_index: String,
|
pub search_index: String,
|
||||||
pub search_tokenizers: SearchTokenizerConfig,
|
pub search_tokenizers: SearchTokenizerConfig,
|
||||||
pub rocket: Result<RocketConfig, RocketError>,
|
pub rocket: Result<RocketConfig, InvalidRocketConfig>,
|
||||||
pub logo: LogoConfig,
|
pub logo: LogoConfig,
|
||||||
pub default_theme: String,
|
pub default_theme: String,
|
||||||
pub media_directory: String,
|
pub media_directory: String,
|
||||||
@ -31,21 +31,21 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum RocketError {
|
pub enum InvalidRocketConfig {
|
||||||
InvalidEnv,
|
Env,
|
||||||
InvalidAddress,
|
Address,
|
||||||
InvalidSecretKey,
|
SecretKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rocket_config() -> Result<RocketConfig, RocketError> {
|
fn get_rocket_config() -> Result<RocketConfig, InvalidRocketConfig> {
|
||||||
let mut c = RocketConfig::active().map_err(|_| RocketError::InvalidEnv)?;
|
let mut c = RocketConfig::active().map_err(|_| InvalidRocketConfig::Env)?;
|
||||||
|
|
||||||
let address = var("ROCKET_ADDRESS").unwrap_or_else(|_| "localhost".to_owned());
|
let address = var("ROCKET_ADDRESS").unwrap_or_else(|_| "localhost".to_owned());
|
||||||
let port = var("ROCKET_PORT")
|
let port = var("ROCKET_PORT")
|
||||||
.ok()
|
.ok()
|
||||||
.map(|s| s.parse::<u16>().unwrap())
|
.map(|s| s.parse::<u16>().unwrap())
|
||||||
.unwrap_or(7878);
|
.unwrap_or(7878);
|
||||||
let secret_key = var("ROCKET_SECRET_KEY").map_err(|_| RocketError::InvalidSecretKey)?;
|
let secret_key = var("ROCKET_SECRET_KEY").map_err(|_| InvalidRocketConfig::SecretKey)?;
|
||||||
let form_size = var("FORM_SIZE")
|
let form_size = var("FORM_SIZE")
|
||||||
.unwrap_or_else(|_| "128".to_owned())
|
.unwrap_or_else(|_| "128".to_owned())
|
||||||
.parse::<u64>()
|
.parse::<u64>()
|
||||||
@ -56,10 +56,10 @@ fn get_rocket_config() -> Result<RocketConfig, RocketError> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
c.set_address(address)
|
c.set_address(address)
|
||||||
.map_err(|_| RocketError::InvalidAddress)?;
|
.map_err(|_| InvalidRocketConfig::Address)?;
|
||||||
c.set_port(port);
|
c.set_port(port);
|
||||||
c.set_secret_key(secret_key)
|
c.set_secret_key(secret_key)
|
||||||
.map_err(|_| RocketError::InvalidSecretKey)?;
|
.map_err(|_| InvalidRocketConfig::SecretKey)?;
|
||||||
|
|
||||||
c.set_limits(
|
c.set_limits(
|
||||||
Limits::new()
|
Limits::new()
|
||||||
@ -155,7 +155,7 @@ impl Default for LogoConfig {
|
|||||||
.ok()
|
.ok()
|
||||||
.or_else(|| custom_main.clone());
|
.or_else(|| custom_main.clone());
|
||||||
let other = if let Some(main) = custom_main.clone() {
|
let other = if let Some(main) = custom_main.clone() {
|
||||||
let ext = |path: &str| match path.rsplitn(2, '.').next() {
|
let ext = |path: &str| match path.rsplit_once('.').map(|x| x.1) {
|
||||||
Some("png") => Some("image/png".to_owned()),
|
Some("png") => Some("image/png".to_owned()),
|
||||||
Some("jpg") | Some("jpeg") => Some("image/jpeg".to_owned()),
|
Some("jpg") | Some("jpeg") => Some("image/jpeg".to_owned()),
|
||||||
Some("svg") => Some("image/svg+xml".to_owned()),
|
Some("svg") => Some("image/svg+xml".to_owned()),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ap_url, db_conn::DbConn, notifications::*, schema::follows, users::User, Connection, Error,
|
ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User,
|
||||||
Result, CONFIG,
|
Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitypub::activity::{Accept, Follow as FollowAct, Undo};
|
use activitypub::activity::{Accept, Follow as FollowAct, Undo};
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
|
||||||
@ -183,6 +183,10 @@ impl FromId<DbConn> for Follow {
|
|||||||
.map_err(|(_, e)| e)?;
|
.map_err(|(_, e)| e)?;
|
||||||
Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id)
|
Follow::accept_follow(conn, &actor, &target, follow, actor.id, target.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Follow {
|
impl AsObject<User, Undo, &DbConn> for Follow {
|
||||||
@ -195,7 +199,7 @@ impl AsObject<User, Undo, &DbConn> for Follow {
|
|||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(&conn, notification_kind::FOLLOW, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,12 @@ use crate::{
|
|||||||
medias::Media,
|
medias::Media,
|
||||||
safe_string::SafeString,
|
safe_string::SafeString,
|
||||||
schema::{instances, users},
|
schema::{instances, users},
|
||||||
users::{Role, User},
|
users::{NewUser, Role, User},
|
||||||
Connection, Error, Result,
|
Connection, Error, Result,
|
||||||
};
|
};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, result::Error::NotFound, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
use plume_common::utils::md_to_html;
|
use plume_common::utils::md_to_html;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
@ -45,6 +46,9 @@ lazy_static! {
|
|||||||
static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None);
|
static ref LOCAL_INSTANCE: RwLock<Option<Instance>> = RwLock::new(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LOCAL_INSTANCE_USERNAME: &str = "__instance__";
|
||||||
|
static LOCAL_INSTANCE_USER: OnceCell<User> = OnceCell::new();
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn set_local(self) {
|
pub fn set_local(self) {
|
||||||
LOCAL_INSTANCE.write().unwrap().replace(self);
|
LOCAL_INSTANCE.write().unwrap().replace(self);
|
||||||
@ -76,6 +80,42 @@ impl Instance {
|
|||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_local_instance_user(conn: &Connection) -> Result<User> {
|
||||||
|
let instance = Instance::get_local()?;
|
||||||
|
let email = format!("{}@{}", LOCAL_INSTANCE_USERNAME, &instance.public_domain);
|
||||||
|
NewUser::new_local(
|
||||||
|
conn,
|
||||||
|
LOCAL_INSTANCE_USERNAME.into(),
|
||||||
|
instance.public_domain,
|
||||||
|
Role::Instance,
|
||||||
|
"Local instance",
|
||||||
|
email,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_local_instance_user() -> Option<&'static User> {
|
||||||
|
LOCAL_INSTANCE_USER.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_local_instance_user_uncached(conn: &Connection) -> Result<User> {
|
||||||
|
users::table
|
||||||
|
.filter(users::role.eq(3))
|
||||||
|
.first(conn)
|
||||||
|
.or_else(|err| match err {
|
||||||
|
NotFound => Self::create_local_instance_user(conn),
|
||||||
|
_ => Err(Error::Db(err)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_local_instance_user(conn: &Connection) {
|
||||||
|
let _ = LOCAL_INSTANCE_USER.get_or_init(|| {
|
||||||
|
Self::get_local_instance_user_uncached(conn)
|
||||||
|
.or_else(|_| Self::create_local_instance_user(conn))
|
||||||
|
.expect("Failed to cache local instance user")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> {
|
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> {
|
||||||
instances::table
|
instances::table
|
||||||
.order(instances::public_domain.asc())
|
.order(instances::public_domain.asc())
|
||||||
@ -304,6 +344,7 @@ pub(crate) mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Instance::cache_local(conn);
|
Instance::cache_local(conn);
|
||||||
|
Instance::cache_local_instance_user(conn);
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#![feature(try_trait)]
|
|
||||||
#![feature(never_type)]
|
#![feature(never_type)]
|
||||||
#![feature(proc_macro_hygiene)]
|
#![feature(proc_macro_hygiene)]
|
||||||
#![feature(box_patterns)]
|
#![feature(box_patterns)]
|
||||||
@ -18,7 +17,7 @@ extern crate serde_json;
|
|||||||
extern crate tantivy;
|
extern crate tantivy;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use plume_common::activity_pub::inbox::InboxError;
|
use plume_common::activity_pub::{inbox::InboxError, request, sign};
|
||||||
use posts::PostEvent;
|
use posts::PostEvent;
|
||||||
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
|
||||||
use users::UserEvent;
|
use users::UserEvent;
|
||||||
@ -80,15 +79,15 @@ impl From<openssl::error::ErrorStack> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<diesel::result::Error> for Error {
|
impl From<sign::Error> for Error {
|
||||||
fn from(err: diesel::result::Error) -> Self {
|
fn from(_: sign::Error) -> Self {
|
||||||
Error::Db(err)
|
Error::Signature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::option::NoneError> for Error {
|
impl From<diesel::result::Error> for Error {
|
||||||
fn from(_: std::option::NoneError) -> Self {
|
fn from(err: diesel::result::Error) -> Self {
|
||||||
Error::NotFound
|
Error::Db(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +157,12 @@ impl From<InboxError<Error>> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<request::Error> for Error {
|
||||||
|
fn from(_err: request::Error) -> Error {
|
||||||
|
Error::Request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
/// Adds a function to a model, that returns the first
|
/// Adds a function to a model, that returns the first
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn, notifications::*, posts::Post, schema::likes, timeline::*, users::User,
|
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*,
|
||||||
Connection, Error, Result, CONFIG,
|
users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitypub::activity;
|
use activitypub::activity;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -137,6 +138,10 @@ impl FromId<DbConn> for Like {
|
|||||||
res.notify(conn)?;
|
res.notify(conn)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, activity::Undo, &DbConn> for Like {
|
impl AsObject<User, activity::Undo, &DbConn> for Like {
|
||||||
@ -148,7 +153,7 @@ impl AsObject<User, activity::Undo, &DbConn> for Like {
|
|||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(&conn, notification_kind::LIKE, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -143,6 +143,7 @@ macro_rules! func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, Queryable, Identifiable)]
|
#[derive(Clone, Queryable, Identifiable)]
|
||||||
struct ListElem {
|
struct ListElem {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
@ -7,7 +7,7 @@ use askama_escape::escape;
|
|||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use guid_create::GUID;
|
use guid_create::GUID;
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{inbox::FromId, Id},
|
activity_pub::{inbox::FromId, request, Id},
|
||||||
utils::MediaProcessor,
|
utils::MediaProcessor,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
@ -104,8 +104,8 @@ impl Media {
|
|||||||
pub fn category(&self) -> MediaCategory {
|
pub fn category(&self) -> MediaCategory {
|
||||||
match &*self
|
match &*self
|
||||||
.file_path
|
.file_path
|
||||||
.rsplitn(2, '.')
|
.rsplit_once('.')
|
||||||
.next()
|
.map(|x| x.1)
|
||||||
.expect("Media::category: extension error")
|
.expect("Media::category: extension error")
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
{
|
{
|
||||||
@ -208,31 +208,33 @@ impl Media {
|
|||||||
|
|
||||||
// TODO: merge with save_remote?
|
// TODO: merge with save_remote?
|
||||||
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> {
|
pub fn from_activity(conn: &DbConn, image: &Image) -> Result<Media> {
|
||||||
let remote_url = image.object_props.url_string().ok()?;
|
let remote_url = image
|
||||||
|
.object_props
|
||||||
|
.url_string()
|
||||||
|
.or(Err(Error::MissingApProperty))?;
|
||||||
let path = determine_mirror_file_path(&remote_url);
|
let path = determine_mirror_file_path(&remote_url);
|
||||||
let parent = path.parent()?;
|
let parent = path.parent().ok_or(Error::InvalidValue)?;
|
||||||
if !parent.is_dir() {
|
if !parent.is_dir() {
|
||||||
DirBuilder::new().recursive(true).create(parent)?;
|
DirBuilder::new().recursive(true).create(parent)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dest = fs::File::create(path.clone()).ok()?;
|
let mut dest = fs::File::create(path.clone())?;
|
||||||
// TODO: conditional GET
|
// TODO: conditional GET
|
||||||
if let Some(proxy) = CONFIG.proxy() {
|
request::get(
|
||||||
reqwest::ClientBuilder::new().proxy(proxy.clone()).build()?
|
remote_url.as_str(),
|
||||||
} else {
|
User::get_sender(),
|
||||||
reqwest::Client::new()
|
CONFIG.proxy().cloned(),
|
||||||
}
|
)?
|
||||||
.get(remote_url.as_str())
|
.copy_to(&mut dest)?;
|
||||||
.send()
|
|
||||||
.ok()?
|
|
||||||
.copy_to(&mut dest)
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
Media::find_by_file_path(conn, &path.to_str()?)
|
Media::find_by_file_path(conn, path.to_str().ok_or(Error::InvalidValue)?)
|
||||||
.and_then(|mut media| {
|
.and_then(|mut media| {
|
||||||
let mut updated = false;
|
let mut updated = false;
|
||||||
|
|
||||||
let alt_text = image.object_props.content_string().ok()?;
|
let alt_text = image
|
||||||
|
.object_props
|
||||||
|
.content_string()
|
||||||
|
.or(Err(Error::NotFound))?;
|
||||||
let sensitive = image.object_props.summary_string().is_ok();
|
let sensitive = image.object_props.summary_string().is_ok();
|
||||||
let content_warning = image.object_props.summary_string().ok();
|
let content_warning = image.object_props.summary_string().ok();
|
||||||
if media.alt_text != alt_text {
|
if media.alt_text != alt_text {
|
||||||
@ -264,8 +266,11 @@ impl Media {
|
|||||||
Media::insert(
|
Media::insert(
|
||||||
conn,
|
conn,
|
||||||
NewMedia {
|
NewMedia {
|
||||||
file_path: path.to_str()?.to_string(),
|
file_path: path.to_str().ok_or(Error::InvalidValue)?.to_string(),
|
||||||
alt_text: image.object_props.content_string().ok()?,
|
alt_text: image
|
||||||
|
.object_props
|
||||||
|
.content_string()
|
||||||
|
.or(Err(Error::NotFound))?,
|
||||||
is_remote: false,
|
is_remote: false,
|
||||||
remote_url: None,
|
remote_url: None,
|
||||||
sensitive: image.object_props.summary_string().is_ok(),
|
sensitive: image.object_props.summary_string().is_ok(),
|
||||||
@ -275,9 +280,10 @@ impl Media {
|
|||||||
image
|
image
|
||||||
.object_props
|
.object_props
|
||||||
.attributed_to_link_vec::<Id>()
|
.attributed_to_link_vec::<Id>()
|
||||||
.ok()?
|
.or(Err(Error::NotFound))?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()?
|
.next()
|
||||||
|
.ok_or(Error::NotFound)?
|
||||||
.as_ref(),
|
.as_ref(),
|
||||||
None,
|
None,
|
||||||
CONFIG.proxy(),
|
CONFIG.proxy(),
|
||||||
@ -325,7 +331,7 @@ fn determine_mirror_file_path(url: &str) -> PathBuf {
|
|||||||
.next()
|
.next()
|
||||||
.map(ToOwned::to_owned)
|
.map(ToOwned::to_owned)
|
||||||
.unwrap_or_else(|| String::from("png"));
|
.unwrap_or_else(|| String::from("png"));
|
||||||
file_path.push(format!("{}.{}", GUID::rand().to_string(), ext));
|
file_path.push(format!("{}.{}", GUID::rand(), ext));
|
||||||
});
|
});
|
||||||
file_path
|
file_path
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,11 @@ impl Mention {
|
|||||||
|
|
||||||
pub fn get_user(&self, conn: &Connection) -> Result<User> {
|
pub fn get_user(&self, conn: &Connection) -> Result<User> {
|
||||||
match self.get_post(conn) {
|
match self.get_post(conn) {
|
||||||
Ok(p) => Ok(p.get_authors(conn)?.into_iter().next()?),
|
Ok(p) => Ok(p
|
||||||
|
.get_authors(conn)?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or(Error::NotFound)?),
|
||||||
Err(_) => self.get_comment(conn).and_then(|c| c.get_author(conn)),
|
Err(_) => self.get_comment(conn).and_then(|c| c.get_author(conn)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +81,7 @@ impl Mention {
|
|||||||
in_post: bool,
|
in_post: bool,
|
||||||
notify: bool,
|
notify: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let ap_url = ment.link_props.href_string().ok()?;
|
let ap_url = ment.link_props.href_string().or(Err(Error::NotFound))?;
|
||||||
let mentioned = User::find_by_ap_url(conn, &ap_url)?;
|
let mentioned = User::find_by_ap_url(conn, &ap_url)?;
|
||||||
|
|
||||||
if in_post {
|
if in_post {
|
||||||
|
@ -105,7 +105,8 @@ impl ImportedMigrations {
|
|||||||
pub fn rerun_last_migration(&self, conn: &Connection, path: &Path) -> Result<()> {
|
pub fn rerun_last_migration(&self, conn: &Connection, path: &Path) -> Result<()> {
|
||||||
let latest_migration = conn.latest_run_migration_version()?;
|
let latest_migration = conn.latest_run_migration_version()?;
|
||||||
let id = latest_migration
|
let id = latest_migration
|
||||||
.and_then(|m| self.0.binary_search_by_key(&m.as_str(), |m| m.name).ok())?;
|
.and_then(|m| self.0.binary_search_by_key(&m.as_str(), |m| m.name).ok())
|
||||||
|
.ok_or(Error::NotFound)?;
|
||||||
let migration = &self.0[id];
|
let migration = &self.0[id];
|
||||||
conn.transaction(|| {
|
conn.transaction(|| {
|
||||||
migration.revert(conn, path)?;
|
migration.revert(conn, path)?;
|
||||||
|
@ -61,7 +61,7 @@ impl PasswordResetRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_and_delete_by_token(conn: &Connection, token: &str) -> Result<Self> {
|
pub fn find_and_delete_by_token(conn: &Connection, token: &str) -> Result<Self> {
|
||||||
let request = Self::find_by_token(&conn, &token)?;
|
let request = Self::find_by_token(conn, token)?;
|
||||||
|
|
||||||
let filter =
|
let filter =
|
||||||
password_reset_requests::table.filter(password_reset_requests::id.eq(request.id));
|
password_reset_requests::table.filter(password_reset_requests::id.eq(request.id));
|
||||||
|
@ -15,6 +15,7 @@ use once_cell::sync::Lazy;
|
|||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils::{iri_percent_encode_seg, md_to_html},
|
utils::{iri_percent_encode_seg, md_to_html},
|
||||||
@ -97,7 +98,7 @@ impl Post {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||||
for m in Mention::list_for_post(&conn, self.id)? {
|
for m in Mention::list_for_post(conn, self.id)? {
|
||||||
m.delete(conn)?;
|
m.delete(conn)?;
|
||||||
}
|
}
|
||||||
diesel::delete(self).execute(conn)?;
|
diesel::delete(self).execute(conn)?;
|
||||||
@ -457,14 +458,14 @@ impl Post {
|
|||||||
.filter_map(|(id, m)| id.map(|id| (m, id)))
|
.filter_map(|(id, m)| id.map(|id| (m, id)))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let old_mentions = Mention::list_for_post(&conn, self.id)?;
|
let old_mentions = Mention::list_for_post(conn, self.id)?;
|
||||||
let old_user_mentioned = old_mentions
|
let old_user_mentioned = old_mentions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|m| m.mentioned_id)
|
.map(|m| m.mentioned_id)
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
for (m, id) in &mentions {
|
for (m, id) in &mentions {
|
||||||
if !old_user_mentioned.contains(&id) {
|
if !old_user_mentioned.contains(id) {
|
||||||
Mention::from_activity(&*conn, &m, self.id, true, true)?;
|
Mention::from_activity(&*conn, m, self.id, true, true)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,7 +477,7 @@ impl Post {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|m| !new_mentions.contains(&m.mentioned_id))
|
.filter(|m| !new_mentions.contains(&m.mentioned_id))
|
||||||
{
|
{
|
||||||
m.delete(&conn)?;
|
m.delete(conn)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -700,7 +701,7 @@ impl FromId<DbConn> for Post {
|
|||||||
Post::insert(
|
Post::insert(
|
||||||
conn,
|
conn,
|
||||||
NewPost {
|
NewPost {
|
||||||
blog_id: blog?.id,
|
blog_id: blog.ok_or(Error::NotFound)?.id,
|
||||||
slug: Self::slug(&title).to_string(),
|
slug: Self::slug(&title).to_string(),
|
||||||
title,
|
title,
|
||||||
content: SafeString::new(&article.object_props.content_string()?),
|
content: SafeString::new(&article.object_props.content_string()?),
|
||||||
@ -759,6 +760,10 @@ impl FromId<DbConn> for Post {
|
|||||||
|
|
||||||
Ok(post)
|
Ok(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Create, &DbConn> for Post {
|
impl AsObject<User, Create, &DbConn> for Post {
|
||||||
@ -830,6 +835,10 @@ impl FromId<DbConn> for PostUpdate {
|
|||||||
tags: updated.object.object_props.tag,
|
tags: updated.object.object_props.tag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Update, &DbConn> for PostUpdate {
|
impl AsObject<User, Update, &DbConn> for PostUpdate {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db_conn::DbConn, notifications::*, posts::Post, schema::reshares, timeline::*, users::User,
|
db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares,
|
||||||
Connection, Error, Result, CONFIG,
|
timeline::*, users::User, Connection, Error, Result, CONFIG,
|
||||||
};
|
};
|
||||||
use activitypub::activity::{Announce, Undo};
|
use activitypub::activity::{Announce, Undo};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
|
sign::Signer,
|
||||||
Id, IntoId, PUBLIC_VISIBILITY,
|
Id, IntoId, PUBLIC_VISIBILITY,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -162,6 +163,10 @@ impl FromId<DbConn> for Reshare {
|
|||||||
res.notify(conn)?;
|
res.notify(conn)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsObject<User, Undo, &DbConn> for Reshare {
|
impl AsObject<User, Undo, &DbConn> for Reshare {
|
||||||
@ -173,7 +178,7 @@ impl AsObject<User, Undo, &DbConn> for Reshare {
|
|||||||
diesel::delete(&self).execute(&**conn)?;
|
diesel::delete(&self).execute(&**conn)?;
|
||||||
|
|
||||||
// delete associated notification if any
|
// delete associated notification if any
|
||||||
if let Ok(notif) = Notification::find(&conn, notification_kind::RESHARE, self.id) {
|
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
||||||
diesel::delete(¬if).execute(&**conn)?;
|
diesel::delete(¬if).execute(&**conn)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ pub struct SafeString {
|
|||||||
impl SafeString {
|
impl SafeString {
|
||||||
pub fn new(value: &str) -> Self {
|
pub fn new(value: &str) -> Self {
|
||||||
SafeString {
|
SafeString {
|
||||||
value: CLEAN.clean(&value).to_string(),
|
value: CLEAN.clean(value).to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ impl PlumeQuery {
|
|||||||
|
|
||||||
/// Parse a query string into this Query
|
/// Parse a query string into this Query
|
||||||
pub fn parse_query(&mut self, query: &str) -> &mut Self {
|
pub fn parse_query(&mut self, query: &str) -> &mut Self {
|
||||||
self.from_str_req(&query.trim())
|
self.from_str_req(query.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this Query to a Tantivy Query
|
/// Convert this Query to a Tantivy Query
|
||||||
@ -360,7 +360,7 @@ impl std::str::FromStr for PlumeQuery {
|
|||||||
fn from_str(query: &str) -> Result<PlumeQuery, !> {
|
fn from_str(query: &str) -> Result<PlumeQuery, !> {
|
||||||
let mut res: PlumeQuery = Default::default();
|
let mut res: PlumeQuery = Default::default();
|
||||||
|
|
||||||
res.from_str_req(&query.trim());
|
res.from_str_req(query.trim());
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,6 @@ pub enum QueryError {
|
|||||||
RuntimeError(String),
|
RuntimeError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::option::NoneError> for QueryError {
|
|
||||||
fn from(_: std::option::NoneError) -> Self {
|
|
||||||
QueryError::UnexpectedEndOfQuery
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type QueryResult<T> = std::result::Result<T, QueryError>;
|
pub type QueryResult<T> = std::result::Result<T, QueryError>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
@ -239,7 +233,7 @@ impl WithList {
|
|||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
match list {
|
match list {
|
||||||
List::List(name) => {
|
List::List(name) => {
|
||||||
let list = lists::List::find_for_user_by_name(conn, timeline.user_id, &name)?;
|
let list = lists::List::find_for_user_by_name(conn, timeline.user_id, name)?;
|
||||||
match (self, list.kind()) {
|
match (self, list.kind()) {
|
||||||
(WithList::Blog, ListType::Blog) => list.contains_blog(conn, post.blog_id),
|
(WithList::Blog, ListType::Blog) => list.contains_blog(conn, post.blog_id),
|
||||||
(WithList::Author { boosts, likes }, ListType::User) => match kind {
|
(WithList::Author { boosts, likes }, ListType::User) => match kind {
|
||||||
@ -414,7 +408,7 @@ enum List<'a> {
|
|||||||
|
|
||||||
fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
let (left, token) = parse_a(&stream)?;
|
let (left, token) = parse_a(stream)?;
|
||||||
res.push(token);
|
res.push(token);
|
||||||
stream = left;
|
stream = left;
|
||||||
while !stream.is_empty() {
|
while !stream.is_empty() {
|
||||||
@ -436,7 +430,7 @@ fn parse_s<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
|
|
||||||
fn parse_a<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
fn parse_a<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<'a>)> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
let (left, token) = parse_b(&stream)?;
|
let (left, token) = parse_b(stream)?;
|
||||||
res.push(token);
|
res.push(token);
|
||||||
stream = left;
|
stream = left;
|
||||||
while !stream.is_empty() {
|
while !stream.is_empty() {
|
||||||
@ -463,7 +457,7 @@ fn parse_b<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<
|
|||||||
match left.get(0) {
|
match left.get(0) {
|
||||||
Some(Token::RParent(_)) => Ok((&left[1..], token)),
|
Some(Token::RParent(_)) => Ok((&left[1..], token)),
|
||||||
Some(t) => t.get_error(Token::RParent(0)),
|
Some(t) => t.get_error(Token::RParent(0)),
|
||||||
None => None?,
|
None => Err(QueryError::UnexpectedEndOfQuery),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => parse_c(stream),
|
_ => parse_c(stream),
|
||||||
@ -484,9 +478,13 @@ fn parse_c<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], TQ<
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Arg<'a>)> {
|
fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Arg<'a>)> {
|
||||||
match stream.get(0).map(Token::get_text)? {
|
match stream
|
||||||
|
.get(0)
|
||||||
|
.map(Token::get_text)
|
||||||
|
.ok_or(QueryError::UnexpectedEndOfQuery)?
|
||||||
|
{
|
||||||
s @ "blog" | s @ "author" | s @ "license" | s @ "tags" | s @ "lang" => {
|
s @ "blog" | s @ "author" | s @ "license" | s @ "tags" | s @ "lang" => {
|
||||||
match stream.get(1)? {
|
match stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
||||||
Token::Word(_, _, r#in) if r#in == &"in" => {
|
Token::Word(_, _, r#in) if r#in == &"in" => {
|
||||||
let (mut left, list) = parse_l(&stream[2..])?;
|
let (mut left, list) = parse_l(&stream[2..])?;
|
||||||
let kind = match s {
|
let kind = match s {
|
||||||
@ -498,7 +496,12 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
if *clude != "include" && *clude != "exclude" {
|
if *clude != "include" && *clude != "exclude" {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match (*clude, left.get(1).map(Token::get_text)?) {
|
match (
|
||||||
|
*clude,
|
||||||
|
left.get(1)
|
||||||
|
.map(Token::get_text)
|
||||||
|
.ok_or(QueryError::UnexpectedEndOfQuery)?,
|
||||||
|
) {
|
||||||
("include", "reshares") | ("include", "reshare") => {
|
("include", "reshares") | ("include", "reshare") => {
|
||||||
boosts = true
|
boosts = true
|
||||||
}
|
}
|
||||||
@ -529,7 +532,10 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
t => t.get_error(Token::Word(0, 0, "'in'")),
|
t => t.get_error(Token::Word(0, 0, "'in'")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s @ "title" | s @ "subtitle" | s @ "content" => match (stream.get(1)?, stream.get(2)?) {
|
s @ "title" | s @ "subtitle" | s @ "content" => match (
|
||||||
|
stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)?,
|
||||||
|
stream.get(2).ok_or(QueryError::UnexpectedEndOfQuery)?,
|
||||||
|
) {
|
||||||
(Token::Word(_, _, contains), Token::Word(_, _, w)) if contains == &"contains" => Ok((
|
(Token::Word(_, _, contains), Token::Word(_, _, w)) if contains == &"contains" => Ok((
|
||||||
&stream[3..],
|
&stream[3..],
|
||||||
Arg::Contains(
|
Arg::Contains(
|
||||||
@ -555,7 +561,13 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
if *clude != "include" && *clude != "exclude" {
|
if *clude != "include" && *clude != "exclude" {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match (*clude, stream.get(2).map(Token::get_text)?) {
|
match (
|
||||||
|
*clude,
|
||||||
|
stream
|
||||||
|
.get(2)
|
||||||
|
.map(Token::get_text)
|
||||||
|
.ok_or(QueryError::UnexpectedEndOfQuery)?,
|
||||||
|
) {
|
||||||
("include", "reshares") | ("include", "reshare") => boosts = true,
|
("include", "reshares") | ("include", "reshare") => boosts = true,
|
||||||
("exclude", "reshares") | ("exclude", "reshare") => boosts = false,
|
("exclude", "reshares") | ("exclude", "reshare") => boosts = false,
|
||||||
("include", "likes") | ("include", "like") => likes = true,
|
("include", "likes") | ("include", "like") => likes = true,
|
||||||
@ -577,20 +589,23 @@ fn parse_d<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>],
|
|||||||
"all" => Ok((&stream[1..], Arg::Boolean(Bool::All))),
|
"all" => Ok((&stream[1..], Arg::Boolean(Bool::All))),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
_ => stream.get(0)?.get_error(Token::Word(
|
_ => stream
|
||||||
0,
|
.get(0)
|
||||||
0,
|
.ok_or(QueryError::UnexpectedEndOfQuery)?
|
||||||
"one of 'blog', 'author', 'license', 'tags', 'lang', \
|
.get_error(Token::Word(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
"one of 'blog', 'author', 'license', 'tags', 'lang', \
|
||||||
'title', 'subtitle', 'content', 'followed', 'has_cover', 'local' or 'all'",
|
'title', 'subtitle', 'content', 'followed', 'has_cover', 'local' or 'all'",
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], List<'a>)> {
|
fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], List<'a>)> {
|
||||||
match stream.get(0)? {
|
match stream.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
||||||
Token::LBracket(_) => {
|
Token::LBracket(_) => {
|
||||||
let (left, list) = parse_m(&stream[1..])?;
|
let (left, list) = parse_m(&stream[1..])?;
|
||||||
match left.get(0)? {
|
match left.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
||||||
Token::RBracket(_) => Ok((&left[1..], List::Array(list))),
|
Token::RBracket(_) => Ok((&left[1..], List::Array(list))),
|
||||||
t => t.get_error(Token::Word(0, 0, "one of ']' or ','")),
|
t => t.get_error(Token::Word(0, 0, "one of ']' or ','")),
|
||||||
}
|
}
|
||||||
@ -601,16 +616,20 @@ fn parse_l<'a, 'b>(stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Lis
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn parse_m<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Vec<&'a str>)> {
|
fn parse_m<'a, 'b>(mut stream: &'b [Token<'a>]) -> QueryResult<(&'b [Token<'a>], Vec<&'a str>)> {
|
||||||
let mut res: Vec<&str> = vec![match stream.get(0)? {
|
let mut res: Vec<&str> = vec![
|
||||||
Token::Word(_, _, w) => w,
|
match stream.get(0).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
||||||
t => return t.get_error(Token::Word(0, 0, "any word")),
|
|
||||||
}];
|
|
||||||
stream = &stream[1..];
|
|
||||||
while let Token::Comma(_) = stream[0] {
|
|
||||||
res.push(match stream.get(1)? {
|
|
||||||
Token::Word(_, _, w) => w,
|
Token::Word(_, _, w) => w,
|
||||||
t => return t.get_error(Token::Word(0, 0, "any word")),
|
t => return t.get_error(Token::Word(0, 0, "any word")),
|
||||||
});
|
},
|
||||||
|
];
|
||||||
|
stream = &stream[1..];
|
||||||
|
while let Token::Comma(_) = stream[0] {
|
||||||
|
res.push(
|
||||||
|
match stream.get(1).ok_or(QueryError::UnexpectedEndOfQuery)? {
|
||||||
|
Token::Word(_, _, w) => w,
|
||||||
|
t => return t.get_error(Token::Word(0, 0, "any word")),
|
||||||
|
},
|
||||||
|
);
|
||||||
stream = &stream[2..];
|
stream = &stream[2..];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,17 +22,13 @@ use openssl::{
|
|||||||
};
|
};
|
||||||
use plume_common::{
|
use plume_common::{
|
||||||
activity_pub::{
|
activity_pub::{
|
||||||
ap_accept_header,
|
|
||||||
inbox::{AsActor, AsObject, FromId},
|
inbox::{AsActor, AsObject, FromId},
|
||||||
sign::{gen_keypair, Signer},
|
request::get,
|
||||||
|
sign::{gen_keypair, Error as SignError, Result as SignResult, Signer},
|
||||||
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
|
||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
use reqwest::{
|
|
||||||
header::{HeaderValue, ACCEPT},
|
|
||||||
ClientBuilder,
|
|
||||||
};
|
|
||||||
use riker::actors::{Publish, Tell};
|
use riker::actors::{Publish, Tell};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
outcome::IntoOutcome,
|
outcome::IntoOutcome,
|
||||||
@ -52,6 +48,7 @@ pub enum Role {
|
|||||||
Admin = 0,
|
Admin = 0,
|
||||||
Moderator = 1,
|
Moderator = 1,
|
||||||
Normal = 2,
|
Normal = 2,
|
||||||
|
Instance = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
#[derive(Queryable, Identifiable, Clone, Debug, AsChangeset)]
|
||||||
@ -78,6 +75,7 @@ pub struct User {
|
|||||||
pub summary_html: SafeString,
|
pub summary_html: SafeString,
|
||||||
/// 0 = admin
|
/// 0 = admin
|
||||||
/// 1 = moderator
|
/// 1 = moderator
|
||||||
|
/// 3 = local instance
|
||||||
/// anything else = normal user
|
/// anything else = normal user
|
||||||
pub role: i32,
|
pub role: i32,
|
||||||
pub preferred_theme: Option<String>,
|
pub preferred_theme: Option<String>,
|
||||||
@ -210,7 +208,13 @@ impl User {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||||
.ok_or(Error::Webfinger)?;
|
.ok_or(Error::Webfinger)?;
|
||||||
User::from_id(conn, link.href.as_ref()?, None, CONFIG.proxy()).map_err(|(_, e)| e)
|
User::from_id(
|
||||||
|
conn,
|
||||||
|
link.href.as_ref().ok_or(Error::Webfinger)?,
|
||||||
|
None,
|
||||||
|
CONFIG.proxy(),
|
||||||
|
)
|
||||||
|
.map_err(|(_, e)| e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_remote_interact_uri(acct: &str) -> Result<String> {
|
pub fn fetch_remote_interact_uri(acct: &str) -> Result<String> {
|
||||||
@ -223,20 +227,7 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fetch(url: &str) -> Result<CustomPerson> {
|
fn fetch(url: &str) -> Result<CustomPerson> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
|
||||||
.build()?
|
|
||||||
.get(url)
|
|
||||||
.header(
|
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
// without this workaround, publicKey is not correctly deserialized
|
// without this workaround, publicKey is not correctly deserialized
|
||||||
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
let ap_sign = serde_json::from_str::<ApSignature>(text)?;
|
||||||
@ -255,10 +246,10 @@ impl User {
|
|||||||
conn,
|
conn,
|
||||||
json.object
|
json.object
|
||||||
.object_props
|
.object_props
|
||||||
.icon_image()?
|
.icon_image()? // FIXME: Fails when icon is not set
|
||||||
.object_props
|
.object_props
|
||||||
.url_string()?,
|
.url_string()?,
|
||||||
&self,
|
self,
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@ -427,12 +418,12 @@ impl User {
|
|||||||
let last = &format!(
|
let last = &format!(
|
||||||
"{}?page={}",
|
"{}?page={}",
|
||||||
&self.outbox_url,
|
&self.outbox_url,
|
||||||
self.get_activities_count(&conn) / i64::from(ITEMS_PER_PAGE) + 1
|
self.get_activities_count(conn) / i64::from(ITEMS_PER_PAGE) + 1
|
||||||
);
|
);
|
||||||
coll.collection_props.set_first_link(Id::new(first))?;
|
coll.collection_props.set_first_link(Id::new(first))?;
|
||||||
coll.collection_props.set_last_link(Id::new(last))?;
|
coll.collection_props.set_last_link(Id::new(last))?;
|
||||||
coll.collection_props
|
coll.collection_props
|
||||||
.set_total_items_u64(self.get_activities_count(&conn) as u64)?;
|
.set_total_items_u64(self.get_activities_count(conn) as u64)?;
|
||||||
Ok(ActivityStream::new(coll))
|
Ok(ActivityStream::new(coll))
|
||||||
}
|
}
|
||||||
pub fn outbox_page(
|
pub fn outbox_page(
|
||||||
@ -441,7 +432,7 @@ impl User {
|
|||||||
(min, max): (i32, i32),
|
(min, max): (i32, i32),
|
||||||
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
) -> Result<ActivityStream<OrderedCollectionPage>> {
|
||||||
let acts = self.get_activities_page(conn, (min, max))?;
|
let acts = self.get_activities_page(conn, (min, max))?;
|
||||||
let n_acts = self.get_activities_count(&conn);
|
let n_acts = self.get_activities_count(conn);
|
||||||
let mut coll = OrderedCollectionPage::default();
|
let mut coll = OrderedCollectionPage::default();
|
||||||
if n_acts - i64::from(min) >= i64::from(ITEMS_PER_PAGE) {
|
if n_acts - i64::from(min) >= i64::from(ITEMS_PER_PAGE) {
|
||||||
coll.collection_page_props.set_next_link(Id::new(&format!(
|
coll.collection_page_props.set_next_link(Id::new(&format!(
|
||||||
@ -463,20 +454,7 @@ impl User {
|
|||||||
Ok(ActivityStream::new(coll))
|
Ok(ActivityStream::new(coll))
|
||||||
}
|
}
|
||||||
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
fn fetch_outbox_page<T: Activity>(&self, url: &str) -> Result<(Vec<T>, Option<String>)> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(url, Self::get_sender(), CONFIG.proxy().cloned())?;
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
|
||||||
.build()?
|
|
||||||
.get(url)
|
|
||||||
.header(
|
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
let items = json["items"]
|
let items = json["items"]
|
||||||
@ -490,20 +468,11 @@ impl User {
|
|||||||
Ok((items, next))
|
Ok((items, next))
|
||||||
}
|
}
|
||||||
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
pub fn fetch_outbox<T: Activity>(&self) -> Result<Vec<T>> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
&self.outbox_url[..],
|
||||||
.build()?
|
Self::get_sender(),
|
||||||
.get(&self.outbox_url[..])
|
CONFIG.proxy().cloned(),
|
||||||
.header(
|
)?;
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
if let Some(first) = json.get("first") {
|
if let Some(first) = json.get("first") {
|
||||||
@ -513,7 +482,7 @@ impl User {
|
|||||||
if page.is_empty() {
|
if page.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
items.extend(page.drain(..));
|
items.append(&mut page);
|
||||||
if let Some(n) = nxt {
|
if let Some(n) = nxt {
|
||||||
if n == next {
|
if n == next {
|
||||||
break;
|
break;
|
||||||
@ -535,20 +504,11 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
pub fn fetch_followers_ids(&self) -> Result<Vec<String>> {
|
||||||
let mut res = ClientBuilder::new()
|
let mut res = get(
|
||||||
.connect_timeout(Some(std::time::Duration::from_secs(5)))
|
&self.followers_endpoint[..],
|
||||||
.build()?
|
Self::get_sender(),
|
||||||
.get(&self.followers_endpoint[..])
|
CONFIG.proxy().cloned(),
|
||||||
.header(
|
)?;
|
||||||
ACCEPT,
|
|
||||||
HeaderValue::from_str(
|
|
||||||
&ap_accept_header()
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", "),
|
|
||||||
)?,
|
|
||||||
)
|
|
||||||
.send()?;
|
|
||||||
let text = &res.text()?;
|
let text = &res.text()?;
|
||||||
let json: serde_json::Value = serde_json::from_str(text)?;
|
let json: serde_json::Value = serde_json::from_str(text)?;
|
||||||
Ok(json["items"]
|
Ok(json["items"]
|
||||||
@ -720,7 +680,7 @@ impl User {
|
|||||||
|
|
||||||
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
||||||
PKey::from_rsa(Rsa::private_key_from_pem(
|
PKey::from_rsa(Rsa::private_key_from_pem(
|
||||||
self.private_key.clone()?.as_ref(),
|
self.private_key.clone().ok_or(Error::Signature)?.as_ref(),
|
||||||
)?)
|
)?)
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
@ -943,7 +903,7 @@ impl FromId<DbConn> for User {
|
|||||||
|
|
||||||
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> {
|
fn from_activity(conn: &DbConn, acct: CustomPerson) -> Result<Self> {
|
||||||
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
let url = Url::parse(&acct.object.object_props.id_string()?)?;
|
||||||
let inst = url.host_str()?;
|
let inst = url.host_str().ok_or(Error::Url)?;
|
||||||
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
let instance = Instance::find_by_domain(conn, inst).or_else(|_| {
|
||||||
Instance::insert(
|
Instance::insert(
|
||||||
conn,
|
conn,
|
||||||
@ -1031,6 +991,10 @@ impl FromId<DbConn> for User {
|
|||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_sender() -> &'static dyn Signer {
|
||||||
|
Instance::get_local_instance_user().expect("Failed to local instance user")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsActor<&DbConn> for User {
|
impl AsActor<&DbConn> for User {
|
||||||
@ -1063,24 +1027,22 @@ impl AsObject<User, Delete, &DbConn> for User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Signer for User {
|
impl Signer for User {
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn get_key_id(&self) -> String {
|
fn get_key_id(&self) -> String {
|
||||||
format!("{}#main-key", self.ap_url)
|
format!("{}#main-key", self.ap_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
fn sign(&self, to_sign: &str) -> SignResult<Vec<u8>> {
|
||||||
let key = self.get_keypair()?;
|
let key = self.get_keypair().map_err(|_| SignError())?;
|
||||||
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
|
||||||
signer.update(to_sign.as_bytes())?;
|
signer.update(to_sign.as_bytes())?;
|
||||||
signer.sign_to_vec().map_err(Error::from)
|
signer.sign_to_vec().map_err(SignError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
fn verify(&self, data: &str, signature: &[u8]) -> SignResult<bool> {
|
||||||
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
|
||||||
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
|
||||||
verifier.update(data.as_bytes())?;
|
verifier.update(data.as_bytes())?;
|
||||||
verifier.verify(&signature).map_err(Error::from)
|
verifier.verify(signature).map_err(SignError::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1121,7 +1083,7 @@ impl NewUser {
|
|||||||
display_name,
|
display_name,
|
||||||
role: role as i32,
|
role: role as i32,
|
||||||
summary: summary.to_owned(),
|
summary: summary.to_owned(),
|
||||||
summary_html: SafeString::new(&utils::md_to_html(&summary, None, false, None).0),
|
summary_html: SafeString::new(&utils::md_to_html(summary, None, false, None).0),
|
||||||
email: Some(email),
|
email: Some(email),
|
||||||
hashed_password: password,
|
hashed_password: password,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
|
@ -1 +1 @@
|
|||||||
nightly-2021-03-25
|
nightly-2021-11-27
|
||||||
|
@ -25,7 +25,7 @@ parts:
|
|||||||
plume:
|
plume:
|
||||||
plugin: rust
|
plugin: rust
|
||||||
source: .
|
source: .
|
||||||
rust-revision: nightly-2021-03-25
|
rust-revision: nightly-2021-11-27
|
||||||
build-packages:
|
build-packages:
|
||||||
- libssl-dev
|
- libssl-dev
|
||||||
- pkg-config
|
- pkg-config
|
||||||
|
@ -19,12 +19,6 @@ impl From<Error> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<std::option::NoneError> for ApiError {
|
|
||||||
fn from(err: std::option::NoneError) -> ApiError {
|
|
||||||
ApiError(err.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> Responder<'r> for ApiError {
|
impl<'r> Responder<'r> for ApiError {
|
||||||
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> {
|
fn respond_to(self, req: &Request<'_>) -> response::Result<'r> {
|
||||||
match self.0 {
|
match self.0 {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
use crate::api::{authorization::*, Api};
|
use crate::api::{authorization::*, Api, ApiError};
|
||||||
use plume_api::posts::*;
|
use plume_api::posts::*;
|
||||||
use plume_common::{activity_pub::broadcast, utils::md_to_html};
|
use plume_common::{activity_pub::broadcast, utils::md_to_html};
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
@ -121,14 +121,17 @@ pub fn create(
|
|||||||
Some(Media::get_media_processor(&conn, vec![&author])),
|
Some(Media::get_media_processor(&conn, vec![&author])),
|
||||||
);
|
);
|
||||||
|
|
||||||
let blog = payload.blog_id.or_else(|| {
|
let blog = payload
|
||||||
let blogs = Blog::find_for_author(&conn, &author).ok()?;
|
.blog_id
|
||||||
if blogs.len() == 1 {
|
.or_else(|| {
|
||||||
Some(blogs[0].id)
|
let blogs = Blog::find_for_author(&conn, &author).ok()?;
|
||||||
} else {
|
if blogs.len() == 1 {
|
||||||
None
|
Some(blogs[0].id)
|
||||||
}
|
} else {
|
||||||
})?;
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok_or(ApiError(Error::NotFound))?;
|
||||||
|
|
||||||
if Post::find_by_slug(&conn, slug, blog).is_ok() {
|
if Post::find_by_slug(&conn, slug, blog).is_ok() {
|
||||||
return Err(Error::InvalidValue.into());
|
return Err(Error::InvalidValue.into());
|
||||||
|
@ -90,8 +90,8 @@ impl<'a, T: Deserialize<'a>> FromData<'a> for SignedJson<T> {
|
|||||||
o: Transformed<'a, Self>,
|
o: Transformed<'a, Self>,
|
||||||
) -> rocket::data::Outcome<Self, Self::Error> {
|
) -> rocket::data::Outcome<Self, Self::Error> {
|
||||||
let string = o.borrowed()?;
|
let string = o.borrowed()?;
|
||||||
match serde_json::from_str(&string) {
|
match serde_json::from_str(string) {
|
||||||
Ok(v) => Success(SignedJson(Digest::from_body(&string), Json(v))),
|
Ok(v) => Success(SignedJson(Digest::from_body(string), Json(v))),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.is_data() {
|
if e.is_data() {
|
||||||
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
|
Failure((Status::UnprocessableEntity, JsonError::Parse(string, e)))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
#![feature(decl_macro, proc_macro_hygiene, try_trait)]
|
#![feature(decl_macro, proc_macro_hygiene)]
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate gettext_macros;
|
extern crate gettext_macros;
|
||||||
@ -57,7 +57,10 @@ fn init_pool() -> Option<DbPool> {
|
|||||||
builder = builder.max_size(max_size);
|
builder = builder.max_size(max_size);
|
||||||
};
|
};
|
||||||
let pool = builder.build(manager).ok()?;
|
let pool = builder.build(manager).ok()?;
|
||||||
Instance::cache_local(&pool.get().unwrap());
|
let conn = pool.get().unwrap();
|
||||||
|
Instance::cache_local(&conn);
|
||||||
|
let _ = Instance::create_local_instance_user(&conn);
|
||||||
|
Instance::cache_local_instance_user(&conn);
|
||||||
Some(pool)
|
Some(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,6 +390,7 @@ mod tests {
|
|||||||
posts::{NewPost, Post},
|
posts::{NewPost, Post},
|
||||||
safe_string::SafeString,
|
safe_string::SafeString,
|
||||||
users::{NewUser, User, AUTH_COOKIE},
|
users::{NewUser, User, AUTH_COOKIE},
|
||||||
|
Connection as Conn, CONFIG,
|
||||||
};
|
};
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::{Cookie, Cookies, SameSite},
|
http::{Cookie, Cookies, SameSite},
|
||||||
@ -398,6 +399,22 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn edit_link_within_post_card() {
|
fn edit_link_within_post_card() {
|
||||||
|
let conn = Conn::establish(CONFIG.database_url.as_str()).unwrap();
|
||||||
|
Instance::insert(
|
||||||
|
&conn,
|
||||||
|
NewInstance {
|
||||||
|
public_domain: "example.org".to_string(),
|
||||||
|
name: "Plume".to_string(),
|
||||||
|
local: true,
|
||||||
|
long_description: SafeString::new(""),
|
||||||
|
short_description: SafeString::new(""),
|
||||||
|
default_license: "CC-BY-SA".to_string(),
|
||||||
|
open_registrations: true,
|
||||||
|
short_description_html: String::new(),
|
||||||
|
long_description_html: String::new(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
let rocket = init_rocket();
|
let rocket = init_rocket();
|
||||||
let client = Client::new(rocket).expect("valid rocket instance");
|
let client = Client::new(rocket).expect("valid rocket instance");
|
||||||
let dbpool = client.rocket().state::<DbPool>().unwrap();
|
let dbpool = client.rocket().state::<DbPool>().unwrap();
|
||||||
|
@ -372,7 +372,7 @@ fn ban(id: i32, conn: &Connection, worker: &ScheduledThreadPool) -> Result<(), E
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
BlocklistedEmail::insert(
|
BlocklistedEmail::insert(
|
||||||
&conn,
|
conn,
|
||||||
NewBlocklistedEmail {
|
NewBlocklistedEmail {
|
||||||
email_address: u.email.clone().unwrap(),
|
email_address: u.email.clone().unwrap(),
|
||||||
note: "Banned".to_string(),
|
note: "Banned".to_string(),
|
||||||
|
@ -77,12 +77,7 @@ pub fn upload(
|
|||||||
.map(|ext| format!(".{}", ext))
|
.map(|ext| format!(".{}", ext))
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let dest = format!(
|
let dest = format!("{}/{}{}", CONFIG.media_directory, GUID::rand(), ext);
|
||||||
"{}/{}{}",
|
|
||||||
CONFIG.media_directory,
|
|
||||||
GUID::rand().to_string(),
|
|
||||||
ext
|
|
||||||
);
|
|
||||||
|
|
||||||
match fields["file"][0].data {
|
match fields["file"][0].data {
|
||||||
SavedData::Bytes(ref bytes) => fs::write(&dest, bytes)
|
SavedData::Bytes(ref bytes) => fs::write(&dest, bytes)
|
||||||
|
@ -425,7 +425,7 @@ pub fn create(
|
|||||||
Ok(_) => ValidationErrors::new(),
|
Ok(_) => ValidationErrors::new(),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
if Post::find_by_slug(&conn, &slug, blog.id).is_ok() {
|
if Post::find_by_slug(&conn, slug, blog.id).is_ok() {
|
||||||
errors.add(
|
errors.add(
|
||||||
"title",
|
"title",
|
||||||
ValidationError {
|
ValidationError {
|
||||||
|
@ -54,7 +54,7 @@ pub fn search(query: Option<Form<SearchQuery>>, conn: DbConn, rockets: PlumeRock
|
|||||||
let query = query.map(Form::into_inner).unwrap_or_default();
|
let query = query.map(Form::into_inner).unwrap_or_default();
|
||||||
let page = query.page.unwrap_or_default();
|
let page = query.page.unwrap_or_default();
|
||||||
let mut parsed_query =
|
let mut parsed_query =
|
||||||
Query::from_str(&query.q.as_deref().unwrap_or_default()).unwrap_or_default();
|
Query::from_str(query.q.as_deref().unwrap_or_default()).unwrap_or_default();
|
||||||
|
|
||||||
param_to_query!(query, parsed_query; normal: title, subtitle, content, tag,
|
param_to_query!(query, parsed_query; normal: title, subtitle, content, tag,
|
||||||
instance, author, blog, lang, license;
|
instance, author, blog, lang, license;
|
||||||
|
@ -553,14 +553,14 @@ pub fn ap_followers(
|
|||||||
#[get("/@/<name>/atom.xml")]
|
#[get("/@/<name>/atom.xml")]
|
||||||
pub fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
|
pub fn atom_feed(name: String, conn: DbConn) -> Option<Content<String>> {
|
||||||
let conn = &conn;
|
let conn = &conn;
|
||||||
let author = User::find_by_fqn(&conn, &name).ok()?;
|
let author = User::find_by_fqn(conn, &name).ok()?;
|
||||||
let entries = Post::get_recents_for_author(&conn, &author, 15).ok()?;
|
let entries = Post::get_recents_for_author(conn, &author, 15).ok()?;
|
||||||
let uri = Instance::get_local()
|
let uri = Instance::get_local()
|
||||||
.ok()?
|
.ok()?
|
||||||
.compute_box("@", &name, "atom.xml");
|
.compute_box("@", &name, "atom.xml");
|
||||||
let title = &author.display_name;
|
let title = &author.display_name;
|
||||||
let default_updated = &author.creation_date;
|
let default_updated = &author.creation_date;
|
||||||
let feed = super::build_atom_feed(entries, &uri, title, default_updated, &conn);
|
let feed = super::build_atom_feed(entries, &uri, title, default_updated, conn);
|
||||||
Some(Content(
|
Some(Content(
|
||||||
ContentType::new("application", "atom+xml"),
|
ContentType::new("application", "atom+xml"),
|
||||||
feed.to_string(),
|
feed.to_string(),
|
||||||
|
@ -41,7 +41,7 @@ impl IntoContext for (&DbConn, &PlumeRocket) {
|
|||||||
Option<(String, String)>,
|
Option<(String, String)>,
|
||||||
) {
|
) {
|
||||||
(
|
(
|
||||||
&self.0,
|
self.0,
|
||||||
&self.1.intl.catalog,
|
&self.1.intl.catalog,
|
||||||
self.1.user.clone(),
|
self.1.user.clone(),
|
||||||
self.1.flash_msg.clone(),
|
self.1.flash_msg.clone(),
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
@(ctx: BaseContext, comment_tree: &CommentTree, in_reply_to: Option<&str>, blog: &str, slug: &str)
|
@(ctx: BaseContext, comment_tree: &CommentTree, in_reply_to: Option<&str>, blog: &str, slug: &str)
|
||||||
|
|
||||||
@if let Some(ref comm) = Some(&comment_tree.comment) {
|
@if let Some(comm) = Some(&comment_tree.comment) {
|
||||||
@if let Ok(author) = comm.get_author(ctx.0) {
|
@if let Ok(author) = comm.get_author(ctx.0) {
|
||||||
<div class="comment u-comment h-cite" id="comment-@comm.id">
|
<div class="comment u-comment h-cite" id="comment-@comm.id">
|
||||||
<main class="content">
|
<main class="content">
|
||||||
|
Loading…
Reference in New Issue
Block a user