forked from mirrors/relay
Start work on admin API
This commit is contained in:
parent
08374d0382
commit
fe844a807f
9 changed files with 471 additions and 4 deletions
42
Cargo.lock
generated
42
Cargo.lock
generated
|
@ -291,6 +291,7 @@ dependencies = [
|
||||||
"awc",
|
"awc",
|
||||||
"background-jobs",
|
"background-jobs",
|
||||||
"base64",
|
"base64",
|
||||||
|
"bcrypt",
|
||||||
"clap",
|
"clap",
|
||||||
"config",
|
"config",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
|
@ -544,6 +545,18 @@ version = "1.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
|
checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bcrypt"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"blowfish",
|
||||||
|
"getrandom",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
@ -559,6 +572,16 @@ dependencies = [
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blowfish"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.11.1"
|
version = "3.11.1"
|
||||||
|
@ -614,6 +637,16 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.0.26"
|
version = "4.0.26"
|
||||||
|
@ -1313,6 +1346,15 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
|
|
@ -29,6 +29,7 @@ activitystreams = "0.7.0-alpha.19"
|
||||||
activitystreams-ext = "0.1.0-alpha.2"
|
activitystreams-ext = "0.1.0-alpha.2"
|
||||||
ammonia = "3.1.0"
|
ammonia = "3.1.0"
|
||||||
awc = { version = "3.0.0", default-features = false, features = ["rustls"] }
|
awc = { version = "3.0.0", default-features = false, features = ["rustls"] }
|
||||||
|
bcrypt = "0.13"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
clap = { version = "4.0.0", features = ["derive"] }
|
clap = { version = "4.0.0", features = ["derive"] }
|
||||||
config = "0.13.0"
|
config = "0.13.0"
|
||||||
|
|
24
src/admin.rs
Normal file
24
src/admin.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use activitystreams::iri_string::types::IriString;
|
||||||
|
|
||||||
|
pub mod client;
|
||||||
|
pub mod routes;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct Domains {
|
||||||
|
domains: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct AllowedDomains {
|
||||||
|
allowed_domains: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct BlockedDomains {
|
||||||
|
blocked_domains: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
|
pub(crate) struct ConnectedActors {
|
||||||
|
connected_actors: Vec<IriString>,
|
||||||
|
}
|
87
src/admin/client.rs
Normal file
87
src/admin/client.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use crate::{
|
||||||
|
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
|
||||||
|
config::{AdminUrlKind, Config},
|
||||||
|
error::{Error, ErrorKind},
|
||||||
|
};
|
||||||
|
use awc::Client;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
pub(crate) async fn allow(
|
||||||
|
client: &Client,
|
||||||
|
config: &Config,
|
||||||
|
domains: Vec<String>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
post_domains(client, config, domains, AdminUrlKind::Allow).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn block(
|
||||||
|
client: &Client,
|
||||||
|
config: &Config,
|
||||||
|
domains: Vec<String>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
post_domains(client, config, domains, AdminUrlKind::Block).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn allowed(client: &Client, config: &Config) -> Result<AllowedDomains, Error> {
|
||||||
|
get_results(client, config, AdminUrlKind::Allowed).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn blocked(client: &Client, config: &Config) -> Result<BlockedDomains, Error> {
|
||||||
|
get_results(client, config, AdminUrlKind::Blocked).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn connected(client: &Client, config: &Config) -> Result<ConnectedActors, Error> {
|
||||||
|
get_results(client, config, AdminUrlKind::Connected).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_results<T: DeserializeOwned>(
|
||||||
|
client: &Client,
|
||||||
|
config: &Config,
|
||||||
|
url_kind: AdminUrlKind,
|
||||||
|
) -> Result<T, Error> {
|
||||||
|
let x_api_token = config.x_api_token().ok_or(ErrorKind::MissingApiToken)?;
|
||||||
|
|
||||||
|
let iri = config.generate_admin_url(url_kind);
|
||||||
|
|
||||||
|
let mut res = client
|
||||||
|
.get(iri.as_str())
|
||||||
|
.insert_header(x_api_token)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ErrorKind::SendRequest(iri.to_string(), e.to_string()))?;
|
||||||
|
|
||||||
|
if !res.status().is_success() {
|
||||||
|
return Err(ErrorKind::Status(iri.to_string(), res.status()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = res
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ErrorKind::ReceiveResponse(iri.to_string(), e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_domains(
|
||||||
|
client: &Client,
|
||||||
|
config: &Config,
|
||||||
|
domains: Vec<String>,
|
||||||
|
url_kind: AdminUrlKind,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let x_api_token = config.x_api_token().ok_or(ErrorKind::MissingApiToken)?;
|
||||||
|
|
||||||
|
let iri = config.generate_admin_url(url_kind);
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.post(iri.as_str())
|
||||||
|
.insert_header(x_api_token)
|
||||||
|
.send_json(&Domains { domains })
|
||||||
|
.await
|
||||||
|
.map_err(|e| ErrorKind::SendRequest(iri.to_string(), e.to_string()))?;
|
||||||
|
|
||||||
|
if !res.status().is_success() {
|
||||||
|
tracing::warn!("Failed to allow domains");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
42
src/admin/routes.rs
Normal file
42
src/admin/routes.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use crate::{
|
||||||
|
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
|
||||||
|
error::Error,
|
||||||
|
extractors::Admin,
|
||||||
|
};
|
||||||
|
use actix_web::{web::Json, HttpResponse};
|
||||||
|
|
||||||
|
pub(crate) async fn allow(
|
||||||
|
admin: Admin,
|
||||||
|
Json(Domains { domains }): Json<Domains>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
admin.db_ref().add_allows(domains).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn block(
|
||||||
|
admin: Admin,
|
||||||
|
Json(Domains { domains }): Json<Domains>,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
admin.db_ref().add_blocks(domains).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn allowed(admin: Admin) -> Result<HttpResponse, Error> {
|
||||||
|
let allowed_domains = admin.db_ref().allowed_domains().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(AllowedDomains { allowed_domains }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn blocked(admin: Admin) -> Result<HttpResponse, Error> {
|
||||||
|
let blocked_domains = admin.db_ref().blocks().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(BlockedDomains { blocked_domains }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn connected(admin: Admin) -> Result<HttpResponse, Error> {
|
||||||
|
let connected_actors = admin.db_ref().connected_ids().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(ConnectedActors { connected_actors }))
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{ActorCache, State},
|
data::{ActorCache, State},
|
||||||
error::Error,
|
error::Error,
|
||||||
|
extractors::{AdminConfig, XApiToken},
|
||||||
middleware::MyVerify,
|
middleware::MyVerify,
|
||||||
requests::Requests,
|
requests::Requests,
|
||||||
};
|
};
|
||||||
|
@ -32,6 +33,7 @@ pub(crate) struct ParsedConfig {
|
||||||
opentelemetry_url: Option<IriString>,
|
opentelemetry_url: Option<IriString>,
|
||||||
telegram_token: Option<String>,
|
telegram_token: Option<String>,
|
||||||
telegram_admin_handle: Option<String>,
|
telegram_admin_handle: Option<String>,
|
||||||
|
api_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -49,6 +51,7 @@ pub struct Config {
|
||||||
opentelemetry_url: Option<IriString>,
|
opentelemetry_url: Option<IriString>,
|
||||||
telegram_token: Option<String>,
|
telegram_token: Option<String>,
|
||||||
telegram_admin_handle: Option<String>,
|
telegram_admin_handle: Option<String>,
|
||||||
|
api_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -65,6 +68,15 @@ pub enum UrlKind {
|
||||||
Outbox,
|
Outbox,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AdminUrlKind {
|
||||||
|
Allow,
|
||||||
|
Block,
|
||||||
|
Allowed,
|
||||||
|
Blocked,
|
||||||
|
Connected,
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Config {
|
impl std::fmt::Debug for Config {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Config")
|
f.debug_struct("Config")
|
||||||
|
@ -84,6 +96,7 @@ impl std::fmt::Debug for Config {
|
||||||
)
|
)
|
||||||
.field("telegram_token", &"[redacted]")
|
.field("telegram_token", &"[redacted]")
|
||||||
.field("telegram_admin_handle", &self.telegram_admin_handle)
|
.field("telegram_admin_handle", &self.telegram_admin_handle)
|
||||||
|
.field("api_token", &"[redacted]")
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +106,7 @@ impl Config {
|
||||||
let config = config::Config::builder()
|
let config = config::Config::builder()
|
||||||
.set_default("hostname", "localhost:8080")?
|
.set_default("hostname", "localhost:8080")?
|
||||||
.set_default("addr", "127.0.0.1")?
|
.set_default("addr", "127.0.0.1")?
|
||||||
.set_default::<_, u64>("port", 8080)?
|
.set_default("port", 8080u64)?
|
||||||
.set_default("debug", true)?
|
.set_default("debug", true)?
|
||||||
.set_default("restricted_mode", false)?
|
.set_default("restricted_mode", false)?
|
||||||
.set_default("validate_signatures", false)?
|
.set_default("validate_signatures", false)?
|
||||||
|
@ -104,6 +117,7 @@ impl Config {
|
||||||
.set_default("opentelemetry_url", None as Option<&str>)?
|
.set_default("opentelemetry_url", None as Option<&str>)?
|
||||||
.set_default("telegram_token", None as Option<&str>)?
|
.set_default("telegram_token", None as Option<&str>)?
|
||||||
.set_default("telegram_admin_handle", None as Option<&str>)?
|
.set_default("telegram_admin_handle", None as Option<&str>)?
|
||||||
|
.set_default("api_token", None as Option<&str>)?
|
||||||
.add_source(Environment::default())
|
.add_source(Environment::default())
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
|
@ -126,6 +140,7 @@ impl Config {
|
||||||
opentelemetry_url: config.opentelemetry_url,
|
opentelemetry_url: config.opentelemetry_url,
|
||||||
telegram_token: config.telegram_token,
|
telegram_token: config.telegram_token,
|
||||||
telegram_admin_handle: config.telegram_admin_handle,
|
telegram_admin_handle: config.telegram_admin_handle,
|
||||||
|
api_token: config.api_token,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,6 +173,24 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn x_api_token(&self) -> Option<XApiToken> {
|
||||||
|
self.api_token.clone().map(XApiToken::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn admin_config(&self) -> Option<actix_web::web::Data<AdminConfig>> {
|
||||||
|
if let Some(api_token) = &self.api_token {
|
||||||
|
match AdminConfig::build(api_token) {
|
||||||
|
Ok(conf) => Some(actix_web::web::Data::new(conf)),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Error creating admin config: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn bind_address(&self) -> (IpAddr, u16) {
|
pub(crate) fn bind_address(&self) -> (IpAddr, u16) {
|
||||||
(self.addr, self.port)
|
(self.addr, self.port)
|
||||||
}
|
}
|
||||||
|
@ -281,4 +314,26 @@ impl Config {
|
||||||
|
|
||||||
Ok(iri)
|
Ok(iri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn generate_admin_url(&self, kind: AdminUrlKind) -> IriString {
|
||||||
|
self.do_generate_admin_url(kind)
|
||||||
|
.expect("Generated valid IRI")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_generate_admin_url(&self, kind: AdminUrlKind) -> Result<IriString, Error> {
|
||||||
|
let iri = match kind {
|
||||||
|
AdminUrlKind::Allow => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||||
|
.try_resolve(IriRelativeStr::new("api/v1/admin/allow")?.as_ref())?,
|
||||||
|
AdminUrlKind::Block => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||||
|
.try_resolve(IriRelativeStr::new("api/v1/admin/block")?.as_ref())?,
|
||||||
|
AdminUrlKind::Allowed => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||||
|
.try_resolve(IriRelativeStr::new("api/v1/admin/allowed")?.as_ref())?,
|
||||||
|
AdminUrlKind::Blocked => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||||
|
.try_resolve(IriRelativeStr::new("api/v1/admin/blocked")?.as_ref())?,
|
||||||
|
AdminUrlKind::Connected => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||||
|
.try_resolve(IriRelativeStr::new("api/v1/admin/connected")?.as_ref())?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(iri)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,6 +180,9 @@ pub(crate) enum ErrorKind {
|
||||||
|
|
||||||
#[error("Failed to extract fields from {0}")]
|
#[error("Failed to extract fields from {0}")]
|
||||||
Extract(&'static str),
|
Extract(&'static str),
|
||||||
|
|
||||||
|
#[error("No API Token supplied")]
|
||||||
|
MissingApiToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for Error {
|
impl ResponseError for Error {
|
||||||
|
|
194
src/extractors.rs
Normal file
194
src/extractors.rs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
use actix_web::{
|
||||||
|
dev::Payload,
|
||||||
|
error::ParseError,
|
||||||
|
http::{
|
||||||
|
header::{from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue},
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
web::Data,
|
||||||
|
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
|
||||||
|
};
|
||||||
|
use bcrypt::{BcryptError, DEFAULT_COST};
|
||||||
|
use http_signature_normalization_actix::prelude::InvalidHeaderValue;
|
||||||
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
future::{ready, Ready},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use tracing_error::SpanTrace;
|
||||||
|
|
||||||
|
use crate::db::Db;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct AdminConfig {
|
||||||
|
hashed_api_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AdminConfig {
|
||||||
|
pub(crate) fn build(api_token: &str) -> Result<Self, Error> {
|
||||||
|
Ok(AdminConfig {
|
||||||
|
hashed_api_token: bcrypt::hash(api_token, DEFAULT_COST).map_err(Error::bcrypt_hash)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify(&self, token: XApiToken) -> Result<bool, Error> {
|
||||||
|
Ok(bcrypt::verify(&self.hashed_api_token, &token.0).map_err(Error::bcrypt_verify)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Admin {
|
||||||
|
db: Data<Db>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Admin {
|
||||||
|
#[tracing::instrument(level = "debug", skip(req))]
|
||||||
|
fn verify(req: &HttpRequest) -> Result<Self, Error> {
|
||||||
|
let hashed_api_token = req
|
||||||
|
.app_data::<Data<AdminConfig>>()
|
||||||
|
.ok_or_else(Error::missing_config)?;
|
||||||
|
|
||||||
|
let x_api_token = XApiToken::parse(req).map_err(Error::parse_header)?;
|
||||||
|
|
||||||
|
if hashed_api_token.verify(x_api_token)? {
|
||||||
|
let db = req.app_data::<Data<Db>>().ok_or_else(Error::missing_db)?;
|
||||||
|
|
||||||
|
return Ok(Self { db: db.clone() });
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::invalid())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn db_ref(&self) -> &Db {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("Failed authentication")]
|
||||||
|
pub(crate) struct Error {
|
||||||
|
context: SpanTrace,
|
||||||
|
#[source]
|
||||||
|
kind: ErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn invalid() -> Self {
|
||||||
|
Error {
|
||||||
|
context: SpanTrace::capture(),
|
||||||
|
kind: ErrorKind::Invalid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn missing_config() -> Self {
|
||||||
|
Error {
|
||||||
|
context: SpanTrace::capture(),
|
||||||
|
kind: ErrorKind::MissingConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn missing_db() -> Self {
|
||||||
|
Error {
|
||||||
|
context: SpanTrace::capture(),
|
||||||
|
kind: ErrorKind::MissingDb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bcrypt_verify(e: BcryptError) -> Self {
|
||||||
|
Error {
|
||||||
|
context: SpanTrace::capture(),
|
||||||
|
kind: ErrorKind::BCryptVerify(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bcrypt_hash(e: BcryptError) -> Self {
|
||||||
|
Error {
|
||||||
|
context: SpanTrace::capture(),
|
||||||
|
kind: ErrorKind::BCryptHash(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_header(e: ParseError) -> Self {
|
||||||
|
Error {
|
||||||
|
context: SpanTrace::capture(),
|
||||||
|
kind: ErrorKind::ParseHeader(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
enum ErrorKind {
|
||||||
|
#[error("Invalid API Token")]
|
||||||
|
Invalid,
|
||||||
|
|
||||||
|
#[error("Missing Config")]
|
||||||
|
MissingConfig,
|
||||||
|
|
||||||
|
#[error("Missing Db")]
|
||||||
|
MissingDb,
|
||||||
|
|
||||||
|
#[error("Verifying")]
|
||||||
|
BCryptVerify(#[source] BcryptError),
|
||||||
|
|
||||||
|
#[error("Hashing")]
|
||||||
|
BCryptHash(#[source] BcryptError),
|
||||||
|
|
||||||
|
#[error("Parse Header")]
|
||||||
|
ParseHeader(#[source] ParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for Error {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self.kind {
|
||||||
|
ErrorKind::Invalid | ErrorKind::ParseHeader(_) => StatusCode::BAD_REQUEST,
|
||||||
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::build(self.status_code())
|
||||||
|
.json(serde_json::json!({ "msg": self.kind.to_string() }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for Admin {
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
ready(Admin::verify(req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct XApiToken(String);
|
||||||
|
|
||||||
|
impl XApiToken {
|
||||||
|
pub(crate) fn new(token: String) -> Self {
|
||||||
|
Self(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Header for XApiToken {
|
||||||
|
fn name() -> HeaderName {
|
||||||
|
HeaderName::from_static("x-api-token")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse<M: HttpMessage>(msg: &M) -> Result<Self, ParseError> {
|
||||||
|
from_one_raw_str(msg.headers().get(Self::name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryIntoHeaderValue for XApiToken {
|
||||||
|
type Error = InvalidHeaderValue;
|
||||||
|
|
||||||
|
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||||
|
HeaderValue::from_str(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for XApiToken {
|
||||||
|
type Err = Infallible;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(XApiToken(s.to_string()))
|
||||||
|
}
|
||||||
|
}
|
25
src/main.rs
25
src/main.rs
|
@ -12,12 +12,14 @@ use tracing_error::ErrorLayer;
|
||||||
use tracing_log::LogTracer;
|
use tracing_log::LogTracer;
|
||||||
use tracing_subscriber::{filter::Targets, fmt::format::FmtSpan, layer::SubscriberExt, Layer};
|
use tracing_subscriber::{filter::Targets, fmt::format::FmtSpan, layer::SubscriberExt, Layer};
|
||||||
|
|
||||||
|
mod admin;
|
||||||
mod apub;
|
mod apub;
|
||||||
mod args;
|
mod args;
|
||||||
mod config;
|
mod config;
|
||||||
mod data;
|
mod data;
|
||||||
mod db;
|
mod db;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod extractors;
|
||||||
mod jobs;
|
mod jobs;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
mod requests;
|
mod requests;
|
||||||
|
@ -135,15 +137,22 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
|
|
||||||
let bind_address = config.bind_address();
|
let bind_address = config.bind_address();
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
let app = App::new()
|
||||||
.wrap(TracingLogger::default())
|
|
||||||
.app_data(web::Data::new(db.clone()))
|
.app_data(web::Data::new(db.clone()))
|
||||||
.app_data(web::Data::new(state.clone()))
|
.app_data(web::Data::new(state.clone()))
|
||||||
.app_data(web::Data::new(state.requests(&config)))
|
.app_data(web::Data::new(state.requests(&config)))
|
||||||
.app_data(web::Data::new(actors.clone()))
|
.app_data(web::Data::new(actors.clone()))
|
||||||
.app_data(web::Data::new(config.clone()))
|
.app_data(web::Data::new(config.clone()))
|
||||||
.app_data(web::Data::new(job_server.clone()))
|
.app_data(web::Data::new(job_server.clone()))
|
||||||
.app_data(web::Data::new(media.clone()))
|
.app_data(web::Data::new(media.clone()));
|
||||||
|
|
||||||
|
let app = if let Some(data) = config.admin_config() {
|
||||||
|
app.app_data(data)
|
||||||
|
} else {
|
||||||
|
app
|
||||||
|
};
|
||||||
|
|
||||||
|
app.wrap(TracingLogger::default())
|
||||||
.service(web::resource("/").route(web::get().to(index)))
|
.service(web::resource("/").route(web::get().to(index)))
|
||||||
.service(web::resource("/media/{path}").route(web::get().to(routes::media)))
|
.service(web::resource("/media/{path}").route(web::get().to(routes::media)))
|
||||||
.service(
|
.service(
|
||||||
|
@ -165,6 +174,16 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
.service(web::resource("/nodeinfo").route(web::get().to(nodeinfo_meta))),
|
.service(web::resource("/nodeinfo").route(web::get().to(nodeinfo_meta))),
|
||||||
)
|
)
|
||||||
.service(web::resource("/static/{filename}").route(web::get().to(statics)))
|
.service(web::resource("/static/{filename}").route(web::get().to(statics)))
|
||||||
|
.service(
|
||||||
|
web::scope("/api/v1").service(
|
||||||
|
web::scope("/admin")
|
||||||
|
.route("/allow", web::post().to(admin::routes::allow))
|
||||||
|
.route("/block", web::post().to(admin::routes::block))
|
||||||
|
.route("/allowed", web::get().to(admin::routes::allowed))
|
||||||
|
.route("/blocked", web::get().to(admin::routes::blocked))
|
||||||
|
.route("/connected", web::get().to(admin::routes::connected)),
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.bind(bind_address)?
|
.bind(bind_address)?
|
||||||
.run()
|
.run()
|
||||||
|
|
Loading…
Reference in a new issue