feat: v1.0.9, add init via ssh
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1039,7 +1039,7 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "local-mini-kms"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
dependencies = [
|
||||
"aes-gcm-stream",
|
||||
"aes-kw",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "local-mini-kms"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -38,6 +38,12 @@ Init with Yubikey:
|
||||
local-mini-kms serve [--init-encrypted-master-key LKMS:*** [--yubikey-challenge *challenge*]]
|
||||
```
|
||||
|
||||
## Local Client init via SSH
|
||||
|
||||
```shell
|
||||
local-mini-kms cli --init --ssh-remote root@example.com [--read-from-pinentry]
|
||||
```
|
||||
|
||||
## Local Client
|
||||
|
||||
```shell
|
||||
|
||||
138
src/cli.rs
138
src/cli.rs
@@ -6,7 +6,7 @@ use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use hyper::body::Buf;
|
||||
use hyper::{Body, Client, Method, Request, Response, StatusCode};
|
||||
use rust_util::util_clap::{Command, CommandError};
|
||||
use rust_util::{debugging, opt_result, opt_value_result, simple_error, success, XResult};
|
||||
use rust_util::{debugging, iff, opt_result, opt_value_result, simple_error, success, XResult};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::jose;
|
||||
@@ -34,6 +34,8 @@ impl Command for CommandImpl {
|
||||
.arg(Arg::with_name("yubikey-challenge").long("yubikey-challenge").short("c").takes_value(true).help("Yubikey challenge"))
|
||||
.arg(Arg::with_name("comment").long("comment").takes_value(true).help("Comment"))
|
||||
.arg(Arg::with_name("force-write").long("force-write").short("F").help("Force write value"))
|
||||
.arg(Arg::with_name("read-from-pinentry").long("read-from-pinentry").help("Read from pin-entry"))
|
||||
.arg(Arg::with_name("ssh-remote").long("ssh-remote").takes_value(true).help("SSH remote, root@example or localhost"))
|
||||
}
|
||||
|
||||
fn run(&self, arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||
@@ -104,17 +106,16 @@ async fn do_direct_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatc
|
||||
}
|
||||
|
||||
async fn do_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError {
|
||||
let ssh_remote = sub_arg_matches.value_of("ssh-remote").map(|s| s.to_string());
|
||||
let connect = sub_arg_matches.value_of("connect").expect("Get argument listen error");
|
||||
let read_from_pinentry = sub_arg_matches.is_present("read-from-pinentry");
|
||||
|
||||
let client = Client::new();
|
||||
// let client = Client::new();
|
||||
let uri = format!("http://{}/status", connect);
|
||||
debugging!("Request uri: {}", &uri);
|
||||
let req = Request::builder().method(Method::GET).uri(uri).body(Body::empty())?;
|
||||
let req_response = client.request(req).await?;
|
||||
if req_response.status() != StatusCode::OK {
|
||||
return simple_error!("Server status is not success: {}", req_response.status().as_u16());
|
||||
}
|
||||
let data = response_to_value(req_response).await?;
|
||||
|
||||
let data = send_kms_request_with_ssh_enabled(&ssh_remote, true, &uri, &None).await?;
|
||||
|
||||
debugging!("Get status: {}", &data);
|
||||
let status = &data["status"];
|
||||
if let Some(status) = status.as_str() {
|
||||
@@ -129,25 +130,81 @@ async fn do_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>
|
||||
let instance_public_key_jwk = &data["instance_public_key_jwk"];
|
||||
println!("Instance server public key JWK: {}", instance_public_key_jwk);
|
||||
|
||||
let line = read_line("Input encrypted master key: ")?;
|
||||
let line = {
|
||||
let line = read_line("Input clear(starts with hex: or base64:) or encrypted master key: ", read_from_pinentry)?;
|
||||
if line.starts_with("hex:") || line.starts_with("base64:") {
|
||||
let jwk = opt_result!(serde_json::to_string(&instance_public_key_jwk), "Serialize instance server public key JWK: {} failed");
|
||||
master_key_encrypt(&line, &jwk)?
|
||||
} else {
|
||||
line
|
||||
}
|
||||
};
|
||||
let uri = format!("http://{}/init", connect);
|
||||
debugging!("Request uri: {}", &uri);
|
||||
let body = json!({
|
||||
"encrypted_master_key": line,
|
||||
});
|
||||
let body = serde_json::to_string(&body)?;
|
||||
let req = Request::builder().method(Method::POST).uri(uri).body(Body::from(body))?;
|
||||
|
||||
let req_response = client.request(req).await?;
|
||||
if req_response.status() != StatusCode::OK {
|
||||
let status = req_response.status().as_u16();
|
||||
let data = response_to_value(req_response).await?;
|
||||
return simple_error!("Server status is not success: {}, response: {}", status, data);
|
||||
}
|
||||
let _ = send_kms_request_with_ssh_enabled(&ssh_remote, false, &uri, &Some(body)).await?;
|
||||
|
||||
success!("Init finished");
|
||||
Ok(Some(0))
|
||||
}
|
||||
|
||||
async fn send_kms_request_with_ssh_enabled(ssh_remote: &Option<String>, get_request: bool, uri: &str, body: &Option<String>) -> XResult<Value> {
|
||||
match ssh_remote {
|
||||
None => {
|
||||
let client = Client::new();
|
||||
let method = iff!(get_request, Method::GET, Method::POST);
|
||||
let request_body = match body {
|
||||
None => Body::empty(),
|
||||
Some(body) => Body::from(body.clone()),
|
||||
};
|
||||
let req = Request::builder().method(method).uri(uri).body(request_body)?;
|
||||
let req_response = client.request(req).await?;
|
||||
if req_response.status() != StatusCode::OK {
|
||||
return simple_error!("Server status is not success: {}", req_response.status().as_u16());
|
||||
}
|
||||
let data = response_to_value(req_response).await?;
|
||||
Ok(data)
|
||||
}
|
||||
Some(ssh_remote) => {
|
||||
let mut c;
|
||||
if ssh_remote == "localhost" {
|
||||
c = std::process::Command::new("curl");
|
||||
} else {
|
||||
c = std::process::Command::new("ssh");
|
||||
c.args([ssh_remote, "curl"]);
|
||||
}
|
||||
c.arg("-s");
|
||||
if !get_request {
|
||||
c.args(["-X", "POST"]);
|
||||
}
|
||||
if let Some(body) = body {
|
||||
c.args(["-H", "x-body-based64-encoded:1"]);
|
||||
c.args(["--data-raw", &format!("{}", STANDARD.encode(&body))]);
|
||||
}
|
||||
c.arg(uri);
|
||||
debugging!("Run command: {:?}", c);
|
||||
let output = opt_result!(c.output(), "Call: {:?} failed: {}", c);
|
||||
if !output.status.success() {
|
||||
return simple_error!("Call: {:?} exit with error", output);
|
||||
}
|
||||
debugging!("Output: {:?}", output);
|
||||
let data: Value = serde_json::from_slice(&output.stdout)?;
|
||||
if let Value::Object(data_map) = &data {
|
||||
if let Some(error) = data_map.get("error") {
|
||||
if let Value::String(error) = error {
|
||||
return simple_error!("Get error: {}, details: {}", error, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_read(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError {
|
||||
let body = if let Some(name) = sub_arg_matches.value_of("name") {
|
||||
json!({ "name": name })
|
||||
@@ -214,15 +271,26 @@ async fn do_decrypt(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<
|
||||
Ok(Some(0))
|
||||
}
|
||||
|
||||
fn do_offline_init(_arg_matches: &ArgMatches<'_>, _sub_arg_matches: &ArgMatches<'_>) -> CommandError {
|
||||
let line = read_line("Input master key: ")?;
|
||||
let master_key = if line.starts_with("hex:") {
|
||||
let hex: String = line.chars().skip(4).collect();
|
||||
fn do_offline_init(_arg_matches: &ArgMatches<'_>, sub_arg_matches: &ArgMatches<'_>) -> CommandError {
|
||||
let read_from_pinentry = sub_arg_matches.is_present("read-from-pinentry");
|
||||
|
||||
let line = read_line("Input master key: ", read_from_pinentry)?;
|
||||
let jwk = read_line("Input JWK: ", read_from_pinentry)?;
|
||||
|
||||
let encrypted_master_key = master_key_encrypt(&line, &jwk)?;
|
||||
|
||||
success!("Encrypted master key: {}", encrypted_master_key);
|
||||
Ok(Some(0))
|
||||
}
|
||||
|
||||
fn master_key_encrypt(master_key: &str, jwk: &str) -> XResult<String> {
|
||||
let master_key = if master_key.starts_with("hex:") {
|
||||
let hex: String = master_key.chars().skip(4).collect();
|
||||
hex::decode(&hex)?
|
||||
} else if line.starts_with("base64:") {
|
||||
let base64: String = line.chars().skip(7).collect();
|
||||
} else if master_key.starts_with("base64:") {
|
||||
let base64: String = master_key.chars().skip(7).collect();
|
||||
STANDARD.decode(&base64)?
|
||||
} else if line.starts_with("LKMS:") {
|
||||
} else if master_key.starts_with("LKMS:") {
|
||||
#[cfg(feature = "yubikey")]
|
||||
{
|
||||
use crate::yubikey_hmac;
|
||||
@@ -230,23 +298,28 @@ fn do_offline_init(_arg_matches: &ArgMatches<'_>, _sub_arg_matches: &ArgMatches<
|
||||
let challenge = opt_result!(
|
||||
pinentry_util::read_pin(Some("Input yubikey challenge"), Some("Challenge: ")), "Read challenge failed: {}");
|
||||
let derived_key = yubikey_hmac::yubikey_challenge_as_32_bytes(challenge.get_pin().as_bytes())?;
|
||||
let (key, _) = jose::deserialize_jwe_aes(&line, &derived_key)?;
|
||||
let (key, _) = jose::deserialize_jwe_aes(&master_key, &derived_key)?;
|
||||
key
|
||||
}
|
||||
#[cfg(not(feature = "yubikey"))]
|
||||
return simple_error!("Yubikey feature is not enabled.");
|
||||
} else {
|
||||
line.as_bytes().to_vec()
|
||||
master_key.as_bytes().to_vec()
|
||||
};
|
||||
let jwk = read_line("Input JWK: ")?;
|
||||
let rsa_public_key = jwk_to_rsa_pubic_key(&jwk)?;
|
||||
let encrypted_master_key = jose::serialize_jwe_rsa(&master_key, &rsa_public_key)?;
|
||||
|
||||
success!("Encrypted master key: {}", encrypted_master_key);
|
||||
Ok(Some(0))
|
||||
Ok(encrypted_master_key)
|
||||
}
|
||||
|
||||
fn read_line(prompt: &str) -> XResult<String> {
|
||||
fn read_line(prompt: &str, pinentry: bool) -> XResult<String> {
|
||||
if pinentry {
|
||||
read_line_from_pinentry_util(prompt)
|
||||
} else {
|
||||
read_line_from_terminal(prompt)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_line_from_terminal(prompt: &str) -> XResult<String> {
|
||||
std::io::stdout().write_all(prompt.as_bytes()).ok();
|
||||
std::io::stdout().flush().ok();
|
||||
let mut line = String::new();
|
||||
@@ -256,6 +329,11 @@ fn read_line(prompt: &str) -> XResult<String> {
|
||||
Ok(line.trim().to_string())
|
||||
}
|
||||
|
||||
fn read_line_from_pinentry_util(prompt: &str) -> XResult<String> {
|
||||
let pin = opt_result!(pinentry_util::read_pin(Some(prompt.to_string()), Some("PIN:".to_string())), "Read from pin-entry failed: {}");
|
||||
Ok(pin.get_pin().to_string())
|
||||
}
|
||||
|
||||
async fn response_to_value(response: Response<Body>) -> XResult<Value> {
|
||||
let req_body = response.into_body();
|
||||
let whole_body = hyper::body::aggregate(req_body).await?;
|
||||
|
||||
@@ -140,8 +140,9 @@ async fn response_requests(
|
||||
let process = proc::get_process(remote_addr.port());
|
||||
match process {
|
||||
None => log::info!(
|
||||
"[{:06}] Receive request: {}, from: {}",
|
||||
"[{:06}] Receive request: {} {}, from: {}",
|
||||
request_idx,
|
||||
req.method(),
|
||||
req.uri(),
|
||||
remote_addr
|
||||
),
|
||||
|
||||
@@ -1,20 +1,41 @@
|
||||
use std::io::{Cursor, Read};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
use hyper::StatusCode;
|
||||
use hyper::body::Buf;
|
||||
use hyper::{Body, Request, StatusCode};
|
||||
use rsa::RsaPrivateKey;
|
||||
use rusqlite::Connection;
|
||||
use rust_util::{opt_result, simple_error, XResult};
|
||||
use seckey::SecBytes;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{de, Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use crate::db;
|
||||
|
||||
pub type GenericError = Box<dyn std::error::Error + Send + Sync>;
|
||||
pub type Result<T> = std::result::Result<T, GenericError>;
|
||||
|
||||
pub async fn parse_request<T>(req: Request<Body>) -> XResult<T>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
let based64_encoded = req.headers().get("x-body-based64-encoded").is_some();
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let mut body = Vec::<u8>::new();
|
||||
whole_body.reader().read_to_end(&mut body)?;
|
||||
if based64_encoded {
|
||||
let mut based64_decoded = opt_result!(STANDARD.decode(&body), "Decode request body base64 failed: {}");
|
||||
body.clear();
|
||||
body.extend_from_slice(&based64_decoded);
|
||||
based64_decoded.zeroize();
|
||||
}
|
||||
let req_object = serde_json::from_reader(Cursor::new(&body))?;
|
||||
body.zeroize();
|
||||
Ok(req_object)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! do_response {
|
||||
($ex: expr) => (
|
||||
@@ -35,17 +56,11 @@ pub fn ok(body: Value) -> XResult<(StatusCode, Value)> {
|
||||
}
|
||||
|
||||
pub fn client_error(error: &str) -> XResult<(StatusCode, Value)> {
|
||||
Ok((
|
||||
StatusCode::BAD_REQUEST,
|
||||
json!({ "error": error })
|
||||
))
|
||||
Ok((StatusCode::BAD_REQUEST, json!({ "error": error })))
|
||||
}
|
||||
|
||||
pub fn server_error(error: &str) -> XResult<(StatusCode, Value)> {
|
||||
Ok((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
json!({ "error": error })
|
||||
))
|
||||
Ok((StatusCode::INTERNAL_SERVER_ERROR, json!({ "error": error })))
|
||||
}
|
||||
|
||||
// pub fn bad_request(error: &str, error_message: &str) -> XResult<(StatusCode, Value)> {
|
||||
@@ -104,7 +119,8 @@ impl MultipleViewValue {
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! require_master_key {
|
||||
#[macro_export]
|
||||
macro_rules! require_master_key {
|
||||
() => {
|
||||
match $crate::serve_common::get_master_key() {
|
||||
None => return $crate::serve_common::client_error("status_not_ready"),
|
||||
@@ -114,7 +130,9 @@ impl MultipleViewValue {
|
||||
}
|
||||
|
||||
pub fn get_master_key() -> Option<SecBytes> {
|
||||
let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error");
|
||||
let startup_rw_lock = STATUP_RW_LOCK
|
||||
.lock()
|
||||
.expect("Lock read startup rw lock error");
|
||||
match &*startup_rw_lock {
|
||||
None => None,
|
||||
Some(k) => match &k.master_key {
|
||||
@@ -147,11 +165,14 @@ pub fn byte_to_multi_view_map(bytes: &[u8], with_value: bool) -> Map<String, Val
|
||||
}
|
||||
|
||||
pub fn open_local_db() -> XResult<Connection> {
|
||||
let startup_rw_lock = STATUP_RW_LOCK.lock().expect("Lock read startup rw lock error");
|
||||
let startup_rw_lock = STATUP_RW_LOCK
|
||||
.lock()
|
||||
.expect("Lock read startup rw lock error");
|
||||
match &*startup_rw_lock {
|
||||
None => simple_error!("Db is not initiated!"),
|
||||
Some(k) => {
|
||||
Ok(opt_result!(db::open_db(& k.database_file), "Open db failed: {}"))
|
||||
}
|
||||
Some(k) => Ok(opt_result!(
|
||||
db::open_db(&k.database_file),
|
||||
"Open db failed: {}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::db::Key;
|
||||
use crate::serve_common::{open_local_db, Result};
|
||||
use crate::serve_common::{open_local_db, parse_request, Result};
|
||||
use crate::{db, do_response, jose, require_master_key, serve_common};
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
use hyper::body::Buf;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use rand::random;
|
||||
use rust_util::{iff, XResult};
|
||||
@@ -30,8 +29,7 @@ pub async fn generate(req: Request<Body>) -> Result<Response<Body>> {
|
||||
}
|
||||
|
||||
async fn inner_generate(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let request: DataKeyRequest = serde_json::from_reader(whole_body.reader())?;
|
||||
let request: DataKeyRequest = parse_request(req).await?;
|
||||
|
||||
log::debug!("Generate data key: {} {}", &request.r#type, &request.spec);
|
||||
let key = require_master_key!();
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use hyper::body::Buf;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use rust_util::XResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
use crate::jose;
|
||||
use crate::serve_common::{self, byte_to_multi_view_map, MultipleViewValue, Result};
|
||||
use crate::serve_common::{self, byte_to_multi_view_map, parse_request, MultipleViewValue, Result};
|
||||
use crate::{do_response, require_master_key};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -18,8 +17,7 @@ pub async fn decrypt(req: Request<Body>) -> Result<Response<Body>> {
|
||||
}
|
||||
|
||||
async fn inner_decrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let data: DecryptRequest = serde_json::from_reader(whole_body.reader())?;
|
||||
let data: DecryptRequest = parse_request(req).await?;
|
||||
|
||||
log::trace!("To be decrypted value: {}", &data.encrypted_value);
|
||||
let key = require_master_key!();
|
||||
@@ -52,8 +50,7 @@ pub async fn encrypt(req: Request<Body>) -> Result<Response<Body>> {
|
||||
}
|
||||
|
||||
async fn inner_encrypt(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let data: MultipleViewValue = serde_json::from_reader(whole_body.reader())?;
|
||||
let data: MultipleViewValue = parse_request(req).await?;
|
||||
let value = data.to_bytes()?;
|
||||
let key = require_master_key!();
|
||||
let encrypt_result = jose::serialize_jwe_aes(&value, &key.read());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
use hyper::body::Buf;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use rust_util::{opt_result, XResult};
|
||||
use seckey::SecBytes;
|
||||
@@ -10,7 +9,7 @@ use zeroize::Zeroize;
|
||||
|
||||
use crate::db::Key;
|
||||
use crate::do_response;
|
||||
use crate::serve_common::{self, Result};
|
||||
use crate::serve_common::{self, parse_request, Result};
|
||||
#[cfg(feature = "yubikey")]
|
||||
use crate::yubikey_hmac;
|
||||
use crate::{db, jose};
|
||||
@@ -28,9 +27,7 @@ pub struct InitRequest {
|
||||
}
|
||||
|
||||
async fn inner_init(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let init_request: InitRequest = serde_json::from_reader(whole_body.reader())?;
|
||||
|
||||
let init_request: InitRequest = parse_request(req).await?;
|
||||
inner_init_request(init_request).await
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::db::Key;
|
||||
use crate::serve_common::{self, byte_to_multi_view_map, open_local_db, MultipleViewValue, Result};
|
||||
use crate::serve_common::{self, byte_to_multi_view_map, open_local_db, parse_request, MultipleViewValue, Result};
|
||||
use crate::{db, jose};
|
||||
use crate::{do_response, require_master_key};
|
||||
use hyper::body::Buf;
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use rust_util::XResult;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -33,8 +32,7 @@ pub async fn list(req: Request<Body>) -> Result<Response<Body>> {
|
||||
}
|
||||
|
||||
async fn inner_list(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let keys_query: KeysQuery = serde_json::from_reader(whole_body.reader())?;
|
||||
let keys_query: KeysQuery = parse_request(req).await?;
|
||||
|
||||
let conn = open_local_db()?;
|
||||
let keys = db::list_keys(
|
||||
@@ -61,8 +59,7 @@ pub async fn read(req: Request<Body>) -> Result<Response<Body>> {
|
||||
}
|
||||
|
||||
async fn inner_read(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let named: Named = serde_json::from_reader(whole_body.reader())?;
|
||||
let named: Named = parse_request(req).await?;
|
||||
let name = &named.name;
|
||||
if name.is_empty() {
|
||||
return serve_common::client_error("name_is_empty");
|
||||
@@ -92,8 +89,7 @@ pub async fn write(req: Request<Body>) -> Result<Response<Body>> {
|
||||
}
|
||||
|
||||
async fn inner_write(req: Request<Body>) -> XResult<(StatusCode, Value)> {
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
let named_value: NamedValue = serde_json::from_reader(whole_body.reader())?;
|
||||
let named_value: NamedValue = parse_request(req).await?;
|
||||
let name = &named_value.name;
|
||||
if name.is_empty() {
|
||||
return serve_common::client_error("name_is_empty");
|
||||
|
||||
Reference in New Issue
Block a user