This repository has no description
1use std::sync::Arc;
2
3use axum::body::Bytes;
4use axum::extract::State;
5use axum::response::Response;
6use http::HeaderMap;
7use serde::Deserialize;
8
9use knot_acl::{KnotAcl, can_admin_knot};
10use knot_cob::{CobHome, CobStore};
11use knot_cobs::{BlocklistChange, BlocklistCob, Grant, Removal};
12use knot_git::Repo;
13use knot_index::Resolved;
14use knot_runtime::{Clock, HttpTransport};
15use knot_types::AccountDid;
16
17use crate::cob::grant_set_apply;
18use crate::error::XrpcError;
19use crate::{XrpcState, decode, ok_empty, run_blocking};
20
21pub(crate) const BAN_ROUTE: &str = "/xrpc/sh.tangled.knot.ban";
22pub(crate) const UNBAN_ROUTE: &str = "/xrpc/sh.tangled.knot.unban";
23
24#[derive(Deserialize)]
25struct SubjectInput {
26 subject: AccountDid,
27}
28
29pub(crate) async fn ban<H: HttpTransport, C: Clock>(
30 State(state): State<Arc<XrpcState<H, C>>>,
31 headers: HeaderMap,
32 method: crate::Method,
33 body: Bytes,
34) -> Result<Response, XrpcError> {
35 let actor = state.authenticate(&headers, &method).await?;
36 let acl = KnotAcl::new(&state.admins, state.admission, &state.index);
37 if !can_admin_knot(&acl, &actor).is_allowed() {
38 return Err(XrpcError::forbidden("only knot admin may ban accounts"));
39 }
40
41 let SubjectInput { subject } = decode(&body)?;
42 if state.admins.contains(&subject) {
43 return Err(XrpcError::forbidden("admin cannot be banned"));
44 }
45 if matches!(state.index.is_blocked(&subject), Resolved::Ready(true)) {
46 return Ok(ok_empty());
47 }
48
49 let now = state.now();
50 let grant = Grant {
51 subject,
52 added_by: actor,
53 created_at: now,
54 };
55 let signer = state.secrets.signer(&state.knot_did)?;
56 let meta_path = state.meta_path.clone();
57 let index = Arc::clone(&state.index);
58 let cob_locks = Arc::clone(&state.cob_locks);
59 let home = CobHome::from(&state.knot_did);
60 run_blocking(move || {
61 let _guard = cob_locks.meta();
62 let meta = Repo::open(&meta_path)?;
63 grant_set_apply::<BlocklistCob>(
64 &CobStore::new(&meta),
65 &home,
66 BlocklistChange::Add(grant),
67 &signer,
68 now,
69 true,
70 )?;
71 index.refresh_blocklist().map_err(XrpcError::from)
72 })
73 .await?;
74
75 Ok(ok_empty())
76}
77
78pub(crate) async fn unban<H: HttpTransport, C: Clock>(
79 State(state): State<Arc<XrpcState<H, C>>>,
80 headers: HeaderMap,
81 method: crate::Method,
82 body: Bytes,
83) -> Result<Response, XrpcError> {
84 let actor = state.authenticate(&headers, &method).await?;
85 let acl = KnotAcl::new(&state.admins, state.admission, &state.index);
86 if !can_admin_knot(&acl, &actor).is_allowed() {
87 return Err(XrpcError::forbidden("only knot admin may unban accounts"));
88 }
89
90 let SubjectInput { subject } = decode(&body)?;
91 if matches!(state.index.is_blocked(&subject), Resolved::Ready(false)) {
92 return Ok(ok_empty());
93 }
94
95 let now = state.now();
96 let removal = Removal { subject };
97 let signer = state.secrets.signer(&state.knot_did)?;
98 let meta_path = state.meta_path.clone();
99 let index = Arc::clone(&state.index);
100 let cob_locks = Arc::clone(&state.cob_locks);
101 let home = CobHome::from(&state.knot_did);
102 run_blocking(move || {
103 let _guard = cob_locks.meta();
104 let meta = Repo::open(&meta_path)?;
105 grant_set_apply::<BlocklistCob>(
106 &CobStore::new(&meta),
107 &home,
108 BlocklistChange::Remove(removal),
109 &signer,
110 now,
111 false,
112 )?;
113 index.refresh_blocklist().map_err(XrpcError::from)
114 })
115 .await?;
116
117 Ok(ok_empty())
118}