forked from mirrors/relay
Make admin API & client work
This commit is contained in:
parent
fe844a807f
commit
ebdc739c84
12 changed files with 145 additions and 48 deletions
1
.env
1
.env
|
@ -1,4 +1,5 @@
|
|||
HOSTNAME=localhost:8079
|
||||
PORT=8079
|
||||
RESTRICTED_MODE=true
|
||||
API_TOKEN=somesecretpassword
|
||||
# OPENTELEMETRY_URL=http://localhost:4317
|
||||
|
|
|
@ -36,6 +36,9 @@ To simply run the server, the command is as follows
|
|||
$ ./relay
|
||||
```
|
||||
|
||||
#### Administration
|
||||
> **NOTE:** The server _must be running_ in order to update the lists with the following commands
|
||||
|
||||
To learn about any other tasks, the `--help` flag can be passed
|
||||
```bash
|
||||
An activitypub relay
|
||||
|
@ -91,6 +94,7 @@ PRETTY_LOG=false
|
|||
PUBLISH_BLOCKS=true
|
||||
SLED_PATH=./sled/db-0.34
|
||||
RUST_LOG=warn
|
||||
API_TOKEN=somepasswordishtoken
|
||||
OPENTELEMETRY_URL=localhost:4317
|
||||
TELEGRAM_TOKEN=secret
|
||||
TELEGRAM_ADMIN_HANDLE=your_handle
|
||||
|
@ -119,6 +123,8 @@ Where to store the on-disk database of connected servers. This defaults to `./sl
|
|||
The log level to print. Available levels are `ERROR`, `WARN`, `INFO`, `DEBUG`, and `TRACE`. You can also specify module paths to enable some logs but not others, such as `RUST_LOG=warn,tracing_actix_web=info,relay=info`
|
||||
##### `SOURCE_REPO`
|
||||
The URL to the source code for the relay. This defaults to `https://git.asonix.dog/asonix/relay`, but should be changed if you're running a fork hosted elsewhere.
|
||||
##### `API_TOKEN`
|
||||
The Secret token used to access the admin APIs. This must be set for the commandline to function
|
||||
##### `OPENTELEMETRY_URL`
|
||||
A URL for exporting opentelemetry spans. This is mostly useful for debugging. There is no default, since most people probably don't run an opentelemetry collector.
|
||||
##### `TELEGRAM_TOKEN`
|
||||
|
|
|
@ -10,15 +10,15 @@ pub(crate) struct Domains {
|
|||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct AllowedDomains {
|
||||
allowed_domains: Vec<String>,
|
||||
pub(crate) allowed_domains: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct BlockedDomains {
|
||||
blocked_domains: Vec<String>,
|
||||
pub(crate) blocked_domains: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub(crate) struct ConnectedActors {
|
||||
connected_actors: Vec<IriString>,
|
||||
pub(crate) connected_actors: Vec<IriString>,
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@ pub(crate) async fn allow(
|
|||
post_domains(client, config, domains, AdminUrlKind::Allow).await
|
||||
}
|
||||
|
||||
pub(crate) async fn disallow(
|
||||
client: &Client,
|
||||
config: &Config,
|
||||
domains: Vec<String>,
|
||||
) -> Result<(), Error> {
|
||||
post_domains(client, config, domains, AdminUrlKind::Disallow).await
|
||||
}
|
||||
|
||||
pub(crate) async fn block(
|
||||
client: &Client,
|
||||
config: &Config,
|
||||
|
@ -22,6 +30,14 @@ pub(crate) async fn block(
|
|||
post_domains(client, config, domains, AdminUrlKind::Block).await
|
||||
}
|
||||
|
||||
pub(crate) async fn unblock(
|
||||
client: &Client,
|
||||
config: &Config,
|
||||
domains: Vec<String>,
|
||||
) -> Result<(), Error> {
|
||||
post_domains(client, config, domains, AdminUrlKind::Unblock).await
|
||||
}
|
||||
|
||||
pub(crate) async fn allowed(client: &Client, config: &Config) -> Result<AllowedDomains, Error> {
|
||||
get_results(client, config, AdminUrlKind::Allowed).await
|
||||
}
|
||||
|
|
|
@ -14,6 +14,15 @@ pub(crate) async fn allow(
|
|||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
pub(crate) async fn disallow(
|
||||
admin: Admin,
|
||||
Json(Domains { domains }): Json<Domains>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
admin.db_ref().remove_allows(domains).await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
pub(crate) async fn block(
|
||||
admin: Admin,
|
||||
Json(Domains { domains }): Json<Domains>,
|
||||
|
@ -23,20 +32,29 @@ pub(crate) async fn block(
|
|||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
pub(crate) async fn allowed(admin: Admin) -> Result<HttpResponse, Error> {
|
||||
let allowed_domains = admin.db_ref().allowed_domains().await?;
|
||||
pub(crate) async fn unblock(
|
||||
admin: Admin,
|
||||
Json(Domains { domains }): Json<Domains>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
admin.db_ref().remove_blocks(domains).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(AllowedDomains { allowed_domains }))
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
pub(crate) async fn blocked(admin: Admin) -> Result<HttpResponse, Error> {
|
||||
pub(crate) async fn allowed(admin: Admin) -> Result<Json<AllowedDomains>, Error> {
|
||||
let allowed_domains = admin.db_ref().allows().await?;
|
||||
|
||||
Ok(Json(AllowedDomains { allowed_domains }))
|
||||
}
|
||||
|
||||
pub(crate) async fn blocked(admin: Admin) -> Result<Json<BlockedDomains>, Error> {
|
||||
let blocked_domains = admin.db_ref().blocks().await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(BlockedDomains { blocked_domains }))
|
||||
Ok(Json(BlockedDomains { blocked_domains }))
|
||||
}
|
||||
|
||||
pub(crate) async fn connected(admin: Admin) -> Result<HttpResponse, Error> {
|
||||
pub(crate) async fn connected(admin: Admin) -> Result<Json<ConnectedActors>, Error> {
|
||||
let connected_actors = admin.db_ref().connected_ids().await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(ConnectedActors { connected_actors }))
|
||||
Ok(Json(ConnectedActors { connected_actors }))
|
||||
}
|
||||
|
|
|
@ -17,6 +17,10 @@ pub(crate) struct Args {
|
|||
}
|
||||
|
||||
impl Args {
|
||||
pub(crate) fn any(&self) -> bool {
|
||||
!self.blocks.is_empty() || !self.allowed.is_empty() || self.list
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::parse()
|
||||
}
|
||||
|
|
|
@ -71,7 +71,9 @@ pub enum UrlKind {
|
|||
#[derive(Debug)]
|
||||
pub enum AdminUrlKind {
|
||||
Allow,
|
||||
Disallow,
|
||||
Block,
|
||||
Unblock,
|
||||
Allowed,
|
||||
Blocked,
|
||||
Connected,
|
||||
|
@ -324,8 +326,12 @@ impl Config {
|
|||
let iri = match kind {
|
||||
AdminUrlKind::Allow => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||
.try_resolve(IriRelativeStr::new("api/v1/admin/allow")?.as_ref())?,
|
||||
AdminUrlKind::Disallow => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||
.try_resolve(IriRelativeStr::new("api/v1/admin/disallow")?.as_ref())?,
|
||||
AdminUrlKind::Block => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||
.try_resolve(IriRelativeStr::new("api/v1/admin/block")?.as_ref())?,
|
||||
AdminUrlKind::Unblock => FixedBaseResolver::new(self.base_uri.as_ref())
|
||||
.try_resolve(IriRelativeStr::new("api/v1/admin/unblock")?.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())
|
||||
|
|
|
@ -281,10 +281,6 @@ impl Db {
|
|||
self.unblock(|inner| Ok(inner.connected().collect())).await
|
||||
}
|
||||
|
||||
pub(crate) async fn allowed_domains(&self) -> Result<Vec<String>, Error> {
|
||||
self.unblock(|inner| Ok(inner.allowed().collect())).await
|
||||
}
|
||||
|
||||
pub(crate) async fn save_info(&self, actor_id: IriString, info: Info) -> Result<(), Error> {
|
||||
self.unblock(move |inner| {
|
||||
let vec = serde_json::to_vec(&info)?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use actix_web::{
|
||||
dev::Payload,
|
||||
error::ParseError,
|
||||
error::{BlockingError, ParseError},
|
||||
http::{
|
||||
header::{from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue},
|
||||
StatusCode,
|
||||
|
@ -9,12 +9,9 @@ use actix_web::{
|
|||
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
|
||||
};
|
||||
use bcrypt::{BcryptError, DEFAULT_COST};
|
||||
use futures_util::future::LocalBoxFuture;
|
||||
use http_signature_normalization_actix::prelude::InvalidHeaderValue;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::{ready, Ready},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::{convert::Infallible, str::FromStr};
|
||||
use tracing_error::SpanTrace;
|
||||
|
||||
use crate::db::Db;
|
||||
|
@ -32,7 +29,7 @@ impl AdminConfig {
|
|||
}
|
||||
|
||||
fn verify(&self, token: XApiToken) -> Result<bool, Error> {
|
||||
Ok(bcrypt::verify(&self.hashed_api_token, &token.0).map_err(Error::bcrypt_verify)?)
|
||||
Ok(bcrypt::verify(&token.0, &self.hashed_api_token).map_err(Error::bcrypt_verify)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,18 +38,34 @@ pub(crate) struct Admin {
|
|||
}
|
||||
|
||||
impl Admin {
|
||||
#[tracing::instrument(level = "debug", skip(req))]
|
||||
fn verify(req: &HttpRequest) -> Result<Self, Error> {
|
||||
fn prepare_verify(
|
||||
req: &HttpRequest,
|
||||
) -> Result<(Data<Db>, Data<AdminConfig>, XApiToken), Error> {
|
||||
let hashed_api_token = req
|
||||
.app_data::<Data<AdminConfig>>()
|
||||
.ok_or_else(Error::missing_config)?;
|
||||
.ok_or_else(Error::missing_config)?
|
||||
.clone();
|
||||
|
||||
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)?;
|
||||
let db = req
|
||||
.app_data::<Data<Db>>()
|
||||
.ok_or_else(Error::missing_db)?
|
||||
.clone();
|
||||
|
||||
return Ok(Self { db: db.clone() });
|
||||
Ok((db, hashed_api_token, x_api_token))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn verify(
|
||||
hashed_api_token: Data<AdminConfig>,
|
||||
x_api_token: XApiToken,
|
||||
) -> Result<(), Error> {
|
||||
if actix_web::web::block(move || hashed_api_token.verify(x_api_token))
|
||||
.await
|
||||
.map_err(Error::canceled)??
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(Error::invalid())
|
||||
|
@ -113,6 +126,13 @@ impl Error {
|
|||
kind: ErrorKind::ParseHeader(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn canceled(_: BlockingError) -> Self {
|
||||
Error {
|
||||
context: SpanTrace::capture(),
|
||||
kind: ErrorKind::Canceled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -126,6 +146,9 @@ enum ErrorKind {
|
|||
#[error("Missing Db")]
|
||||
MissingDb,
|
||||
|
||||
#[error("Panic in verify")]
|
||||
Canceled,
|
||||
|
||||
#[error("Verifying")]
|
||||
BCryptVerify(#[source] BcryptError),
|
||||
|
||||
|
@ -152,10 +175,15 @@ impl ResponseError for Error {
|
|||
|
||||
impl FromRequest for Admin {
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ready(Admin::verify(req))
|
||||
let res = Self::prepare_verify(req);
|
||||
Box::pin(async move {
|
||||
let (db, c, t) = res?;
|
||||
Self::verify(c, t).await?;
|
||||
Ok(Admin { db })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
56
src/main.rs
56
src/main.rs
|
@ -99,30 +99,50 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
|
||||
init_subscriber(Config::software_name(), config.opentelemetry_url())?;
|
||||
|
||||
let db = Db::build(&config)?;
|
||||
|
||||
let args = Args::new();
|
||||
|
||||
if args.list() {
|
||||
for domain in db.blocks().await? {
|
||||
println!("block {}", domain);
|
||||
if args.any() {
|
||||
let client = requests::build_client(&config.user_agent());
|
||||
|
||||
if !args.blocks().is_empty() || !args.allowed().is_empty() {
|
||||
if args.undo() {
|
||||
admin::client::unblock(&client, &config, args.blocks().to_vec()).await?;
|
||||
admin::client::disallow(&client, &config, args.allowed().to_vec()).await?;
|
||||
} else {
|
||||
admin::client::block(&client, &config, args.blocks().to_vec()).await?;
|
||||
admin::client::allow(&client, &config, args.allowed().to_vec()).await?;
|
||||
}
|
||||
println!("Updated lists");
|
||||
}
|
||||
for domain in db.allows().await? {
|
||||
println!("allow {}", domain);
|
||||
|
||||
if args.list() {
|
||||
let (blocked, allowed, connected) = tokio::try_join!(
|
||||
admin::client::blocked(&client, &config),
|
||||
admin::client::allowed(&client, &config),
|
||||
admin::client::connected(&client, &config)
|
||||
)?;
|
||||
|
||||
let mut report = String::from("Report:\n");
|
||||
if !allowed.allowed_domains.is_empty() {
|
||||
report += "\nAllowed\n\t";
|
||||
report += &allowed.allowed_domains.join("\n\t");
|
||||
}
|
||||
if !blocked.blocked_domains.is_empty() {
|
||||
report += "\n\nBlocked\n\t";
|
||||
report += &blocked.blocked_domains.join("\n\t");
|
||||
}
|
||||
if !connected.connected_actors.is_empty() {
|
||||
report += "\n\nConnected\n\t";
|
||||
report += &connected.connected_actors.join("\n\t");
|
||||
}
|
||||
report += "\n";
|
||||
println!("{report}");
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !args.blocks().is_empty() || !args.allowed().is_empty() {
|
||||
if args.undo() {
|
||||
db.remove_blocks(args.blocks().to_vec()).await?;
|
||||
db.remove_allows(args.allowed().to_vec()).await?;
|
||||
} else {
|
||||
db.add_blocks(args.blocks().to_vec()).await?;
|
||||
db.add_allows(args.allowed().to_vec()).await?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let db = Db::build(&config)?;
|
||||
|
||||
let media = MediaCache::new(db.clone());
|
||||
let state = State::build(db.clone()).await?;
|
||||
|
@ -178,7 +198,9 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||
web::scope("/api/v1").service(
|
||||
web::scope("/admin")
|
||||
.route("/allow", web::post().to(admin::routes::allow))
|
||||
.route("/disallow", web::post().to(admin::routes::disallow))
|
||||
.route("/block", web::post().to(admin::routes::block))
|
||||
.route("/unblock", web::post().to(admin::routes::unblock))
|
||||
.route("/allowed", web::get().to(admin::routes::allowed))
|
||||
.route("/blocked", web::get().to(admin::routes::blocked))
|
||||
.route("/connected", web::get().to(admin::routes::connected)),
|
||||
|
|
|
@ -160,7 +160,7 @@ impl std::fmt::Debug for Requests {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_client(user_agent: &str) -> Client {
|
||||
pub(crate) fn build_client(user_agent: &str) -> Client {
|
||||
Client::builder()
|
||||
.wrap(Tracing)
|
||||
.add_default_header(("User-Agent", user_agent.to_string()))
|
||||
|
|
|
@ -105,7 +105,7 @@ async fn answer(bot: Bot, msg: Message, cmd: Command, db: Db) -> ResponseResult<
|
|||
.await?;
|
||||
}
|
||||
Command::ListAllowed => {
|
||||
if let Ok(allowed) = db.allowed_domains().await {
|
||||
if let Ok(allowed) = db.allows().await {
|
||||
bot.send_message(msg.chat.id, allowed.join("\n")).await?;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue