Implement web authentication session validation
This commit is contained in:
parent
d761a6df47
commit
3498e57e04
11 changed files with 491 additions and 109 deletions
77
Cargo.lock
generated
77
Cargo.lock
generated
|
@ -309,7 +309,7 @@ dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
"time",
|
"time 0.1.44",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1126,6 +1126,20 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "8.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc9051c17f81bae79440afa041b3a278e1de71bfb96d32454b477fd4703ccb6f"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -1389,6 +1403,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
|
@ -1528,6 +1551,15 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -1769,6 +1801,15 @@ version = "1.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quickcheck"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||||
|
dependencies = [
|
||||||
|
"rand 0.8.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
@ -2210,6 +2251,18 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
|
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a762b1c38b9b990c694b9c2f8abe3372ce6a9ceaae6bca39cfc46e054f45745"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
|
"time 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sized-chunks"
|
name = "sized-chunks"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
|
@ -2290,6 +2343,8 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
name = "surreal"
|
name = "surreal"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -2297,6 +2352,7 @@ dependencies = [
|
||||||
"futures 0.3.21",
|
"futures 0.3.21",
|
||||||
"http",
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"jsonwebtoken",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
@ -2527,6 +2583,25 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
|
"quickcheck",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
|
|
@ -16,6 +16,8 @@ panic = 'abort'
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
argon2 = "0.4.0"
|
||||||
|
base64 = "0.13.0"
|
||||||
bytes = "1.1.0"
|
bytes = "1.1.0"
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { version = "0.4.19", features = ["serde"] }
|
||||||
clap = "3.1.17"
|
clap = "3.1.17"
|
||||||
|
@ -23,6 +25,7 @@ fern = { version = "0.6.1", features = ["colored"] }
|
||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
http = "0.2.7"
|
http = "0.2.7"
|
||||||
hyper = "0.14.18"
|
hyper = "0.14.18"
|
||||||
|
jsonwebtoken = "8.1.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
once_cell = "1.10.0"
|
once_cell = "1.10.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::fmt;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Algorithm {
|
pub enum Algorithm {
|
||||||
|
EdDSA,
|
||||||
Es256,
|
Es256,
|
||||||
Es384,
|
Es384,
|
||||||
Es512,
|
Es512,
|
||||||
|
@ -30,6 +31,7 @@ impl Default for Algorithm {
|
||||||
impl fmt::Display for Algorithm {
|
impl fmt::Display for Algorithm {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
Algorithm::EdDSA => write!(f, "EDDSA"),
|
||||||
Algorithm::Es256 => write!(f, "ES256"),
|
Algorithm::Es256 => write!(f, "ES256"),
|
||||||
Algorithm::Es384 => write!(f, "ES384"),
|
Algorithm::Es384 => write!(f, "ES384"),
|
||||||
Algorithm::Es512 => write!(f, "ES512"),
|
Algorithm::Es512 => write!(f, "ES512"),
|
||||||
|
@ -48,6 +50,7 @@ impl fmt::Display for Algorithm {
|
||||||
|
|
||||||
pub fn algorithm(i: &str) -> IResult<&str, Algorithm> {
|
pub fn algorithm(i: &str) -> IResult<&str, Algorithm> {
|
||||||
alt((
|
alt((
|
||||||
|
map(tag("EDDSA"), |_| Algorithm::EdDSA),
|
||||||
map(tag("ES256"), |_| Algorithm::Es256),
|
map(tag("ES256"), |_| Algorithm::Es256),
|
||||||
map(tag("ES384"), |_| Algorithm::Es384),
|
map(tag("ES384"), |_| Algorithm::Es384),
|
||||||
map(tag("ES512"), |_| Algorithm::Es512),
|
map(tag("ES512"), |_| Algorithm::Es512),
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use http::Error as HttpError;
|
use base64::DecodeError as Base64Error;
|
||||||
use hyper::Error as HyperError;
|
use jsonwebtoken::errors::Error as JWTError;
|
||||||
use reqwest::Error as ReqwestError;
|
use reqwest::Error as ReqwestError;
|
||||||
use serde_cbor::error::Error as CborError;
|
use serde_cbor::error::Error as CborError;
|
||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
use serde_pack::encode::Error as PackError;
|
use serde_pack::encode::Error as PackError;
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
|
use std::string::FromUtf8Error as Utf8Error;
|
||||||
use surrealdb::Error as DbError;
|
use surrealdb::Error as DbError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -13,29 +14,44 @@ pub enum Error {
|
||||||
#[error("The request body contains invalid data")]
|
#[error("The request body contains invalid data")]
|
||||||
Request,
|
Request,
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("There was a problem with authentication")]
|
||||||
|
InvalidAuth,
|
||||||
|
|
||||||
|
#[error("There was a problem with the database: {0}")]
|
||||||
Db(#[from] DbError),
|
Db(#[from] DbError),
|
||||||
|
|
||||||
#[error("IO error: {0}")]
|
#[error("Couldn't open the specified file: {0}")]
|
||||||
Io(#[from] IoError),
|
Io(#[from] IoError),
|
||||||
|
|
||||||
#[error("HTTP Error: {0}")]
|
#[error("There was an error serializing to JSON: {0}")]
|
||||||
Hyper(#[from] HyperError),
|
|
||||||
|
|
||||||
#[error("HTTP Error: {0}")]
|
|
||||||
Http(#[from] HttpError),
|
|
||||||
|
|
||||||
#[error("JSON Error: {0}")]
|
|
||||||
Json(#[from] JsonError),
|
Json(#[from] JsonError),
|
||||||
|
|
||||||
#[error("CBOR Error: {0}")]
|
#[error("There was an error serializing to CBOR: {0}")]
|
||||||
Cbor(#[from] CborError),
|
Cbor(#[from] CborError),
|
||||||
|
|
||||||
#[error("PACK Error: {0}")]
|
#[error("There was an error serializing to MessagePack: {0}")]
|
||||||
Pack(#[from] PackError),
|
Pack(#[from] PackError),
|
||||||
|
|
||||||
#[error("Reqwest Error: {0}")]
|
#[error("There was an error with the remote request: {0}")]
|
||||||
Reqwest(#[from] ReqwestError),
|
Remote(#[from] ReqwestError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl warp::reject::Reject for Error {}
|
impl warp::reject::Reject for Error {}
|
||||||
|
|
||||||
|
impl From<Base64Error> for Error {
|
||||||
|
fn from(_: Base64Error) -> Error {
|
||||||
|
Error::InvalidAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Utf8Error> for Error {
|
||||||
|
fn from(_: Utf8Error) -> Error {
|
||||||
|
Error::InvalidAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JWTError> for Error {
|
||||||
|
fn from(_: JWTError) -> Error {
|
||||||
|
Error::InvalidAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use surrealdb::Auth;
|
|
||||||
use surrealdb::Session;
|
|
||||||
use warp::Filter;
|
|
||||||
|
|
||||||
pub fn build() -> impl Filter<Extract = (Session,), Error = warp::Rejection> + Copy {
|
|
||||||
// Enable on any path
|
|
||||||
let conf = warp::any();
|
|
||||||
// Add remote ip address
|
|
||||||
let conf = conf.and(warp::filters::addr::remote());
|
|
||||||
// Add authorization header
|
|
||||||
let conf = conf.and(warp::header::optional::<String>("authorization"));
|
|
||||||
// Add http origin header
|
|
||||||
let conf = conf.and(warp::header::optional::<String>("origin"));
|
|
||||||
// Add session id header
|
|
||||||
let conf = conf.and(warp::header::optional::<String>("id"));
|
|
||||||
// Add namespace header
|
|
||||||
let conf = conf.and(warp::header::optional::<String>("ns"));
|
|
||||||
// Add database header
|
|
||||||
let conf = conf.and(warp::header::optional::<String>("db"));
|
|
||||||
// Process all headers
|
|
||||||
conf.map(process)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process(
|
|
||||||
ip: Option<SocketAddr>,
|
|
||||||
au: Option<String>,
|
|
||||||
or: Option<String>,
|
|
||||||
id: Option<String>,
|
|
||||||
ns: Option<String>,
|
|
||||||
db: Option<String>,
|
|
||||||
) -> Session {
|
|
||||||
// Create session
|
|
||||||
let conf = Session {
|
|
||||||
au: Arc::new(Auth::default()),
|
|
||||||
ip: ip.map(|v| v.to_string()),
|
|
||||||
or,
|
|
||||||
id,
|
|
||||||
ns,
|
|
||||||
db,
|
|
||||||
sc: None,
|
|
||||||
sd: None,
|
|
||||||
};
|
|
||||||
// Parse authentication
|
|
||||||
match au {
|
|
||||||
Some(auth) if auth.starts_with("Basic") => basic(auth, conf),
|
|
||||||
Some(auth) if auth.starts_with("Bearer") => token(auth, conf),
|
|
||||||
_ => conf,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn basic(_auth: String, conf: Session) -> Session {
|
|
||||||
conf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn token(_auth: String, conf: Session) -> Session {
|
|
||||||
conf
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::net::conf;
|
|
||||||
// use crate::net::DB;
|
// use crate::net::DB;
|
||||||
// use hyper::body::Body;
|
// use hyper::body::Body;
|
||||||
// use surrealdb::dbs::export;
|
// use surrealdb::dbs::export;
|
||||||
|
use crate::net::session;
|
||||||
use surrealdb::Session;
|
use surrealdb::Session;
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,28 @@ struct Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rejection> {
|
pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
if err.is_not_found() {
|
if let Some(err) = err.find::<Error>() {
|
||||||
|
match err {
|
||||||
|
Error::InvalidAuth => Ok(warp::reply::with_status(
|
||||||
|
warp::reply::json(&Message {
|
||||||
|
code: 403,
|
||||||
|
details: Some("Authentication failed".to_string()),
|
||||||
|
description: Some("Your authentication details are invalid. Reauthenticate using valid authentication parameters.".to_string()),
|
||||||
|
information: Some(err.to_string()),
|
||||||
|
}),
|
||||||
|
StatusCode::FORBIDDEN,
|
||||||
|
)),
|
||||||
|
_ => Ok(warp::reply::with_status(
|
||||||
|
warp::reply::json(&Message {
|
||||||
|
code: 400,
|
||||||
|
details: Some("Request problems detected".to_string()),
|
||||||
|
description: Some("There is a problem with your request. Refer to the documentation for further information.".to_string()),
|
||||||
|
information: Some(err.to_string()),
|
||||||
|
}),
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else if err.is_not_found() {
|
||||||
Ok(warp::reply::with_status(
|
Ok(warp::reply::with_status(
|
||||||
warp::reply::json(&Message {
|
warp::reply::json(&Message {
|
||||||
code: 404,
|
code: 404,
|
||||||
|
@ -24,16 +45,6 @@ pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rej
|
||||||
}),
|
}),
|
||||||
StatusCode::NOT_FOUND,
|
StatusCode::NOT_FOUND,
|
||||||
))
|
))
|
||||||
} else if let Some(err) = err.find::<Error>() {
|
|
||||||
Ok(warp::reply::with_status(
|
|
||||||
warp::reply::json(&Message {
|
|
||||||
code: 400,
|
|
||||||
details: Some("Request problems detected".to_string()),
|
|
||||||
description: Some("There is a problem with your request. Refer to the documentation for further information.".to_string()),
|
|
||||||
information: Some(err.to_string()),
|
|
||||||
}),
|
|
||||||
StatusCode::BAD_REQUEST,
|
|
||||||
))
|
|
||||||
} else if err.find::<warp::reject::MethodNotAllowed>().is_some() {
|
} else if err.find::<warp::reject::MethodNotAllowed>().is_some() {
|
||||||
Ok(warp::reply::with_status(
|
Ok(warp::reply::with_status(
|
||||||
warp::reply::json(&Message {
|
warp::reply::json(&Message {
|
||||||
|
@ -44,6 +55,16 @@ pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rej
|
||||||
}),
|
}),
|
||||||
StatusCode::METHOD_NOT_ALLOWED,
|
StatusCode::METHOD_NOT_ALLOWED,
|
||||||
))
|
))
|
||||||
|
} else if err.find::<warp::reject::MissingHeader>().is_some() {
|
||||||
|
Ok(warp::reply::with_status(
|
||||||
|
warp::reply::json(&Message {
|
||||||
|
code: 412,
|
||||||
|
details: Some("Request problems detected".to_string()),
|
||||||
|
description: Some("The request appears to be missing a required header. Refer to the documentation for request requirements.".to_string()),
|
||||||
|
information: None,
|
||||||
|
}),
|
||||||
|
StatusCode::PRECONDITION_FAILED,
|
||||||
|
))
|
||||||
} else if err.find::<warp::reject::PayloadTooLarge>().is_some() {
|
} else if err.find::<warp::reject::PayloadTooLarge>().is_some() {
|
||||||
Ok(warp::reply::with_status(
|
Ok(warp::reply::with_status(
|
||||||
warp::reply::json(&Message {
|
warp::reply::json(&Message {
|
||||||
|
@ -64,16 +85,6 @@ pub async fn recover(err: warp::Rejection) -> Result<impl warp::Reply, warp::Rej
|
||||||
}),
|
}),
|
||||||
StatusCode::UNSUPPORTED_MEDIA_TYPE,
|
StatusCode::UNSUPPORTED_MEDIA_TYPE,
|
||||||
))
|
))
|
||||||
} else if err.find::<warp::reject::MissingHeader>().is_some() {
|
|
||||||
Ok(warp::reply::with_status(
|
|
||||||
warp::reply::json(&Message {
|
|
||||||
code: 412,
|
|
||||||
details: Some("Request problems detected".to_string()),
|
|
||||||
description: Some("The request appears to be missing a required header. Refer to the documentation for request requirements.".to_string()),
|
|
||||||
information: None,
|
|
||||||
}),
|
|
||||||
StatusCode::PRECONDITION_FAILED,
|
|
||||||
))
|
|
||||||
} else if err.find::<warp::reject::InvalidQuery>().is_some() {
|
} else if err.find::<warp::reject::InvalidQuery>().is_some() {
|
||||||
Ok(warp::reply::with_status(
|
Ok(warp::reply::with_status(
|
||||||
warp::reply::json(&Message {
|
warp::reply::json(&Message {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::net::conf;
|
|
||||||
use crate::net::head;
|
use crate::net::head;
|
||||||
use crate::net::output;
|
use crate::net::output;
|
||||||
|
use crate::net::session;
|
||||||
use crate::net::DB;
|
use crate::net::DB;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -35,7 +35,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set select method
|
// Set select method
|
||||||
let select = warp::any()
|
let select = warp::any()
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String).and(warp::path::end()))
|
.and(path!("key" / String).and(warp::path::end()))
|
||||||
.and(warp::query())
|
.and(warp::query())
|
||||||
|
@ -43,7 +43,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set create method
|
// Set create method
|
||||||
let create = warp::any()
|
let create = warp::any()
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String).and(warp::path::end()))
|
.and(path!("key" / String).and(warp::path::end()))
|
||||||
.and(warp::body::content_length_limit(MAX))
|
.and(warp::body::content_length_limit(MAX))
|
||||||
|
@ -52,7 +52,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set delete method
|
// Set delete method
|
||||||
let delete = warp::any()
|
let delete = warp::any()
|
||||||
.and(warp::delete())
|
.and(warp::delete())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String).and(warp::path::end()))
|
.and(path!("key" / String).and(warp::path::end()))
|
||||||
.and_then(delete_all);
|
.and_then(delete_all);
|
||||||
|
@ -66,14 +66,14 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set select method
|
// Set select method
|
||||||
let select = warp::any()
|
let select = warp::any()
|
||||||
.and(warp::get())
|
.and(warp::get())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String / String).and(warp::path::end()))
|
.and(path!("key" / String / String).and(warp::path::end()))
|
||||||
.and_then(select_one);
|
.and_then(select_one);
|
||||||
// Set create method
|
// Set create method
|
||||||
let create = warp::any()
|
let create = warp::any()
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String / String).and(warp::path::end()))
|
.and(path!("key" / String / String).and(warp::path::end()))
|
||||||
.and(warp::body::content_length_limit(MAX))
|
.and(warp::body::content_length_limit(MAX))
|
||||||
|
@ -82,7 +82,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set update method
|
// Set update method
|
||||||
let update = warp::any()
|
let update = warp::any()
|
||||||
.and(warp::put())
|
.and(warp::put())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String / String).and(warp::path::end()))
|
.and(path!("key" / String / String).and(warp::path::end()))
|
||||||
.and(warp::body::content_length_limit(MAX))
|
.and(warp::body::content_length_limit(MAX))
|
||||||
|
@ -91,7 +91,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set modify method
|
// Set modify method
|
||||||
let modify = warp::any()
|
let modify = warp::any()
|
||||||
.and(warp::patch())
|
.and(warp::patch())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String / String).and(warp::path::end()))
|
.and(path!("key" / String / String).and(warp::path::end()))
|
||||||
.and(warp::body::content_length_limit(MAX))
|
.and(warp::body::content_length_limit(MAX))
|
||||||
|
@ -100,7 +100,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set delete method
|
// Set delete method
|
||||||
let delete = warp::any()
|
let delete = warp::any()
|
||||||
.and(warp::delete())
|
.and(warp::delete())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(path!("key" / String / String).and(warp::path::end()))
|
.and(path!("key" / String / String).and(warp::path::end()))
|
||||||
.and_then(delete_one);
|
.and_then(delete_one);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
mod conf;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod export;
|
mod export;
|
||||||
mod fail;
|
mod fail;
|
||||||
|
@ -8,6 +7,7 @@ mod index;
|
||||||
mod key;
|
mod key;
|
||||||
mod log;
|
mod log;
|
||||||
mod output;
|
mod output;
|
||||||
|
mod session;
|
||||||
mod signin;
|
mod signin;
|
||||||
mod signup;
|
mod signup;
|
||||||
mod sql;
|
mod sql;
|
||||||
|
|
333
src/net/session.rs
Normal file
333
src/net/session.rs
Normal file
|
@ -0,0 +1,333 @@
|
||||||
|
use crate::err::Error;
|
||||||
|
use crate::net::CF;
|
||||||
|
use crate::net::DB;
|
||||||
|
use argon2::password_hash::{PasswordHash, PasswordVerifier};
|
||||||
|
use argon2::Argon2;
|
||||||
|
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use surrealdb::sql::Algorithm;
|
||||||
|
use surrealdb::sql::Thing;
|
||||||
|
use surrealdb::sql::Value;
|
||||||
|
use surrealdb::Auth;
|
||||||
|
use surrealdb::Session;
|
||||||
|
use warp::Filter;
|
||||||
|
|
||||||
|
const BASIC: &str = "Basic ";
|
||||||
|
const TOKEN: &str = "Bearer ";
|
||||||
|
|
||||||
|
fn config(algo: Algorithm, code: String) -> Result<(DecodingKey, Validation), Error> {
|
||||||
|
match algo {
|
||||||
|
Algorithm::Hs256 => Ok((
|
||||||
|
DecodingKey::from_secret(code.as_ref()),
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::HS256),
|
||||||
|
)),
|
||||||
|
Algorithm::Hs384 => Ok((
|
||||||
|
DecodingKey::from_secret(code.as_ref()),
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::HS384),
|
||||||
|
)),
|
||||||
|
Algorithm::Hs512 => Ok((
|
||||||
|
DecodingKey::from_secret(code.as_ref()),
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::HS512),
|
||||||
|
)),
|
||||||
|
Algorithm::EdDSA => Ok((
|
||||||
|
DecodingKey::from_ed_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::EdDSA),
|
||||||
|
)),
|
||||||
|
Algorithm::Es256 => Ok((
|
||||||
|
DecodingKey::from_ec_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::ES256),
|
||||||
|
)),
|
||||||
|
Algorithm::Es384 => Ok((
|
||||||
|
DecodingKey::from_ec_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::ES384),
|
||||||
|
)),
|
||||||
|
Algorithm::Es512 => Ok((
|
||||||
|
DecodingKey::from_ec_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::ES384),
|
||||||
|
)),
|
||||||
|
Algorithm::Ps256 => Ok((
|
||||||
|
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::PS256),
|
||||||
|
)),
|
||||||
|
Algorithm::Ps384 => Ok((
|
||||||
|
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::PS384),
|
||||||
|
)),
|
||||||
|
Algorithm::Ps512 => Ok((
|
||||||
|
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::PS512),
|
||||||
|
)),
|
||||||
|
Algorithm::Rs256 => Ok((
|
||||||
|
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::RS256),
|
||||||
|
)),
|
||||||
|
Algorithm::Rs384 => Ok((
|
||||||
|
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::RS384),
|
||||||
|
)),
|
||||||
|
Algorithm::Rs512 => Ok((
|
||||||
|
DecodingKey::from_rsa_pem(code.as_ref())?,
|
||||||
|
Validation::new(jsonwebtoken::Algorithm::RS512),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Claims {
|
||||||
|
iss: String,
|
||||||
|
iat: usize,
|
||||||
|
nbf: usize,
|
||||||
|
exp: usize,
|
||||||
|
ns: Option<String>,
|
||||||
|
db: Option<String>,
|
||||||
|
sc: Option<String>,
|
||||||
|
tk: Option<String>,
|
||||||
|
tb: Option<String>,
|
||||||
|
id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static KEY: Lazy<DecodingKey> = Lazy::new(|| DecodingKey::from_secret(&[]));
|
||||||
|
|
||||||
|
static DUD: Lazy<Validation> = Lazy::new(|| {
|
||||||
|
let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256);
|
||||||
|
validation.insecure_disable_signature_validation();
|
||||||
|
validation.validate_nbf = true;
|
||||||
|
validation.validate_exp = true;
|
||||||
|
validation
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn build() -> impl Filter<Extract = (Session,), Error = warp::Rejection> + Clone {
|
||||||
|
// Enable on any path
|
||||||
|
let conf = warp::any();
|
||||||
|
// Add remote ip address
|
||||||
|
let conf = conf.and(warp::filters::addr::remote());
|
||||||
|
// Add remote ip address
|
||||||
|
let conf = conf.map(|addr: Option<SocketAddr>| addr.map(|v| v.to_string()));
|
||||||
|
// Add authorization header
|
||||||
|
let conf = conf.and(warp::header::optional::<String>("authorization"));
|
||||||
|
// Add http origin header
|
||||||
|
let conf = conf.and(warp::header::optional::<String>("origin"));
|
||||||
|
// Add session id header
|
||||||
|
let conf = conf.and(warp::header::optional::<String>("id"));
|
||||||
|
// Add namespace header
|
||||||
|
let conf = conf.and(warp::header::optional::<String>("ns"));
|
||||||
|
// Add database header
|
||||||
|
let conf = conf.and(warp::header::optional::<String>("db"));
|
||||||
|
// Process all headers
|
||||||
|
conf.and_then(process)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn process(
|
||||||
|
ip: Option<String>,
|
||||||
|
au: Option<String>,
|
||||||
|
or: Option<String>,
|
||||||
|
id: Option<String>,
|
||||||
|
ns: Option<String>,
|
||||||
|
db: Option<String>,
|
||||||
|
) -> Result<Session, warp::Rejection> {
|
||||||
|
// Create session
|
||||||
|
#[rustfmt::skip]
|
||||||
|
let session = Session { ip, or, id, ns, db, ..Default::default() };
|
||||||
|
// Parse the authentication header
|
||||||
|
let session = match au {
|
||||||
|
// Basic authentication data was supplied
|
||||||
|
Some(auth) if auth.starts_with(BASIC) => basic(auth, session).await,
|
||||||
|
// Token authentication data was supplied
|
||||||
|
Some(auth) if auth.starts_with(TOKEN) => token(auth, session).await,
|
||||||
|
// Wrong authentication data was supplied
|
||||||
|
Some(_) => Err(Error::InvalidAuth),
|
||||||
|
// No authentication data was supplied
|
||||||
|
None => Ok(session),
|
||||||
|
}?;
|
||||||
|
// Pass the authenticated session through
|
||||||
|
Ok(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn basic(auth: String, mut session: Session) -> Result<Session, Error> {
|
||||||
|
// Get the config options
|
||||||
|
let opts = CF.get().unwrap();
|
||||||
|
// Retrieve just the auth data
|
||||||
|
if let Some((_, auth)) = auth.split_once(' ') {
|
||||||
|
// Get a database reference
|
||||||
|
let db = DB.get().unwrap();
|
||||||
|
// Decode the encoded auth data
|
||||||
|
let auth = base64::decode(auth)?;
|
||||||
|
// Convert the auth data to String
|
||||||
|
let auth = String::from_utf8(auth)?;
|
||||||
|
// Create a new readonly transaction
|
||||||
|
let mut tx = db.transaction(false, false).await?;
|
||||||
|
// Split the auth data into user and pass
|
||||||
|
if let Some((user, pass)) = auth.split_once(':') {
|
||||||
|
// Check that the details are not empty
|
||||||
|
if user.is_empty() || pass.is_empty() {
|
||||||
|
return Err(Error::InvalidAuth);
|
||||||
|
}
|
||||||
|
// Check if this is root authentication
|
||||||
|
if user == opts.user && pass == opts.pass {
|
||||||
|
session.au = Arc::new(Auth::Kv);
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
// Check if this is NS authentication
|
||||||
|
if let Some(ns) = &session.ns {
|
||||||
|
// Check if the supplied NS Login exists
|
||||||
|
if let Ok(nl) = tx.get_nl(ns, user).await {
|
||||||
|
// Compute the hash and verify the password
|
||||||
|
let hash = PasswordHash::new(&nl.hash).unwrap();
|
||||||
|
if Argon2::default().verify_password(pass.as_ref(), &hash).is_ok() {
|
||||||
|
session.au = Arc::new(Auth::Ns(ns.to_owned()));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Check if this is DB authentication
|
||||||
|
if let Some(db) = &session.db {
|
||||||
|
// Check if the supplied DB Login exists
|
||||||
|
if let Ok(dl) = tx.get_dl(ns, db, user).await {
|
||||||
|
// Compute the hash and verify the password
|
||||||
|
let hash = PasswordHash::new(&dl.hash).unwrap();
|
||||||
|
if Argon2::default().verify_password(pass.as_ref(), &hash).is_ok() {
|
||||||
|
session.au = Arc::new(Auth::Db(ns.to_owned(), db.to_owned()));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// There was an auth error
|
||||||
|
Err(Error::InvalidAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn token(auth: String, mut session: Session) -> Result<Session, Error> {
|
||||||
|
// Retrieve just the auth data
|
||||||
|
if let Some((_, auth)) = auth.split_once(' ') {
|
||||||
|
// Get a database reference
|
||||||
|
let db = DB.get().unwrap();
|
||||||
|
// Create a new readonly transaction
|
||||||
|
let mut tx = db.transaction(false, false).await?;
|
||||||
|
// Decode the token without verifying
|
||||||
|
let token = decode::<Claims>(auth, &KEY, &DUD)?;
|
||||||
|
// Check the token authentication claims
|
||||||
|
match token.claims {
|
||||||
|
// Check if this is scope token authentication
|
||||||
|
Claims {
|
||||||
|
ns: Some(ns),
|
||||||
|
db: Some(db),
|
||||||
|
sc: Some(sc),
|
||||||
|
tk: Some(tk),
|
||||||
|
tb: Some(tb),
|
||||||
|
id: Some(id),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Get the scope token
|
||||||
|
let de = tx.get_st(&ns, &db, &sc, &tk).await?;
|
||||||
|
let cf = config(de.kind, de.code)?;
|
||||||
|
// Verify the token
|
||||||
|
decode::<Claims>(auth, &cf.0, &cf.1)?;
|
||||||
|
// Set the session
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.db = Some(db.to_owned());
|
||||||
|
session.sc = Some(sc.to_owned());
|
||||||
|
session.sd = Some(Value::from(Thing::from((tb, id))));
|
||||||
|
session.au = Arc::new(Auth::Sc(ns, db, sc));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
// Check if this is scope authentication
|
||||||
|
Claims {
|
||||||
|
ns: Some(ns),
|
||||||
|
db: Some(db),
|
||||||
|
sc: Some(sc),
|
||||||
|
tb: Some(tb),
|
||||||
|
id: Some(id),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Get the scope
|
||||||
|
let de = tx.get_sc(&ns, &db, &sc).await?;
|
||||||
|
let cf = config(Algorithm::Hs512, de.code)?;
|
||||||
|
// Verify the token
|
||||||
|
decode::<Claims>(auth, &cf.0, &cf.1)?;
|
||||||
|
// Set the session
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.db = Some(db.to_owned());
|
||||||
|
session.sc = Some(sc.to_owned());
|
||||||
|
session.sd = Some(Value::from(Thing::from((tb, id))));
|
||||||
|
session.au = Arc::new(Auth::Sc(ns, db, sc));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
// Check if this is database token authentication
|
||||||
|
Claims {
|
||||||
|
ns: Some(ns),
|
||||||
|
db: Some(db),
|
||||||
|
tk: Some(tk),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Get the database token
|
||||||
|
let de = tx.get_dt(&ns, &db, &tk).await?;
|
||||||
|
let cf = config(de.kind, de.code)?;
|
||||||
|
// Verify the token
|
||||||
|
decode::<Claims>(auth, &cf.0, &cf.1)?;
|
||||||
|
// Set the session
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.db = Some(db.to_owned());
|
||||||
|
session.au = Arc::new(Auth::Db(ns, db));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
// Check if this is database authentication
|
||||||
|
Claims {
|
||||||
|
ns: Some(ns),
|
||||||
|
db: Some(db),
|
||||||
|
id: Some(id),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Get the database login
|
||||||
|
let de = tx.get_dl(&ns, &db, &id).await?;
|
||||||
|
let cf = config(Algorithm::Hs512, de.code)?;
|
||||||
|
// Verify the token
|
||||||
|
decode::<Claims>(auth, &cf.0, &cf.1)?;
|
||||||
|
// Set the session
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.db = Some(db.to_owned());
|
||||||
|
session.au = Arc::new(Auth::Db(ns, db));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
// Check if this is namespace token authentication
|
||||||
|
Claims {
|
||||||
|
ns: Some(ns),
|
||||||
|
tk: Some(tk),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Get the namespace token
|
||||||
|
let de = tx.get_nt(&ns, &tk).await?;
|
||||||
|
let cf = config(de.kind, de.code)?;
|
||||||
|
// Verify the token
|
||||||
|
decode::<Claims>(auth, &cf.0, &cf.1)?;
|
||||||
|
// Set the session
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.au = Arc::new(Auth::Ns(ns));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
// Check if this is namespace authentication
|
||||||
|
Claims {
|
||||||
|
ns: Some(ns),
|
||||||
|
id: Some(id),
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Get the namespace login
|
||||||
|
let de = tx.get_nl(&ns, &id).await?;
|
||||||
|
let cf = config(Algorithm::Hs512, de.code)?;
|
||||||
|
// Verify the token
|
||||||
|
decode::<Claims>(auth, &cf.0, &cf.1)?;
|
||||||
|
// Set the session
|
||||||
|
session.ns = Some(ns.to_owned());
|
||||||
|
session.au = Arc::new(Auth::Ns(ns));
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
// There was an auth error
|
||||||
|
_ => return Err(Error::InvalidAuth),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// There was an auth error
|
||||||
|
Err(Error::InvalidAuth)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::err::Error;
|
use crate::err::Error;
|
||||||
use crate::net::conf;
|
|
||||||
use crate::net::head;
|
use crate::net::head;
|
||||||
use crate::net::output;
|
use crate::net::output;
|
||||||
|
use crate::net::session;
|
||||||
use crate::net::DB;
|
use crate::net::DB;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures::{FutureExt, StreamExt};
|
use futures::{FutureExt, StreamExt};
|
||||||
|
@ -18,7 +18,7 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
|
||||||
// Set post method
|
// Set post method
|
||||||
let post = base
|
let post = base
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(conf::build())
|
.and(session::build())
|
||||||
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
.and(warp::header::<String>(http::header::CONTENT_TYPE.as_str()))
|
||||||
.and(warp::body::content_length_limit(MAX))
|
.and(warp::body::content_length_limit(MAX))
|
||||||
.and(warp::body::bytes())
|
.and(warp::body::bytes())
|
||||||
|
|
Loading…
Reference in a new issue