forked from mirrors/relay
Add keys, http-sigs, webfinger
This commit is contained in:
parent
d741e2b202
commit
eea0577686
11 changed files with 561 additions and 80 deletions
207
Cargo.lock
generated
207
Cargo.lock
generated
|
@ -100,7 +100,7 @@ dependencies = [
|
||||||
"actix-threadpool",
|
"actix-threadpool",
|
||||||
"actix-tls",
|
"actix-tls",
|
||||||
"actix-utils",
|
"actix-utils",
|
||||||
"base64",
|
"base64 0.11.0",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"brotli2",
|
"brotli2",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -313,6 +313,19 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-webfinger"
|
||||||
|
version = "0.3.0-alpha.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "221e03224c654d7c6f35cc7a8bb7bc01ce0e53eb61b7722de199c6182cd9f3fe"
|
||||||
|
dependencies = [
|
||||||
|
"actix-http",
|
||||||
|
"actix-web",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix_derive"
|
name = "actix_derive"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -362,15 +375,23 @@ dependencies = [
|
||||||
"actix",
|
"actix",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"actix-webfinger",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64 0.12.0",
|
||||||
"bb8-postgres",
|
"bb8-postgres",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"futures",
|
"futures",
|
||||||
|
"http-signature-normalization-actix",
|
||||||
"log",
|
"log",
|
||||||
"lru",
|
"lru",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
"rand",
|
||||||
|
"rsa",
|
||||||
|
"rsa-magic-public-key",
|
||||||
|
"rsa-pem",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"ttl_cache",
|
"ttl_cache",
|
||||||
|
@ -427,7 +448,7 @@ dependencies = [
|
||||||
"actix-http",
|
"actix-http",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"actix-service",
|
"actix-service",
|
||||||
"base64",
|
"base64 0.11.0",
|
||||||
"bytes",
|
"bytes",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -475,6 +496,12 @@ version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bb8"
|
name = "bb8"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -674,7 +701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
|
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.12.3",
|
"generic-array 0.12.3",
|
||||||
"subtle",
|
"subtle 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1046,6 +1073,32 @@ dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-signature-normalization"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2db9cb1c64aaabb27523433fc8df0467670df81642fd770086cf8a2eb11b24a"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-signature-normalization-actix"
|
||||||
|
version = "0.3.0-alpha.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "406cae6778fb05b34885ee9aaec4e55931ac763aac3edf9a1e25bd40d4490e86"
|
||||||
|
dependencies = [
|
||||||
|
"actix-http",
|
||||||
|
"actix-web",
|
||||||
|
"base64 0.11.0",
|
||||||
|
"bytes",
|
||||||
|
"futures",
|
||||||
|
"http-signature-normalization",
|
||||||
|
"sha2",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
|
@ -1129,6 +1182,9 @@ name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
dependencies = [
|
||||||
|
"spin",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
|
@ -1136,6 +1192,12 @@ version = "0.2.67"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
|
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -1276,6 +1338,36 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.0",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint-dig"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3d03c330f9f7a2c19e3c0b42698e48141d0809c78cd9b6219f85bd7d7e892aa"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 0.1.7",
|
||||||
|
"byteorder",
|
||||||
|
"lazy_static",
|
||||||
|
"libm",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.42"
|
version = "0.1.42"
|
||||||
|
@ -1286,6 +1378,17 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-iter"
|
||||||
|
version = "0.1.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg 1.0.0",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
|
@ -1362,6 +1465,17 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.11.0",
|
||||||
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -1430,7 +1544,7 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a30f0e172ae0fb0653dbf777ad10a74b8e58d6de95a892f2e1d3e94a9df9a844"
|
checksum = "a30f0e172ae0fb0653dbf777ad10a74b8e58d6de95a892f2e1d3e94a9df9a844"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.11.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
|
@ -1586,6 +1700,49 @@ dependencies = [
|
||||||
"quick-error",
|
"quick-error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rsa"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ed8692d8e0ea3baae03f0f32ecfc13a6c6f1f85fcd6d9fdefcdf364e70f4df9"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"failure",
|
||||||
|
"lazy_static",
|
||||||
|
"num-bigint-dig",
|
||||||
|
"num-integer",
|
||||||
|
"num-iter",
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
"subtle 2.2.2",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rsa-magic-public-key"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.asonix.dog/Aardwolf/rsa-magic-public-key#82878d8274530c8184be8e82f0de36623b51d3f6"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.11.0",
|
||||||
|
"num-bigint-dig",
|
||||||
|
"rsa",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rsa-pem"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.asonix.dog/Aardwolf/rsa-pem#6c47c3fc377375a5bfedbb7457832fc013d3227d"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-bigint-dig",
|
||||||
|
"num-traits",
|
||||||
|
"pem",
|
||||||
|
"rsa",
|
||||||
|
"thiserror",
|
||||||
|
"yasna",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
|
@ -1752,6 +1909,12 @@ dependencies = [
|
||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spin"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "standback"
|
name = "standback"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -1823,6 +1986,12 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.16"
|
version = "1.0.16"
|
||||||
|
@ -2260,3 +2429,33 @@ dependencies = [
|
||||||
"winapi 0.2.8",
|
"winapi 0.2.8",
|
||||||
"winapi-build",
|
"winapi-build",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yasna"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a563d10ead87e2d798e357d44f40f495ad70bcee4d5c0d3f77a5b1b7376645d9"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -9,18 +9,29 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
actix = "0.10.0-alpha.2"
|
actix = "0.10.0-alpha.2"
|
||||||
actix-web = { version = "3.0.0-alpha.1", features = ["openssl"] }
|
|
||||||
actix-rt = "1.0.0"
|
actix-rt = "1.0.0"
|
||||||
|
actix-web = { version = "3.0.0-alpha.1", features = ["openssl"] }
|
||||||
|
actix-webfinger = { version = "0.3.0-alpha.2" }
|
||||||
activitystreams = "0.5.0-alpha.6"
|
activitystreams = "0.5.0-alpha.6"
|
||||||
|
base64 = "0.12"
|
||||||
bb8-postgres = "0.4.0"
|
bb8-postgres = "0.4.0"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
futures = "0.3.4"
|
futures = "0.3.4"
|
||||||
|
http-signature-normalization-actix = { version = "0.3.0-alpha.1", default-features = false, features = ["sha-2"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lru = "0.4.3"
|
lru = "0.4.3"
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
|
rand = "0.7"
|
||||||
|
rsa = "0.2"
|
||||||
|
rsa-magic-public-key = { version = "0.1.0", git = "https://git.asonix.dog/Aardwolf/rsa-magic-public-key" }
|
||||||
|
rsa-pem = { version = "0.1.0", git = "https://git.asonix.dog/Aardwolf/rsa-pem" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
sha2 = "0.8"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tokio = { version = "0.2.13", features = ["sync"] }
|
tokio = { version = "0.2.13", features = ["sync"] }
|
||||||
ttl_cache = "0.5.1"
|
ttl_cache = "0.5.1"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
|
||||||
|
[profile.dev.package.rsa]
|
||||||
|
opt-level = 3
|
||||||
|
|
3
migrations/2020-03-16-012053_create-settings/down.sql
Normal file
3
migrations/2020-03-16-012053_create-settings/down.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP INDEX settings_key_index;
|
||||||
|
DROP TABLE settings;
|
12
migrations/2020-03-16-012053_create-settings/up.sql
Normal file
12
migrations/2020-03-16-012053_create-settings/up.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
-- Your SQL goes here
|
||||||
|
CREATE TABLE settings (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
key TEXT UNIQUE NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX settings_key_index ON settings(key);
|
||||||
|
|
||||||
|
SELECT diesel_manage_updated_at('settings');
|
26
src/apub.rs
26
src/apub.rs
|
@ -5,6 +5,23 @@ use activitystreams::{
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PublicKey {
|
||||||
|
pub id: XsdAnyUri,
|
||||||
|
pub owner: XsdAnyUri,
|
||||||
|
pub public_key_pem: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PublicKeyExtension<T> {
|
||||||
|
public_key: PublicKey,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
extending: T,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PropRefs)]
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PropRefs)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[prop_refs(Object)]
|
#[prop_refs(Object)]
|
||||||
|
@ -72,6 +89,15 @@ pub struct Endpoints {
|
||||||
shared_inbox: Option<XsdAnyUri>,
|
shared_inbox: Option<XsdAnyUri>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PublicKey {
|
||||||
|
pub fn extend<T>(self, extending: T) -> PublicKeyExtension<T> {
|
||||||
|
PublicKeyExtension {
|
||||||
|
public_key: self,
|
||||||
|
extending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ValidObjects {
|
impl ValidObjects {
|
||||||
pub fn id(&self) -> &XsdAnyUri {
|
pub fn id(&self) -> &XsdAnyUri {
|
||||||
match self {
|
match self {
|
||||||
|
|
77
src/error.rs
Normal file
77
src/error.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
use activitystreams::primitives::XsdAnyUriError;
|
||||||
|
use actix_web::{error::ResponseError, http::StatusCode, HttpResponse};
|
||||||
|
use log::error;
|
||||||
|
use rsa_pem::KeyError;
|
||||||
|
use std::{convert::Infallible, io::Error};
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum MyError {
|
||||||
|
#[error("Couldn't parse key, {0}")]
|
||||||
|
Key(#[from] KeyError),
|
||||||
|
|
||||||
|
#[error("Couldn't parse URI, {0}")]
|
||||||
|
Uri(#[from] XsdAnyUriError),
|
||||||
|
|
||||||
|
#[error("Couldn't perform IO, {0}")]
|
||||||
|
Io(#[from] Error),
|
||||||
|
|
||||||
|
#[error("Couldn't sign string")]
|
||||||
|
Rsa(rsa::errors::Error),
|
||||||
|
|
||||||
|
#[error("Couldn't do the json thing")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("Couldn't serialzize the signature header")]
|
||||||
|
HeaderSerialize(#[from] actix_web::http::header::ToStrError),
|
||||||
|
|
||||||
|
#[error("Couldn't parse the signature header")]
|
||||||
|
HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue),
|
||||||
|
|
||||||
|
#[error("Wrong ActivityPub kind")]
|
||||||
|
Kind,
|
||||||
|
|
||||||
|
#[error("Object has already been relayed")]
|
||||||
|
Duplicate,
|
||||||
|
|
||||||
|
#[error("Actor is blocked")]
|
||||||
|
Blocked,
|
||||||
|
|
||||||
|
#[error("Actor is not whitelisted")]
|
||||||
|
Whitelist,
|
||||||
|
|
||||||
|
#[error("Couldn't send request")]
|
||||||
|
SendRequest,
|
||||||
|
|
||||||
|
#[error("Couldn't receive request response")]
|
||||||
|
ReceiveResponse,
|
||||||
|
|
||||||
|
#[error("Response has invalid status code")]
|
||||||
|
Status,
|
||||||
|
|
||||||
|
#[error("URI is missing domain field")]
|
||||||
|
Domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for MyError {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::InternalServerError()
|
||||||
|
.header("Content-Type", "application/activity+json")
|
||||||
|
.json(serde_json::json!({}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Infallible> for MyError {
|
||||||
|
fn from(i: Infallible) -> Self {
|
||||||
|
match i {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<rsa::errors::Error> for MyError {
|
||||||
|
fn from(e: rsa::errors::Error) -> Self {
|
||||||
|
MyError::Rsa(e)
|
||||||
|
}
|
||||||
|
}
|
104
src/inbox.rs
104
src/inbox.rs
|
@ -4,20 +4,17 @@ use activitystreams::{
|
||||||
primitives::XsdAnyUri,
|
primitives::XsdAnyUri,
|
||||||
};
|
};
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_web::{client::Client, http::StatusCode, web, HttpResponse};
|
use actix_web::{client::Client, web, HttpResponse};
|
||||||
use futures::join;
|
use futures::join;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{AcceptedActors, AcceptedObjects, ValidTypes},
|
apub::{AcceptedActors, AcceptedObjects, ValidTypes},
|
||||||
db_actor::{DbActor, DbQuery, Pool},
|
db_actor::{DbActor, DbQuery, Pool},
|
||||||
|
error::MyError,
|
||||||
state::{State, UrlKind},
|
state::{State, UrlKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
|
||||||
#[error("Something went wrong :(")]
|
|
||||||
pub struct MyError;
|
|
||||||
|
|
||||||
pub async fn inbox(
|
pub async fn inbox(
|
||||||
db_actor: web::Data<Addr<DbActor>>,
|
db_actor: web::Data<Addr<DbActor>>,
|
||||||
state: web::Data<State>,
|
state: web::Data<State>,
|
||||||
|
@ -57,7 +54,7 @@ async fn handle_undo(
|
||||||
actor: AcceptedActors,
|
actor: AcceptedActors,
|
||||||
) -> Result<HttpResponse, MyError> {
|
) -> Result<HttpResponse, MyError> {
|
||||||
if !input.object.is_kind("Follow") {
|
if !input.object.is_kind("Follow") {
|
||||||
return Err(MyError);
|
return Err(MyError::Kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
let inbox = actor.inbox().to_owned();
|
let inbox = actor.inbox().to_owned();
|
||||||
|
@ -99,7 +96,7 @@ async fn handle_undo(
|
||||||
let undo2 = undo.clone();
|
let undo2 = undo.clone();
|
||||||
let client = client.into_inner();
|
let client = client.into_inner();
|
||||||
actix::Arbiter::spawn(async move {
|
actix::Arbiter::spawn(async move {
|
||||||
let _ = deliver(&client, actor.id, &undo2).await;
|
let _ = deliver(&state.into_inner(), &client, actor.id, &undo2).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +113,7 @@ async fn handle_forward(
|
||||||
|
|
||||||
let inboxes = get_inboxes(&state, &actor, &object_id).await?;
|
let inboxes = get_inboxes(&state, &actor, &object_id).await?;
|
||||||
|
|
||||||
deliver_many(client, inboxes, input.clone());
|
deliver_many(state, client, inboxes, input.clone());
|
||||||
|
|
||||||
Ok(response(input))
|
Ok(response(input))
|
||||||
}
|
}
|
||||||
|
@ -130,7 +127,7 @@ async fn handle_relay(
|
||||||
let object_id = input.object.id();
|
let object_id = input.object.id();
|
||||||
|
|
||||||
if state.is_cached(object_id).await {
|
if state.is_cached(object_id).await {
|
||||||
return Err(MyError);
|
return Err(MyError::Duplicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
let activity_id: XsdAnyUri = state.generate_url(UrlKind::Activity).parse()?;
|
let activity_id: XsdAnyUri = state.generate_url(UrlKind::Activity).parse()?;
|
||||||
|
@ -149,10 +146,10 @@ async fn handle_relay(
|
||||||
|
|
||||||
let inboxes = get_inboxes(&state, &actor, &object_id).await?;
|
let inboxes = get_inboxes(&state, &actor, &object_id).await?;
|
||||||
|
|
||||||
deliver_many(client, inboxes, announce.clone());
|
|
||||||
|
|
||||||
state.cache(object_id.to_owned(), activity_id).await;
|
state.cache(object_id.to_owned(), activity_id).await;
|
||||||
|
|
||||||
|
deliver_many(state, client, inboxes, announce.clone());
|
||||||
|
|
||||||
Ok(response(announce))
|
Ok(response(announce))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,12 +168,12 @@ async fn handle_follow(
|
||||||
|
|
||||||
if is_blocked {
|
if is_blocked {
|
||||||
error!("Follow from blocked listener, {}", actor.id);
|
error!("Follow from blocked listener, {}", actor.id);
|
||||||
return Err(MyError);
|
return Err(MyError::Blocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_whitelisted {
|
if !is_whitelisted {
|
||||||
error!("Follow from non-whitelisted listener, {}", actor.id);
|
error!("Follow from non-whitelisted listener, {}", actor.id);
|
||||||
return Err(MyError);
|
return Err(MyError::Whitelist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_listener {
|
if !is_listener {
|
||||||
|
@ -220,7 +217,7 @@ async fn handle_follow(
|
||||||
let client = client.into_inner();
|
let client = client.into_inner();
|
||||||
let accept2 = accept.clone();
|
let accept2 = accept.clone();
|
||||||
actix::Arbiter::spawn(async move {
|
actix::Arbiter::spawn(async move {
|
||||||
let _ = deliver(&client, actor_inbox, &accept2).await;
|
let _ = deliver(&state.into_inner(), &client, actor_inbox, &accept2).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(response(accept))
|
Ok(response(accept))
|
||||||
|
@ -242,13 +239,13 @@ async fn fetch_actor(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Couldn't send request to {} for actor, {}", actor_id, e);
|
error!("Couldn't send request to {} for actor, {}", actor_id, e);
|
||||||
MyError
|
MyError::SendRequest
|
||||||
})?
|
})?
|
||||||
.json()
|
.json()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Coudn't fetch actor from {}, {}", actor_id, e);
|
error!("Coudn't fetch actor from {}, {}", actor_id, e);
|
||||||
MyError
|
MyError::ReceiveResponse
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
state.cache_actor(actor_id.to_owned(), actor.clone()).await;
|
state.cache_actor(actor_id.to_owned(), actor.clone()).await;
|
||||||
|
@ -256,20 +253,24 @@ async fn fetch_actor(
|
||||||
Ok(actor)
|
Ok(actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deliver_many<T>(client: web::Data<Client>, inboxes: Vec<XsdAnyUri>, item: T)
|
fn deliver_many<T>(
|
||||||
where
|
state: web::Data<State>,
|
||||||
|
client: web::Data<Client>,
|
||||||
|
inboxes: Vec<XsdAnyUri>,
|
||||||
|
item: T,
|
||||||
|
) where
|
||||||
T: serde::ser::Serialize + 'static,
|
T: serde::ser::Serialize + 'static,
|
||||||
{
|
{
|
||||||
let client = client.into_inner();
|
let client = client.into_inner();
|
||||||
|
let state = state.into_inner();
|
||||||
|
|
||||||
actix::Arbiter::spawn(async move {
|
actix::Arbiter::spawn(async move {
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
|
|
||||||
let client = client.clone();
|
|
||||||
let mut unordered = futures::stream::FuturesUnordered::new();
|
let mut unordered = futures::stream::FuturesUnordered::new();
|
||||||
|
|
||||||
for inbox in inboxes {
|
for inbox in inboxes {
|
||||||
unordered.push(deliver(&client, inbox, &item));
|
unordered.push(deliver(&state, &client, inbox, &item));
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(_) = unordered.next().await {}
|
while let Some(_) = unordered.next().await {}
|
||||||
|
@ -277,6 +278,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn deliver<T>(
|
async fn deliver<T>(
|
||||||
|
state: &std::sync::Arc<State>,
|
||||||
client: &std::sync::Arc<Client>,
|
client: &std::sync::Arc<Client>,
|
||||||
inbox: XsdAnyUri,
|
inbox: XsdAnyUri,
|
||||||
item: &T,
|
item: &T,
|
||||||
|
@ -284,20 +286,38 @@ async fn deliver<T>(
|
||||||
where
|
where
|
||||||
T: serde::ser::Serialize,
|
T: serde::ser::Serialize,
|
||||||
{
|
{
|
||||||
|
use http_signature_normalization_actix::prelude::*;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
let config = Config::default();
|
||||||
|
let mut digest = Sha256::new();
|
||||||
|
|
||||||
|
let key_id = state.generate_url(UrlKind::Actor);
|
||||||
|
|
||||||
|
let item_string = serde_json::to_string(item)?;
|
||||||
|
|
||||||
let res = client
|
let res = client
|
||||||
.post(inbox.as_str())
|
.post(inbox.as_str())
|
||||||
.header("Accept", "application/activity+json")
|
.header("Accept", "application/activity+json")
|
||||||
.header("Content-Type", "application/activity+json")
|
.header("Content-Type", "application/activity+json")
|
||||||
.send_json(item)
|
.header("User-Agent", "Aode Relay v0.1.0")
|
||||||
|
.signature_with_digest(
|
||||||
|
&config,
|
||||||
|
&key_id,
|
||||||
|
&mut digest,
|
||||||
|
item_string,
|
||||||
|
|signing_string| state.sign(signing_string.as_bytes()),
|
||||||
|
)?
|
||||||
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Couldn't send deliver request to {}, {}", inbox, e);
|
error!("Couldn't send deliver request to {}, {}", inbox, e);
|
||||||
MyError
|
MyError::SendRequest
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !res.status().is_success() {
|
if !res.status().is_success() {
|
||||||
error!("Invalid response status from {}, {}", inbox, res.status());
|
error!("Invalid response status from {}, {}", inbox, res.status());
|
||||||
return Err(MyError);
|
return Err(MyError::Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -308,41 +328,13 @@ async fn get_inboxes(
|
||||||
actor: &AcceptedActors,
|
actor: &AcceptedActors,
|
||||||
object_id: &XsdAnyUri,
|
object_id: &XsdAnyUri,
|
||||||
) -> Result<Vec<XsdAnyUri>, MyError> {
|
) -> Result<Vec<XsdAnyUri>, MyError> {
|
||||||
let domain = object_id.as_url().host().ok_or(MyError)?.to_string();
|
let domain = object_id
|
||||||
|
.as_url()
|
||||||
|
.host()
|
||||||
|
.ok_or(MyError::Domain)?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let inbox = actor.inbox();
|
let inbox = actor.inbox();
|
||||||
|
|
||||||
Ok(state.listeners_without(&inbox, &domain).await)
|
Ok(state.listeners_without(&inbox, &domain).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl actix_web::error::ResponseError for MyError {
|
|
||||||
fn status_code(&self) -> StatusCode {
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
|
||||||
HttpResponse::InternalServerError()
|
|
||||||
.header("Content-Type", "application/activity+json")
|
|
||||||
.json(serde_json::json!({}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::convert::Infallible> for MyError {
|
|
||||||
fn from(_: std::convert::Infallible) -> Self {
|
|
||||||
MyError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<activitystreams::primitives::XsdAnyUriError> for MyError {
|
|
||||||
fn from(_: activitystreams::primitives::XsdAnyUriError) -> Self {
|
|
||||||
error!("Error parsing URI");
|
|
||||||
MyError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::io::Error> for MyError {
|
|
||||||
fn from(e: std::io::Error) -> Self {
|
|
||||||
error!("JSON Error, {}", e);
|
|
||||||
MyError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -2,16 +2,20 @@
|
||||||
use activitystreams::{actor::apub::Application, context, endpoint::EndpointProperties};
|
use activitystreams::{actor::apub::Application, context, endpoint::EndpointProperties};
|
||||||
use actix_web::{client::Client, middleware::Logger, web, App, HttpServer, Responder};
|
use actix_web::{client::Client, middleware::Logger, web, App, HttpServer, Responder};
|
||||||
use bb8_postgres::tokio_postgres;
|
use bb8_postgres::tokio_postgres;
|
||||||
|
use rsa_pem::KeyExt;
|
||||||
|
|
||||||
mod apub;
|
mod apub;
|
||||||
mod db_actor;
|
mod db_actor;
|
||||||
|
mod error;
|
||||||
mod inbox;
|
mod inbox;
|
||||||
mod label;
|
mod label;
|
||||||
mod state;
|
mod state;
|
||||||
|
mod webfinger;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
|
apub::PublicKey,
|
||||||
db_actor::DbActor,
|
db_actor::DbActor,
|
||||||
inbox::MyError,
|
error::MyError,
|
||||||
label::ArbiterLabelFactory,
|
label::ArbiterLabelFactory,
|
||||||
state::{State, UrlKind},
|
state::{State, UrlKind},
|
||||||
};
|
};
|
||||||
|
@ -42,7 +46,13 @@ async fn actor_route(state: web::Data<State>) -> Result<impl Responder, MyError>
|
||||||
.set_inbox(state.generate_url(UrlKind::Inbox))?
|
.set_inbox(state.generate_url(UrlKind::Inbox))?
|
||||||
.set_endpoints(endpoint)?;
|
.set_endpoints(endpoint)?;
|
||||||
|
|
||||||
Ok(inbox::response(application))
|
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(inbox::response(public_key.extend(application)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
|
@ -81,6 +91,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
.service(web::resource("/").route(web::get().to(index)))
|
.service(web::resource("/").route(web::get().to(index)))
|
||||||
.service(web::resource("/inbox").route(web::post().to(inbox::inbox)))
|
.service(web::resource("/inbox").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(actix_webfinger::resource::<_, webfinger::RelayResolver>())
|
||||||
})
|
})
|
||||||
.bind("127.0.0.1:8080")?
|
.bind("127.0.0.1:8080")?
|
||||||
.run()
|
.run()
|
||||||
|
|
|
@ -16,6 +16,16 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
settings (id) {
|
||||||
|
id -> Uuid,
|
||||||
|
key -> Text,
|
||||||
|
value -> Text,
|
||||||
|
created_at -> Timestamp,
|
||||||
|
updated_at -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
whitelists (id) {
|
whitelists (id) {
|
||||||
id -> Uuid,
|
id -> Uuid,
|
||||||
|
@ -28,5 +38,6 @@ table! {
|
||||||
allow_tables_to_appear_in_same_query!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
blocks,
|
blocks,
|
||||||
listeners,
|
listeners,
|
||||||
|
settings,
|
||||||
whitelists,
|
whitelists,
|
||||||
);
|
);
|
||||||
|
|
121
src/state.rs
121
src/state.rs
|
@ -2,7 +2,11 @@ use activitystreams::primitives::XsdAnyUri;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use bb8_postgres::tokio_postgres::{row::Row, Client};
|
use bb8_postgres::tokio_postgres::{row::Row, Client};
|
||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
|
use log::{error, info};
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
|
use rand::thread_rng;
|
||||||
|
use rsa::{RSAPrivateKey, RSAPublicKey};
|
||||||
|
use rsa_pem::KeyExt;
|
||||||
use std::{collections::HashSet, sync::Arc};
|
use std::{collections::HashSet, sync::Arc};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use ttl_cache::TtlCache;
|
use ttl_cache::TtlCache;
|
||||||
|
@ -12,9 +16,7 @@ use crate::{apub::AcceptedActors, db_actor::Pool};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
use_https: bool,
|
pub settings: Settings,
|
||||||
hostname: String,
|
|
||||||
whitelist_enabled: bool,
|
|
||||||
actor_cache: Arc<RwLock<TtlCache<XsdAnyUri, AcceptedActors>>>,
|
actor_cache: Arc<RwLock<TtlCache<XsdAnyUri, AcceptedActors>>>,
|
||||||
actor_id_cache: Arc<RwLock<LruCache<XsdAnyUri, XsdAnyUri>>>,
|
actor_id_cache: Arc<RwLock<LruCache<XsdAnyUri, XsdAnyUri>>>,
|
||||||
blocks: Arc<RwLock<HashSet<String>>>,
|
blocks: Arc<RwLock<HashSet<String>>>,
|
||||||
|
@ -22,20 +24,73 @@ pub struct State {
|
||||||
listeners: Arc<RwLock<HashSet<XsdAnyUri>>>,
|
listeners: Arc<RwLock<HashSet<XsdAnyUri>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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 {
|
pub enum UrlKind {
|
||||||
Activity,
|
Activity,
|
||||||
Actor,
|
Actor,
|
||||||
Followers,
|
Followers,
|
||||||
Following,
|
Following,
|
||||||
Inbox,
|
Inbox,
|
||||||
|
MainKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
#[error("No host present in URI")]
|
#[error("No host present in URI")]
|
||||||
pub struct HostError;
|
pub struct HostError;
|
||||||
|
|
||||||
impl State {
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
pub fn generate_url(&self, kind: UrlKind) -> String {
|
#[error("Error generating RSA key")]
|
||||||
|
pub struct RsaError;
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
async fn hydrate(
|
||||||
|
client: &Client,
|
||||||
|
use_https: bool,
|
||||||
|
whitelist_enabled: bool,
|
||||||
|
hostname: String,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
info!("SELECT value FROM settings WHERE key = 'private_key'");
|
||||||
|
let rows = client
|
||||||
|
.query("SELECT value FROM settings WHERE key = 'private_key'", &[])
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let private_key = if let Some(row) = rows.into_iter().next() {
|
||||||
|
let key_str: String = row.get(0);
|
||||||
|
KeyExt::from_pem_pkcs8(&key_str)?
|
||||||
|
} else {
|
||||||
|
info!("Generating new keys");
|
||||||
|
let mut rng = thread_rng();
|
||||||
|
let key = RSAPrivateKey::new(&mut rng, 4096).map_err(|e| {
|
||||||
|
error!("Error generating RSA key, {}", e);
|
||||||
|
RsaError
|
||||||
|
})?;
|
||||||
|
let pem_pkcs8 = key.to_pem_pkcs8()?;
|
||||||
|
|
||||||
|
info!("INSERT INTO settings (key, value, created_at) VALUES ('private_key', $1::TEXT, 'now');");
|
||||||
|
client.execute("INSERT INTO settings (key, value, created_at) VALUES ('private_key', $1::TEXT, 'now');", &[&pem_pkcs8]).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" };
|
let scheme = if self.use_https { "https" } else { "http" };
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
|
@ -46,13 +101,40 @@ impl State {
|
||||||
UrlKind::Followers => format!("{}://{}/followers", scheme, self.hostname),
|
UrlKind::Followers => format!("{}://{}/followers", scheme, self.hostname),
|
||||||
UrlKind::Following => format!("{}://{}/following", scheme, self.hostname),
|
UrlKind::Following => format!("{}://{}/following", scheme, self.hostname),
|
||||||
UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname),
|
UrlKind::Inbox => format!("{}://{}/inbox", scheme, self.hostname),
|
||||||
|
UrlKind::MainKey => format!("{}://{}/actor#main-key", scheme, self.hostname),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_resource(&self) -> String {
|
||||||
|
format!("relay@{}", self.hostname)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, bytes: &[u8]) -> Result<String, crate::error::MyError> {
|
||||||
|
use rsa::{hash::Hashes, padding::PaddingScheme};
|
||||||
|
let bytes =
|
||||||
|
self.private_key
|
||||||
|
.sign(PaddingScheme::PKCS1v15, Some(&Hashes::SHA2_256), bytes)?;
|
||||||
|
Ok(base64::encode_config(bytes, base64::URL_SAFE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn generate_url(&self, kind: UrlKind) -> String {
|
||||||
|
self.settings.generate_url(kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_resource(&self) -> String {
|
||||||
|
self.settings.generate_resource()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(&self, bytes: &[u8]) -> Result<String, crate::error::MyError> {
|
||||||
|
self.settings.sign(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn remove_listener(&self, client: &Client, inbox: &XsdAnyUri) -> Result<(), Error> {
|
pub async fn remove_listener(&self, client: &Client, inbox: &XsdAnyUri) -> Result<(), Error> {
|
||||||
let hs = self.listeners.clone();
|
let hs = self.listeners.clone();
|
||||||
|
|
||||||
log::info!("DELETE FROM listeners WHERE actor_id = {};", inbox.as_str());
|
info!("DELETE FROM listeners WHERE actor_id = {};", inbox.as_str());
|
||||||
client
|
client
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM listeners WHERE actor_id = $1::TEXT;",
|
"DELETE FROM listeners WHERE actor_id = $1::TEXT;",
|
||||||
|
@ -86,7 +168,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_whitelisted(&self, actor_id: &XsdAnyUri) -> bool {
|
pub async fn is_whitelisted(&self, actor_id: &XsdAnyUri) -> bool {
|
||||||
if !self.whitelist_enabled {
|
if !self.settings.whitelist_enabled {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +237,7 @@ impl State {
|
||||||
return Err(HostError.into());
|
return Err(HostError.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!(
|
info!(
|
||||||
"INSERT INTO blocks (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
"INSERT INTO blocks (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
||||||
host.to_string()
|
host.to_string()
|
||||||
);
|
);
|
||||||
|
@ -181,7 +263,7 @@ impl State {
|
||||||
return Err(HostError.into());
|
return Err(HostError.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!(
|
info!(
|
||||||
"INSERT INTO whitelists (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
"INSERT INTO whitelists (domain_name, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
||||||
host.to_string()
|
host.to_string()
|
||||||
);
|
);
|
||||||
|
@ -201,7 +283,7 @@ impl State {
|
||||||
pub async fn add_listener(&self, client: &Client, listener: XsdAnyUri) -> Result<(), Error> {
|
pub async fn add_listener(&self, client: &Client, listener: XsdAnyUri) -> Result<(), Error> {
|
||||||
let listeners = self.listeners.clone();
|
let listeners = self.listeners.clone();
|
||||||
|
|
||||||
log::info!(
|
info!(
|
||||||
"INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
"INSERT INTO listeners (actor_id, created_at) VALUES ($1::TEXT, 'now'); [{}]",
|
||||||
listener.as_str(),
|
listener.as_str(),
|
||||||
);
|
);
|
||||||
|
@ -226,6 +308,7 @@ impl State {
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let pool1 = pool.clone();
|
let pool1 = pool.clone();
|
||||||
let pool2 = pool.clone();
|
let pool2 = pool.clone();
|
||||||
|
let pool3 = pool.clone();
|
||||||
|
|
||||||
let f1 = async move {
|
let f1 = async move {
|
||||||
let conn = pool.get().await?;
|
let conn = pool.get().await?;
|
||||||
|
@ -245,12 +328,16 @@ impl State {
|
||||||
hydrate_listeners(&conn).await
|
hydrate_listeners(&conn).await
|
||||||
};
|
};
|
||||||
|
|
||||||
let (blocks, whitelists, listeners) = try_join!(f1, f2, f3)?;
|
let f4 = async move {
|
||||||
|
let conn = pool3.get().await?;
|
||||||
|
|
||||||
|
Settings::hydrate(&conn, use_https, whitelist_enabled, hostname).await
|
||||||
|
};
|
||||||
|
|
||||||
|
let (blocks, whitelists, listeners, settings) = try_join!(f1, f2, f3, f4)?;
|
||||||
|
|
||||||
Ok(State {
|
Ok(State {
|
||||||
use_https,
|
settings,
|
||||||
whitelist_enabled,
|
|
||||||
hostname,
|
|
||||||
actor_cache: Arc::new(RwLock::new(TtlCache::new(1024 * 8))),
|
actor_cache: Arc::new(RwLock::new(TtlCache::new(1024 * 8))),
|
||||||
actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))),
|
actor_id_cache: Arc::new(RwLock::new(LruCache::new(1024 * 8))),
|
||||||
blocks: Arc::new(RwLock::new(blocks)),
|
blocks: Arc::new(RwLock::new(blocks)),
|
||||||
|
@ -261,14 +348,14 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn hydrate_blocks(client: &Client) -> Result<HashSet<String>, Error> {
|
pub async fn hydrate_blocks(client: &Client) -> Result<HashSet<String>, Error> {
|
||||||
log::info!("SELECT domain_name FROM blocks");
|
info!("SELECT domain_name FROM blocks");
|
||||||
let rows = client.query("SELECT domain_name FROM blocks", &[]).await?;
|
let rows = client.query("SELECT domain_name FROM blocks", &[]).await?;
|
||||||
|
|
||||||
parse_rows(rows)
|
parse_rows(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn hydrate_whitelists(client: &Client) -> Result<HashSet<String>, Error> {
|
pub async fn hydrate_whitelists(client: &Client) -> Result<HashSet<String>, Error> {
|
||||||
log::info!("SELECT domain_name FROM whitelists");
|
info!("SELECT domain_name FROM whitelists");
|
||||||
let rows = client
|
let rows = client
|
||||||
.query("SELECT domain_name FROM whitelists", &[])
|
.query("SELECT domain_name FROM whitelists", &[])
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -277,7 +364,7 @@ pub async fn hydrate_whitelists(client: &Client) -> Result<HashSet<String>, Erro
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn hydrate_listeners(client: &Client) -> Result<HashSet<XsdAnyUri>, Error> {
|
pub async fn hydrate_listeners(client: &Client) -> Result<HashSet<XsdAnyUri>, Error> {
|
||||||
log::info!("SELECT actor_id FROM listeners");
|
info!("SELECT actor_id FROM listeners");
|
||||||
let rows = client.query("SELECT actor_id FROM listeners", &[]).await?;
|
let rows = client.query("SELECT actor_id FROM listeners", &[]).await?;
|
||||||
|
|
||||||
parse_rows(rows)
|
parse_rows(rows)
|
||||||
|
|
52
src/webfinger.rs
Normal file
52
src/webfinger.rs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
use crate::state::{State, UrlKind};
|
||||||
|
use activitystreams::context;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use actix_webfinger::{Link, Resolver, Webfinger};
|
||||||
|
use rsa_magic_public_key::AsMagicPublicKey;
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
|
||||||
|
pub struct RelayResolver;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
|
#[error("Error resolving webfinger data")]
|
||||||
|
pub struct RelayError;
|
||||||
|
|
||||||
|
impl Resolver<Data<State>> for RelayResolver {
|
||||||
|
type Error = RelayError;
|
||||||
|
|
||||||
|
fn find(
|
||||||
|
account: &str,
|
||||||
|
domain: &str,
|
||||||
|
state: Data<State>,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<Option<Webfinger>, Self::Error>>>> {
|
||||||
|
let domain = domain.to_owned();
|
||||||
|
let account = account.to_owned();
|
||||||
|
|
||||||
|
let fut = async move {
|
||||||
|
if domain != state.settings.hostname {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if account != "relay" {
|
||||||
|
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())
|
||||||
|
.add_link(Link {
|
||||||
|
rel: "self".to_owned(),
|
||||||
|
href: Some(state.generate_url(UrlKind::Actor)),
|
||||||
|
template: None,
|
||||||
|
kind: Some(format!("application/ld+json; profile=\"{}\"", context())),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Some(wf))
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl actix_web::error::ResponseError for RelayError {}
|
Loading…
Reference in a new issue