Add authentication support for local engines (#1908)

This commit is contained in:
Rushmore Mushambi 2023-06-09 15:45:07 +02:00 committed by GitHub
parent 2237afb21a
commit 409ad61477
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 1303 additions and 755 deletions

1
Cargo.lock generated
View file

@ -4348,6 +4348,7 @@ dependencies = [
"geo",
"indexmap",
"indxdb",
"jsonwebtoken",
"lexicmp",
"log",
"md-5",

View file

@ -32,11 +32,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1683699806,
"narHash": "sha256-Nw3pzHv9oRdIDmgetGWG5mqLDK78v3fTsReqANMjZ1Q=",
"lastModified": 1683872481,
"narHash": "sha256-BLXcc6oCbv98MGn/MSUTH8C4oKdczk/C5JqZ3ZlGAXU=",
"owner": "nix-community",
"repo": "fenix",
"rev": "71e7505152ef95dfddd0f5f0e5a755f4c3bbee86",
"rev": "dd518e99e2833bb13e25570f88f2e16cdc5f8b4e",
"type": "github"
},
"original": {
@ -110,11 +110,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1683627095,
"narHash": "sha256-8u9SejRpL2TrMuHBdhYh4FKc1OGPDLyWTpIbNTtoHsA=",
"lastModified": 1683802553,
"narHash": "sha256-1KQfY1wJ0QgAl2hwbvzy09uhEtFlGUKqwfAzjX7NCYA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a08e061a4ee8329747d54ddf1566d34c55c895eb",
"rev": "1a0ffed73a73ba9527480150412982adeeeecb03",
"type": "github"
},
"original": {
@ -136,11 +136,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1683653808,
"narHash": "sha256-GiKwJySG/YCPIKwz9wSm9fJa5e4CU3GvTYAKZzjBhFo=",
"lastModified": 1683815219,
"narHash": "sha256-dC79Q2Xw8sBGz6a41H15XaT3pHQ6xP4EVY4axdxUb4E=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "b7cdd93f3e1533e96d4cfa1ac8573e6210a2bedf",
"rev": "9b3387454d7c70ec768114871682ee2946ec88a8",
"type": "github"
},
"original": {

View file

@ -76,7 +76,8 @@ fuzzy-matcher = "0.3.7"
geo = { version = "0.25.0", features = ["use-serde"] }
indexmap = { version = "1.9.3", features = ["serde"] }
indxdb = { version = "0.3.0", optional = true }
js = { version = "0.2.1" , package = "rquickjs", features = ["array-buffer", "bindgen", "classes", "futures", "loader", "macro", "parallel", "properties"], optional = true }
js = { version = "0.2.1", package = "rquickjs", features = ["array-buffer", "bindgen", "classes", "futures", "loader", "macro", "parallel", "properties"], optional = true }
jsonwebtoken = "8.3.0"
lexicmp = "0.1.0"
log = "0.4.17"
md-5 = "0.10.5"

View file

@ -90,6 +90,14 @@ mod wasm;
use crate::api::conn::Method;
use crate::api::err::Error;
#[cfg(any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
))]
use crate::api::opt::auth::Root;
use crate::api::opt::Endpoint;
#[cfg(any(
feature = "kv-mem",
@ -105,6 +113,7 @@ use crate::api::opt::Tls;
use crate::api::Connect;
use crate::api::Result;
use crate::api::Surreal;
use crate::dbs::Level;
use std::marker::PhantomData;
use url::Url;
@ -125,6 +134,9 @@ impl IntoEndpoint for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -149,9 +161,9 @@ where
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config) = self;
let mut address = address.into().into_endpoint()?;
address.tls_config = Some(Tls::Native(config));
Ok(address)
let mut endpoint = address.into().into_endpoint()?;
endpoint.tls_config = Some(Tls::Native(config));
Ok(endpoint)
}
}
@ -163,9 +175,9 @@ where
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config) = self;
let mut address = address.into().into_endpoint()?;
address.tls_config = Some(Tls::Rust(config));
Ok(address)
let mut endpoint = address.into().into_endpoint()?;
endpoint.tls_config = Some(Tls::Rust(config));
Ok(endpoint)
}
}
@ -193,9 +205,140 @@ where
T: Into<String>,
{
fn into_endpoint(self) -> Result<Endpoint> {
let mut address = IntoEndpoint::into_endpoint(self.0.into())?;
address.strict = true;
Ok(address)
let mut endpoint = IntoEndpoint::into_endpoint(self.0.into())?;
endpoint.strict = true;
Ok(endpoint)
}
}
#[cfg(any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
)))
)]
impl<T> IntoEndpoint for (T, Root<'_>)
where
T: Into<String>,
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, root) = self;
let mut endpoint = IntoEndpoint::into_endpoint(address.into())?;
endpoint.auth = Level::Kv;
endpoint.username = root.username.to_owned();
endpoint.password = root.password.to_owned();
Ok(endpoint)
}
}
#[cfg(any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
)))
)]
impl<T> IntoEndpoint for (T, Strict, Root<'_>)
where
T: Into<String>,
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, _, root) = self;
let mut endpoint = IntoEndpoint::into_endpoint((address, root))?;
endpoint.strict = true;
Ok(endpoint)
}
}
#[cfg(all(
any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
),
feature = "native-tls",
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
),
feature = "native-tls",
)))
)]
impl<T> IntoEndpoint for (T, Strict, native_tls::TlsConnector)
where
T: Into<String>,
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, _, config) = self;
let mut endpoint = address.into().into_endpoint()?;
endpoint.tls_config = Some(Tls::Native(config));
endpoint.strict = true;
Ok(endpoint)
}
}
#[cfg(all(
any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
),
feature = "native-tls",
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
),
feature = "native-tls",
)))
)]
impl<T> IntoEndpoint for (T, native_tls::TlsConnector, Root<'_>)
where
T: Into<String>,
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config, root) = self;
let mut endpoint = (address, root).into_endpoint()?;
endpoint.tls_config = Some(Tls::Native(config));
Ok(endpoint)
}
}
@ -224,16 +367,51 @@ where
feature = "rustls",
)))
)]
impl<T> IntoEndpoint for (T, rustls::ClientConfig, Strict)
impl<T> IntoEndpoint for (T, Strict, rustls::ClientConfig)
where
T: Into<String>,
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config, _) = self;
let mut address = address.into().into_endpoint()?;
address.tls_config = Some(Tls::Rust(config));
address.strict = true;
Ok(address)
let (address, _, config) = self;
let mut endpoint = address.into().into_endpoint()?;
endpoint.tls_config = Some(Tls::Rust(config));
endpoint.strict = true;
Ok(endpoint)
}
}
#[cfg(all(
any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
),
feature = "rustls",
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-fdb",
feature = "kv-indxdb",
),
feature = "rustls",
)))
)]
impl<T> IntoEndpoint for (T, rustls::ClientConfig, Root<'_>)
where
T: Into<String>,
{
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config, root) = self;
let mut endpoint = (address, root).into_endpoint()?;
endpoint.tls_config = Some(Tls::Rust(config));
Ok(endpoint)
}
}

View file

