diff --git a/.env b/.env index 8d33ab5..c60e236 100644 --- a/.env +++ b/.env @@ -1,2 +1 @@ DATABASE_URL=postgres://ap_actix:ap_actix@localhost:5432/ap_actix -HOSTNAME=localhost:8080 diff --git a/Cargo.lock b/Cargo.lock index ab4e8e5..eb8043e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ dependencies = [ "activitystreams-derive", "chrono", "mime", - "serde", + "serde 1.0.105", "serde_json", "thiserror", "url", @@ -127,7 +127,7 @@ dependencies = [ "pin-project", "rand", "regex", - "serde", + "serde 1.0.105", "serde_json", "serde_urlencoded", "sha-1", @@ -155,7 +155,7 @@ dependencies = [ "http", "log", "regex", - "serde", + "serde 1.0.105", ] [[package]] @@ -298,7 +298,7 @@ dependencies = [ "pin-project", "regex", "rustls", - "serde", + "serde 1.0.105", "serde_json", "serde_urlencoded", "time 0.2.9", @@ -324,7 +324,7 @@ checksum = "120ce509b4ad2a0dedfbaebc1c1fb2b5e7bb34430a851c3eb264a704135e30a7" dependencies = [ "actix-http", "actix-web", - "serde", + "serde 1.0.105", "serde_derive", "thiserror", ] @@ -364,6 +364,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.8", +] + [[package]] name = "anyhow" version = "1.0.27" @@ -382,6 +391,7 @@ dependencies = [ "anyhow", "base64 0.12.0", "bb8-postgres", + "config", "dotenv", "futures", "http-signature-normalization-actix", @@ -393,9 +403,10 @@ dependencies = [ "rsa", "rsa-magic-public-key", "rsa-pem", - "serde", + "serde 1.0.105", "serde_json", "sha2", + "structopt", "thiserror", "tokio", "ttl_cache", @@ -408,6 +419,15 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "async-trait" version = "0.1.24" @@ -461,7 +481,7 @@ dependencies = [ "percent-encoding", "rand", "rustls", - "serde", + "serde 1.0.105", "serde_json", "serde_urlencoded", ] @@ -635,10 +655,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" dependencies = [ "num-integer", - "num-traits", + "num-traits 0.2.11", "time 0.1.42", ] +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -648,6 +683,22 @@ dependencies = [ "bitflags", ] +[[package]] +name = "config" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.105", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const-random" version = "0.1.8" @@ -1183,6 +1234,19 @@ dependencies = [ "spin", ] +[[package]] +name = "lexical-core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" +dependencies = [ + "arrayvec", + "cfg-if", + "rustc_version", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.68" @@ -1195,6 +1259,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +[[package]] +name = "linked-hash-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" +dependencies = [ + "serde 0.8.23", + "serde_test", +] + [[package]] name = "linked-hash-map" version = "0.5.2" @@ -1234,7 +1308,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ - "linked-hash-map", + "linked-hash-map 0.5.2", ] [[package]] @@ -1335,6 +1409,23 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -1343,7 +1434,7 @@ checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ "autocfg 1.0.0", "num-integer", - "num-traits", + "num-traits 0.2.11", ] [[package]] @@ -1358,9 +1449,9 @@ dependencies = [ "libm", "num-integer", "num-iter", - "num-traits", + "num-traits 0.2.11", "rand", - "serde", + "serde 1.0.105", "smallvec", "zeroize", ] @@ -1372,7 +1463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" dependencies = [ "autocfg 1.0.0", - "num-traits", + "num-traits 0.2.11", ] [[package]] @@ -1383,7 +1474,16 @@ checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" dependencies = [ "autocfg 1.0.0", "num-integer", - "num-traits", + "num-traits 0.2.11", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.11", ] [[package]] @@ -1547,6 +1647,32 @@ dependencies = [ "log", ] +[[package]] +name = "proc-macro-error" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7959c6467d962050d639361f7703b2051c43036d03493c36f01d440fdd3138a" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4002d9f55991d5e019fb940a90e1a95eb80c24e77cb2462dd4dc869604d543a" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "syn-mid", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.12" @@ -1690,7 +1816,7 @@ dependencies = [ "num-bigint-dig", "num-integer", "num-iter", - "num-traits", + "num-traits 0.2.11", "rand", "subtle 2.2.2", "zeroize", @@ -1716,13 +1842,19 @@ dependencies = [ "log", "num-bigint", "num-bigint-dig", - "num-traits", + "num-traits 0.2.11", "pem", "rsa", "thiserror", "yasna", ] +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + [[package]] name = "rustc-demangle" version = "0.1.16" @@ -1799,6 +1931,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + [[package]] name = "serde" version = "1.0.105" @@ -1808,6 +1946,19 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "linked-hash-map 0.3.0", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + [[package]] name = "serde_derive" version = "1.0.105" @@ -1827,7 +1978,16 @@ checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" dependencies = [ "itoa", "ryu", - "serde", + "serde 1.0.105", +] + +[[package]] +name = "serde_test" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" +dependencies = [ + "serde 0.8.23", ] [[package]] @@ -1838,7 +1998,7 @@ checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" dependencies = [ "dtoa", "itoa", - "serde", + "serde 1.0.105", "url", ] @@ -1924,6 +2084,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4edf667ea8f60afc06d6aeec079d20d5800351109addec1faea678a8663da4e1" +[[package]] +name = "static_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" + [[package]] name = "stdweb" version = "0.4.20" @@ -1946,7 +2112,7 @@ checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ "proc-macro2", "quote", - "serde", + "serde 1.0.105", "serde_derive", "syn", ] @@ -1960,7 +2126,7 @@ dependencies = [ "base-x", "proc-macro2", "quote", - "serde", + "serde 1.0.105", "serde_derive", "serde_json", "sha1", @@ -1983,6 +2149,36 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8faa2719539bbe9d77869bfb15d4ee769f99525e707931452c97b693b3f159d" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f88b8e18c69496aad6f9ddf4630dd7d585bcaf765786cb415b9aec2fe5a0430" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "1.0.0" @@ -2006,6 +2202,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "synstructure" version = "0.12.3" @@ -2027,6 +2234,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.11" @@ -2196,6 +2412,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde 1.0.105", +] + [[package]] name = "trust-dns-proto" version = "0.19.3" @@ -2242,7 +2467,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4189890526f0168710b6ee65ceaedf1460c48a14318ceec933cb26baa492096a" dependencies = [ - "linked-hash-map", + "linked-hash-map 0.5.2", ] [[package]] @@ -2275,6 +2500,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + [[package]] name = "unicode-xid" version = "0.2.0" @@ -2307,6 +2538,18 @@ dependencies = [ "rand", ] +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -2464,6 +2707,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "yaml-rust" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +dependencies = [ + "linked-hash-map 0.5.2", +] + [[package]] name = "yasna" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 4dc1f23..25c1bbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ actix-webfinger = "0.3.0-alpha.3" activitystreams = "0.5.0-alpha.11" base64 = "0.12" bb8-postgres = "0.4.0" +config = "0.10.1" dotenv = "0.15.0" futures = "0.3.4" http-signature-normalization-actix = { version = "0.3.0-alpha.5", default-features = false, features = ["sha-2"] } @@ -29,6 +30,7 @@ rsa-pem = { version = "0.1.0", git = "https://git.asonix.dog/Aardwolf/rsa-pem" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha2 = "0.8" +structopt = "0.3.12" thiserror = "1.0" tokio = { version = "0.2.13", features = ["sync"] } ttl_cache = "0.5.1" diff --git a/src/actor.rs b/src/actor.rs new file mode 100644 index 0000000..d352e4c --- /dev/null +++ b/src/actor.rs @@ -0,0 +1,48 @@ +use crate::{ + apub::PublicKey, + config::{Config, UrlKind}, + error::MyError, + responses::ok, + state::State, +}; +use activitystreams::{ + actor::Application, context, endpoint::EndpointProperties, ext::Extensible, + object::properties::ObjectProperties, security, +}; +use actix_web::{web, Responder}; +use rsa_pem::KeyExt; + +pub async fn route( + state: web::Data, + config: web::Data, +) -> Result { + let mut application = Application::full(); + let mut endpoint = EndpointProperties::default(); + + endpoint.set_shared_inbox(config.generate_url(UrlKind::Inbox))?; + + let props: &mut ObjectProperties = application.as_mut(); + props + .set_id(config.generate_url(UrlKind::Actor))? + .set_summary_xsd_string("AodeRelay bot")? + .set_name_xsd_string("AodeRelay")? + .set_url_xsd_any_uri(config.generate_url(UrlKind::Actor))? + .set_many_context_xsd_any_uris(vec![context(), security()])?; + + application + .extension + .set_preferred_username("relay")? + .set_followers(config.generate_url(UrlKind::Followers))? + .set_following(config.generate_url(UrlKind::Following))? + .set_inbox(config.generate_url(UrlKind::Inbox))? + .set_outbox(config.generate_url(UrlKind::Outbox))? + .set_endpoints(endpoint)?; + + let public_key = PublicKey { + id: config.generate_url(UrlKind::MainKey).parse()?, + owner: config.generate_url(UrlKind::Actor).parse()?, + public_key_pem: state.public_key.to_pem_pkcs8()?, + }; + + Ok(ok(application.extend(public_key.to_ext()))) +} diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..25c8dc9 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,32 @@ +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "relay", about = "An activitypub relay")] +pub struct Args { + #[structopt(short, help = "A list of domains that should be blocked")] + blocks: Vec, + + #[structopt(short, help = "A list of domains that should be whitelisted")] + whitelists: Vec, + + #[structopt(short, help = "Undo whitelisting or blocking these domains")] + undo: bool, +} + +impl Args { + pub fn new() -> Self { + Self::from_args() + } + + pub fn blocks(&self) -> &[String] { + &self.blocks + } + + pub fn whitelists(&self) -> &[String] { + &self.whitelists + } + + pub fn undo(&self) -> bool { + self.undo + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5069bf4 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,111 @@ +use crate::{error::MyError, requests::Requests, verifier::MyVerify}; +use config::Environment; +use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature}; +use sha2::{Digest, Sha256}; +use std::net::IpAddr; +use uuid::Uuid; + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct Config { + hostname: String, + addr: IpAddr, + port: u16, + debug: bool, + whitelist_mode: bool, + validate_signatures: bool, + https: bool, + database_url: String, +} + +pub enum UrlKind { + Activity, + Actor, + Followers, + Following, + Inbox, + MainKey, + NodeInfo, + Outbox, +} + +impl Config { + pub fn build() -> Result { + let mut config = config::Config::new(); + config + .set_default("hostname", "localhost:8080")? + .set_default("addr", "127.0.0.1")? + .set_default("port", 8080)? + .set_default("debug", true)? + .set_default("whitelist_mode", false)? + .set_default("validate_signatures", false)? + .set_default("https", false)? + .merge(Environment::new())?; + + Ok(config.try_into()?) + } + + pub fn digest_middleware(&self) -> VerifyDigest { + if self.validate_signatures { + VerifyDigest::new(Sha256::new()) + } else { + VerifyDigest::new(Sha256::new()).optional() + } + } + + pub fn signature_middleware(&self, requests: Requests) -> VerifySignature { + if self.validate_signatures { + VerifySignature::new(MyVerify(requests), Default::default()) + } else { + VerifySignature::new(MyVerify(requests), Default::default()).optional() + } + } + + pub fn bind_address(&self) -> (IpAddr, u16) { + (self.addr, self.port) + } + + pub fn debug(&self) -> bool { + self.debug + } + + pub fn whitelist_mode(&self) -> bool { + self.whitelist_mode + } + + pub fn database_url(&self) -> &str { + &self.database_url + } + + pub fn hostname(&self) -> &str { + &self.hostname + } + + pub fn generate_resource(&self) -> String { + format!("relay@{}", self.hostname) + } + + pub fn software_name(&self) -> String { + "AodeRelay".to_owned() + } + + pub fn software_version(&self) -> String { + "v0.1.0-master".to_owned() + } + + pub fn generate_url(&self, kind: UrlKind) -> String { + let scheme = if self.https { "https" } else { "http" }; + + match kind { + UrlKind::Activity => { + format!("{}://{}/activity/{}", scheme, self.hostname, Uuid::new_v4()) + } + UrlKind::Actor => format!("{}://{}/actor", scheme, self.hostname), + UrlKind::Followers => format!("{}://{}/followers", scheme, self.hostname), + UrlKind::Following => format!("{}://{}/following", scheme, self.hostname), + UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname), + UrlKind::MainKey => format!("{}://{}/actor#main-key", scheme, self.hostname), + UrlKind::NodeInfo => format!("{}://{}/nodeinfo/2.0", scheme, self.hostname), + UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname), + } + } +} diff --git a/src/db.rs b/src/db.rs index 4791430..1c9dc99 100644 --- a/src/db.rs +++ b/src/db.rs @@ -2,7 +2,11 @@ use crate::error::MyError; use activitystreams::primitives::XsdAnyUri; use bb8_postgres::{ bb8, - tokio_postgres::{row::Row, Client, Config, NoTls}, + tokio_postgres::{ + error::{Error, SqlState}, + row::Row, + Client, Config, NoTls, + }, PostgresConnectionManager, }; use log::{info, warn}; @@ -43,6 +47,48 @@ impl Db { Ok(()) } + pub async fn add_blocks(&self, domains: &[String]) -> Result<(), MyError> { + let conn = self.pool.get().await?; + for domain in domains { + match add_block(&conn, domain.as_str()).await { + Err(e) if e.code() != Some(&SqlState::UNIQUE_VIOLATION) => { + Err(e)?; + } + _ => (), + }; + } + Ok(()) + } + + pub async fn remove_blocks(&self, domains: &[String]) -> Result<(), MyError> { + let conn = self.pool.get().await?; + for domain in domains { + remove_block(&conn, domain.as_str()).await? + } + Ok(()) + } + + pub async fn add_whitelists(&self, domains: &[String]) -> Result<(), MyError> { + let conn = self.pool.get().await?; + for domain in domains { + match add_whitelist(&conn, domain.as_str()).await { + Err(e) if e.code() != Some(&SqlState::UNIQUE_VIOLATION) => { + Err(e)?; + } + _ => (), + }; + } + Ok(()) + } + + pub async fn remove_whitelists(&self, domains: &[String]) -> Result<(), MyError> { + let conn = self.pool.get().await?; + for domain in domains { + remove_whitelist(&conn, domain.as_str()).await? + } + Ok(()) + } + pub async fn hydrate_blocks(&self) -> Result, MyError> { let conn = self.pool.get().await?; @@ -74,7 +120,7 @@ impl Db { } } -pub async fn listen(client: &Client) -> Result<(), MyError> { +pub async fn listen(client: &Client) -> Result<(), Error> { info!("LISTEN new_blocks;"); info!("LISTEN new_whitelists;"); info!("LISTEN new_listeners;"); @@ -117,49 +163,67 @@ async fn update_private_key(client: &Client, key: &RSAPrivateKey) -> Result<(), Ok(()) } -async fn add_block(client: &Client, block: &XsdAnyUri) -> Result<(), MyError> { - let host = if let Some(host) = block.as_url().host() { - host - } else { - return Err(MyError::Host(block.to_string())); - }; - +async fn add_block(client: &Client, domain: &str) -> Result<(), Error> { info!( "INSERT INTO blocks (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]", - host.to_string() + domain, ); client .execute( "INSERT INTO blocks (domain_name, created_at) VALUES ($1::TEXT, 'now');", - &[&host.to_string()], + &[&domain], ) .await?; Ok(()) } -async fn add_whitelist(client: &Client, whitelist: &XsdAnyUri) -> Result<(), MyError> { - let host = if let Some(host) = whitelist.as_url().host() { - host - } else { - return Err(MyError::Host(whitelist.to_string())); - }; +async fn remove_block(client: &Client, domain: &str) -> Result<(), Error> { + info!( + "DELETE FROM blocks WHERE domain_name = $1::TEXT; [{}]", + domain, + ); + client + .execute( + "DELETE FROM blocks WHERE domain_name = $1::TEXT;", + &[&domain], + ) + .await?; + Ok(()) +} + +async fn add_whitelist(client: &Client, domain: &str) -> Result<(), Error> { info!( "INSERT INTO whitelists (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]", - host.to_string() + domain, ); client .execute( "INSERT INTO whitelists (domain_name, created_at) VALUES ($1::TEXT, 'now');", - &[&host.to_string()], + &[&domain], ) .await?; Ok(()) } -async fn remove_listener(client: &Client, listener: &XsdAnyUri) -> Result<(), MyError> { +async fn remove_whitelist(client: &Client, domain: &str) -> Result<(), Error> { + info!( + "DELETE FROM whitelists WHERE domain_name = $1::TEXT; [{}]", + domain, + ); + client + .execute( + "DELETE FROM whitelists WHERE domain_name = $1::TEXT;", + &[&domain], + ) + .await?; + + Ok(()) +} + +async fn remove_listener(client: &Client, listener: &XsdAnyUri) -> Result<(), Error> { info!( "DELETE FROM listeners WHERE actor_id = {};", listener.as_str() @@ -174,7 +238,7 @@ async fn remove_listener(client: &Client, listener: &XsdAnyUri) -> Result<(), My Ok(()) } -async fn add_listener(client: &Client, listener: &XsdAnyUri) -> Result<(), MyError> { +async fn add_listener(client: &Client, listener: &XsdAnyUri) -> Result<(), Error> { info!( "INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, 'now'); [{}]", listener.as_str(), @@ -189,14 +253,14 @@ async fn add_listener(client: &Client, listener: &XsdAnyUri) -> Result<(), MyErr Ok(()) } -async fn hydrate_blocks(client: &Client) -> Result, MyError> { +async fn hydrate_blocks(client: &Client) -> Result, Error> { info!("SELECT domain_name FROM blocks"); let rows = client.query("SELECT domain_name FROM blocks", &[]).await?; parse_rows(rows) } -async fn hydrate_whitelists(client: &Client) -> Result, MyError> { +async fn hydrate_whitelists(client: &Client) -> Result, Error> { info!("SELECT domain_name FROM whitelists"); let rows = client .query("SELECT domain_name FROM whitelists", &[]) @@ -205,14 +269,14 @@ async fn hydrate_whitelists(client: &Client) -> Result, MyError> parse_rows(rows) } -async fn hydrate_listeners(client: &Client) -> Result, MyError> { +async fn hydrate_listeners(client: &Client) -> Result, Error> { info!("SELECT actor_id FROM listeners"); let rows = client.query("SELECT actor_id FROM listeners", &[]).await?; parse_rows(rows) } -fn parse_rows(rows: Vec) -> Result, MyError> +fn parse_rows(rows: Vec) -> Result, Error> where T: std::str::FromStr + Eq + std::hash::Hash, E: std::fmt::Display, diff --git a/src/error.rs b/src/error.rs index 4d394d7..adcbc76 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,18 @@ use activitystreams::primitives::XsdAnyUriError; -use actix_web::{error::ResponseError, http::StatusCode, HttpResponse}; +use actix_web::{ + error::{BlockingError, ResponseError}, + http::StatusCode, + HttpResponse, +}; use log::error; use rsa_pem::KeyError; -use std::{convert::Infallible, io::Error}; +use std::{convert::Infallible, fmt::Debug, io::Error}; #[derive(Debug, thiserror::Error)] pub enum MyError { + #[error("Error in configuration, {0}")] + Config(#[from] config::ConfigError), + #[error("Error in db, {0}")] DbError(#[from] bb8_postgres::tokio_postgres::error::Error), @@ -51,9 +58,6 @@ pub enum MyError { #[error("Wrong ActivityPub kind, {0}")] Kind(String), - #[error("No host present in URI, {0}")] - Host(String), - #[error("Too many CPUs, {0}")] CpuCount(#[from] std::num::TryFromIntError), @@ -77,6 +81,9 @@ pub enum MyError { #[error("URI is missing domain field")] Domain, + + #[error("Blocking operation was canceled")] + Canceled, } impl ResponseError for MyError { @@ -102,6 +109,18 @@ impl ResponseError for MyError { } } +impl From> for MyError +where + T: Into + Debug, +{ + fn from(e: BlockingError) -> Self { + match e { + BlockingError::Error(e) => e.into(), + BlockingError::Canceled => MyError::Canceled, + } + } +} + impl From> for MyError where T: Into, diff --git a/src/inbox.rs b/src/inbox.rs index cbfaf14..a85f7d5 100644 --- a/src/inbox.rs +++ b/src/inbox.rs @@ -1,10 +1,11 @@ use crate::{ - accepted, apub::{AcceptedActors, AcceptedObjects, ValidTypes}, + config::{Config, UrlKind}, db::Db, error::MyError, requests::Requests, - state::{State, UrlKind}, + responses::accepted, + state::State, }; use activitystreams::{ activity::{Accept, Announce, Follow, Undo}, @@ -22,6 +23,7 @@ use std::convert::TryInto; pub async fn inbox( db: web::Data, state: web::Data, + config: web::Data, client: web::Data, input: web::Json, verified: SignatureVerified, @@ -58,19 +60,20 @@ pub async fn inbox( match input.kind { ValidTypes::Announce | ValidTypes::Create => { - handle_announce(&state, &client, input, actor).await + handle_announce(&state, &config, &client, input, actor).await } - ValidTypes::Follow => handle_follow(&db, &state, &client, input, actor, is_listener).await, + ValidTypes::Follow => handle_follow(&db, &config, &client, input, actor, is_listener).await, ValidTypes::Delete | ValidTypes::Update => { handle_forward(&state, &client, input, actor).await } - ValidTypes::Undo => handle_undo(&db, &state, &client, input, actor).await, + ValidTypes::Undo => handle_undo(&db, &state, &config, &client, input, actor).await, } } async fn handle_undo( db: &Db, state: &State, + config: &Config, client: &Requests, input: AcceptedObjects, actor: AcceptedActors, @@ -88,7 +91,7 @@ async fn handle_undo( return handle_forward(state, client, input, actor).await; } - let my_id: XsdAnyUri = state.generate_url(UrlKind::Actor).parse()?; + let my_id: XsdAnyUri = config.generate_url(UrlKind::Actor).parse()?; if !input.object.child_object_is(&my_id) && !input.object.child_object_is(&public()) { return Err(MyError::WrongActor(input.object.id().to_string())); @@ -97,7 +100,7 @@ async fn handle_undo( let inbox = actor.inbox().to_owned(); db.remove_listener(inbox).await?; - let undo = generate_undo_follow(state, &actor.id, &my_id)?; + let undo = generate_undo_follow(config, &actor.id, &my_id)?; let client2 = client.clone(); let inbox = actor.inbox().clone(); @@ -125,6 +128,7 @@ async fn handle_forward( async fn handle_announce( state: &State, + config: &Config, client: &Requests, input: AcceptedObjects, actor: AcceptedActors, @@ -135,9 +139,9 @@ async fn handle_announce( return Err(MyError::Duplicate); } - let activity_id: XsdAnyUri = state.generate_url(UrlKind::Activity).parse()?; + let activity_id: XsdAnyUri = config.generate_url(UrlKind::Activity).parse()?; - let announce = generate_announce(state, &activity_id, object_id)?; + let announce = generate_announce(config, &activity_id, object_id)?; let inboxes = get_inboxes(state, &actor, &object_id).await?; client.deliver_many(inboxes, announce.clone()); @@ -148,13 +152,13 @@ async fn handle_announce( async fn handle_follow( db: &Db, - state: &State, + config: &Config, client: &Requests, input: AcceptedObjects, actor: AcceptedActors, is_listener: bool, ) -> Result { - let my_id: XsdAnyUri = state.generate_url(UrlKind::Actor).parse()?; + let my_id: XsdAnyUri = config.generate_url(UrlKind::Actor).parse()?; if !input.object.is(&my_id) && !input.object.is(&public()) { return Err(MyError::WrongActor(input.object.id().to_string())); @@ -166,7 +170,7 @@ async fn handle_follow( // if following relay directly, not just following 'public', followback if input.object.is(&my_id) { - let follow = generate_follow(state, &actor.id, &my_id)?; + let follow = generate_follow(config, &actor.id, &my_id)?; let client2 = client.clone(); let inbox = actor.inbox().clone(); let follow2 = follow.clone(); @@ -176,7 +180,7 @@ async fn handle_follow( } } - let accept = generate_accept_follow(state, &actor.id, &input.id, &my_id)?; + let accept = generate_accept_follow(config, &actor.id, &input.id, &my_id)?; let client2 = client.clone(); let inbox = actor.inbox().clone(); @@ -190,7 +194,7 @@ async fn handle_follow( // Generate a type that says "I want to stop following you" fn generate_undo_follow( - state: &State, + config: &Config, actor_id: &XsdAnyUri, my_id: &XsdAnyUri, ) -> Result { @@ -203,7 +207,7 @@ fn generate_undo_follow( follow .object_props - .set_id(state.generate_url(UrlKind::Activity))?; + .set_id(config.generate_url(UrlKind::Activity))?; follow .follow_props .set_actor_xsd_any_uri(actor_id.clone())? @@ -212,12 +216,12 @@ fn generate_undo_follow( follow })?; - prepare_activity(undo, state.generate_url(UrlKind::Actor), actor_id.clone()) + prepare_activity(undo, config.generate_url(UrlKind::Actor), actor_id.clone()) } // Generate a type that says "Look at this object" fn generate_announce( - state: &State, + config: &Config, activity_id: &XsdAnyUri, object_id: &XsdAnyUri, ) -> Result { @@ -226,18 +230,18 @@ fn generate_announce( announce .announce_props .set_object_xsd_any_uri(object_id.clone())? - .set_actor_xsd_any_uri(state.generate_url(UrlKind::Actor))?; + .set_actor_xsd_any_uri(config.generate_url(UrlKind::Actor))?; prepare_activity( announce, activity_id.clone(), - state.generate_url(UrlKind::Followers), + config.generate_url(UrlKind::Followers), ) } // Generate a type that says "I want to follow you" fn generate_follow( - state: &State, + config: &Config, actor_id: &XsdAnyUri, my_id: &XsdAnyUri, ) -> Result { @@ -250,14 +254,14 @@ fn generate_follow( prepare_activity( follow, - state.generate_url(UrlKind::Activity), + config.generate_url(UrlKind::Activity), actor_id.clone(), ) } // Generate a type that says "I accept your follow request" fn generate_accept_follow( - state: &State, + config: &Config, actor_id: &XsdAnyUri, input_id: &XsdAnyUri, my_id: &XsdAnyUri, @@ -281,7 +285,7 @@ fn generate_accept_follow( prepare_activity( accept, - state.generate_url(UrlKind::Activity), + config.generate_url(UrlKind::Activity), actor_id.clone(), ) } diff --git a/src/main.rs b/src/main.rs index c063268..58f308b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,122 +1,77 @@ -use activitystreams::{ - actor::Application, context, endpoint::EndpointProperties, ext::Extensible, - object::properties::ObjectProperties, security, -}; -use actix_web::{middleware::Logger, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{middleware::Logger, web, App, HttpServer, Responder}; use bb8_postgres::tokio_postgres; -use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature}; -use rsa_pem::KeyExt; -use sha2::{Digest, Sha256}; +mod actor; mod apub; +mod args; +mod config; mod db; mod error; mod inbox; mod nodeinfo; mod notify; mod requests; +mod responses; mod state; mod verifier; mod webfinger; -use self::{ - apub::PublicKey, - db::Db, - error::MyError, - state::{State, UrlKind}, - verifier::MyVerify, - webfinger::RelayResolver, -}; - -pub fn ok(item: T) -> HttpResponse -where - T: serde::ser::Serialize, -{ - HttpResponse::Ok() - .content_type("application/activity+json") - .json(item) -} - -pub fn accepted(item: T) -> HttpResponse -where - T: serde::ser::Serialize, -{ - HttpResponse::Accepted() - .content_type("application/activity+json") - .json(item) -} +use self::{args::Args, config::Config, db::Db, state::State, webfinger::RelayResolver}; async fn index() -> impl Responder { "hewwo, mr obama" } -async fn actor_route(state: web::Data) -> Result { - let mut application = Application::full(); - let mut endpoint = EndpointProperties::default(); - - endpoint.set_shared_inbox(state.generate_url(UrlKind::Inbox))?; - - let props: &mut ObjectProperties = application.as_mut(); - props - .set_id(state.generate_url(UrlKind::Actor))? - .set_summary_xsd_string("AodeRelay bot")? - .set_name_xsd_string("AodeRelay")? - .set_url_xsd_any_uri(state.generate_url(UrlKind::Actor))? - .set_many_context_xsd_any_uris(vec![context(), security()])?; - - application - .extension - .set_preferred_username("relay")? - .set_followers(state.generate_url(UrlKind::Followers))? - .set_following(state.generate_url(UrlKind::Following))? - .set_inbox(state.generate_url(UrlKind::Inbox))? - .set_outbox(state.generate_url(UrlKind::Outbox))? - .set_endpoints(endpoint)?; - - let public_key = PublicKey { - id: state.generate_url(UrlKind::MainKey).parse()?, - owner: state.generate_url(UrlKind::Actor).parse()?, - public_key_pem: state.settings.public_key.to_pem_pkcs8()?, - }; - - Ok(ok(application.extend(public_key.to_ext()))) -} - #[actix_rt::main] async fn main() -> Result<(), anyhow::Error> { dotenv::dotenv().ok(); + + let config = Config::build()?; + + if config.debug() { + std::env::set_var("RUST_LOG", "debug") + } else { + std::env::set_var("RUST_LOG", "info") + } + pretty_env_logger::init(); - let pg_config: tokio_postgres::Config = std::env::var("DATABASE_URL")?.parse()?; - let hostname: String = std::env::var("HOSTNAME")?; - let use_whitelist = std::env::var("USE_WHITELIST").is_ok(); - let use_https = std::env::var("USE_HTTPS").is_ok(); - + let pg_config: tokio_postgres::Config = config.database_url().parse()?; let db = Db::build(pg_config.clone()).await?; - let state = State::hydrate(use_https, use_whitelist, hostname, &db).await?; + let args = Args::new(); + + if !args.blocks().is_empty() || !args.whitelists().is_empty() { + if args.undo() { + db.remove_blocks(args.blocks()).await?; + db.remove_whitelists(args.whitelists()).await?; + } else { + db.add_blocks(args.blocks()).await?; + db.add_whitelists(args.whitelists()).await?; + } + return Ok(()); + } + + let state = State::hydrate(config.clone(), &db).await?; let _ = notify::NotifyHandler::start_handler(state.clone(), pg_config.clone()); + let bind_address = config.bind_address(); HttpServer::new(move || { - let state = state.clone(); - App::new() .wrap(Logger::default()) .data(db.clone()) .data(state.clone()) .data(state.requests()) + .data(config.clone()) .service(web::resource("/").route(web::get().to(index))) .service( web::resource("/inbox") - .wrap(VerifyDigest::new(Sha256::new())) - .wrap(VerifySignature::new( - MyVerify(state.requests()), - Default::default(), - )) + .wrap(config.digest_middleware()) + .wrap(config.signature_middleware(state.requests())) .route(web::post().to(inbox::inbox)), ) - .service(web::resource("/actor").route(web::get().to(actor_route))) + .service(web::resource("/actor").route(web::get().to(actor::route))) .service(web::resource("/nodeinfo/2.0").route(web::get().to(nodeinfo::route))) .service( web::scope("/.well-known") @@ -124,7 +79,7 @@ async fn main() -> Result<(), anyhow::Error> { .service(web::resource("/nodeinfo").route(web::get().to(nodeinfo::well_known))), ) }) - .bind("0.0.0.0:8080")? + .bind(bind_address)? .run() .await?; Ok(()) diff --git a/src/nodeinfo.rs b/src/nodeinfo.rs index 0adc1bd..ed3236b 100644 --- a/src/nodeinfo.rs +++ b/src/nodeinfo.rs @@ -1,24 +1,24 @@ -use crate::state::{State, UrlKind}; +use crate::config::{Config, UrlKind}; use actix_web::{web, Responder}; use actix_webfinger::Link; use std::collections::HashMap; -pub async fn well_known(state: web::Data) -> impl Responder { +pub async fn well_known(config: web::Data) -> impl Responder { web::Json(Link { rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".to_owned(), - href: Some(state.generate_url(UrlKind::NodeInfo)), + href: Some(config.generate_url(UrlKind::NodeInfo)), template: None, kind: None, }) .with_header("Content-Type", "application/jrd+json") } -pub async fn route(state: web::Data) -> web::Json { +pub async fn route(config: web::Data) -> web::Json { web::Json(NodeInfo { version: NodeInfoVersion, software: Software { - name: state.software_name(), - version: state.software_version(), + name: config.software_name(), + version: config.software_version(), }, protocols: vec![Protocol::ActivityPub], services: vec![], diff --git a/src/responses.rs b/src/responses.rs new file mode 100644 index 0000000..fdf0537 --- /dev/null +++ b/src/responses.rs @@ -0,0 +1,20 @@ +use actix_web::HttpResponse; +use serde::ser::Serialize; + +static CONTENT_TYPE: &str = "application/activity+json"; + +pub fn ok(item: T) -> HttpResponse +where + T: Serialize, +{ + HttpResponse::Ok().content_type(CONTENT_TYPE).json(item) +} + +pub fn accepted(item: T) -> HttpResponse +where + T: Serialize, +{ + HttpResponse::Accepted() + .content_type(CONTENT_TYPE) + .json(item) +} diff --git a/src/state.rs b/src/state.rs index 1688cbf..ad83e2c 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,12 @@ -use crate::{apub::AcceptedActors, db::Db, error::MyError, requests::Requests}; +use crate::{ + apub::AcceptedActors, + config::{Config, UrlKind}, + db::Db, + error::MyError, + requests::Requests, +}; use activitystreams::primitives::XsdAnyUri; +use actix_web::web; use futures::try_join; use log::info; use lru::LruCache; @@ -8,13 +15,14 @@ use rsa::{RSAPrivateKey, RSAPublicKey}; use std::{collections::HashSet, sync::Arc}; use tokio::sync::RwLock; use ttl_cache::TtlCache; -use uuid::Uuid; pub type ActorCache = Arc>>; #[derive(Clone)] pub struct State { - pub settings: Settings, + pub public_key: RSAPublicKey, + private_key: RSAPrivateKey, + config: Config, actor_cache: ActorCache, actor_id_cache: Arc>>, blocks: Arc>>, @@ -22,112 +30,20 @@ pub struct State { listeners: Arc>>, } -#[derive(Clone)] -pub struct Settings { - pub use_https: bool, - pub whitelist_enabled: bool, - pub hostname: String, - pub public_key: RSAPublicKey, - private_key: RSAPrivateKey, -} - -pub enum UrlKind { - Activity, - Actor, - Followers, - Following, - Inbox, - MainKey, - NodeInfo, - Outbox, -} - -impl Settings { - async fn hydrate( - db: &Db, - use_https: bool, - whitelist_enabled: bool, - hostname: String, - ) -> Result { - let private_key = if let Some(key) = db.hydrate_private_key().await? { - key - } else { - info!("Generating new keys"); - let mut rng = thread_rng(); - let key = RSAPrivateKey::new(&mut rng, 4096)?; - - db.update_private_key(&key).await?; - - key - }; - - let public_key = private_key.to_public_key(); - - Ok(Settings { - use_https, - whitelist_enabled, - hostname, - private_key, - public_key, - }) - } - - fn generate_url(&self, kind: UrlKind) -> String { - let scheme = if self.use_https { "https" } else { "http" }; - - match kind { - UrlKind::Activity => { - format!("{}://{}/activity/{}", scheme, self.hostname, Uuid::new_v4()) - } - UrlKind::Actor => format!("{}://{}/actor", scheme, self.hostname), - UrlKind::Followers => format!("{}://{}/followers", scheme, self.hostname), - UrlKind::Following => format!("{}://{}/following", scheme, self.hostname), - UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname), - UrlKind::MainKey => format!("{}://{}/actor#main-key", scheme, self.hostname), - UrlKind::NodeInfo => format!("{}://{}/nodeinfo/2.0", scheme, self.hostname), - UrlKind::Outbox => format!("{}://{}/outbox", scheme, self.hostname), - } - } - - fn generate_resource(&self) -> String { - format!("relay@{}", self.hostname) - } - - fn software_name(&self) -> String { - "AodeRelay".to_owned() - } - - fn software_version(&self) -> String { - "v0.1.0-master".to_owned() - } -} - impl State { - pub fn software_name(&self) -> String { - self.settings.software_name() - } - - pub fn software_version(&self) -> String { - self.settings.software_version() - } - pub fn requests(&self) -> Requests { Requests::new( - self.generate_url(UrlKind::MainKey), - self.settings.private_key.clone(), + self.config.generate_url(UrlKind::MainKey), + self.private_key.clone(), self.actor_cache.clone(), - format!("{} {}", self.software_name(), self.software_version()), + format!( + "{} {}", + self.config.software_name(), + self.config.software_version() + ), ) } - pub fn generate_url(&self, kind: UrlKind) -> String { - self.settings.generate_url(kind) - } - - pub fn generate_resource(&self) -> String { - self.settings.generate_resource() - } - pub async fn bust_whitelist(&self, whitelist: &str) { let hs = self.whitelists.clone(); @@ -169,7 +85,7 @@ impl State { } pub async fn is_whitelisted(&self, actor_id: &XsdAnyUri) -> bool { - if !self.settings.whitelist_enabled { + if !self.config.whitelist_mode() { return true; } @@ -236,21 +152,36 @@ impl State { write_guard.insert(listener); } - pub async fn hydrate( - use_https: bool, - whitelist_enabled: bool, - hostname: String, - db: &Db, - ) -> Result { + pub async fn hydrate(config: Config, db: &Db) -> Result { let f1 = db.hydrate_blocks(); let f2 = db.hydrate_whitelists(); let f3 = db.hydrate_listeners(); - let f4 = Settings::hydrate(db, use_https, whitelist_enabled, hostname); - let (blocks, whitelists, listeners, settings) = try_join!(f1, f2, f3, f4)?; + let f4 = async move { + if let Some(key) = db.hydrate_private_key().await? { + Ok(key) + } else { + info!("Generating new keys"); + let key = web::block(move || { + let mut rng = thread_rng(); + RSAPrivateKey::new(&mut rng, 4096) + }) + .await?; + + db.update_private_key(&key).await?; + + Ok(key) + } + }; + + let (blocks, whitelists, listeners, private_key) = try_join!(f1, f2, f3, f4)?; + + let public_key = private_key.to_public_key(); Ok(State { - settings, + public_key, + private_key, + config, actor_cache: Arc::new(RwLock::new(TtlCache::new(1024 * 8))), actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))), blocks: Arc::new(RwLock::new(blocks)), diff --git a/src/webfinger.rs b/src/webfinger.rs index 8d8f14e..703cb71 100644 --- a/src/webfinger.rs +++ b/src/webfinger.rs @@ -1,4 +1,7 @@ -use crate::state::{State, UrlKind}; +use crate::{ + config::{Config, UrlKind}, + state::State, +}; use activitystreams::context; use actix_web::web::Data; use actix_webfinger::{Link, Resolver, Webfinger}; @@ -11,19 +14,19 @@ pub struct RelayResolver; #[error("Error resolving webfinger data")] pub struct RelayError; -impl Resolver> for RelayResolver { +impl Resolver<(Data, Data)> for RelayResolver { type Error = RelayError; fn find( account: &str, domain: &str, - state: Data, + (state, config): (Data, Data), ) -> Pin, Self::Error>>>> { let domain = domain.to_owned(); let account = account.to_owned(); let fut = async move { - if domain != state.settings.hostname { + if domain != config.hostname() { return Ok(None); } @@ -31,13 +34,13 @@ impl Resolver> for RelayResolver { return Ok(None); } - let mut wf = Webfinger::new(&state.generate_resource()); - wf.add_alias(&state.generate_url(UrlKind::Actor)) - .add_activitypub(&state.generate_url(UrlKind::Actor)) - .add_magic_public_key(&state.settings.public_key.as_magic_public_key()) + let mut wf = Webfinger::new(&config.generate_resource()); + wf.add_alias(&config.generate_url(UrlKind::Actor)) + .add_activitypub(&config.generate_url(UrlKind::Actor)) + .add_magic_public_key(&state.public_key.as_magic_public_key()) .add_link(Link { rel: "self".to_owned(), - href: Some(state.generate_url(UrlKind::Actor)), + href: Some(config.generate_url(UrlKind::Actor)), template: None, kind: Some(format!("application/ld+json; profile=\"{}\"", context())), });