~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
86 lines · 3.0 KB Raw History
//! 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),
    }))
}