Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Move `rad-web` binary to `radicle-httpd` package
Thomas Scholtes committed 2 years ago
commit cd071f031cf37c9edbbc9bdb4cdf16954b3f857d
parent d707f0978e55e8f523cfae90d7c215dbd78df933
6 files changed +142 -2
modified radicle-httpd/Cargo.toml
@@ -37,6 +37,7 @@ tower-http = { version = "0.3.4", default-features = false, features = ["trace",
tracing = { version = "0.1.37", default-features = false, features = ["std", "log"] }
tracing-logfmt = { version = "0.2", optional = true }
tracing-subscriber = { version = "0.3", default-features = false, features = ["std", "ansi", "fmt"] }
+
ureq = { version = "2.9", default-features = false, features = ["json"] }

[dependencies.radicle]
path = "../radicle"
@@ -45,6 +46,9 @@ version = "0.2.0"
[dependencies.radicle-term]
path = "../radicle-term"

+
[dependencies.radicle-cli]
+
path = "../radicle-cli"
+

[dev-dependencies]
hyper = { version = "0.14.17", default-features = false, features = ["client"] }
pretty_assertions = { version = "1.3.0" }
modified radicle-httpd/src/api/v1/sessions.rs
@@ -122,9 +122,9 @@ async fn session_delete_handler(

#[cfg(test)]
mod routes {
+
    use crate::commands::web::{sign, SessionInfo};
    use axum::body::Body;
    use axum::http::StatusCode;
-
    use radicle_cli::commands::rad_web::{self, SessionInfo};

    use crate::api::auth::{AuthState, Session};
    use crate::test::{self, get, post, put};
@@ -154,7 +154,7 @@ mod routes {

        // Create request body
        let signer = ctx.profile.signer().unwrap();
-
        let signature = rad_web::sign(signer, &session_info).unwrap();
+
        let signature = sign(signer, &session_info).unwrap();
        let body = serde_json::to_vec(&super::AuthChallenge {
            sig: signature,
            pk: session_info.public_key,
added radicle-httpd/src/bin/rad-web.rs
@@ -0,0 +1,10 @@
+
use radicle_cli::terminal as term;
+
use radicle_httpd::commands::web as rad_web;
+

+
fn main() {
+
    term::run_command_args::<rad_web::Options, _>(
+
        rad_web::HELP,
+
        rad_web::run,
+
        std::env::args_os().skip(1).collect(),
+
    )
+
}
added radicle-httpd/src/commands.rs
@@ -0,0 +1,2 @@
+
//! Extra CLI commands relating to HTTPd.
+
pub mod web;
added radicle-httpd/src/commands/web.rs
@@ -0,0 +1,123 @@
+
use std::ffi::OsString;
+

+
use anyhow::anyhow;
+
use serde::{Deserialize, Serialize};
+

+
use radicle::crypto::{PublicKey, Signature, Signer};
+

+
use radicle_cli::terminal as term;
+
use radicle_cli::terminal::args::{Args, Error, Help};
+

+
pub const HELP: Help = Help {
+
    name: "web",
+
    description: "Connect web with node",
+
    version: env!("CARGO_PKG_VERSION"),
+
    usage: r#"
+
Usage
+

+
    rad web [<option>...]
+

+
Options
+

+
    --backend, -b          httpd to bind to
+
    --frontend, -f         Web interface to bind to
+
    --verbose, -v          Verbose output
+
    --json                 Output as json
+
    --help                 Print help
+
"#,
+
};
+

+
#[derive(Debug, Clone, Deserialize, Serialize)]
+
#[serde(rename_all = "camelCase")]
+
pub struct SessionInfo {
+
    pub session_id: String,
+
    pub public_key: PublicKey,
+
}
+

+
#[derive(Debug)]
+
pub struct Options {
+
    pub backend: String,
+
    pub frontend: String,
+
    pub json: bool,
+
    pub verbose: bool,
+
}
+

+
impl Args for Options {
+
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
+
        use lexopt::prelude::*;
+

+
        let mut parser = lexopt::Parser::from_args(args);
+
        let mut backend = None;
+
        let mut frontend = None;
+
        let mut json = false;
+
        let mut verbose = false;
+

+
        while let Some(arg) = parser.next()? {
+
            match arg {
+
                Long("verbose") | Short('v') => verbose = true,
+
                Long("backend") | Short('b') => {
+
                    backend = Some(parser.value()?.to_string_lossy().to_string())
+
                }
+
                Long("frontend") | Short('f') => {
+
                    frontend = Some(parser.value()?.to_string_lossy().to_string())
+
                }
+
                Long("json") => json = true,
+
                Long("help") | Short('h') => {
+
                    return Err(Error::Help.into());
+
                }
+
                _ => {
+
                    return Err(anyhow!(arg.unexpected()));
+
                }
+
            }
+
        }
+

+
        Ok((
+
            Options {
+
                json,
+
                verbose,
+
                backend: backend.unwrap_or(String::from("http://0.0.0.0:8080")),
+
                frontend: frontend.unwrap_or(String::from("https://app.radicle.xyz")),
+
            },
+
            vec![],
+
        ))
+
    }
+
}
+

+
pub fn sign(signer: Box<dyn Signer>, session: &SessionInfo) -> Result<Signature, anyhow::Error> {
+
    signer
+
        .try_sign(format!("{}:{}", session.session_id, session.public_key).as_bytes())
+
        .map_err(anyhow::Error::from)
+
}
+

+
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
    let session: SessionInfo = ureq::post(&format!("{}/api/v1/sessions", options.backend))
+
        .call()?
+
        .into_json()?;
+
    let profile = ctx.profile()?;
+
    let signer = profile.signer()?;
+
    let signature = sign(signer, &session)?;
+

+
    if options.json {
+
        println!(
+
            "{}",
+
            serde_json::json!({
+
                "sessionId": session.session_id,
+
                "publicKey": session.public_key,
+
                "signature": signature,
+
            })
+
        )
+
    } else {
+
        term::blank();
+
        term::info!("Open the following link to authenticate:");
+
        term::info!(
+
            "  👉 {}/session/{}?pk={}&sig={}",
+
            options.frontend,
+
            session.session_id,
+
            session.public_key,
+
            signature,
+
        );
+
        term::blank();
+
    }
+

+
    Ok(())
+
}
modified radicle-httpd/src/lib.rs
@@ -1,6 +1,7 @@
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![recursion_limit = "256"]
+
pub mod commands;
pub mod error;

use std::collections::HashMap;