mirror of
https://github.com/mikedilger/chorus.git
synced 2026-05-03 06:51:42 +00:00
Compare commits
11 Commits
72c874ff37
...
61fcbcf257
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61fcbcf257 | ||
|
|
4ff23fb2a6 | ||
|
|
1f2c33d532 | ||
|
|
a1e52a2d07 | ||
|
|
b68431bba0 | ||
|
|
ce612a1fc5 | ||
|
|
6f77156684 | ||
|
|
17f9687b4c | ||
|
|
3f15894aaa | ||
|
|
2ca4b17b73 | ||
|
|
e5251e0f57 |
1282
Cargo.lock
generated
1282
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chorus"
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
description = "A personal relay for nostr"
|
||||
authors = ["Mike Dilger <mike@mikedilger.com>"]
|
||||
license = "MIT"
|
||||
@ -9,7 +9,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22"
|
||||
bitcoin_hashes = { version = "0.16", features = [ "bitcoin-io" ] }
|
||||
bitcoin_hashes = "0.19"
|
||||
dashmap = "6"
|
||||
env_logger = "0.11"
|
||||
futures = "0.3"
|
||||
@ -24,12 +24,12 @@ log = "0.4"
|
||||
mime-sniffer = "0.1"
|
||||
mime2ext = "0.1"
|
||||
negentropy = "0.5"
|
||||
pocket-types = { git = "https://github.com/mikedilger/pocket", branch = "master" }
|
||||
pocket-db = { git = "https://github.com/mikedilger/pocket", branch = "master" }
|
||||
pocket-types = { git = "https://github.com/mikedilger/pocket", ref = "43d35015f7caf1db48eb846a1d6916a5716048da" }
|
||||
pocket-db = { git = "https://github.com/mikedilger/pocket", ref = "43d35015f7caf1db48eb846a1d6916a5716048da" }
|
||||
parking_lot = "0.12"
|
||||
rustls-pki-types = "1.11"
|
||||
rustls-pemfile = "2.2"
|
||||
secp256k1 = { version = "0.30", features = [ "hashes", "global-context" ] }
|
||||
secp256k1 = { version = "0.31", features = [ "hashes", "global-context" ] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
speedy = "0.8"
|
||||
|
||||
@ -97,6 +97,14 @@ key_pem_path = "/opt/chorus/etc/tls/privkey.pem"
|
||||
# description = "A default config of the Chorus relay"
|
||||
|
||||
|
||||
# This is a banner URL pointing to an image representing your relay, displayed in the NIP-11
|
||||
# response.
|
||||
#
|
||||
# Default is not set
|
||||
#
|
||||
# banner_url =
|
||||
|
||||
|
||||
# This is an icon URL pointing to an image representing your relay, displayed in the NIP-11
|
||||
# response.
|
||||
#
|
||||
@ -105,6 +113,20 @@ key_pem_path = "/opt/chorus/etc/tls/privkey.pem"
|
||||
# icon_url =
|
||||
|
||||
|
||||
# This is an optional privacy policy as a blob of text (not a URL, not HTML).
|
||||
#
|
||||
# Default is not set
|
||||
#
|
||||
# privacy_policy = ""
|
||||
|
||||
|
||||
# This is an optional terms of service document as a blob of text (not a URL, not HTML).
|
||||
#
|
||||
# Default is not set
|
||||
#
|
||||
# terms_of_service = ""
|
||||
|
||||
|
||||
# This is an optional contact for your relay, displayed in the NIP-11 response.
|
||||
#
|
||||
# Default is not set
|
||||
|
||||
@ -94,10 +94,22 @@ This is an optional description for your relay, displayed in the NIP-11 response
|
||||
|
||||
Default is "A default config of the Chorus relay".
|
||||
|
||||
### banner_url
|
||||
|
||||
This is an optional URL for an graphical banner representing your relay, displayed in the NIP-11 response.
|
||||
|
||||
### icon_url
|
||||
|
||||
This is an optional URL for an graphical icon representing your relay, displayed in the NIP-11 response.
|
||||
|
||||
### privacy_policy
|
||||
|
||||
This is an optional privacy policy as a blob of text (not a URL, not HTML).
|
||||
|
||||
### terms_of_service
|
||||
|
||||
This is an optional terms of service document as a blob of text (not a URL, not HTML).
|
||||
|
||||
### contact
|
||||
|
||||
This is an optional administrative contact for your relay, displayed in the NIP-11 response.
|
||||
|
||||
@ -83,7 +83,8 @@ Go ahead and edit that file to your liking. In particular:
|
||||
- Change the `ip_address` to your internet-accessible IP address (if you are running directly)
|
||||
or to 127.0.0.1 with a local port like 8080 (if you are proxying behind nginx)
|
||||
- Change the port if necessary
|
||||
- Change the name, description, icon_url and contact (e.g. your email address) as desired
|
||||
- Change the name, description, banner_url, icon_url, privacy_policy, terms_of_service and
|
||||
contact (e.g. your email address) as desired
|
||||
- Set your contact_public_key_hex (it is an option, so use `Some()`)
|
||||
- Set hex keys of users for which this relay will act as a personal relay
|
||||
|
||||
|
||||
@ -10,10 +10,10 @@
|
||||
2) Users and moderators are now dynamically configured in the database. Use `chorus_cmd` to
|
||||
manage them from the command line:
|
||||
|
||||
* Adding a user: `chorus_cmd add_user <pubkey> 0`
|
||||
* Adding a moderator: `chorus_cmd add_user <pubkey> 1`
|
||||
* Removing a user or moderator: `chorus_cmd rm_user <pubkey>`
|
||||
* Listing users and moderators: `chorus_cmd dump_users`
|
||||
* Adding a user: `chorus_cmd <chorus.toml> add_user <pubkey> 0`
|
||||
* Adding a moderator: `chorus_cmd <chorus.toml> add_user <pubkey> 1`
|
||||
* Removing a user or moderator: `chorus_cmd <chorus.toml> rm_user <pubkey>`
|
||||
* Listing users and moderators: `chorus_cmd <chorus.toml> dump_users`
|
||||
|
||||
3) Remove the following from your config file as these are no longer used:
|
||||
|
||||
|
||||
@ -10,6 +10,8 @@ certchain_pem_path = "tls/fullchain.pem"
|
||||
key_pem_path = "tls/privkey.pem"
|
||||
name = "Chorus Sample"
|
||||
description = "A sample run of the Chorus relay"
|
||||
privacy_policy = "This relay guarantees nothing. Privacy is your concern, not ours."
|
||||
terms_of_service = "This relay guarantees nothing. Use at your own risk."
|
||||
# icon_url =
|
||||
open_relay = false
|
||||
admin_hex_keys = [
|
||||
|
||||
@ -10,7 +10,7 @@ fn main() -> Result<(), Error> {
|
||||
// Get args (config path)
|
||||
let mut args = env::args();
|
||||
if args.len() <= 1 {
|
||||
panic!("USAGE: chorus_moderate <config_path>");
|
||||
panic!("USAGE: chorus_cmd <config_path> <command> [args...]");
|
||||
}
|
||||
let _ = args.next(); // ignore program name
|
||||
|
||||
|
||||
27
src/bin/chorus_init.rs
Normal file
27
src/bin/chorus_init.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use chorus::error::Error;
|
||||
use chorus::globals::GLOBALS;
|
||||
use std::env;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
// Get args (config path)
|
||||
let mut args = env::args();
|
||||
if args.len() <= 1 {
|
||||
panic!("USAGE: chorus <config_path>");
|
||||
}
|
||||
let _ = args.next(); // ignore program name
|
||||
let config_path = args.next().unwrap();
|
||||
|
||||
let config = chorus::load_config(&config_path)?;
|
||||
|
||||
chorus::setup_logging(&config);
|
||||
|
||||
// Log host name
|
||||
log::info!(target: "Server", "HOSTNAME = {}", config.hostname);
|
||||
|
||||
chorus::setup_store(&config)?;
|
||||
|
||||
let _ = GLOBALS.store.get().unwrap().sync();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -19,7 +19,10 @@ pub struct FriendlyConfig {
|
||||
pub key_pem_path: String,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub banner_url: Option<String>,
|
||||
pub icon_url: Option<String>,
|
||||
pub privacy_policy: Option<String>,
|
||||
pub terms_of_service: Option<String>,
|
||||
pub contact: Option<String>,
|
||||
#[serde(alias = "public_key_hex")]
|
||||
pub contact_public_key_hex: Option<String>,
|
||||
@ -60,7 +63,10 @@ impl Default for FriendlyConfig {
|
||||
key_pem_path: "/opt/chorus/etc/tls/privkey.pem".to_string(),
|
||||
name: Some("Chorus Default".to_string()),
|
||||
description: Some("A default config of the Chorus relay".to_string()),
|
||||
banner_url: None,
|
||||
icon_url: None,
|
||||
privacy_policy: None,
|
||||
terms_of_service: None,
|
||||
contact: None,
|
||||
contact_public_key_hex: None,
|
||||
open_relay: false,
|
||||
@ -102,7 +108,10 @@ impl FriendlyConfig {
|
||||
key_pem_path,
|
||||
name,
|
||||
description,
|
||||
banner_url,
|
||||
icon_url,
|
||||
privacy_policy,
|
||||
terms_of_service,
|
||||
contact,
|
||||
contact_public_key_hex,
|
||||
open_relay,
|
||||
@ -159,7 +168,10 @@ impl FriendlyConfig {
|
||||
key_pem_path,
|
||||
name,
|
||||
description,
|
||||
banner_url,
|
||||
icon_url,
|
||||
privacy_policy,
|
||||
terms_of_service,
|
||||
contact,
|
||||
contact_public_key,
|
||||
open_relay,
|
||||
@ -201,7 +213,10 @@ pub struct Config {
|
||||
pub key_pem_path: String,
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub banner_url: Option<String>,
|
||||
pub icon_url: Option<String>,
|
||||
pub privacy_policy: Option<String>,
|
||||
pub terms_of_service: Option<String>,
|
||||
pub contact: Option<String>,
|
||||
pub contact_public_key: Option<Pubkey>,
|
||||
pub open_relay: bool,
|
||||
@ -238,6 +253,8 @@ impl Default for Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Get the URI for our server matching the inner Uri, overridden with either
|
||||
/// our base_url parts or our hostname/port.
|
||||
pub fn uri_parts(&self, inner: Uri, http: bool) -> Result<http::uri::Parts, Error> {
|
||||
let mut uri_parts = inner.into_parts();
|
||||
|
||||
|
||||
34
src/error.rs
34
src/error.rs
@ -45,7 +45,7 @@ pub enum ChorusError {
|
||||
BannedUser,
|
||||
|
||||
// Base64 Decode Error
|
||||
Base64Decode(base64::DecodeError),
|
||||
Base64Decode(base64::DecodeError), // 24b
|
||||
|
||||
// Blocked IP
|
||||
BlockedIp,
|
||||
@ -54,16 +54,16 @@ pub enum ChorusError {
|
||||
BlossomAuthFailure(String),
|
||||
|
||||
// Channel Recv
|
||||
ChannelRecv(tokio::sync::broadcast::error::RecvError),
|
||||
ChannelRecv(tokio::sync::broadcast::error::RecvError), // 24b
|
||||
|
||||
// Channel Send
|
||||
ChannelSend(tokio::sync::broadcast::error::SendError<u64>),
|
||||
ChannelSend(tokio::sync::broadcast::error::SendError<u64>), // 16b
|
||||
|
||||
// Config
|
||||
Config(toml::de::Error),
|
||||
Config(Box<toml::de::Error>), // 16b
|
||||
|
||||
// Crypto
|
||||
Crypto(secp256k1::Error),
|
||||
Crypto(secp256k1::Error), // 16b
|
||||
|
||||
// Closing on error(s)
|
||||
ErrorClose,
|
||||
@ -72,16 +72,16 @@ pub enum ChorusError {
|
||||
EventIsInvalid(String),
|
||||
|
||||
// From hex
|
||||
FromHex(hex::FromHexError),
|
||||
FromHex(hex::FromHexError), // 24b
|
||||
|
||||
// From UTF8
|
||||
FromUtf8(std::string::FromUtf8Error),
|
||||
FromUtf8(Box<std::string::FromUtf8Error>),
|
||||
|
||||
// General
|
||||
General(String),
|
||||
|
||||
// Http
|
||||
Http(hyper::http::Error),
|
||||
Http(Box<hyper::http::Error>),
|
||||
|
||||
// Hyper
|
||||
Hyper(hyper::Error),
|
||||
@ -126,7 +126,7 @@ pub enum ChorusError {
|
||||
PocketDb(pocket_db::Error),
|
||||
|
||||
// Pocket Db Heed Error
|
||||
PocketDbHeed(pocket_db::heed::Error),
|
||||
PocketDbHeed(Box<pocket_db::heed::Error>),
|
||||
|
||||
// Pocket Types Error
|
||||
PocketType(pocket_types::Error),
|
||||
@ -141,7 +141,7 @@ pub enum ChorusError {
|
||||
Restricted,
|
||||
|
||||
// Rustls
|
||||
Rustls(tokio_rustls::rustls::Error),
|
||||
Rustls(Box<tokio_rustls::rustls::Error>),
|
||||
|
||||
// Filter is underspecified
|
||||
Scraper,
|
||||
@ -165,7 +165,7 @@ pub enum ChorusError {
|
||||
TooManySubscriptions,
|
||||
|
||||
// Tungstenite
|
||||
Tungstenite(hyper_tungstenite::tungstenite::error::Error),
|
||||
Tungstenite(Box<hyper_tungstenite::tungstenite::error::Error>),
|
||||
|
||||
// URL Parse
|
||||
UrlParse(url::ParseError),
|
||||
@ -382,7 +382,7 @@ impl From<toml::de::Error> for Error {
|
||||
#[track_caller]
|
||||
fn from(err: toml::de::Error) -> Self {
|
||||
Error {
|
||||
inner: ChorusError::Config(err),
|
||||
inner: ChorusError::Config(Box::new(err)),
|
||||
location: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@ -402,7 +402,7 @@ impl From<hyper::http::Error> for Error {
|
||||
#[track_caller]
|
||||
fn from(err: hyper::http::Error) -> Self {
|
||||
Error {
|
||||
inner: ChorusError::Http(err),
|
||||
inner: ChorusError::Http(Box::new(err)),
|
||||
location: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@ -472,7 +472,7 @@ impl From<pocket_db::heed::Error> for Error {
|
||||
#[track_caller]
|
||||
fn from(err: pocket_db::heed::Error) -> Self {
|
||||
Error {
|
||||
inner: ChorusError::PocketDbHeed(err),
|
||||
inner: ChorusError::PocketDbHeed(Box::new(err)),
|
||||
location: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@ -492,7 +492,7 @@ impl From<tokio_rustls::rustls::Error> for Error {
|
||||
#[track_caller]
|
||||
fn from(err: tokio_rustls::rustls::Error) -> Self {
|
||||
Error {
|
||||
inner: ChorusError::Rustls(err),
|
||||
inner: ChorusError::Rustls(Box::new(err)),
|
||||
location: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@ -502,7 +502,7 @@ impl From<hyper_tungstenite::tungstenite::error::Error> for Error {
|
||||
#[track_caller]
|
||||
fn from(err: hyper_tungstenite::tungstenite::error::Error) -> Self {
|
||||
Error {
|
||||
inner: ChorusError::Tungstenite(err),
|
||||
inner: ChorusError::Tungstenite(Box::new(err)),
|
||||
location: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
@ -572,7 +572,7 @@ impl From<std::string::FromUtf8Error> for Error {
|
||||
#[track_caller]
|
||||
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||
Error {
|
||||
inner: ChorusError::FromUtf8(err),
|
||||
inner: ChorusError::FromUtf8(Box::new(err)),
|
||||
location: std::panic::Location::caller(),
|
||||
}
|
||||
}
|
||||
|
||||
47
src/lib.rs
47
src/lib.rs
@ -180,9 +180,9 @@ async fn handle_http_request(
|
||||
}
|
||||
|
||||
let mut web_socket_config = WebSocketConfig::default();
|
||||
web_socket_config.max_write_buffer_size = 1024 * 1024; // 1 MB
|
||||
web_socket_config.max_write_buffer_size = 1024 * 1024; // 1 MB
|
||||
web_socket_config.max_message_size = Some(1024 * 1024); // 1 MB
|
||||
web_socket_config.max_frame_size = Some(1024 * 1024); // 1 MB
|
||||
web_socket_config.max_frame_size = Some(1024 * 1024); // 1 MB
|
||||
|
||||
let (mut response, websocket) =
|
||||
hyper_tungstenite::upgrade(&mut request, Some(web_socket_config))?;
|
||||
@ -220,7 +220,7 @@ async fn websocket_thread(peer: HashedPeer, websocket: HyperWebsocket, origin: S
|
||||
last_message: Instant::now(),
|
||||
burst_tokens: GLOBALS.config.read().throttling_burst,
|
||||
challenge: TextNonce::new().into_string(),
|
||||
user: None,
|
||||
authed_as: Vec::new(),
|
||||
error_punishment: 0.0,
|
||||
replied: false,
|
||||
negentropy_sub: None,
|
||||
@ -254,21 +254,30 @@ async fn websocket_thread(peer: HashedPeer, websocket: HyperWebsocket, origin: S
|
||||
// Handle the websocket
|
||||
if let Err(e) = ws_service.handle_websocket_stream().await {
|
||||
match e.inner {
|
||||
ChorusError::Tungstenite(tungstenite::error::Error::Protocol(
|
||||
tungstenite::error::ProtocolError::ResetWithoutClosingHandshake,
|
||||
)) => {
|
||||
// So they disconnected ungracefully.
|
||||
// No big deal, still SessionExit::Ok
|
||||
msg = "Reset";
|
||||
}
|
||||
ChorusError::Tungstenite(tungstenite::error::Error::Io(ref ioerror)) => {
|
||||
match ioerror.kind() {
|
||||
std::io::ErrorKind::ConnectionReset
|
||||
| std::io::ErrorKind::ConnectionAborted
|
||||
| std::io::ErrorKind::UnexpectedEof => {
|
||||
// no biggie.
|
||||
ChorusError::Tungstenite(ref t) => {
|
||||
match *t.as_ref() {
|
||||
tungstenite::error::Error::Protocol(
|
||||
tungstenite::error::ProtocolError::ResetWithoutClosingHandshake,
|
||||
) => {
|
||||
// So they disconnected ungracefully.
|
||||
// No big deal, still SessionExit::Ok
|
||||
msg = "Reset";
|
||||
}
|
||||
tungstenite::error::Error::Io(ref ioerror) => {
|
||||
match ioerror.kind() {
|
||||
std::io::ErrorKind::ConnectionReset
|
||||
| std::io::ErrorKind::ConnectionAborted
|
||||
| std::io::ErrorKind::UnexpectedEof => {
|
||||
// no biggie.
|
||||
msg = "Reset";
|
||||
}
|
||||
_ => {
|
||||
log::error!(target: "Client", "{}: {}", peer, e);
|
||||
session_exit = SessionExit::ErrorExit;
|
||||
msg = "Error Exited";
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
log::error!(target: "Client", "{}: {}", peer, e);
|
||||
session_exit = SessionExit::ErrorExit;
|
||||
@ -356,7 +365,7 @@ struct WebSocketService {
|
||||
pub last_message: Instant,
|
||||
pub burst_tokens: usize,
|
||||
pub challenge: String,
|
||||
pub user: Option<Pubkey>,
|
||||
pub authed_as: Vec<Pubkey>,
|
||||
pub error_punishment: f32,
|
||||
pub replied: bool,
|
||||
pub negentropy_sub: Option<String>,
|
||||
@ -473,8 +482,8 @@ impl WebSocketService {
|
||||
.unwrap()
|
||||
.get_event_by_offset(new_event_offset)?;
|
||||
|
||||
let event_flags = nostr::event_flags(event, &self.user);
|
||||
let authorized_user = self.user.map(is_authorized_user).unwrap_or(false);
|
||||
let event_flags = nostr::event_flags(event, self.authed_as.as_slice());
|
||||
let authorized_user = self.authed_as.iter().any(|u| is_authorized_user(*u));
|
||||
|
||||
'subs: for (subid, filters) in self.subscriptions.iter() {
|
||||
for filter in filters.iter() {
|
||||
|
||||
38
src/nostr.rs
38
src/nostr.rs
@ -124,10 +124,9 @@ impl WebSocketService {
|
||||
return Err(ChorusError::TooManySubscriptions.into());
|
||||
}
|
||||
|
||||
let user = self.user;
|
||||
let authorized_user = self.user.map(crate::is_authorized_user).unwrap_or(false);
|
||||
let authorized_user = self.authed_as.iter().any(|u| crate::is_authorized_user(*u));
|
||||
|
||||
if user.is_none() {
|
||||
if self.authed_as.is_empty() {
|
||||
for filter in filters.iter() {
|
||||
// If any DM kinds were requested, complain.
|
||||
// But if NO kinds were requested, we will just silently not return DMs (elsewhere)
|
||||
@ -163,7 +162,7 @@ impl WebSocketService {
|
||||
|
||||
for filter in filters.iter() {
|
||||
let screen = |event: &Event| -> ScreenResult {
|
||||
let event_flags = event_flags(event, &user);
|
||||
let event_flags = event_flags(event, self.authed_as.as_slice());
|
||||
screen_outgoing_event(event, &event_flags, authorized_user)
|
||||
};
|
||||
let (filter_events, was_redacted) = {
|
||||
@ -317,13 +316,12 @@ impl WebSocketService {
|
||||
}
|
||||
|
||||
async fn event_inner(&mut self) -> Result<(), Error> {
|
||||
let user = self.user;
|
||||
let authorized_user = self.user.map(crate::is_authorized_user).unwrap_or(false);
|
||||
let authorized_user = self.authed_as.iter().any(|u| crate::is_authorized_user(*u));
|
||||
|
||||
// Delineate the event back out of the session buffer
|
||||
let event = unsafe { Event::delineate(&self.buffer)? };
|
||||
|
||||
let event_flags = event_flags(event, &user);
|
||||
let event_flags = event_flags(event, self.authed_as.as_slice());
|
||||
|
||||
if GLOBALS.config.read().verify_events {
|
||||
// Verify the event is valid (id is hash, signature is valid)
|
||||
@ -347,10 +345,10 @@ impl WebSocketService {
|
||||
|
||||
// Screen the event to see if we are willing to accept it
|
||||
if !screen_incoming_event(event, event_flags, authorized_user).await? {
|
||||
if self.user.is_some() {
|
||||
return Err(ChorusError::Restricted.into());
|
||||
} else {
|
||||
if self.authed_as.is_empty() {
|
||||
return Err(ChorusError::AuthRequired.into());
|
||||
} else {
|
||||
return Err(ChorusError::Restricted.into());
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,7 +461,7 @@ impl WebSocketService {
|
||||
}
|
||||
|
||||
// They are now authenticated
|
||||
self.user = Some(event.pubkey());
|
||||
self.authed_as.push(event.pubkey());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -542,13 +540,12 @@ impl WebSocketService {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let user = self.user;
|
||||
let authorized_user = self.user.map(crate::is_authorized_user).unwrap_or(false);
|
||||
let authorized_user = self.authed_as.iter().any(|u| crate::is_authorized_user(*u));
|
||||
|
||||
// Find all matching events
|
||||
let mut events: Vec<&Event> = Vec::new();
|
||||
let screen = |event: &Event| -> ScreenResult {
|
||||
let event_flags = event_flags(event, &user);
|
||||
let event_flags = event_flags(event, self.authed_as.as_slice());
|
||||
screen_outgoing_event(event, &event_flags, authorized_user)
|
||||
};
|
||||
let (filter_events, _redacted) = {
|
||||
@ -871,13 +868,10 @@ pub struct EventFlags {
|
||||
pub tags_current_user: bool,
|
||||
}
|
||||
|
||||
pub fn event_flags(event: &Event, user: &Option<Pubkey>) -> EventFlags {
|
||||
pub fn event_flags(event: &Event, authed_as: &[Pubkey]) -> EventFlags {
|
||||
let author_is_an_authorized_user = crate::is_authorized_user(event.pubkey());
|
||||
|
||||
let author_is_current_user = match user {
|
||||
None => false,
|
||||
Some(pk) => event.pubkey() == *pk,
|
||||
};
|
||||
let author_is_current_user = authed_as.iter().any(|u| *u == event.pubkey());
|
||||
|
||||
let mut tags_an_authorized_user = false;
|
||||
let mut tags_current_user = false;
|
||||
@ -887,10 +881,8 @@ pub fn event_flags(event: &Event, user: &Option<Pubkey>) -> EventFlags {
|
||||
if let Some(b"p") = tag.next() {
|
||||
if let Some(value) = tag.next() {
|
||||
if let Ok(tagged_pk) = Pubkey::read_hex(value) {
|
||||
if let Some(current_user) = user {
|
||||
if *current_user == tagged_pk {
|
||||
tags_current_user = true;
|
||||
}
|
||||
if authed_as.contains(&tagged_pk) {
|
||||
tags_current_user = true;
|
||||
}
|
||||
|
||||
if crate::is_authorized_user(tagged_pk) {
|
||||
|
||||
@ -53,6 +53,36 @@ pub async fn serve_http(
|
||||
|
||||
let uri = request.uri().to_owned();
|
||||
|
||||
if p == "/privacy-policy" {
|
||||
let config = &*GLOBALS.config.read();
|
||||
if let Some(pp) = &config.privacy_policy {
|
||||
let response = Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Access-Control-Allow-Headers", "Authorization, *")
|
||||
.header("Access-Control-Allow-Methods", "*")
|
||||
.header("Allow", "OPTIONS, GET, HEAD")
|
||||
.header("Content-Type", "text/plain")
|
||||
.status(StatusCode::OK)
|
||||
.body(Full::new(pp.clone().into()).map_err(|e| e.into()).boxed())?;
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
if p == "/terms-of-service" {
|
||||
let config = &*GLOBALS.config.read();
|
||||
if let Some(tos) = &config.terms_of_service {
|
||||
let response = Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Access-Control-Allow-Headers", "Authorization, *")
|
||||
.header("Access-Control-Allow-Methods", "*")
|
||||
.header("Allow", "OPTIONS, GET, HEAD")
|
||||
.header("Content-Type", "text/plain")
|
||||
.status(StatusCode::OK)
|
||||
.body(Full::new(tos.clone().into()).map_err(|e| e.into()).boxed())?;
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
// Try blossom if enabled
|
||||
if GLOBALS.config.read().blossom_directory.is_some() {
|
||||
match blossom::handle(request).await {
|
||||
|
||||
@ -5,6 +5,7 @@ use crate::ip::HashedPeer;
|
||||
use http_body_util::combinators::BoxBody;
|
||||
use http_body_util::{BodyExt, Full};
|
||||
use hyper::body::Bytes;
|
||||
use hyper::http::uri::Uri;
|
||||
use hyper::{Response, StatusCode};
|
||||
|
||||
pub async fn serve_nip11(peer: HashedPeer) -> Result<Response<BoxBody<Bytes, Error>>, Error> {
|
||||
@ -80,18 +81,18 @@ fn build_rid(config: &Config) -> String {
|
||||
rid.push_str(description);
|
||||
rid.push('\"');
|
||||
}
|
||||
if let Some(banner_url) = &config.banner_url {
|
||||
rid.push(',');
|
||||
rid.push_str("\"banner\":\"");
|
||||
rid.push_str(banner_url);
|
||||
rid.push('\"');
|
||||
}
|
||||
if let Some(icon_url) = &config.icon_url {
|
||||
rid.push(',');
|
||||
rid.push_str("\"icon\":\"");
|
||||
rid.push_str(icon_url);
|
||||
rid.push('\"');
|
||||
}
|
||||
if let Some(contact) = &config.contact {
|
||||
rid.push(',');
|
||||
rid.push_str("\"contact\":\"");
|
||||
rid.push_str(contact);
|
||||
rid.push('\"');
|
||||
}
|
||||
if let Some(pubkey) = &config.contact_public_key {
|
||||
let mut pkh: [u8; 64] = [0; 64];
|
||||
pubkey.write_hex(&mut pkh).unwrap();
|
||||
@ -100,6 +101,44 @@ fn build_rid(config: &Config) -> String {
|
||||
rid.push_str(unsafe { std::str::from_utf8_unchecked(pkh.as_slice()) });
|
||||
rid.push('\"');
|
||||
}
|
||||
if let Some(contact) = &config.contact {
|
||||
rid.push(',');
|
||||
rid.push_str("\"contact\":\"");
|
||||
rid.push_str(contact);
|
||||
rid.push('\"');
|
||||
}
|
||||
if config.privacy_policy.is_some() {
|
||||
rid.push(',');
|
||||
rid.push_str("\"privacy_policy\":\"");
|
||||
let url = match config.uri_parts(
|
||||
Uri::from_static("https://authority-will-be-replaced/privacy-policy"),
|
||||
true,
|
||||
) {
|
||||
Ok(parts) => match Uri::from_parts(parts) {
|
||||
Ok(uri) => format!("{}", uri),
|
||||
Err(_) => "".to_owned(),
|
||||
},
|
||||
Err(_) => "".to_owned(),
|
||||
};
|
||||
rid.push_str(&url);
|
||||
rid.push('\"');
|
||||
}
|
||||
if config.terms_of_service.is_some() {
|
||||
rid.push(',');
|
||||
rid.push_str("\"terms_of_service\":\"");
|
||||
let url = match config.uri_parts(
|
||||
Uri::from_static("https://authority-will-be-replaced/terms-of-service"),
|
||||
true,
|
||||
) {
|
||||
Ok(parts) => match Uri::from_parts(parts) {
|
||||
Ok(uri) => format!("{}", uri),
|
||||
Err(_) => "".to_owned(),
|
||||
},
|
||||
Err(_) => "".to_owned(),
|
||||
};
|
||||
rid.push_str(&url);
|
||||
rid.push('\"');
|
||||
}
|
||||
|
||||
// Limitation
|
||||
rid.push(',');
|
||||
|
||||
@ -18,6 +18,12 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ADD ADMIN AS A USER ------------
|
||||
|
||||
echo "Adding user..."
|
||||
PUBKEY=12bb541d03bfc3cab0f4a8e4db28947f60faae6fca4e315eb27f809c6eff9a0b
|
||||
../target/release/chorus_cmd ./config.toml add_user $PUBKEY 0
|
||||
|
||||
# UPLOAD TEST ------------
|
||||
|
||||
FILE="./Example.png"
|
||||
|
||||
3
test_with_relay_tester/.gitignore
vendored
Normal file
3
test_with_relay_tester/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
data
|
||||
relay-tester
|
||||
|
||||
12
test_with_relay_tester/README.md
Normal file
12
test_with_relay_tester/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Testing Chorus with relay-tester
|
||||
|
||||
First, git clone https://github.com/mikedilger/relay-tester and build that project
|
||||
(cargo build --release).
|
||||
|
||||
Then copy that target/release/relay-tester binary into this directory.
|
||||
|
||||
Then run from two different shells, in this order:
|
||||
|
||||
shell1: ./test_chorus.sh
|
||||
|
||||
shell2: ./run_relay_tester.sh
|
||||
12
test_with_relay_tester/run_relay_tester.sh
Executable file
12
test_with_relay_tester/run_relay_tester.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ ! -x relay-tester ] ; then
|
||||
echo "You must build https://github.com/mikedilge/relay-tester and copy the "
|
||||
echo "resultant target/release/relay-tester binary into this directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./relay-tester \
|
||||
ws://localhost:8080/ \
|
||||
nsec16xfd467kyd3xpu9x5u4933u00v73xrl0jyq9rk5ktd9t2j38k20qtwxuj3 \
|
||||
nsec1l50yuf6uxm2l5qxm87fkm56z3m7g88jnfy5s6az5wscxpu5l2yqq6qwk88
|
||||
10
test_with_relay_tester/test_chorus.sh
Executable file
10
test_with_relay_tester/test_chorus.sh
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Use ws://localhost:8080/ as the relay url"
|
||||
|
||||
cargo build --release
|
||||
rm -rf ./data/
|
||||
../target/release/chorus_init ./test_chorus.toml
|
||||
../target/release/chorus_cmd ./test_chorus.toml add_user de16d3ed2d5ceb91d33e39dbe30585164e0c19f3f2e2a5b121def086b447a2e5 0
|
||||
../target/release/chorus_cmd ./test_chorus.toml add_user 35d6bbcf17fc31a9c4f7a2f68aa40ad32c8f9de1ae77505dc5eb3722d8b2987d 0
|
||||
../target/release/chorus ./test_chorus.toml
|
||||
40
test_with_relay_tester/test_chorus.toml
Normal file
40
test_with_relay_tester/test_chorus.toml
Normal file
@ -0,0 +1,40 @@
|
||||
# See contrib/chorus.toml for a documented config file
|
||||
|
||||
data_directory = "./data"
|
||||
ip_address = "127.0.0.1"
|
||||
port = 8080
|
||||
hostname = "localhost"
|
||||
chorus_is_behind_a_proxy = false
|
||||
use_tls = false
|
||||
certchain_pem_path = "tls/fullchain.pem"
|
||||
key_pem_path = "tls/privkey.pem"
|
||||
name = "Chorus Sample"
|
||||
description = "A sample run of the Chorus relay"
|
||||
#icon_url =
|
||||
open_relay = false
|
||||
admin_hex_keys = [
|
||||
"ee11a5dff40c19a555f41fe42b48f00e618c91225622ae37b6c2bb67b76c4e49",
|
||||
|
||||
# npub1mctd8mfdtn4er5e788d7xpv9ze8qcx0n7t32tvfpmmcgddz85tjsuyxe7z
|
||||
"de16d3ed2d5ceb91d33e39dbe30585164e0c19f3f2e2a5b121def086b447a2e5",
|
||||
|
||||
# npub1xhtthnchlsc6n38h5tmg4fq26vkgl80p4em4qhw9avmj9k9jnp7sm78ql6
|
||||
"35d6bbcf17fc31a9c4f7a2f68aa40ad32c8f9de1ae77505dc5eb3722d8b2987d"
|
||||
]
|
||||
verify_events = true
|
||||
allow_scraping = false
|
||||
allow_scrape_if_limited_to = 100
|
||||
allow_scrape_if_max_seconds = 7200
|
||||
max_subscriptions = 128
|
||||
serve_ephemeral = true
|
||||
serve_relay_lists = true
|
||||
server_log_level = "Info"
|
||||
library_log_level = "Info"
|
||||
client_log_level = "Debug"
|
||||
enable_ip_blocking = false
|
||||
minimum_ban_seconds = 1
|
||||
timeout_seconds = 60
|
||||
max_connections_per_ip = 5
|
||||
throttling_bytes_per_second = 131072
|
||||
throttling_burst = 4194304
|
||||
enable_negentropy = true
|
||||
Loading…
x
Reference in New Issue
Block a user