This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-xrpc / src / members.rs
4.0 kB 130 lines
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::{Grant, MembersChange, MembersCob, Removal}; 12use knot_events::KnotMemberUpdate; 13use knot_git::Repo; 14use knot_index::Resolved; 15use knot_runtime::{Clock, HttpTransport}; 16use knot_types::AccountDid; 17 18use crate::cob::grant_set_apply; 19use crate::error::XrpcError; 20use crate::{XrpcState, decode, ok_empty, run_blocking}; 21 22pub(crate) const ADD_ROUTE: &str = "/xrpc/sh.tangled.knot.addMember"; 23pub(crate) const REMOVE_ROUTE: &str = "/xrpc/sh.tangled.knot.removeMember"; 24 25#[derive(Deserialize)] 26struct SubjectInput { 27 subject: AccountDid, 28} 29 30pub(crate) async fn add_member<H: HttpTransport, C: Clock>( 31 State(state): State<Arc<XrpcState<H, C>>>, 32 headers: HeaderMap, 33 method: crate::Method, 34 body: Bytes, 35) -> Result<Response, XrpcError> { 36 let actor = state.authenticate(&headers, &method).await?; 37 let acl = KnotAcl::new(&state.admins, state.admission, &state.index); 38 if !can_admin_knot(&acl, &actor).is_allowed() { 39 return Err(XrpcError::forbidden("only knot admin may add members")); 40 } 41 42 let SubjectInput { subject } = decode(&body)?; 43 if state.admins.contains(&subject) 44 || matches!(state.index.is_member(&subject), Resolved::Ready(true)) 45 { 46 return Ok(ok_empty()); 47 } 48 49 let now = state.now(); 50 let event_subject = subject.clone(); 51 let grant = Grant { 52 subject, 53 added_by: actor, 54 created_at: now, 55 }; 56 let signer = state.secrets.signer(&state.knot_did)?; 57 let meta_path = state.meta_path.clone(); 58 let index = Arc::clone(&state.index); 59 let cob_locks = Arc::clone(&state.cob_locks); 60 let events = Arc::clone(&state.events); 61 let home = CobHome::from(&state.knot_did); 62 run_blocking(move || { 63 let _guard = cob_locks.meta(); 64 let meta = Repo::open(&meta_path)?; 65 let changed = grant_set_apply::<MembersCob>( 66 &CobStore::new(&meta), 67 &home, 68 MembersChange::Add(grant), 69 &signer, 70 now, 71 true, 72 )?; 73 index.refresh_members()?; 74 if changed { 75 events.publish(&KnotMemberUpdate::added(event_subject)); 76 } 77 Ok(()) 78 }) 79 .await?; 80 81 Ok(ok_empty()) 82} 83 84pub(crate) async fn remove_member<H: HttpTransport, C: Clock>( 85 State(state): State<Arc<XrpcState<H, C>>>, 86 headers: HeaderMap, 87 method: crate::Method, 88 body: Bytes, 89) -> Result<Response, XrpcError> { 90 let actor = state.authenticate(&headers, &method).await?; 91 let acl = KnotAcl::new(&state.admins, state.admission, &state.index); 92 if !can_admin_knot(&acl, &actor).is_allowed() { 93 return Err(XrpcError::forbidden("only knot admin may remove members")); 94 } 95 96 let SubjectInput { subject } = decode(&body)?; 97 if matches!(state.index.is_member(&subject), Resolved::Ready(false)) { 98 return Ok(ok_empty()); 99 } 100 101 let now = state.now(); 102 let event_subject = subject.clone(); 103 let removal = Removal { subject }; 104 let signer = state.secrets.signer(&state.knot_did)?; 105 let meta_path = state.meta_path.clone(); 106 let index = Arc::clone(&state.index); 107 let cob_locks = Arc::clone(&state.cob_locks); 108 let events = Arc::clone(&state.events); 109 let home = CobHome::from(&state.knot_did); 110 run_blocking(move || { 111 let _guard = cob_locks.meta(); 112 let meta = Repo::open(&meta_path)?; 113 let changed = grant_set_apply::<MembersCob>( 114 &CobStore::new(&meta), 115 &home, 116 MembersChange::Remove(removal), 117 &signer, 118 now, 119 false, 120 )?; 121 index.refresh_members()?; 122 if changed { 123 events.publish(&KnotMemberUpdate::removed(event_subject)); 124 } 125 Ok(()) 126 }) 127 .await?; 128 129 Ok(ok_empty()) 130}