rusty_vault 0.2.1

RustyVault is a powerful identity-based secrets management software, providing features such as cryptographic key management, encryption as a service, public key cryptography, certificates management, identity credentials management and so forth. RustyVault's RESTful API is designed to be fully compatible with Hashicorp Vault.
Documentation
use std::{
    collections::HashMap,
    sync::{Arc, RwLock},
    time::Duration,
};

use actix_web::{
    cookie::{time::OffsetDateTime, Cookie},
    http::{Method, StatusCode},
    web, HttpRequest, HttpResponse,
};
use humantime::parse_duration;
use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::AUTH_COOKIE_NAME;
use crate::{
    core::Core,
    errors::RvError,
    http::{request_auth, response_error, response_json_ok, response_ok, Connection},
    logical::{Connection as ReqConnection, Operation, Response},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Auth {
    client_token: String,
    policies: Vec<String>,
    metadata: HashMap<String, String>,
    lease_duration: u64,
    renewable: bool,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
struct LogicalResponse {
    renewable: bool,
    lease_id: String,
    lease_duration: u64,
    auth: Option<Auth>,
    data: HashMap<String, Value>,
}

async fn logical_request_handler(
    req: HttpRequest,
    mut body: web::Bytes,
    method: Method,
    path: web::Path<String>,
    core: web::Data<Arc<RwLock<Core>>>,
) -> Result<HttpResponse, RvError> {
    let conn = req.conn_data::<Connection>().unwrap();
    log::debug!("logical request, connection info: {:?}, method: {:?}, path: {:?}", conn, method, path);

    let mut req_conn = ReqConnection::default();
    req_conn.peer_addr = conn.peer.to_string();
    if conn.tls.is_some() {
        req_conn.peer_tls_cert = conn.tls.as_ref().unwrap().client_cert_chain.clone();
    }

    let mut r = request_auth(&req);
    r.path = path.into_inner().clone();
    r.connection = Some(req_conn);

    match method {
        Method::GET => {
            r.operation = Operation::Read;
        }
        Method::POST | Method::PUT => {
            r.operation = Operation::Write;
            if body.len() > 0 {
                let payload = serde_json::from_slice(&body)?;
                r.body = Some(payload);
                body.clear();
            }
        }
        Method::DELETE => {
            r.operation = Operation::Delete;
        }
        other => {
            if other.as_str() != "LIST" {
                return Ok(response_error(StatusCode::METHOD_NOT_ALLOWED, ""));
            }
            r.operation = Operation::List;
        }
    }

    match core.read()?.handle_request(&mut r).await? {
        Some(resp) => response_logical(&resp, &r.path),
        None => {
            if matches!(r.operation, Operation::Read | Operation::List) {
                return Ok(response_error(StatusCode::NOT_FOUND, ""));
            }
            Ok(response_ok(None, None))
        }
    }
}

fn response_logical(resp: &Response, path: &str) -> Result<HttpResponse, RvError> {
    let mut logical_resp = LogicalResponse::default();
    let mut cookie: Option<Cookie> = None;
    let mut no_content = true;

    if let Some(ref secret) = &resp.secret {
        logical_resp.lease_id = secret.lease_id.clone();
        logical_resp.renewable = secret.lease.renewable;
        logical_resp.lease_duration = secret.lease.ttl.as_secs();
        no_content = false;
    }

    if let Some(ref auth) = &resp.auth {
        let mut expire_duration = parse_duration("365d")?;
        if logical_resp.lease_duration != 0 {
            expire_duration = Duration::from_secs(logical_resp.lease_duration);
        }

        if !path.starts_with("auth/token/") {
            let expire_time = OffsetDateTime::now_utc() + expire_duration;
            cookie = Some(Cookie::build(AUTH_COOKIE_NAME, &auth.client_token).path("/").expires(expire_time).finish());
        }

        logical_resp.auth = Some(Auth {
            client_token: auth.client_token.clone(),
            policies: auth.policies.clone(),
            metadata: auth.metadata.clone(),
            lease_duration: auth.ttl.as_secs(),
            renewable: auth.renewable(),
        });

        no_content = false;
    }

    if let Some(ref data) = &resp.data {
        logical_resp.data = data.iter().map(|(key, value)| (key.clone(), value.clone())).collect();

        no_content = false;
    }

    if no_content {
        return Ok(response_ok(cookie, None));
    } else {
        return Ok(response_json_ok(cookie, logical_resp));
    }
}

pub fn init_logical_service(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/v1").route("/{path:.*}", web::route().to(logical_request_handler)));
}