//! Delegating writes to this server from the pages, so that an owner can //! share a repository for writing there: `dev.knotless.delegateWrites` //! starts it, and `awaitDelegation` waits for the owner to approve it, //! through the same code as `knotless account delegate`, with the session the //! server keeps for the account logged in. //! //! The grant is the gateway's own session, asked for apart from the //! pages' login and approved by the owner at their PDS, as from the //! command line: the pages' session is never handed to the gateway. use super::repos::refused; use super::stars::logged_in; use super::*; use crate::viewer::Viewer; use axum::Extension; use knotless::env::Ctx; use knotless::gateway; use knotless::session::Session; /// The writes the pages delegate with. pub fn routes() -> Router> { Router::new() .route("/xrpc/dev.knotless.delegateWrites", post(delegate_writes)) .route("/xrpc/dev.knotless.awaitDelegation", post(await_delegation)) } /// The account logged in to the pages, to `act`, as a context acting as /// it with its session, and this server's gateway, which it delegates to. async fn delegating( app: &App, viewer: Viewer, headers: &HeaderMap, act: &str, ) -> Result<(Ctx, Session, String)> { let did = logged_in(app, viewer, headers, act)?; let ctx = crate::login::ctx_for(app, &did); let me = knotless::session::me(&ctx).await; let me = me.map_err(AppError::upstream)?; // The pages log in only through a gateway, so there is one. let ours = app.gateway.as_ref().map(|gw| gw.did.clone()); Ok((ctx, me, ours.unwrap_or_default())) } /// `dev.knotless.delegateWrites`: as `knotless account delegate`, but showing the /// page to approve it at rather than opening it. pub async fn delegate_writes( State(app): State>, Extension(viewer): Extension, headers: HeaderMap, ) -> Result { let act = "delegate writes"; let (ctx, me, ours) = delegating(&app, viewer, &headers, act).await?; let started = gateway::start_delegation(&ctx, &me, &ours).await; Ok(answer(started.map_err(refused)?)) } #[derive(Deserialize)] pub struct AwaitInput { state: String, } /// `dev.knotless.awaitDelegation`'s answer. #[derive(Serialize)] pub struct Awaited { done: bool, #[serde(skip_serializing_if = "Option::is_none")] gateway: Option, } /// `dev.knotless.awaitDelegation`: waits a while for the delegation /// `state` names to be approved, as `knotless account delegate` does. pub async fn await_delegation( State(app): State>, Extension(viewer): Extension, headers: HeaderMap, axum::Json(input): axum::Json, ) -> Result { let act = "delegate writes"; let (ctx, me, ours) = delegating(&app, viewer, &headers, act).await?; let done = gateway::delegation_done(&ctx, &me, &ours, &input.state).await; let done = done.map_err(refused)?; Ok(answer(Awaited { done, gateway: done.then_some(ours), })) }