@ -135,7 +135,6 @@ impl Connection for Any {
"http" | "https" => {
#[cfg(feature = "protocol-http")]
{
features.insert(ExtraFeatures::Auth);
features.insert(ExtraFeatures::Backup);
let headers = http::default_headers();
#[allow(unused_mut)]
@ -168,7 +167,6 @@ impl Connection for Any {
"ws" | "wss" => {
#[cfg(feature = "protocol-ws")]
{
features.insert(ExtraFeatures::Auth);
let url = address.endpoint.join(engine::remote::ws::PATH)?;
#[cfg(any(feature = "native-tls", feature = "rustls"))]
let maybe_connector = address.tls_config.map(Connector::from);

View file

@ -9,8 +9,6 @@ use crate::api::engine::any::Any;
use crate::api::err::Error;
use crate::api::opt::Endpoint;
use crate::api::DbResponse;
#[allow(unused_imports)] // used by the `ws` and `http` protocols
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::api::Surreal;
use crate::error::Db as DbError;
@ -143,7 +141,6 @@ impl Connection for Any {
"http" | "https" => {
#[cfg(feature = "protocol-http")]
{
features.insert(ExtraFeatures::Auth);
engine::remote::http::wasm::router(address, conn_tx, route_rx);
}
@ -157,7 +154,6 @@ impl Connection for Any {
"ws" | "wss" => {
#[cfg(feature = "protocol-ws")]
{
features.insert(ExtraFeatures::Auth);
let mut address = address;
address.endpoint = address.endpoint.join(engine::remote::ws::PATH)?;
engine::remote::ws::wasm::router(address, capacity, conn_tx, route_rx);

View file

@ -46,6 +46,7 @@ use crate::channel;
use crate::dbs::Response;
use crate::dbs::Session;
use crate::kvs::Datastore;
use crate::opt::auth::Root;
use crate::opt::IntoEndpoint;
use crate::sql::Array;
use crate::sql::Query;
@ -402,9 +403,10 @@ async fn take(one: bool, responses: Vec<Response>) -> Result<Value> {
async fn router(
(_, method, param): (i64, Method, Param),
kvs: &Datastore,
configured_root: &Option<Root<'_>>,
strict: bool,
session: &mut Session,
vars: &mut BTreeMap<String, Value>,
strict: bool,
) -> Result<DbResponse> {
let mut params = param.other;
@ -425,8 +427,35 @@ async fn router(
}
Ok(DbResponse::Other(Value::None))
}
Method::Signin | Method::Signup | Method::Authenticate | Method::Invalidate => {
unreachable!()
Method::Signup => {
let credentials = match &mut params[..] {
[Value::Object(credentials)] => mem::take(credentials),
_ => unreachable!(),
};
let response = crate::iam::signup::signup(kvs, strict, session, credentials).await?;
Ok(DbResponse::Other(response.into()))
}
Method::Signin => {
let credentials = match &mut params[..] {
[Value::Object(credentials)] => mem::take(credentials),
_ => unreachable!(),
};
let response =
crate::iam::signin::signin(kvs, configured_root, strict, session, credentials)
.await?;
Ok(DbResponse::Other(response.into()))
}
Method::Authenticate => {
let token = match &mut params[..] {
[Value::Strand(Strand(token))] => mem::take(token),
_ => unreachable!(),
};
crate::iam::verify::token(kvs, session, token).await?;
Ok(DbResponse::Other(Value::None))
}
Method::Invalidate => {
crate::iam::clear::clear(session)?;
Ok(DbResponse::Other(Value::None))
}
Method::Create => {
let statement = create_statement(&mut params);

View file

@ -10,8 +10,10 @@ use crate::api::opt::Endpoint;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::api::Surreal;
use crate::dbs::Level;
use crate::dbs::Session;
use crate::kvs::Datastore;
use crate::opt::auth::Root;
use flume::Receiver;
use flume::Sender;
use futures::StreamExt;
@ -114,12 +116,33 @@ pub(crate) fn router(
}
};
let mut session = Session::for_kv();
let mut vars = BTreeMap::new();
let mut stream = route_rx.into_stream();
let configured_root = match address.auth {
Level::Kv => Some(Root {
username: &address.username,
password: &address.password,
}),
_ => None,
};
let mut session = if configured_root.is_some() {
// If a root user is specified, lock down the database
Session::default()
} else {
// If no root user is specified, the database should be open
Session::for_kv()
};
while let Some(Some(route)) = stream.next().await {
match super::router(route.request, &kvs, &mut session, &mut vars, address.strict).await
match super::router(
route.request,
&kvs,
&configured_root,
address.strict,
&mut session,
&mut vars,
)
.await
{
Ok(value) => {
let _ = route.response.into_send_async(Ok(value)).await;

View file

@ -8,8 +8,10 @@ use crate::api::engine::local::Db;
use crate::api::opt::Endpoint;
use crate::api::Result;
use crate::api::Surreal;
use crate::dbs::Level;
use crate::dbs::Session;
use crate::kvs::Datastore;
use crate::opt::auth::Root;
use flume::Receiver;
use flume::Sender;
use futures::StreamExt;
@ -102,12 +104,33 @@ pub(crate) fn router(
}
};
let mut session = Session::for_kv();
let mut vars = BTreeMap::new();
let mut stream = route_rx.into_stream();
let configured_root = match address.auth {
Level::Kv => Some(Root {
username: &address.username,
password: &address.password,
}),
_ => None,
};
let mut session = if configured_root.is_some() {
// If a root user is specified, lock down the database
Session::default()
} else {
// If no root user is specified, the database should be open
Session::for_kv()
};
while let Some(Some(route)) = stream.next().await {
match super::router(route.request, &kvs, &mut session, &mut vars, address.strict).await
match super::router(
route.request,
&kvs,
&configured_root,
address.strict,
&mut session,
&mut vars,
)
.await
{
Ok(value) => {
let _ = route.response.into_send_async(Ok(value)).await;

View file

@ -68,7 +68,6 @@ impl Connection for Client {
router(base_url, client, route_rx);
let mut features = HashSet::new();
features.insert(ExtraFeatures::Auth);
features.insert(ExtraFeatures::Backup);
Ok(Surreal {

View file

@ -7,7 +7,6 @@ use crate::api::conn::Param;
use crate::api::conn::Route;
use crate::api::conn::Router;
use crate::api::opt::Endpoint;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::api::Surreal;
use flume::Receiver;
@ -53,12 +52,9 @@ impl Connection for Client {
return Err(error);
}
let mut features = HashSet::new();
features.insert(ExtraFeatures::Auth);
Ok(Surreal {
router: OnceCell::with_value(Arc::new(Router {
features,
features: HashSet::new(),
conn: PhantomData,
sender: route_tx,
last_id: AtomicI64::new(0),

View file

@ -14,7 +14,6 @@ use crate::api::err::Error;
use crate::api::opt::Endpoint;
#[cfg(any(feature = "native-tls", feature = "rustls"))]
use crate::api::opt::Tls;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::api::Surreal;
use crate::engine::remote::ws::IntervalStream;
@ -128,12 +127,9 @@ impl Connection for Client {
router(url, maybe_connector, capacity, config, socket, route_rx);
let mut features = HashSet::new();
features.insert(ExtraFeatures::Auth);
Ok(Surreal {
router: OnceCell::with_value(Arc::new(Router {
features,
features: HashSet::new(),
conn: PhantomData,
sender: route_tx,
last_id: AtomicI64::new(0),

View file

@ -12,7 +12,6 @@ use crate::api::engine::remote::ws::PING_INTERVAL;
use crate::api::engine::remote::ws::PING_METHOD;
use crate::api::err::Error;
use crate::api::opt::Endpoint;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::api::Surreal;
use crate::engine::remote::ws::IntervalStream;
@ -83,12 +82,9 @@ impl Connection for Client {
return Err(error);
}
let mut features = HashSet::new();
features.insert(ExtraFeatures::Auth);
Ok(Surreal {
router: OnceCell::with_value(Arc::new(Router {
features,
features: HashSet::new(),
conn: PhantomData,
sender: route_tx,
last_id: AtomicI64::new(0),

View file

@ -146,11 +146,6 @@ pub enum Error {
/// it's running on
#[error("The protocol or storage engine does not support backups on this architecture")]
BackupsNotSupported,
/// The protocol or storage engine being used does not support authentication on the
/// architecture it's running on
#[error("The protocol or storage engine does not support authentication on this architecture")]
AuthNotSupported,
}
#[cfg(feature = "protocol-http")]

View file

@ -3,8 +3,6 @@ use crate::api::conn::Param;
use crate::api::conn::Router;
use crate::api::opt::auth::Jwt;
use crate::api::Connection;
use crate::api::Error;
use crate::api::ExtraFeatures;
use crate::api::Result;
use std::future::Future;
use std::future::IntoFuture;
@ -28,9 +26,6 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let router = self.router?;
if !router.features.contains(&ExtraFeatures::Auth) {
return Err(Error::AuthNotSupported.into());
}
let mut conn = Client::new(Method::Authenticate);
conn.execute_unit(router, Param::new(vec![self.token.into()])).await
})

View file

@ -2,8 +2,6 @@ use crate::api::conn::Method;
use crate::api::conn::Param;
use crate::api::conn::Router;
use crate::api::Connection;
use crate::api::Error;
use crate::api::ExtraFeatures;
use crate::api::Result;
use std::future::Future;
use std::future::IntoFuture;
@ -26,9 +24,6 @@ where
fn into_future(self) -> Self::IntoFuture {
Box::pin(async {
let router = self.router?;
if !router.features.contains(&ExtraFeatures::Auth) {
return Err(Error::AuthNotSupported.into());
}
let mut conn = Client::new(Method::Invalidate);
conn.execute_unit(router, Param::new(Vec::new())).await
})

View file

@ -366,10 +366,6 @@ where
/// Signs up a user to a specific authentication scope
///
/// # Support
///
/// Currently only supported by the WS and HTTP protocols.
///
/// # Examples
///
/// ```no_run
@ -430,10 +426,6 @@ where
/// Signs this connection in to a specific authentication scope
///
/// # Support
///
/// Currently only supported by the WS and HTTP protocols.
///
/// # Examples
///
/// Namespace signin
@ -553,10 +545,6 @@ where
/// Invalidates the authentication for the current connection
///
/// # Support
///
/// Currently only supported by the WS and HTTP protocols.
///
/// # Examples
///
/// ```no_run
@ -575,10 +563,6 @@ where
/// Authenticates the current connection with a JWT token
///
/// # Support
///
/// Currently only supported by the WS and HTTP protocols.
///
/// # Examples
///
/// ```no_run

View file

@ -486,7 +486,7 @@ mod tests {
Err(Error::BackupsNotSupported.into()),
Ok(vec![6.into()]),
Ok(vec![7.into()]),
Err(Error::AuthNotSupported.into()),
Err(Error::DuplicateRequestId(0).into()),
];
let response = Response(to_map(response));
let crate::Error::Api(Error::ConnectionUninitialised) = response.check().unwrap_err() else {
@ -507,14 +507,14 @@ mod tests {
Err(Error::BackupsNotSupported.into()),
Ok(vec![6.into()]),
Ok(vec![7.into()]),
Err(Error::AuthNotSupported.into()),
Err(Error::DuplicateRequestId(0).into()),
];
let mut response = Response(to_map(response));
let errors = response.take_errors();
assert_eq!(response.num_statements(), 8);
assert_eq!(errors.len(), 3);
let crate::Error::Api(Error::AuthNotSupported) = errors.get(&10).unwrap() else {
panic!("index `10` is not `AuthNotSupported`");
let crate::Error::Api(Error::DuplicateRequestId(0)) = errors.get(&10).unwrap() else {
panic!("index `10` is not `DuplicateRequestId`");
};
let crate::Error::Api(Error::BackupsNotSupported) = errors.get(&7).unwrap() else {
panic!("index `7` is not `BackupsNotSupported`");

View file

@ -2,8 +2,6 @@ use crate::api::conn::Method;
use crate::api::conn::Param;
use crate::api::conn::Router;
use crate::api::Connection;
use crate::api::Error;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::sql::Value;
use serde::de::DeserializeOwned;
@ -37,9 +35,6 @@ where
} = self;
Box::pin(async move {
let router = router?;
if !router.features.contains(&ExtraFeatures::Auth) {
return Err(Error::AuthNotSupported.into());
}
let mut conn = Client::new(Method::Signin);
conn.execute(router, Param::new(vec![credentials?])).await
})

View file

@ -2,8 +2,6 @@ use crate::api::conn::Method;
use crate::api::conn::Param;
use crate::api::conn::Router;
use crate::api::Connection;
use crate::api::Error;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::sql::Value;
use serde::de::DeserializeOwned;
@ -37,9 +35,6 @@ where
} = self;
Box::pin(async move {
let router = router?;
if !router.features.contains(&ExtraFeatures::Auth) {
return Err(Error::AuthNotSupported.into());
}
let mut conn = Client::new(Method::Signup);
conn.execute(router, Param::new(vec![credentials?])).await
})

View file

@ -11,6 +11,7 @@ use crate::api::Connect;
use crate::api::ExtraFeatures;
use crate::api::Result;
use crate::api::Surreal;
use crate::dbs::Level;
use flume::Receiver;
use once_cell::sync::OnceCell;
use std::collections::HashSet;
@ -33,6 +34,9 @@ impl IntoEndpoint<Test> for () {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -73,7 +77,6 @@ impl Connection for Client {
Box::pin(async move {
let (route_tx, route_rx) = flume::bounded(capacity);
let mut features = HashSet::new();
features.insert(ExtraFeatures::Auth);
features.insert(ExtraFeatures::Backup);
let router = Router {
features,

View file

@ -128,7 +128,6 @@ where
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) enum ExtraFeatures {
Auth,
Backup,
}

View file

@ -17,7 +17,7 @@ pub struct Signin;
pub trait Credentials<Action, Response>: Serialize {}
/// Credentials for the root user
#[derive(Debug, Serialize)]
#[derive(Debug, Clone, Copy, Serialize)]
pub struct Root<'a> {
/// The username of the root user
#[serde(rename = "user")]
@ -30,7 +30,7 @@ pub struct Root<'a> {
impl Credentials<Signin, ()> for Root<'_> {}
/// Credentials for the namespace user
#[derive(Debug, Serialize)]
#[derive(Debug, Clone, Copy, Serialize)]
pub struct Namespace<'a> {
/// The namespace the user has access to
#[serde(rename = "ns")]
@ -46,7 +46,7 @@ pub struct Namespace<'a> {
impl Credentials<Signin, Jwt> for Namespace<'_> {}
/// Credentials for the database user
#[derive(Debug, Serialize)]
#[derive(Debug, Clone, Copy, Serialize)]
pub struct Database<'a> {
/// The namespace the user has access to
#[serde(rename = "ns")]

View file

@ -1,10 +1,12 @@
use crate::api::engine::local::Db;
use crate::api::engine::local::FDb;
use crate::api::err::Error;
use crate::api::opt::auth::Root;
use crate::api::opt::Endpoint;
use crate::api::opt::IntoEndpoint;
use crate::api::opt::Strict;
use crate::api::Result;
use crate::dbs::Level;
use std::path::Path;
use url::Url;
@ -18,6 +20,7 @@ impl IntoEndpoint<FDb> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
})
}
}
@ -26,13 +29,8 @@ impl IntoEndpoint<FDb> for &Path {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("fdb://{}", self.display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let path = self.display();
IntoEndpoint::<FDb>::into_endpoint(path.as_str())
}
}
@ -43,12 +41,39 @@ where
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("fdb://{}", self.0.as_ref().display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: true,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let (path, _) = self;
let mut endpoint = IntoEndpoint::<FDb>::into_endpoint(path.as_ref());
endpoint.strict = true;
Ok(endpoint)
}
}
impl<T> IntoEndpoint<FDb> for (T, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, root) = self;
let mut endpoint = path.as_ref().into_endpoint()?;
endpoint.auth = Level::Kv;
endpoint.username = root.username.to_owned();
endpoint.password = root.password.to_owned();
Ok(endpoint)
}
}
impl<T> IntoEndpoint<FDb> for (T, Strict, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, _, root) = self;
let mut endpoint = (path, root).into_endpoint()?;
endpoint.strict = true;
Ok(endpoint)
}
}

View file

@ -7,6 +7,7 @@ use crate::api::opt::IntoEndpoint;
use crate::api::opt::Tls;
use crate::api::Endpoint;
use crate::api::Result;
use crate::dbs::Level;
use std::net::SocketAddr;
use url::Url;
@ -20,6 +21,9 @@ impl IntoEndpoint<Http> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -34,6 +38,9 @@ impl IntoEndpoint<Http> for SocketAddr {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -48,6 +55,9 @@ impl IntoEndpoint<Http> for String {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -62,6 +72,9 @@ impl IntoEndpoint<Https> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -76,6 +89,9 @@ impl IntoEndpoint<Https> for SocketAddr {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -90,6 +106,9 @@ impl IntoEndpoint<Https> for String {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -104,9 +123,9 @@ where
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config) = self;
let mut address = address.into_endpoint()?;
address.tls_config = Some(Tls::Native(config));
Ok(address)
let mut endpoint = address.into_endpoint()?;
endpoint.tls_config = Some(Tls::Native(config));
Ok(endpoint)
}
}
@ -120,8 +139,8 @@ where
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config) = self;
let mut address = address.into_endpoint()?;
address.tls_config = Some(Tls::Rust(config));
Ok(address)
let mut endpoint = address.into_endpoint()?;
endpoint.tls_config = Some(Tls::Rust(config));
Ok(endpoint)
}
}

View file

@ -1,10 +1,12 @@
use crate::api::engine::local::Db;
use crate::api::engine::local::IndxDb;
use crate::api::err::Error;
use crate::api::opt::auth::Root;
use crate::api::opt::Endpoint;
use crate::api::opt::IntoEndpoint;
use crate::api::opt::Strict;
use crate::api::Result;
use crate::dbs::Level;
use url::Url;
impl IntoEndpoint<IndxDb> for &str {
@ -17,6 +19,9 @@ impl IntoEndpoint<IndxDb> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -25,8 +30,32 @@ impl IntoEndpoint<IndxDb> for (&str, Strict) {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let mut address = IntoEndpoint::<IndxDb>::into_endpoint(self.0)?;
address.strict = true;
Ok(address)
let mut endpoint = IntoEndpoint::<IndxDb>::into_endpoint(self.0)?;
endpoint.strict = true;
Ok(endpoint)
}
}
impl IntoEndpoint<IndxDb> for (&str, Root<'_>) {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (name, root) = self;
let mut endpoint = IntoEndpoint::<IndxDb>::into_endpoint(name)?;
endpoint.auth = Level::Kv;
endpoint.username = root.username.to_owned();
endpoint.password = root.password.to_owned();
Ok(endpoint)
}
}
impl IntoEndpoint<IndxDb> for (&str, Strict, Root<'_>) {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (name, _, root) = self;
let mut endpoint = IntoEndpoint::<IndxDb>::into_endpoint((name, root))?;
endpoint.strict = true;
Ok(endpoint)
}
}

View file

@ -1,9 +1,11 @@
use crate::api::engine::local::Db;
use crate::api::engine::local::Mem;
use crate::api::opt::auth::Root;
use crate::api::opt::Endpoint;
use crate::api::opt::IntoEndpoint;
use crate::api::opt::Strict;
use crate::api::Result;
use crate::dbs::Level;
use url::Url;
impl IntoEndpoint<Mem> for () {
@ -15,6 +17,9 @@ impl IntoEndpoint<Mem> for () {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -23,8 +28,31 @@ impl IntoEndpoint<Mem> for Strict {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let mut address = IntoEndpoint::<Mem>::into_endpoint(())?;
address.strict = true;
Ok(address)
let mut endpoint = IntoEndpoint::<Mem>::into_endpoint(())?;
endpoint.strict = true;
Ok(endpoint)
}
}
impl IntoEndpoint<Mem> for Root<'_> {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let mut endpoint = IntoEndpoint::<Mem>::into_endpoint(())?;
endpoint.auth = Level::Kv;
endpoint.username = self.username.to_owned();
endpoint.password = self.password.to_owned();
Ok(endpoint)
}
}
impl IntoEndpoint<Mem> for (Strict, Root<'_>) {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (_, root) = self;
let mut endpoint = IntoEndpoint::<Mem>::into_endpoint(root)?;
endpoint.strict = true;
Ok(endpoint)
}
}

View file

@ -18,6 +18,7 @@ mod tikv;
use crate::api::Connection;
use crate::api::Result;
use crate::dbs::Level;
use url::Url;
/// A server address used to connect to the server
@ -29,6 +30,11 @@ pub struct Endpoint {
pub(crate) strict: bool,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
pub(crate) tls_config: Option<super::Tls>,
// Only used by the local engines
// `Level::No` in this context means no authentication information was configured
pub(crate) auth: Level,
pub(crate) username: String,
pub(crate) password: String,
}
/// A trait for converting inputs to a server address object

View file

@ -2,10 +2,12 @@ use crate::api::engine::local::Db;
use crate::api::engine::local::File;
use crate::api::engine::local::RocksDb;
use crate::api::err::Error;
use crate::api::opt::auth::Root;
use crate::api::opt::Endpoint;
use crate::api::opt::IntoEndpoint;
use crate::api::opt::Strict;
use crate::api::Result;
use crate::dbs::Level;
use std::path::Path;
use url::Url;
@ -19,6 +21,9 @@ impl IntoEndpoint<RocksDb> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -27,13 +32,8 @@ impl IntoEndpoint<RocksDb> for &Path {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("rocksdb://{}", self.display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let path = self.display().to_string();
IntoEndpoint::<RocksDb>::into_endpoint(path.as_str())
}
}
@ -44,13 +44,40 @@ where
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("rocksdb://{}", self.0.as_ref().display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: true,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let (path, _) = self;
let mut endpoint = IntoEndpoint::<RocksDb>::into_endpoint(path.as_ref())?;
endpoint.strict = true;
Ok(endpoint)
}
}
impl<T> IntoEndpoint<RocksDb> for (T, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, root) = self;
let mut endpoint = IntoEndpoint::<RocksDb>::into_endpoint(path.as_ref())?;
endpoint.auth = Level::Kv;
endpoint.username = root.username.to_owned();
endpoint.password = root.password.to_owned();
Ok(endpoint)
}
}
impl<T> IntoEndpoint<RocksDb> for (T, Strict, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, _, root) = self;
let mut endpoint = IntoEndpoint::<RocksDb>::into_endpoint((path.as_ref(), root))?;
endpoint.strict = true;
Ok(endpoint)
}
}
@ -64,6 +91,9 @@ impl IntoEndpoint<File> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -72,13 +102,8 @@ impl IntoEndpoint<File> for &Path {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("file://{}", self.display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let path = self.display().to_string();
IntoEndpoint::<File>::into_endpoint(path.as_str())
}
}
@ -89,12 +114,39 @@ where
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("file://{}", self.0.as_ref().display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: true,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let (path, _) = self;
let mut endpoint = IntoEndpoint::<RocksDb>::into_endpoint(path.as_ref())?;
endpoint.strict = true;
Ok(endpoint)
}
}
impl<T> IntoEndpoint<File> for (T, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, root) = self;
let mut endpoint = IntoEndpoint::<File>::into_endpoint(path.as_ref())?;
endpoint.auth = Level::Kv;
endpoint.username = root.username.to_owned();
endpoint.password = root.password.to_owned();
Ok(endpoint)
}
}
impl<T> IntoEndpoint<File> for (T, Strict, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, _, root) = self;
let mut endpoint = IntoEndpoint::<File>::into_endpoint((path.as_ref(), root))?;
endpoint.strict = true;
Ok(endpoint)
}
}

View file

@ -5,6 +5,8 @@ use crate::api::opt::Endpoint;
use crate::api::opt::IntoEndpoint;
use crate::api::opt::Strict;
use crate::api::Result;
use crate::dbs::Level;
use crate::opt::auth::Root;
use std::path::Path;
use url::Url;
@ -18,6 +20,9 @@ impl IntoEndpoint<SpeeDb> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -26,13 +31,8 @@ impl IntoEndpoint<SpeeDb> for &Path {
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("speedb://{}", self.display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let path = self.display().to_string();
IntoEndpoint::<SpeeDb>::into_endpoint(path.as_str())
}
}
@ -43,12 +43,39 @@ where
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let url = format!("speedb://{}", self.0.as_ref().display());
Ok(Endpoint {
endpoint: Url::parse(&url).map_err(|_| Error::InvalidUrl(url))?,
strict: true,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
})
let (path, _) = self;
let mut endpoint = IntoEndpoint::<SpeeDb>::into_endpoint(path.as_ref())?;
endpoint.strict = true;
Ok(endpoint)
}
}
impl<T> IntoEndpoint<SpeeDb> for (T, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, root) = self;
let mut endpoint = IntoEndpoint::<SpeeDb>::into_endpoint(path.as_ref())?;
endpoint.auth = Level::Kv;
endpoint.username = root.username.to_owned();
endpoint.password = root.password.to_owned();
Ok(endpoint)
}
}
impl<T> IntoEndpoint<SpeeDb> for (T, Strict, Root<'_>)
where
T: AsRef<Path>,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (path, _, root) = self;
let mut endpoint = IntoEndpoint::<SpeeDb>::into_endpoint((path.as_ref(), root))?;
endpoint.strict = true;
Ok(endpoint)
}
}

View file

@ -1,10 +1,13 @@
use crate::api::engine::local::Db;
use crate::api::engine::local::TiKv;
use crate::api::err::Error;
use crate::api::opt::auth::Root;
use crate::api::opt::Endpoint;
use crate::api::opt::IntoEndpoint;
use crate::api::opt::Strict;
use crate::api::Result;
use crate::dbs::Level;
use std::fmt::Display;
use std::net::SocketAddr;
use url::Url;
@ -18,6 +21,9 @@ impl IntoEndpoint<TiKv> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -32,6 +38,9 @@ impl IntoEndpoint<TiKv> for SocketAddr {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -46,19 +55,53 @@ impl IntoEndpoint<TiKv> for String {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
impl<T> IntoEndpoint<TiKv> for (T, Strict)
where
T: IntoEndpoint<TiKv>,
T: IntoEndpoint<TiKv> + Display,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let mut address = self.0.into_endpoint()?;
address.strict = true;
Ok(address)
let (address, _) = self;
let mut endpoint = address.into_endpoint()?;
endpoint.strict = true;
Ok(endpoint)
}
}
impl<T> IntoEndpoint<TiKv> for (T, Root<'_>)
where
T: IntoEndpoint<TiKv> + Display,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (address, root) = self;
let mut endpoint = address.into_endpoint()?;
endpoint.auth = Level::Kv;
endpoint.username = root.username.to_owned();
endpoint.password = root.password.to_owned();
Ok(endpoint)
}
}
impl<T> IntoEndpoint<TiKv> for (T, Strict, Root<'_>)
where
T: IntoEndpoint<TiKv> + Display,
{
type Client = Db;
fn into_endpoint(self) -> Result<Endpoint> {
let (address, _, root) = self;
let mut endpoint = (address, root).into_endpoint()?;
endpoint.strict = true;
Ok(endpoint)
}
}

View file

@ -7,6 +7,7 @@ use crate::api::opt::IntoEndpoint;
#[cfg(any(feature = "native-tls", feature = "rustls"))]
use crate::api::opt::Tls;
use crate::api::Result;
use crate::dbs::Level;
use std::net::SocketAddr;
use url::Url;
@ -20,6 +21,9 @@ impl IntoEndpoint<Ws> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -34,6 +38,9 @@ impl IntoEndpoint<Ws> for SocketAddr {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -48,6 +55,9 @@ impl IntoEndpoint<Ws> for String {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -62,6 +72,9 @@ impl IntoEndpoint<Wss> for &str {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -76,6 +89,9 @@ impl IntoEndpoint<Wss> for SocketAddr {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -90,6 +106,9 @@ impl IntoEndpoint<Wss> for String {
strict: false,
#[cfg(any(feature = "native-tls", feature = "rustls"))]
tls_config: None,
auth: Level::No,
username: String::new(),
password: String::new(),
})
}
}
@ -104,9 +123,9 @@ where
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config) = self;
let mut address = address.into_endpoint()?;
address.tls_config = Some(Tls::Native(config));
Ok(address)
let mut endpoint = address.into_endpoint()?;
endpoint.tls_config = Some(Tls::Native(config));
Ok(endpoint)
}
}
@ -120,8 +139,8 @@ where
fn into_endpoint(self) -> Result<Endpoint> {
let (address, config) = self;
let mut address = address.into_endpoint()?;
address.tls_config = Some(Tls::Rust(config));
Ok(address)
let mut endpoint = address.into_endpoint()?;
endpoint.tls_config = Some(Tls::Rust(config));
Ok(endpoint)
}
}

View file

@ -20,3 +20,6 @@ pub const ID_CHARS: [char; 36] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
];
/// The publicly visible name of the server
pub const SERVER_NAME: &str = "SurrealDB";

View file

@ -1,8 +1,10 @@
use crate::sql::idiom::Idiom;
use crate::sql::value::Value;
use base64_lib::DecodeError as Base64Error;
use bincode::Error as BincodeError;
use bung::encode::Error as SerdeError;
use fst::Error as FstError;
use jsonwebtoken::errors::Error as JWTError;
use serde::Serialize;
use std::borrow::Cow;
use std::string::FromUtf8Error;
@ -75,6 +77,10 @@ pub enum Error {
#[error("The SQL query was not parsed fully")]
QueryRemaining,
/// There was an error with authentication
#[error("There was a problem with authentication")]
InvalidAuth,
/// There was an error with the SQL query
#[error("Parse error on line {line} at character {char} when parsing '{sql}'")]
InvalidQuery {
@ -459,6 +465,18 @@ impl From<Error> for String {
}
}
impl From<Base64Error> for Error {
fn from(_: Base64Error) -> Error {
Error::InvalidAuth
}
}
impl From<JWTError> for Error {
fn from(_: JWTError) -> Error {
Error::InvalidAuth
}
}
#[cfg(feature = "kv-mem")]
impl From<echodb::err::Error> for Error {
fn from(e: echodb::err::Error) -> Error {

View file

@ -1,9 +1,9 @@
use base64::alphabet::STANDARD;
use base64::engine::general_purpose::GeneralPurpose;
use base64::engine::general_purpose::GeneralPurposeConfig;
use base64::engine::DecodePaddingMode;
use base64_lib::alphabet::STANDARD;
use base64_lib::engine::general_purpose::GeneralPurpose;
use base64_lib::engine::general_purpose::GeneralPurposeConfig;
use base64_lib::engine::DecodePaddingMode;
pub use base64::Engine;
pub use base64_lib::Engine;
pub const BASE64: GeneralPurpose = GeneralPurpose::new(&STANDARD, CONFIG);

View file

@ -1,9 +1,9 @@
use crate::dbs::Auth;
use crate::dbs::Session;
use crate::err::Error;
use std::sync::Arc;
use surrealdb::dbs::Auth;
use surrealdb::dbs::Session;
pub async fn clear(session: &mut Session) -> Result<(), Error> {
pub fn clear(session: &mut Session) -> Result<(), Error> {
session.au = Arc::new(Auth::No);
session.tk = None;
session.sc = None;

10
lib/src/iam/mod.rs Normal file
View file

@ -0,0 +1,10 @@
pub mod base;
pub mod clear;
pub mod parse;
pub mod signin;
pub mod signup;
pub mod token;
pub mod verify;
pub const LOG: &str = "surrealdb::iam";
pub const TOKEN: &str = "Bearer ";

View file

@ -1,8 +1,8 @@
use crate::err::Error;
use crate::iam::base::{Engine, BASE64};
use crate::sql::json;
use crate::sql::Value;
use std::str;
use surrealdb::sql::json;
use surrealdb::sql::Value;
pub fn parse(value: &str) -> Result<Value, Error> {
// Extract the middle part of the token

View file

@ -1,19 +1,25 @@
use crate::cli::CF;
use crate::cnf::SERVER_NAME;
use crate::dbs::DB;
use crate::dbs::Auth;
use crate::dbs::Session;
use crate::err::Error;
use crate::iam::token::{Claims, HEADER};
use crate::kvs::Datastore;
use crate::opt::auth::Root;
use crate::sql::Object;
use crate::sql::Value;
use argon2::password_hash::{PasswordHash, PasswordVerifier};
use argon2::Argon2;
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey};
use std::sync::Arc;
use surrealdb::dbs::Auth;
use surrealdb::dbs::Session;
use surrealdb::sql::Object;
use surrealdb::sql::Value;
pub async fn signin(session: &mut Session, vars: Object) -> Result<Option<String>, Error> {
pub async fn signin(
kvs: &Datastore,
configured_root: &Option<Root<'_>>,
strict: bool,
session: &mut Session,
vars: Object,
) -> Result<Option<String>, Error> {
// Parse the specified variables
let ns = vars.get("NS").or_else(|| vars.get("ns"));
let db = vars.get("DB").or_else(|| vars.get("db"));
@ -26,7 +32,7 @@ pub async fn signin(session: &mut Session, vars: Object) -> Result<Option<String
let db = db.to_raw_string();
let sc = sc.to_raw_string();
// Attempt to signin to specified scope
super::signin::sc(session, ns, db, sc, vars).await
super::signin::sc(kvs, strict, session, ns, db, sc, vars).await
}
(Some(ns), Some(db), None) => {
// Get the provided user and pass
@ -42,7 +48,7 @@ pub async fn signin(session: &mut Session, vars: Object) -> Result<Option<String
let user = user.to_raw_string();
let pass = pass.to_raw_string();
// Attempt to signin to database
super::signin::db(session, ns, db, user, pass).await
super::signin::db(kvs, session, ns, db, user, pass).await
}
// There is no username or password
_ => Err(Error::InvalidAuth),
@ -61,7 +67,7 @@ pub async fn signin(session: &mut Session, vars: Object) -> Result<Option<String
let user = user.to_raw_string();
let pass = pass.to_raw_string();
// Attempt to signin to namespace
super::signin::ns(session, ns, user, pass).await
super::signin::ns(kvs, session, ns, user, pass).await
}
// There is no username or password
_ => Err(Error::InvalidAuth),
@ -79,7 +85,8 @@ pub async fn signin(session: &mut Session, vars: Object) -> Result<Option<String
let user = user.to_raw_string();
let pass = pass.to_raw_string();
// Attempt to signin to namespace
super::signin::su(session, user, pass).await
super::signin::su(configured_root, session, user, pass)?;
Ok(None)
}
// There is no username or password
_ => Err(Error::InvalidAuth),
@ -90,16 +97,14 @@ pub async fn signin(session: &mut Session, vars: Object) -> Result<Option<String
}
pub async fn sc(
kvs: &Datastore,
strict: bool,
session: &mut Session,
ns: String,
db: String,
sc: String,
vars: Object,
) -> Result<Option<String>, Error> {
// Get a database reference
let kvs = DB.get().unwrap();
// Get local copy of options
let opt = CF.get().unwrap();
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Check if the supplied NS Login exists
@ -113,7 +118,7 @@ pub async fn sc(
// Setup the query session
let sess = Session::for_db(&ns, &db);
// Compute the value with the params
match kvs.compute(val, &sess, vars, opt.strict).await {
match kvs.compute(val, &sess, vars, strict).await {
// The signin value succeeded
Ok(val) => match val.record() {
// There is a record returned
@ -174,14 +179,13 @@ pub async fn sc(
}
pub async fn db(
kvs: &Datastore,
session: &mut Session,
ns: String,
db: String,
user: String,
pass: String,
) -> Result<Option<String>, Error> {
// Get a database reference
let kvs = DB.get().unwrap();
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Check if the supplied DB Login exists
@ -230,13 +234,12 @@ pub async fn db(
}
pub async fn ns(
kvs: &Datastore,
session: &mut Session,
ns: String,
user: String,
pass: String,
) -> Result<Option<String>, Error> {
// Get a database reference
let kvs = DB.get().unwrap();
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Check if the supplied NS Login exists
@ -282,18 +285,17 @@ pub async fn ns(
}
}
pub async fn su(
pub fn su(
configured_root: &Option<Root<'_>>,
session: &mut Session,
user: String,
pass: String,
) -> Result<Option<String>, Error> {
// Get the config options
let opts = CF.get().unwrap();
) -> Result<(), Error> {
// Attempt to verify the root user
if let Some(root) = &opts.pass {
if user == opts.user && &pass == root {
if let Some(root) = configured_root {
if user == root.username && pass == root.password {
session.au = Arc::new(Auth::Kv);
return Ok(None);
return Ok(());
}
}
// The specified user login does not exist

View file

@ -1,17 +1,21 @@
use crate::cli::CF;
use crate::cnf::SERVER_NAME;
use crate::dbs::DB;
use crate::dbs::Auth;
use crate::dbs::Session;
use crate::err::Error;
use crate::iam::token::{Claims, HEADER};
use crate::kvs::Datastore;
use crate::sql::Object;
use crate::sql::Value;
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey};
use std::sync::Arc;
use surrealdb::dbs::Auth;
use surrealdb::dbs::Session;
use surrealdb::sql::Object;
use surrealdb::sql::Value;
pub async fn signup(session: &mut Session, vars: Object) -> Result<Option<String>, Error> {
pub async fn signup(
kvs: &Datastore,
strict: bool,
session: &mut Session,
vars: Object,
) -> Result<Option<String>, Error> {
// Parse the specified variables
let ns = vars.get("NS").or_else(|| vars.get("ns"));
let db = vars.get("DB").or_else(|| vars.get("db"));
@ -24,23 +28,21 @@ pub async fn signup(session: &mut Session, vars: Object) -> Result<Option<String
let db = db.to_raw_string();
let sc = sc.to_raw_string();
// Attempt to signin to specified scope
super::signup::sc(session, ns, db, sc, vars).await
super::signup::sc(kvs, strict, session, ns, db, sc, vars).await
}
_ => Err(Error::InvalidAuth),
}
}
pub async fn sc(
kvs: &Datastore,
strict: bool,
session: &mut Session,
ns: String,
db: String,
sc: String,
vars: Object,
) -> Result<Option<String>, Error> {
// Get a database reference
let kvs = DB.get().unwrap();
// Get local copy of options
let opt = CF.get().unwrap();
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Check if the supplied NS Login exists
@ -54,7 +56,7 @@ pub async fn sc(
// Setup the query session
let sess = Session::for_db(&ns, &db);
// Compute the value with the params
match kvs.compute(val, &sess, vars, opt.strict).await {
match kvs.compute(val, &sess, vars, strict).await {
// The signin value succeeded
Ok(val) => match val.record() {
// There is a record returned

View file

@ -1,8 +1,8 @@
use crate::sql::Object;
use crate::sql::Value;
use jsonwebtoken::{Algorithm, Header};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use surrealdb::sql::Object;
use surrealdb::sql::Value;
pub static HEADER: Lazy<Header> = Lazy::new(|| Header::new(Algorithm::HS512));

270
lib/src/iam/verify.rs Normal file
View file

@ -0,0 +1,270 @@
use crate::dbs::Auth;
use crate::dbs::Session;
use crate::err::Error;
use crate::iam::token::Claims;
use crate::iam::LOG;
use crate::iam::TOKEN;
use crate::kvs::Datastore;
use crate::sql::Algorithm;
use crate::sql::Value;
use chrono::Utc;
use jsonwebtoken::{decode, DecodingKey, Validation};
use once_cell::sync::Lazy;
use std::sync::Arc;
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),
)),
}
}
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 = false;
validation.validate_exp = false;
validation
});
pub async fn token(kvs: &Datastore, session: &mut Session, auth: String) -> Result<(), Error> {
// Log the authentication type
trace!(target: LOG, "Attempting token authentication");
// Retrieve just the auth data
let auth = auth.trim_start_matches(TOKEN).trim();
// Decode the token without verifying
let token = decode::<Claims>(auth, &KEY, &DUD)?;
// Parse the token and catch any errors
let value = super::parse::parse(auth)?;
// Check if the auth token can be used
if let Some(nbf) = token.claims.nbf {
if nbf > Utc::now().timestamp() {
trace!(target: LOG, "The 'nbf' field in the authentication token was invalid");
return Err(Error::InvalidAuth);
}
}
// Check if the auth token has expired
if let Some(exp) = token.claims.exp {
if exp < Utc::now().timestamp() {
trace!(target: LOG, "The 'exp' field in the authentication token was invalid");
return Err(Error::InvalidAuth);
}
}
// 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),
id,
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to scope `{}` with token `{}`", sc, tk);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Parse the record id
let id = match id {
Some(id) => crate::sql::thing(&id)?.into(),
None => Value::None,
};
// 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)?;
// Log the success
debug!(target: LOG, "Authenticated to scope `{}` with token `{}`", sc, tk);
// Set the session
session.sd = Some(id);
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.sc = Some(sc.to_owned());
session.au = Arc::new(Auth::Sc(ns, db, sc));
Ok(())
}
// Check if this is scope authentication
Claims {
ns: Some(ns),
db: Some(db),
sc: Some(sc),
id: Some(id),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to scope `{}`", sc);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Parse the record id
let id = crate::sql::thing(&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)?;
// Log the success
debug!(target: LOG, "Authenticated to scope `{}`", sc);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.sc = Some(sc.to_owned());
session.sd = Some(Value::from(id));
session.au = Arc::new(Auth::Sc(ns, db, sc));
Ok(())
}
// Check if this is database token authentication
Claims {
ns: Some(ns),
db: Some(db),
tk: Some(tk),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to database `{}` with token `{}`", db, tk);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
debug!(target: LOG, "Authenticated to database `{}` with token `{}`", db, tk);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.au = Arc::new(Auth::Db(ns, db));
Ok(())
}
// Check if this is database authentication
Claims {
ns: Some(ns),
db: Some(db),
id: Some(id),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to database `{}` with login `{}`", db, id);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
debug!(target: LOG, "Authenticated to database `{}` with login `{}`", db, id);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.au = Arc::new(Auth::Db(ns, db));
Ok(())
}
// Check if this is namespace token authentication
Claims {
ns: Some(ns),
tk: Some(tk),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to namespace `{}` with token `{}`", ns, tk);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
trace!(target: LOG, "Authenticated to namespace `{}` with token `{}`", ns, tk);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.au = Arc::new(Auth::Ns(ns));
Ok(())
}
// Check if this is namespace authentication
Claims {
ns: Some(ns),
id: Some(id),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to namespace `{}` with login `{}`", ns, id);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
trace!(target: LOG, "Authenticated to namespace `{}` with login `{}`", ns, id);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.au = Arc::new(Auth::Ns(ns));
Ok(())
}
// There was an auth error
_ => Err(Error::InvalidAuth),
}
}

View file

@ -107,7 +107,6 @@ extern crate log;
mod mac;
mod api;
mod cnf;
mod ctx;
mod doc;
mod exe;
@ -116,12 +115,24 @@ mod key;
pub mod sql;
#[doc(hidden)]
pub mod cnf;
#[doc(hidden)]
pub mod dbs;
#[doc(hidden)]
pub mod env;
#[doc(hidden)]
pub mod err;
#[cfg(any(
feature = "kv-mem",
feature = "kv-tikv",
feature = "kv-rocksdb",
feature = "kv-speedb",
feature = "kv-fdb",
feature = "kv-indxdb",
))]
#[doc(hidden)]
pub mod iam;
#[doc(hidden)]
pub mod idx;
#[doc(hidden)]

View file

@ -72,7 +72,6 @@ mod api_integration {
}
include!("api/mod.rs");
include!("api/auth.rs");
}
#[cfg(feature = "protocol-http")]
@ -93,7 +92,6 @@ mod api_integration {
}
include!("api/mod.rs");
include!("api/auth.rs");
include!("api/backup.rs");
}
@ -105,7 +103,13 @@ mod api_integration {
use surrealdb::engine::local::Mem;
async fn new_db() -> Surreal<Db> {
Surreal::new::<Mem>(()).await.unwrap()
let root = Root {
username: ROOT_USER,
password: ROOT_PASS,
};
let db = Surreal::new::<Mem>(root).await.unwrap();
db.signin(root).await.unwrap();
db
}
#[tokio::test]
@ -113,6 +117,43 @@ mod api_integration {
any::connect("memory").await.unwrap();
}
#[tokio::test]
async fn signin_first_not_necessary() {
let db = Surreal::new::<Mem>(()).await.unwrap();
db.use_ns("namespace").use_db("database").await.unwrap();
let Some(record): Option<RecordId> = db.create(("item", "foo")).await.unwrap() else {
panic!("record not found");
};
assert_eq!(record.id.to_string(), "item:foo");
}
#[tokio::test]
async fn cant_sign_into_default_root_account() {
let db = Surreal::new::<Mem>(()).await.unwrap();
let Error::Db(DbError::InvalidAuth) = db.signin(Root {
username: ROOT_USER,
password: ROOT_PASS,
})
.await
.unwrap_err() else {
panic!("unexpected successful login");
};
}
#[tokio::test]
async fn credentials_activate_authentication() {
let db = Surreal::new::<Mem>(Root {
username: ROOT_USER,
password: ROOT_PASS,
})
.await
.unwrap();
db.use_ns("namespace").use_db("database").await.unwrap();
let Error::Db(DbError::QueryPermissions) = db.create(Resource::from("item:foo")).await.unwrap_err() else {
panic!("record not found");
};
}
include!("api/mod.rs");
include!("api/backup.rs");
}
@ -125,7 +166,13 @@ mod api_integration {
async fn new_db() -> Surreal<Db> {
let path = format!("/tmp/{}.db", Ulid::new());
Surreal::new::<File>(path.as_str()).await.unwrap()
let root = Root {
username: ROOT_USER,
password: ROOT_PASS,
};
let db = Surreal::new::<File>((path.as_str(), root)).await.unwrap();
db.signin(root).await.unwrap();
db
}
include!("api/mod.rs");
@ -169,7 +216,13 @@ mod api_integration {
use surrealdb::engine::local::TiKv;
async fn new_db() -> Surreal<Db> {
Surreal::new::<TiKv>("127.0.0.1:2379").await.unwrap()
let root = Root {
username: ROOT_USER,
password: ROOT_PASS,
};
let db = Surreal::new::<TiKv>(("127.0.0.1:2379", root)).await.unwrap();
db.signin(root).await.unwrap();
db
}
include!("api/mod.rs");
@ -183,7 +236,13 @@ mod api_integration {
use surrealdb::engine::local::FDb;
async fn new_db() -> Surreal<Db> {
Surreal::new::<FDb>("/tmp/fdb.cluster").await.unwrap()
let root = Root {
username: ROOT_USER,
password: ROOT_PASS,
};
let db = Surreal::new::<FDb>(("/tmp/fdb.cluster", root)).await.unwrap();
db.signin(root).await.unwrap();
db
}
include!("api/mod.rs");
@ -207,7 +266,6 @@ mod api_integration {
}
include!("api/mod.rs");
include!("api/auth.rs");
include!("api/backup.rs");
}
}

View file

@ -1,138 +0,0 @@
// Auth tests
// Supported by both HTTP and WS protocols
#[tokio::test]
async fn invalidate() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
db.invalidate().await.unwrap();
let error = db.create::<Option<RecordId>>(("user", "john")).await.unwrap_err();
assert!(error.to_string().contains("You don't have permission to perform this query type"));
}
#[tokio::test]
async fn signup_scope() {
let db = new_db().await;
let database = Ulid::new().to_string();
db.use_ns(NS).use_db(&database).await.unwrap();
let scope = Ulid::new().to_string();
let sql = format!(
"
DEFINE SCOPE {scope} SESSION 1s
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
"
);
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signup(Scope {
namespace: NS,
database: &database,
scope: &scope,
params: AuthParams {
email: "john.doe@example.com",
pass: "password123",
},
})
.await
.unwrap();
}
#[tokio::test]
async fn signin_ns() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let user = Ulid::new().to_string();
let pass = "password123";
let sql = format!("DEFINE LOGIN {user} ON NAMESPACE PASSWORD '{pass}'");
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signin(Namespace {
namespace: NS,
username: &user,
password: pass,
})
.await
.unwrap();
}
#[tokio::test]
async fn signin_db() {
let db = new_db().await;
let database = Ulid::new().to_string();
db.use_ns(NS).use_db(&database).await.unwrap();
let user = Ulid::new().to_string();
let pass = "password123";
let sql = format!("DEFINE LOGIN {user} ON DATABASE PASSWORD '{pass}'");
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signin(Database {
namespace: NS,
database: &database,
username: &user,
password: pass,
})
.await
.unwrap();
}
#[tokio::test]
async fn signin_scope() {
let db = new_db().await;
let database = Ulid::new().to_string();
db.use_ns(NS).use_db(&database).await.unwrap();
let scope = Ulid::new().to_string();
let email = format!("{scope}@example.com");
let pass = "password123";
let sql = format!(
"
DEFINE SCOPE {scope} SESSION 1s
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
"
);
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signup(Scope {
namespace: NS,
database: &database,
scope: &scope,
params: AuthParams {
pass,
email: &email,
},
})
.await
.unwrap();
db.signin(Scope {
namespace: NS,
database: &database,
scope: &scope,
params: AuthParams {
pass,
email: &email,
},
})
.await
.unwrap();
}
#[tokio::test]
async fn authenticate() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let user = Ulid::new().to_string();
let pass = "password123";
let sql = format!("DEFINE LOGIN {user} ON NAMESPACE PASSWORD '{pass}'");
let response = db.query(sql).await.unwrap();
response.check().unwrap();
let token = db
.signin(Namespace {
namespace: NS,
username: &user,
password: pass,
})
.await
.unwrap();
db.authenticate(token).await.unwrap();
}

View file

@ -29,6 +29,142 @@ async fn yuse() {
db.create(Resource::from(item)).await.unwrap();
}
#[tokio::test]
async fn invalidate() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
db.invalidate().await.unwrap();
let error = db.create::<Option<RecordId>>(("user", "john")).await.unwrap_err();
assert!(error.to_string().contains("You don't have permission to perform this query type"));
}
#[tokio::test]
async fn signup_scope() {
let db = new_db().await;
let database = Ulid::new().to_string();
db.use_ns(NS).use_db(&database).await.unwrap();
let scope = Ulid::new().to_string();
let sql = format!(
"
DEFINE SCOPE {scope} SESSION 1s
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
"
);
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signup(Scope {
namespace: NS,
database: &database,
scope: &scope,
params: AuthParams {
email: "john.doe@example.com",
pass: "password123",
},
})
.await
.unwrap();
}
#[tokio::test]
async fn signin_ns() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let user = Ulid::new().to_string();
let pass = "password123";
let sql = format!("DEFINE LOGIN {user} ON NAMESPACE PASSWORD '{pass}'");
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signin(Namespace {
namespace: NS,
username: &user,
password: pass,
})
.await
.unwrap();
}
#[tokio::test]
async fn signin_db() {
let db = new_db().await;
let database = Ulid::new().to_string();
db.use_ns(NS).use_db(&database).await.unwrap();
let user = Ulid::new().to_string();
let pass = "password123";
let sql = format!("DEFINE LOGIN {user} ON DATABASE PASSWORD '{pass}'");
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signin(Database {
namespace: NS,
database: &database,
username: &user,
password: pass,
})
.await
.unwrap();
}
#[tokio::test]
async fn signin_scope() {
let db = new_db().await;
let database = Ulid::new().to_string();
db.use_ns(NS).use_db(&database).await.unwrap();
let scope = Ulid::new().to_string();
let email = format!("{scope}@example.com");
let pass = "password123";
let sql = format!(
"
DEFINE SCOPE {scope} SESSION 1s
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
"
);
let response = db.query(sql).await.unwrap();
response.check().unwrap();
db.signup(Scope {
namespace: NS,
database: &database,
scope: &scope,
params: AuthParams {
pass,
email: &email,
},
})
.await
.unwrap();
db.signin(Scope {
namespace: NS,
database: &database,
scope: &scope,
params: AuthParams {
pass,
email: &email,
},
})
.await
.unwrap();
}
#[tokio::test]
async fn authenticate() {
let db = new_db().await;
db.use_ns(NS).use_db(Ulid::new().to_string()).await.unwrap();
let user = Ulid::new().to_string();
let pass = "password123";
let sql = format!("DEFINE LOGIN {user} ON NAMESPACE PASSWORD '{pass}'");
let response = db.query(sql).await.unwrap();
response.check().unwrap();
let token = db
.signin(Namespace {
namespace: NS,
username: &user,
password: pass,
})
.await
.unwrap();
db.authenticate(token).await.unwrap();
}
#[tokio::test]
async fn query() {
let db = new_db().await;

View file

@ -5,9 +5,7 @@ use crate::cli::LOG;
use crate::err::Error;
use clap::Args;
use surrealdb::engine::any::connect;
use surrealdb::error::Api as ApiError;
use surrealdb::opt::auth::Root;
use surrealdb::Error as SurrealError;
#[derive(Args, Debug)]
pub struct ExportCommandArguments {
@ -43,22 +41,14 @@ pub async fn init(
// Initialize opentelemetry and logging
crate::o11y::builder().with_log_level("error").init();
// Connect to the database engine
let client = connect(endpoint).await?;
// Sign in to the server if the specified database engine supports it
let root = Root {
username: &username,
password: &password,
};
if let Err(error) = client.signin(root).await {
match error {
// Authentication not supported by this engine, we can safely continue
SurrealError::Api(ApiError::AuthNotSupported) => {}
error => {
return Err(error.into());
}
}
}
// Connect to the database engine
let client = connect((endpoint, root)).await?;
// Sign in to the server
client.signin(root).await?;
// Use the specified namespace / database
client.use_ns(ns).use_db(db).await?;
// Export the data from the database

View file

@ -5,9 +5,7 @@ use crate::cli::LOG;
use crate::err::Error;
use clap::Args;
use surrealdb::engine::any::connect;
use surrealdb::error::Api as ApiError;
use surrealdb::opt::auth::Root;
use surrealdb::Error as SurrealError;
#[derive(Args, Debug)]
pub struct ImportCommandArguments {
@ -41,22 +39,14 @@ pub async fn init(
// Initialize opentelemetry and logging
crate::o11y::builder().with_log_level("error").init();
// Connect to the database engine
let client = connect(endpoint).await?;
// Sign in to the server if the specified database engine supports it
let root = Root {
username: &username,
password: &password,
};
if let Err(error) = client.signin(root).await {
match error {
// Authentication not supported by this engine, we can safely continue
SurrealError::Api(ApiError::AuthNotSupported) => {}
error => {
return Err(error.into());
}
}
}
// Connect to the database engine
let client = connect((endpoint, root)).await?;
// Sign in to the server
client.signin(root).await?;
// Use the specified namespace / database
client.use_ns(ns).use_db(db).await?;
// Import the data into the database

View file

@ -9,10 +9,9 @@ use rustyline::{Completer, Editor, Helper, Highlighter, Hinter};
use serde::Serialize;
use serde_json::ser::PrettyFormatter;
use surrealdb::engine::any::connect;
use surrealdb::error::Api as ApiError;
use surrealdb::opt::auth::Root;
use surrealdb::sql::{self, Statement, Value};
use surrealdb::{Error as SurrealError, Response};
use surrealdb::Response;
#[derive(Args, Debug)]
pub struct SqlCommandArguments {
@ -52,22 +51,14 @@ pub async fn init(
// Initialize opentelemetry and logging
crate::o11y::builder().with_log_level("warn").init();
// Connect to the database engine
let client = connect(endpoint).await?;
// Sign in to the server if the specified database engine supports it
let root = Root {
username: &username,
password: &password,
};
if let Err(error) = client.signin(root).await {
match error {
// Authentication not supported by this engine, we can safely continue
SurrealError::Api(ApiError::AuthNotSupported) => {}
error => {
return Err(error.into());
}
}
}
// Connect to the database engine
let client = connect((endpoint, root)).await?;
// Sign in to the server
client.signin(root).await?;
// Create a new terminal REPL
let mut rl = Editor::new().unwrap();
// Set custom input validation

View file

@ -16,9 +16,6 @@ Y88b d88P Y88b 888 888 888 Y8b. 888 888 888 888 .d88P 888 d88P
/// The publicly visible name of the server
pub const PKG_NAME: &str = "surrealdb";
/// The publicly visible name of the server
pub const SERVER_NAME: &str = "SurrealDB";
/// The publicly visible user-agent of the command-line tool
pub const SERVER_AGENT: &str = concat!("SurrealDB ", env!("CARGO_PKG_VERSION"));

View file

@ -1,5 +1,4 @@
use base64::DecodeError as Base64Error;
use jsonwebtoken::errors::Error as JWTError;
use reqwest::Error as ReqwestError;
use serde::Serialize;
use serde_cbor::error::Error as CborError;
@ -72,12 +71,6 @@ impl From<Utf8Error> for Error {
}
}
impl From<JWTError> for Error {
fn from(_: JWTError) -> Error {
Error::InvalidAuth
}
}
impl From<surrealdb::error::Db> for Error {
fn from(error: surrealdb::error::Db) -> Error {
Error::Db(error.into())

View file

@ -1,18 +1,10 @@
pub mod base;
pub mod clear;
pub mod parse;
pub mod signin;
pub mod signup;
pub mod token;
pub mod verify;
use crate::cli::CF;
use crate::err::Error;
use surrealdb::iam::LOG;
pub const BASIC: &str = "Basic ";
pub const TOKEN: &str = "Bearer ";
const LOG: &str = "surrealdb::iam";
pub async fn init() -> Result<(), Error> {
// Get local copy of options

View file

@ -1,88 +1,14 @@
use crate::cli::CF;
use crate::dbs::DB;
use crate::err::Error;
use crate::iam::base::{Engine, BASE64};
use crate::iam::token::Claims;
use crate::iam::BASIC;
use crate::iam::LOG;
use crate::iam::TOKEN;
use argon2::password_hash::{PasswordHash, PasswordVerifier};
use argon2::Argon2;
use chrono::Utc;
use jsonwebtoken::{decode, DecodingKey, Validation};
use once_cell::sync::Lazy;
use std::sync::Arc;
use surrealdb::dbs::Auth;
use surrealdb::dbs::Session;
use surrealdb::sql::Algorithm;
use surrealdb::sql::Value;
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),
)),
}
}
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 = false;
validation.validate_exp = false;
validation
});
use surrealdb::iam::base::{Engine, BASE64};
use surrealdb::iam::LOG;
pub async fn basic(session: &mut Session, auth: String) -> Result<(), Error> {
// Log the authentication type
@ -149,195 +75,3 @@ pub async fn basic(session: &mut Session, auth: String) -> Result<(), Error> {
// There was an auth error
Err(Error::InvalidAuth)
}
pub async fn token(session: &mut Session, auth: String) -> Result<(), Error> {
// Log the authentication type
trace!(target: LOG, "Attempting token authentication");
// Retrieve just the auth data
let auth = auth.trim_start_matches(TOKEN).trim();
// Get a database reference
let kvs = DB.get().unwrap();
// Decode the token without verifying
let token = decode::<Claims>(auth, &KEY, &DUD)?;
// Parse the token and catch any errors
let value = super::parse::parse(auth)?;
// Check if the auth token can be used
if let Some(nbf) = token.claims.nbf {
if nbf > Utc::now().timestamp() {
trace!(target: LOG, "The 'nbf' field in the authentication token was invalid");
return Err(Error::InvalidAuth);
}
}
// Check if the auth token has expired
if let Some(exp) = token.claims.exp {
if exp < Utc::now().timestamp() {
trace!(target: LOG, "The 'exp' field in the authentication token was invalid");
return Err(Error::InvalidAuth);
}
}
// 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),
id,
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to scope `{}` with token `{}`", sc, tk);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Parse the record id
let id = match id {
Some(id) => surrealdb::sql::thing(&id)?.into(),
None => Value::None,
};
// 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)?;
// Log the success
debug!(target: LOG, "Authenticated to scope `{}` with token `{}`", sc, tk);
// Set the session
session.sd = Some(id);
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.sc = Some(sc.to_owned());
session.au = Arc::new(Auth::Sc(ns, db, sc));
Ok(())
}
// Check if this is scope authentication
Claims {
ns: Some(ns),
db: Some(db),
sc: Some(sc),
id: Some(id),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to scope `{}`", sc);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// Parse the record id
let id = surrealdb::sql::thing(&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)?;
// Log the success
debug!(target: LOG, "Authenticated to scope `{}`", sc);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.sc = Some(sc.to_owned());
session.sd = Some(Value::from(id));
session.au = Arc::new(Auth::Sc(ns, db, sc));
Ok(())
}
// Check if this is database token authentication
Claims {
ns: Some(ns),
db: Some(db),
tk: Some(tk),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to database `{}` with token `{}`", db, tk);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
debug!(target: LOG, "Authenticated to database `{}` with token `{}`", db, tk);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.au = Arc::new(Auth::Db(ns, db));
Ok(())
}
// Check if this is database authentication
Claims {
ns: Some(ns),
db: Some(db),
id: Some(id),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to database `{}` with login `{}`", db, id);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
debug!(target: LOG, "Authenticated to database `{}` with login `{}`", db, id);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.db = Some(db.to_owned());
session.au = Arc::new(Auth::Db(ns, db));
Ok(())
}
// Check if this is namespace token authentication
Claims {
ns: Some(ns),
tk: Some(tk),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to namespace `{}` with token `{}`", ns, tk);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
trace!(target: LOG, "Authenticated to namespace `{}` with token `{}`", ns, tk);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.au = Arc::new(Auth::Ns(ns));
Ok(())
}
// Check if this is namespace authentication
Claims {
ns: Some(ns),
id: Some(id),
..
} => {
// Log the decoded authentication claims
trace!(target: LOG, "Authenticating to namespace `{}` with login `{}`", ns, id);
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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)?;
// Log the success
trace!(target: LOG, "Authenticated to namespace `{}` with login `{}`", ns, id);
// Set the session
session.tk = Some(value);
session.ns = Some(ns.to_owned());
session.au = Arc::new(Auth::Ns(ns));
Ok(())
}
// There was an auth error
_ => Err(Error::InvalidAuth),
}
}

View file

@ -1,6 +1,6 @@
use crate::cnf::PKG_NAME;
use crate::cnf::PKG_VERSION;
use crate::cnf::SERVER_NAME;
use surrealdb::cnf::SERVER_NAME;
const ID: &str = "ID";
const NS: &str = "NS";

View file

@ -20,7 +20,9 @@ use std::collections::HashMap;
use std::sync::Arc;
use surrealdb::channel;
use surrealdb::channel::Sender;
use surrealdb::dbs::Response;
use surrealdb::dbs::Session;
use surrealdb::opt::auth::Root;
use surrealdb::sql::Array;
use surrealdb::sql::Object;
use surrealdb::sql::Strand;
@ -381,7 +383,9 @@ impl Rpc {
#[instrument(skip_all, name = "rpc signup", fields(websocket=self.uuid.to_string()))]
async fn signup(&mut self, vars: Object) -> Result<Value, Error> {
crate::iam::signup::signup(&mut self.session, vars)
let kvs = DB.get().unwrap();
let opts = CF.get().unwrap();
surrealdb::iam::signup::signup(kvs, opts.strict, &mut self.session, vars)
.await
.map(Into::into)
.map_err(Into::into)
@ -389,20 +393,27 @@ impl Rpc {
#[instrument(skip_all, name = "rpc signin", fields(websocket=self.uuid.to_string()))]
async fn signin(&mut self, vars: Object) -> Result<Value, Error> {
crate::iam::signin::signin(&mut self.session, vars)
let kvs = DB.get().unwrap();
let opts = CF.get().unwrap();
let root = opts.pass.as_ref().map(|pass| Root {
username: &opts.user,
password: pass,
});
surrealdb::iam::signin::signin(kvs, &root, opts.strict, &mut self.session, vars)
.await
.map(Into::into)
.map_err(Into::into)
}
#[instrument(skip_all, name = "rpc invalidate", fields(websocket=self.uuid.to_string()))]
async fn invalidate(&mut self) -> Result<Value, Error> {
crate::iam::clear::clear(&mut self.session).await?;
surrealdb::iam::clear::clear(&mut self.session)?;
Ok(Value::None)
}
#[instrument(skip_all, name = "rpc auth", fields(websocket=self.uuid.to_string()))]
async fn authenticate(&mut self, token: Strand) -> Result<Value, Error> {
crate::iam::verify::token(&mut self.session, token.0).await?;
let kvs = DB.get().unwrap();
surrealdb::iam::verify::token(kvs, &mut self.session, token.0).await?;
Ok(Value::None)
}

View file

@ -1,9 +1,12 @@
use crate::dbs::DB;
use crate::err::Error;
use crate::iam::verify::{basic, token};
use crate::iam::verify::basic;
use crate::iam::BASIC;
use crate::iam::TOKEN;
use crate::net::client_ip;
use std::net::SocketAddr;
use surrealdb::dbs::Session;
use surrealdb::iam::verify::token;
use surrealdb::iam::TOKEN;
use warp::Filter;
pub fn build() -> impl Filter<Extract = (Session,), Error = warp::Rejection> + Clone {
@ -33,6 +36,7 @@ async fn process(
ns: Option<String>,
db: Option<String>,
) -> Result<Session, warp::Rejection> {
let kvs = DB.get().unwrap();
// Create session
#[rustfmt::skip]
let mut session = Session { ip, or, id, ns, db, ..Default::default() };
@ -41,7 +45,9 @@ async fn process(
// Basic authentication data was supplied
Some(auth) if auth.starts_with(BASIC) => basic(&mut session, auth).await,
// Token authentication data was supplied
Some(auth) if auth.starts_with(TOKEN) => token(&mut session, auth).await,
Some(auth) if auth.starts_with(TOKEN) => {
token(kvs, &mut session, auth).await.map_err(Error::from)
}
// Wrong authentication data was supplied
Some(_) => Err(Error::InvalidAuth),
// No authentication data was supplied

View file

@ -1,10 +1,13 @@
use crate::dbs::DB;
use crate::err::Error;
use crate::net::input::bytes_to_utf8;
use crate::net::output;
use crate::net::session;
use crate::net::CF;
use bytes::Bytes;
use serde::Serialize;
use surrealdb::dbs::Session;
use surrealdb::opt::auth::Root;
use surrealdb::sql::Value;
use warp::Filter;
@ -51,30 +54,43 @@ async fn handler(
body: Bytes,
mut session: Session,
) -> Result<impl warp::Reply, warp::Rejection> {
// Get a database reference
let kvs = DB.get().unwrap();
// Get the config options
let opts = CF.get().unwrap();
// Convert the HTTP body into text
let data = bytes_to_utf8(&body)?;
// Parse the provided data as JSON
match surrealdb::sql::json(data) {
// The provided value was an object
Ok(Value::Object(vars)) => match crate::iam::signin::signin(&mut session, vars).await {
// Authentication was successful
Ok(v) => match output.as_deref() {
// Simple serialization
Some("application/json") => Ok(output::json(&Success::new(v))),
Some("application/cbor") => Ok(output::cbor(&Success::new(v))),
Some("application/pack") => Ok(output::pack(&Success::new(v))),
// Internal serialization
Some("application/bung") => Ok(output::full(&Success::new(v))),
// Text serialization
Some("text/plain") => Ok(output::text(v.unwrap_or_default())),
// Return nothing
None => Ok(output::none()),
// An incorrect content-type was requested
_ => Err(warp::reject::custom(Error::InvalidType)),
},
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
},
Ok(Value::Object(vars)) => {
let root = opts.pass.as_ref().map(|pass| Root {
username: &opts.user,
password: pass,
});
match surrealdb::iam::signin::signin(kvs, &root, opts.strict, &mut session, vars)
.await
.map_err(Error::from)
{
// Authentication was successful
Ok(v) => match output.as_deref() {
// Simple serialization
Some("application/json") => Ok(output::json(&Success::new(v))),
Some("application/cbor") => Ok(output::cbor(&Success::new(v))),
Some("application/pack") => Ok(output::pack(&Success::new(v))),
// Internal serialization
Some("application/bung") => Ok(output::full(&Success::new(v))),
// Text serialization
Some("text/plain") => Ok(output::text(v.unwrap_or_default())),
// Return nothing
None => Ok(output::none()),
// An incorrect content-type was requested
_ => Err(warp::reject::custom(Error::InvalidType)),
},
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
}
}
// The provided value was not an object
_ => Err(warp::reject::custom(Error::Request)),
}

View file

@ -1,7 +1,9 @@
use crate::dbs::DB;
use crate::err::Error;
use crate::net::input::bytes_to_utf8;
use crate::net::output;
use crate::net::session;
use crate::net::CF;
use bytes::Bytes;
use serde::Serialize;
use surrealdb::dbs::Session;
@ -51,30 +53,39 @@ async fn handler(
body: Bytes,
mut session: Session,
) -> Result<impl warp::Reply, warp::Rejection> {
// Get a database reference
let kvs = DB.get().unwrap();
// Get the config options
let opts = CF.get().unwrap();
// Convert the HTTP body into text
let data = bytes_to_utf8(&body)?;
// Parse the provided data as JSON
match surrealdb::sql::json(data) {
// The provided value was an object
Ok(Value::Object(vars)) => match crate::iam::signup::signup(&mut session, vars).await {
// Authentication was successful
Ok(v) => match output.as_deref() {
// Simple serialization
Some("application/json") => Ok(output::json(&Success::new(v))),
Some("application/cbor") => Ok(output::cbor(&Success::new(v))),
Some("application/pack") => Ok(output::pack(&Success::new(v))),
// Internal serialization
Some("application/bung") => Ok(output::full(&Success::new(v))),
// Text serialization
Some("text/plain") => Ok(output::text(v.unwrap_or_default())),
// Return nothing
None => Ok(output::none()),
// An incorrect content-type was requested
_ => Err(warp::reject::custom(Error::InvalidType)),
},
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
},
Ok(Value::Object(vars)) => {
match surrealdb::iam::signup::signup(kvs, opts.strict, &mut session, vars)
.await
.map_err(Error::from)
{
// Authentication was successful
Ok(v) => match output.as_deref() {
// Simple serialization
Some("application/json") => Ok(output::json(&Success::new(v))),
Some("application/cbor") => Ok(output::cbor(&Success::new(v))),
Some("application/pack") => Ok(output::pack(&Success::new(v))),
// Internal serialization
Some("application/bung") => Ok(output::full(&Success::new(v))),
// Text serialization
Some("text/plain") => Ok(output::text(v.unwrap_or_default())),
// Return nothing
None => Ok(output::none()),
// An incorrect content-type was requested
_ => Err(warp::reject::custom(Error::InvalidType)),
},
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
}
}
// The provided value was not an object
_ => Err(warp::reject::custom(Error::Request)),
}