mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add server-wide and user-only federation controls (#133)
### Description Completes #122 ### Please confirm - [x] I am the legal owner or represent the legal owner of all work submitted (including LLM-generated code, if any) - [x] I consent to my changes being added to this FOSS project - [x] I have confirmed that this does not add new errors or warnings with `utils/clippy.sh` - [ ] This PR used LLMs to generate some or all of the code changes Reviewed-on: https://git.ngram.ca/OpenJam/rc-servers/pulls/133
This commit is contained in:
@@ -28,6 +28,7 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
let server_settings = actix_web::web::Data::new(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::server_config(&config));
|
||||
let server_links = actix_web::web::Data::new(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::url_links(&config));
|
||||
let server_fed = actix_web::web::Data::new(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::federation(&config));
|
||||
|
||||
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
|
||||
|
||||
@@ -77,6 +78,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.app_data(handlebars_ref.clone())
|
||||
.app_data(server_settings.clone())
|
||||
.app_data(server_links.clone())
|
||||
.app_data(server_fed.clone())
|
||||
.app_data(auth_ref.clone())
|
||||
.app_data(importers_ref.clone())
|
||||
.app_data(parsers_ref.clone())
|
||||
@@ -95,6 +97,12 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(web::garage::import::get_new)
|
||||
.service(web::garage::import::post)
|
||||
.service(web::garage::selected::get)
|
||||
.service(web::user_federation::get)
|
||||
.service(web::user_federation::post)
|
||||
.service(web::user_federation::post_remove)
|
||||
.service(web::user_federation::post_add)
|
||||
.service(web::user_federation::post_off)
|
||||
.service(web::user_federation::post_on)
|
||||
.service(api::config::get)
|
||||
})
|
||||
.bind((cli_args.ip, cli_args.port))?
|
||||
|
||||
@@ -15,6 +15,7 @@ struct RenderData {
|
||||
account: AccountData,
|
||||
sanction: SanctionData,
|
||||
social: SocialData,
|
||||
fediverse: FederationData,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -87,6 +88,12 @@ struct SocialData {
|
||||
chats: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct FederationData {
|
||||
enabled: bool,
|
||||
defederated: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, factory: Data<oj_rc_core::factory::Factory>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match super::try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
@@ -111,6 +118,10 @@ pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, au
|
||||
Ok(x) => x,
|
||||
Err(e) => return Ok(fallback_render(e, user.as_ref(), handlebars_ref.as_ref(), &req)),
|
||||
};
|
||||
let fedi_stats = match build_fedi_data(user.as_ref()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Ok(fallback_render(e, user.as_ref(), handlebars_ref.as_ref(), &req)),
|
||||
};
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
@@ -128,6 +139,7 @@ pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, au
|
||||
account: account_stats,
|
||||
sanction: sanction_stats,
|
||||
social: social_stats,
|
||||
fediverse: fedi_stats,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
@@ -193,7 +205,11 @@ fn fallback_render(error: Box<dyn std::error::Error>, user: &dyn oj_rc_core::per
|
||||
clan: None,
|
||||
friends: 0,
|
||||
friends_of: 0,
|
||||
chats: Vec::default()
|
||||
chats: Vec::default(),
|
||||
},
|
||||
fediverse: FederationData {
|
||||
enabled: false,
|
||||
defederated: Vec::default(),
|
||||
}
|
||||
},
|
||||
format!("Dashboard loading failed: {}", error),
|
||||
@@ -304,6 +320,14 @@ async fn build_social_data(user: &dyn oj_rc_core::persist::user::WebUser) -> Res
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_fedi_data(user: &dyn oj_rc_core::persist::user::WebUser) -> Result<FederationData, Box<dyn std::error::Error>> {
|
||||
let info = user.fedi_get().await;
|
||||
Ok(FederationData {
|
||||
enabled: info.enabled,
|
||||
defederated: info.defederated,
|
||||
})
|
||||
}
|
||||
|
||||
#[get("/dashboard")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, factory: Data<oj_rc_core::factory::Factory>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
dashboard_impl(handlebars_ref, auth, factory, server_config, user_opt, req).await
|
||||
|
||||
@@ -10,6 +10,7 @@ struct RenderData {
|
||||
display_name: Option<String>,
|
||||
server: ServerDetails,
|
||||
links: LinkDetails,
|
||||
fediverse: FederationDetails,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -57,10 +58,31 @@ fn links_details(links: &oj_rc_core::persist::config::LinksConfig) -> LinkDetail
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct FederationDetails {
|
||||
enabled: bool,
|
||||
alias: std::collections::HashMap<String, String>,
|
||||
defederated: Vec<String>,
|
||||
}
|
||||
|
||||
fn fedi_details(fedi: &Option<oj_rc_core::persist::config::Federation>) -> FederationDetails {
|
||||
fedi.as_ref().map(|f| FederationDetails {
|
||||
enabled: true,
|
||||
alias: f.aliases.clone(),
|
||||
defederated: f.defederated.clone(),
|
||||
})
|
||||
.unwrap_or_else(|| FederationDetails {
|
||||
enabled: false,
|
||||
alias: Default::default(),
|
||||
defederated: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, server_links: Data<oj_rc_core::persist::config::LinksConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, server_links: Data<oj_rc_core::persist::config::LinksConfig>, server_fedi: Data<Option<oj_rc_core::persist::config::Federation>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
let server_info = server_details(server_config.as_ref());
|
||||
let links_info = links_details(server_links.as_ref());
|
||||
let federation_info = fedi_details(server_fedi.as_ref());
|
||||
if let Some(user) = user_opt {
|
||||
match super::try_auth_user(Some(user), auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
@@ -71,6 +93,7 @@ pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Bo
|
||||
display_name: Some(user.display_name().to_owned()),
|
||||
server: server_info,
|
||||
links: links_info,
|
||||
fediverse: federation_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
@@ -87,6 +110,7 @@ pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Bo
|
||||
display_name: None,
|
||||
server: server_info,
|
||||
links: links_info,
|
||||
fediverse: federation_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod login;
|
||||
pub mod favicon;
|
||||
pub mod index;
|
||||
pub mod garage;
|
||||
pub mod user_federation;
|
||||
|
||||
use serde::Serialize;
|
||||
use actix_web::{web::{Html, Redirect}, Responder};
|
||||
|
||||
143
rc_society/src/web/user_federation.rs
Normal file
143
rc_society/src/web/user_federation.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use actix_web::{get, post, web::{Data, Path, Redirect, Form}, Responder, HttpRequest};
|
||||
use actix_identity::Identity;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::web::{LoginReturn, try_auth_user, render_ok};
|
||||
|
||||
const FORM_NAME: &str = "user_federation";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
display_name: String,
|
||||
public_id: String,
|
||||
enabled: bool,
|
||||
defederated: Vec<String>,
|
||||
}
|
||||
|
||||
async fn list_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let fedi = user.fedi_get().await;
|
||||
let html = render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
enabled: fedi.enabled,
|
||||
defederated: fedi.defederated,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
);
|
||||
Ok(
|
||||
html
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/federation/list")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
list_impl(handlebars_ref, auth, user_opt, req).await
|
||||
}
|
||||
|
||||
#[post("/federation/list")]
|
||||
pub async fn post(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
list_impl(handlebars_ref, auth, user_opt, req).await
|
||||
}
|
||||
|
||||
#[post("/federation/list/remove/{domain}")]
|
||||
pub async fn post_remove(auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest, domain: Path<String>) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let mut fedi = user.fedi_get().await;
|
||||
if let Some((i, _domain_name)) = fedi.defederated.iter().enumerate().find(|(_i, domain_name)| *domain_name == &*domain) {
|
||||
fedi.defederated.remove(i);
|
||||
user.fedi_set(fedi).await;
|
||||
} else {
|
||||
log::warn!("Failed to find domain {} in defederated list for user {}", &*domain, user.public_id());
|
||||
}
|
||||
let resp = Redirect::to("/federation/list")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body();
|
||||
Ok(
|
||||
resp
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct AddForm {
|
||||
domain: String,
|
||||
}
|
||||
|
||||
#[post("/federation/list/add")]
|
||||
pub async fn post_add(auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest, form: Form<AddForm>) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let mut fedi = user.fedi_get().await;
|
||||
let sanitized_domain = form.domain.trim().to_lowercase();
|
||||
if fedi.defederated.contains(&sanitized_domain) {
|
||||
log::warn!("Domain {} already in defederated list for user {}", sanitized_domain, user.public_id());
|
||||
} else {
|
||||
fedi.defederated.push(sanitized_domain);
|
||||
user.fedi_set(fedi).await;
|
||||
}
|
||||
let resp = Redirect::to("/federation/list")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body();
|
||||
Ok(
|
||||
resp
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/federation/off")]
|
||||
pub async fn post_off(auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let mut fedi = user.fedi_get().await;
|
||||
fedi.enabled = false;
|
||||
user.fedi_set(fedi).await;
|
||||
let resp = Redirect::to("/federation/list")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body();
|
||||
Ok(
|
||||
resp
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/federation/on")]
|
||||
pub async fn post_on(auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
LoginReturn::Success(user) => {
|
||||
let mut fedi = user.fedi_get().await;
|
||||
fedi.enabled = true;
|
||||
user.fedi_set(fedi).await;
|
||||
let resp = Redirect::to("/federation/list")
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body();
|
||||
Ok(
|
||||
resp
|
||||
.respond_to(&req)
|
||||
.map_into_boxed_body()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user