johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
//! 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<Arc<App>> {
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<Arc<App>>,
Extension(viewer): Extension<Viewer>,
headers: HeaderMap,
) -> Result<Response> {
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<String>,
}
/// `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<Arc<App>>,
Extension(viewer): Extension<Viewer>,
headers: HeaderMap,
axum::Json(input): axum::Json<AwaitInput>,
) -> Result<Response> {
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),
}))
}