Move authentication code to separate module

This commit is contained in:
Tobie Morgan Hitchcock 2022-07-04 01:01:24 +01:00
parent 187d9c08dc
commit 7bb4aa74f3
20 changed files with 677 additions and 643 deletions

View file

@ -1,5 +1,8 @@
use once_cell::sync::OnceCell;
use std::net::SocketAddr;
pub static CF: OnceCell<Config> = OnceCell::new();
#[derive(Clone, Debug)]
pub struct Config {
pub bind: SocketAddr,
@ -10,7 +13,7 @@ pub struct Config {
pub key: Option<String>,
}
pub fn parse(matches: &clap::ArgMatches) -> Config {
pub fn init(matches: &clap::ArgMatches) {
// Parse the server binding address
let bind = matches
.value_of("bind")
@ -26,13 +29,13 @@ pub fn parse(matches: &clap::ArgMatches) -> Config {
// Parse any TLS server security options
let crt = matches.value_of("web-crt").map(|v| v.to_owned());
let key = matches.value_of("web-key").map(|v| v.to_owned());
// Return a new config object
Config {
// Store the new config object
let _ = CF.set(Config {
bind,
path,
user,
pass,
crt,
key,
}
});
}

View file

@ -1,10 +1,13 @@
mod backup;
mod config;
mod export;
mod import;
mod log;
mod start;
mod version;
pub use config::CF;
use crate::cnf::LOGO;
use clap::{Arg, Command};
use once_cell::sync::Lazy;

View file

@ -1,12 +1,19 @@
use super::config;
use crate::cnf::LOGO;
use crate::dbs;
use crate::err::Error;
use crate::net;
pub fn init(matches: &clap::ArgMatches) -> Result<(), Error> {
#[tokio::main]
pub async fn init(matches: &clap::ArgMatches) -> Result<(), Error> {
// output SurrealDB logo
println!("{}", LOGO);
// Start up the web server
net::init(matches)?;
// Don't error when done
// Setup the cli options
config::init(matches);
// Start the kvs server
dbs::init().await?;
// Start the web server
net::init().await?;
// All ok
Ok(())
}

View file

@ -11,10 +11,7 @@ macro_rules! get_cfg {
pub fn init(_: &clap::ArgMatches) -> Result<(), Error> {
get_cfg!(target_os: "windows", "macos", "ios", "linux", "android", "freebsd", "openbsd", "netbsd");
get_cfg!(target_arch: "x86", "x86_64", "mips", "powerpc", "powerpc64", "arm", "aarch64");
println!("{} {} for {} on {}", NAME, VERSION, target_os(), target_arch());
Ok(())
}

17
src/dbs/mod.rs Normal file
View file

@ -0,0 +1,17 @@
use crate::cli::CF;
use crate::err::Error;
use once_cell::sync::OnceCell;
use surrealdb::Datastore;
pub static DB: OnceCell<Datastore> = OnceCell::new();
pub async fn init() -> Result<(), Error> {
// Get local copy of options
let opt = CF.get().unwrap();
// Parse and setup the desired kv datastore
let dbs = Datastore::new(&opt.path).await?;
// Store database instance
let _ = DB.set(dbs);
// All ok
Ok(())
}

9
src/iam/clear.rs Normal file
View file

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

5
src/iam/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod clear;
pub mod signin;
pub mod signup;
pub mod token;
pub mod verify;

222
src/iam/signin.rs Normal file
View file

@ -0,0 +1,222 @@
use crate::cnf::SERVER_NAME;
use crate::dbs::DB;
use crate::err::Error;
use crate::iam::token::{Claims, HEADER};
use argon2::password_hash::{PasswordHash, PasswordVerifier};
use argon2::Argon2;
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey};
use surrealdb::sql::Object;
use surrealdb::Session;
pub async fn signin(vars: Object) -> Result<String, Error> {
// Parse the speficied variables
let ns = vars.get("NS").or_else(|| vars.get("ns"));
let db = vars.get("DB").or_else(|| vars.get("db"));
let sc = vars.get("SC").or_else(|| vars.get("sc"));
// Check if the paramaters exist
match (ns, db, sc) {
(Some(ns), Some(db), Some(sc)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let db = db.to_strand().as_string();
let sc = sc.to_strand().as_string();
// Attempt to signin to specified scope
let res = super::signin::sc(ns, db, sc, vars).await?;
// Return the result to the client
Ok(res)
}
(Some(ns), Some(db), None) => {
// Get the provided user and pass
let user = vars.get("user");
let pass = vars.get("pass");
// Validate the user and pass
match (user, pass) {
// There is a username and password
(Some(user), Some(pass)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let db = db.to_strand().as_string();
let user = user.to_strand().as_string();
let pass = pass.to_strand().as_string();
// Attempt to signin to database
let res = super::signin::db(ns, db, user, pass).await?;
// Return the result to the client
Ok(res)
}
// There is no username or password
_ => Err(Error::InvalidAuth),
}
}
(Some(ns), None, None) => {
// Get the provided user and pass
let user = vars.get("user");
let pass = vars.get("pass");
// Validate the user and pass
match (user, pass) {
// There is a username and password
(Some(user), Some(pass)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let user = user.to_strand().as_string();
let pass = pass.to_strand().as_string();
// Attempt to signin to namespace
let res = super::signin::ns(ns, user, pass).await?;
// Return the result to the client
Ok(res)
}
// There is no username or password
_ => Err(Error::InvalidAuth),
}
}
_ => Err(Error::InvalidAuth),
}
}
pub async fn sc(ns: String, db: String, sc: String, vars: Object) -> Result<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
match tx.get_sc(&ns, &db, &sc).await {
Ok(sv) => {
match sv.signin {
// This scope allows signin
Some(val) => {
// Setup the query params
let vars = Some(vars.0);
// Setup the query session
let sess = Session::for_db(&ns, &db);
// Compute the value with the params
match kvs.compute(val, &sess, vars).await {
// The signin value succeeded
Ok(val) => match val.rid() {
// There is a record returned
Some(rid) => {
// Create the authentication key
let key = EncodingKey::from_secret(sv.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: match sv.session {
Some(v) => Utc::now() + Duration::from_std(v.0).unwrap(),
_ => Utc::now() + Duration::hours(1),
}
.timestamp(),
ns: Some(ns),
db: Some(db),
sc: Some(sc),
id: Some(rid.to_raw()),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// No record was returned
_ => Err(Error::InvalidAuth),
},
// The signin query failed
_ => Err(Error::InvalidAuth),
}
}
// This scope does not allow signin
_ => Err(Error::InvalidAuth),
}
}
// The scope does not exists
_ => Err(Error::InvalidAuth),
}
}
pub async fn db(ns: String, db: String, user: String, pass: String) -> Result<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
match tx.get_dl(&ns, &db, &user).await {
Ok(dl) => {
// Compute the hash and verify the password
let hash = PasswordHash::new(&dl.hash).unwrap();
// Attempt to verify the password using Argon2
match Argon2::default().verify_password(pass.as_ref(), &hash) {
Ok(_) => {
// Create the authentication key
let key = EncodingKey::from_secret(dl.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: (Utc::now() + Duration::hours(1)).timestamp(),
ns: Some(ns),
db: Some(db),
id: Some(user),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// The password did not verify
_ => Err(Error::InvalidAuth),
}
}
// The specified user login does not exist
_ => Err(Error::InvalidAuth),
}
}
pub async fn ns(ns: String, user: String, pass: String) -> Result<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
match tx.get_nl(&ns, &user).await {
Ok(nl) => {
// Compute the hash and verify the password
let hash = PasswordHash::new(&nl.hash).unwrap();
// Attempt to verify the password using Argon2
match Argon2::default().verify_password(pass.as_ref(), &hash) {
Ok(_) => {
// Create the authentication key
let key = EncodingKey::from_secret(nl.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: (Utc::now() + Duration::hours(1)).timestamp(),
ns: Some(ns),
id: Some(user),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// The password did not verify
_ => Err(Error::InvalidAuth),
}
}
// The specified user login does not exist
_ => Err(Error::InvalidAuth),
}
}

92
src/iam/signup.rs Normal file
View file

@ -0,0 +1,92 @@
use crate::cnf::SERVER_NAME;
use crate::dbs::DB;
use crate::err::Error;
use crate::iam::token::{Claims, HEADER};
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey};
use surrealdb::sql::Object;
use surrealdb::Session;
pub async fn signup(vars: Object) -> Result<String, Error> {
// Parse the speficied variables
let ns = vars.get("NS").or_else(|| vars.get("ns"));
let db = vars.get("DB").or_else(|| vars.get("db"));
let sc = vars.get("SC").or_else(|| vars.get("sc"));
// Check if the paramaters exist
match (ns, db, sc) {
(Some(ns), Some(db), Some(sc)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let db = db.to_strand().as_string();
let sc = sc.to_strand().as_string();
// Attempt to signin to specified scope
let res = super::signup::sc(ns, db, sc, vars).await?;
// Return the result to the client
Ok(res)
}
_ => Err(Error::InvalidAuth),
}
}
pub async fn sc(ns: String, db: String, sc: String, vars: Object) -> Result<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
match tx.get_sc(&ns, &db, &sc).await {
Ok(sv) => {
match sv.signup {
// This scope allows signin
Some(val) => {
// Setup the query params
let vars = Some(vars.0);
// Setup the query session
let sess = Session::for_db(&ns, &db);
// Compute the value with the params
match kvs.compute(val, &sess, vars).await {
// The signin value succeeded
Ok(val) => match val.rid() {
// There is a record returned
Some(rid) => {
// Create the authentication key
let key = EncodingKey::from_secret(sv.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: match sv.session {
Some(v) => Utc::now() + Duration::from_std(v.0).unwrap(),
_ => Utc::now() + Duration::hours(1),
}
.timestamp(),
ns: Some(ns),
db: Some(db),
sc: Some(sc),
id: Some(rid.to_raw()),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// No record was returned
_ => Err(Error::InvalidAuth),
},
// The signin query failed
_ => Err(Error::InvalidAuth),
}
}
// This scope does not allow signin
_ => Err(Error::InvalidAuth),
}
}
// The scope does not exists
_ => Err(Error::InvalidAuth),
}
}

278
src/iam/verify.rs Normal file
View file

@ -0,0 +1,278 @@
use crate::cli::CF;
use crate::dbs::DB;
use crate::err::Error;
use crate::iam::token::Claims;
use argon2::password_hash::{PasswordHash, PasswordVerifier};
use argon2::Argon2;
use jsonwebtoken::{decode, DecodingKey, Validation};
use once_cell::sync::Lazy;
use std::sync::Arc;
use surrealdb::sql::Algorithm;
use surrealdb::sql::Value;
use surrealdb::Auth;
use surrealdb::Session;
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 = true;
validation.validate_exp = true;
validation
});
pub async fn basic(session: &mut Session, auth: String) -> Result<(), Error> {
// Retrieve just the auth data
if let Some((_, auth)) = auth.split_once(' ') {
// Get a database reference
let kvs = DB.get().unwrap();
// Get the config options
let opts = CF.get().unwrap();
// Decode the encoded auth data
let auth = base64::decode(auth)?;
// Convert the auth data to String
let auth = String::from_utf8(auth)?;
// 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(());
}
// Check if this is NS authentication
if let Some(ns) = &session.ns {
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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(());
}
};
// 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(());
}
};
}
}
}
}
// There was an auth error
Err(Error::InvalidAuth)
}
pub async fn token(session: &mut Session, auth: String) -> Result<(), Error> {
// Retrieve just the auth data
if let Some((_, auth)) = auth.split_once(' ') {
// Get a database reference
let kvs = DB.get().unwrap();
// 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),
id: Some(id),
..
} => {
// 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 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(id));
session.au = Arc::new(Auth::Sc(ns, db, sc));
return Ok(());
}
// Check if this is scope authentication
Claims {
ns: Some(ns),
db: Some(db),
sc: Some(sc),
id: Some(id),
..
} => {
// 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)?;
// 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(id));
session.au = Arc::new(Auth::Sc(ns, db, sc));
return Ok(());
}
// Check if this is database token authentication
Claims {
ns: Some(ns),
db: Some(db),
tk: Some(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)?;
// 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(());
}
// Check if this is database authentication
Claims {
ns: Some(ns),
db: Some(db),
id: Some(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)?;
// 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(());
}
// Check if this is namespace token authentication
Claims {
ns: Some(ns),
tk: Some(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)?;
// Set the session
session.ns = Some(ns.to_owned());
session.au = Arc::new(Auth::Ns(ns));
return Ok(());
}
// Check if this is namespace authentication
Claims {
ns: Some(ns),
id: Some(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)?;
// Set the session
session.ns = Some(ns.to_owned());
session.au = Arc::new(Auth::Ns(ns));
return Ok(());
}
// There was an auth error
_ => return Err(Error::InvalidAuth),
};
}
// There was an auth error
Err(Error::InvalidAuth)
}

View file

@ -17,7 +17,9 @@ mod mac;
mod cli;
mod cnf;
mod dbs;
mod err;
mod iam;
mod net;
fn main() {

View file

@ -1,6 +1,6 @@
use crate::dbs::DB;
use crate::err::Error;
use crate::net::session;
use crate::net::DB;
use bytes::Bytes;
use hyper::body::Body;
use surrealdb::Session;

View file

@ -1,7 +1,7 @@
use crate::dbs::DB;
use crate::err::Error;
use crate::net::output;
use crate::net::session;
use crate::net::DB;
use bytes::Bytes;
use surrealdb::Session;
use warp::http;

View file

@ -1,8 +1,8 @@
use crate::dbs::DB;
use crate::err::Error;
use crate::net::head;
use crate::net::output;
use crate::net::session;
use crate::net::DB;
use bytes::Bytes;
use serde::Deserialize;
use std::str;

View file

@ -1,13 +1,12 @@
mod config;
mod export;
mod fail;
mod head;
mod import;
mod index;
mod jwt;
mod key;
mod log;
mod output;
mod rpc;
mod session;
mod signin;
mod signup;
@ -16,26 +15,11 @@ mod status;
mod sync;
mod version;
use crate::cli::CF;
use crate::err::Error;
use config::Config;
use once_cell::sync::OnceCell;
use surrealdb::Datastore;
use warp::Filter;
static DB: OnceCell<Datastore> = OnceCell::new();
static CF: OnceCell<Config> = OnceCell::new();
#[tokio::main]
pub async fn init(matches: &clap::ArgMatches) -> Result<(), Error> {
// Parse the server config options
let cfg = config::parse(matches);
// Parse and setup the desired kv datastore
let dbs = Datastore::new(&cfg.path).await?;
// Store database instance
let _ = DB.set(dbs);
// Store config options
let _ = CF.set(cfg);
pub async fn init() -> Result<(), Error> {
// Setup web routes
let net = index::config()
// Version endpoint
@ -52,6 +36,8 @@ pub async fn init(matches: &clap::ArgMatches) -> Result<(), Error> {
.or(import::config())
// Backup endpoint
.or(sync::config())
// RPC query endpoint
.or(rpc::config())
// SQL query endpoint
.or(sql::config())
// API query endpoint

View file

@ -1,89 +1,12 @@
use crate::err::Error;
use crate::net::jwt::Claims;
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 crate::iam::verify::{basic, token};
use std::net::SocketAddr;
use std::sync::Arc;
use surrealdb::sql::Algorithm;
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),
)),
}
}
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();
@ -115,216 +38,18 @@ async fn process(
) -> Result<Session, warp::Rejection> {
// Create session
#[rustfmt::skip]
let session = Session { ip, or, id, ns, db, ..Default::default() };
let mut session = Session { ip, or, id, ns, db, ..Default::default() };
// Parse the authentication header
let session = match au {
match au {
// Basic authentication data was supplied
Some(auth) if auth.starts_with(BASIC) => basic(auth, session).await,
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(auth, session).await,
Some(auth) if auth.starts_with(TOKEN) => token(&mut session, auth).await,
// Wrong authentication data was supplied
Some(_) => Err(Error::InvalidAuth),
// No authentication data was supplied
None => Ok(session),
None => Ok(()),
}?;
// Pass the authenticated session through
Ok(session)
}
async fn basic(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 kvs = DB.get().unwrap();
// Get the config options
let opts = CF.get().unwrap();
// Decode the encoded auth data
let auth = base64::decode(auth)?;
// Convert the auth data to String
let auth = String::from_utf8(auth)?;
// 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 {
// Create a new readonly transaction
let mut tx = kvs.transaction(false, false).await?;
// 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 kvs = DB.get().unwrap();
// 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),
id: Some(id),
..
} => {
// 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 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(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),
id: Some(id),
..
} => {
// 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)?;
// 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(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),
..
} => {
// 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)?;
// 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),
..
} => {
// 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)?;
// 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),
..
} => {
// 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)?;
// 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),
..
} => {
// 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)?;
// 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)
}

View file

@ -1,17 +1,8 @@
use crate::cnf::SERVER_NAME;
use crate::err::Error;
use crate::net::head;
use crate::net::jwt::{Claims, HEADER};
use crate::net::DB;
use argon2::password_hash::{PasswordHash, PasswordVerifier};
use argon2::Argon2;
use bytes::Bytes;
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey};
use std::str;
use surrealdb::sql::Object;
use surrealdb::sql::Value;
use surrealdb::Session;
use warp::http::Response;
use warp::Filter;
@ -33,233 +24,18 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
}
async fn handler(body: Bytes) -> Result<impl warp::Reply, warp::Rejection> {
//
// Convert the HTTP body into text
let data = str::from_utf8(&body).unwrap();
//
// Parse the provided data as JSON
match surrealdb::sql::json(data) {
// The provided value was an object
Ok(Value::Object(vars)) => {
// Parse the speficied variables
let ns = vars.get("NS").or_else(|| vars.get("ns"));
let db = vars.get("DB").or_else(|| vars.get("db"));
let sc = vars.get("SC").or_else(|| vars.get("sc"));
// Match the authentication type
match (ns, db, sc) {
(Some(ns), Some(db), Some(sc)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let db = db.to_strand().as_string();
let sc = sc.to_strand().as_string();
// Attempt to signin to specified scope
match signin_sc(ns, db, sc, vars).await {
// Namespace authentication was successful
Ok(Value::Object(vars)) => match crate::iam::signin::signin(vars).await {
// Authentication was successful
Ok(v) => Ok(Response::builder().body(v)),
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
}
}
(Some(ns), Some(db), None) => {
// Get the provided user and pass
let user = vars.get("user");
let pass = vars.get("pass");
// Validate the user and pass
match (user, pass) {
// There is a username and password
(Some(user), Some(pass)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let db = db.to_strand().as_string();
let user = user.to_strand().as_string();
let pass = pass.to_strand().as_string();
// Attempt to signin to database
match signin_db(ns, db, user, pass).await {
// Namespace authentication was successful
Ok(v) => Ok(Response::builder().body(v)),
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
}
}
// There is no username or password
_ => Err(warp::reject::custom(Error::InvalidAuth)),
}
}
(Some(ns), None, None) => {
// Get the provided user and pass
let user = vars.get("user");
let pass = vars.get("pass");
// Validate the user and pass
match (user, pass) {
// There is a username and password
(Some(user), Some(pass)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let user = user.to_strand().as_string();
let pass = pass.to_strand().as_string();
// Attempt to signin to namespace
match signin_ns(ns, user, pass).await {
// Namespace authentication was successful
Ok(v) => Ok(Response::builder().body(v)),
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
}
}
// There is no username or password
_ => Err(warp::reject::custom(Error::InvalidAuth)),
}
}
// No NS, DB, or SC keys were specified
_ => Err(warp::reject::custom(Error::InvalidAuth)),
}
}
},
// The provided value was not an object
_ => Err(warp::reject::custom(Error::Request)),
}
}
async fn signin_sc(ns: String, db: String, sc: String, vars: Object) -> Result<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
match tx.get_sc(&ns, &db, &sc).await {
Ok(sv) => {
match sv.signin {
// This scope allows signin
Some(val) => {
// Setup the query params
let vars = Some(vars.0);
// Setup the query session
let sess = Session::for_db(&ns, &db);
// Compute the value with the params
match kvs.compute(val, &sess, vars).await {
// The signin value succeeded
Ok(val) => match val.rid() {
// There is a record returned
Some(rid) => {
// Create the authentication key
let key = EncodingKey::from_secret(sv.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: match sv.session {
Some(v) => Utc::now() + Duration::from_std(v.0).unwrap(),
_ => Utc::now() + Duration::hours(1),
}
.timestamp(),
ns: Some(ns),
db: Some(db),
sc: Some(sc),
id: Some(rid.to_raw()),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// No record was returned
_ => Err(Error::InvalidAuth),
},
// The signin query failed
_ => Err(Error::InvalidAuth),
}
}
// This scope does not allow signin
_ => Err(Error::InvalidAuth),
}
}
// The scope does not exists
_ => Err(Error::InvalidAuth),
}
}
async fn signin_db(ns: String, db: String, user: String, pass: String) -> Result<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
match tx.get_dl(&ns, &db, &user).await {
Ok(dl) => {
// Compute the hash and verify the password
let hash = PasswordHash::new(&dl.hash).unwrap();
// Attempt to verify the password using Argon2
match Argon2::default().verify_password(pass.as_ref(), &hash) {
Ok(_) => {
// Create the authentication key
let key = EncodingKey::from_secret(dl.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: (Utc::now() + Duration::hours(1)).timestamp(),
ns: Some(ns),
db: Some(db),
id: Some(user),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// The password did not verify
_ => Err(Error::InvalidAuth),
}
}
// The specified user login does not exist
_ => Err(Error::InvalidAuth),
}
}
async fn signin_ns(ns: String, user: String, pass: String) -> Result<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
match tx.get_nl(&ns, &user).await {
Ok(nl) => {
// Compute the hash and verify the password
let hash = PasswordHash::new(&nl.hash).unwrap();
// Attempt to verify the password using Argon2
match Argon2::default().verify_password(pass.as_ref(), &hash) {
Ok(_) => {
// Create the authentication key
let key = EncodingKey::from_secret(nl.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: (Utc::now() + Duration::hours(1)).timestamp(),
ns: Some(ns),
id: Some(user),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// The password did not verify
_ => Err(Error::InvalidAuth),
}
}
// The specified user login does not exist
_ => Err(Error::InvalidAuth),
}
}

View file

@ -1,15 +1,8 @@
use crate::cnf::SERVER_NAME;
use crate::err::Error;
use crate::net::head;
use crate::net::jwt::{Claims, HEADER};
use crate::net::DB;
use bytes::Bytes;
use chrono::{Duration, Utc};
use jsonwebtoken::{encode, EncodingKey};
use std::str;
use surrealdb::sql::Object;
use surrealdb::sql::Value;
use surrealdb::Session;
use warp::http::Response;
use warp::Filter;
@ -31,99 +24,18 @@ pub fn config() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejecti
}
async fn handler(body: Bytes) -> Result<impl warp::Reply, warp::Rejection> {
//
// Convert the HTTP body into text
let data = str::from_utf8(&body).unwrap();
//
// Parse the provided data as JSON
match surrealdb::sql::json(data) {
// The provided value was an object
Ok(Value::Object(vars)) => {
// Parse the speficied variables
let ns = vars.get("NS").or_else(|| vars.get("ns"));
let db = vars.get("DB").or_else(|| vars.get("db"));
let sc = vars.get("SC").or_else(|| vars.get("sc"));
// Match the authentication type
match (ns, db, sc) {
(Some(ns), Some(db), Some(sc)) => {
// Process the provided values
let ns = ns.to_strand().as_string();
let db = db.to_strand().as_string();
let sc = sc.to_strand().as_string();
// Attempt to signin to specified scope
match signup_sc(ns, db, sc, vars).await {
// Namespace authentication was successful
Ok(Value::Object(vars)) => match crate::iam::signup::signup(vars).await {
// Authentication was successful
Ok(v) => Ok(Response::builder().body(v)),
// There was an error with authentication
Err(e) => Err(warp::reject::custom(e)),
}
}
// No NS, DB, or SC keys were specified
_ => Err(warp::reject::custom(Error::InvalidAuth)),
}
}
},
// The provided value was not an object
_ => Err(warp::reject::custom(Error::Request)),
}
}
async fn signup_sc(ns: String, db: String, sc: String, vars: Object) -> Result<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
match tx.get_sc(&ns, &db, &sc).await {
Ok(sv) => {
match sv.signup {
// This scope allows signin
Some(val) => {
// Setup the query params
let vars = Some(vars.0);
// Setup the query session
let sess = Session::for_db(&ns, &db);
// Compute the value with the params
match kvs.compute(val, &sess, vars).await {
// The signin value succeeded
Ok(val) => match val.rid() {
// There is a record returned
Some(rid) => {
// Create the authentication key
let key = EncodingKey::from_secret(sv.code.as_ref());
// Create the authentication claim
let val = Claims {
iss: SERVER_NAME.to_owned(),
iat: Utc::now().timestamp(),
nbf: Utc::now().timestamp(),
exp: match sv.session {
Some(v) => Utc::now() + Duration::from_std(v.0).unwrap(),
_ => Utc::now() + Duration::hours(1),
}
.timestamp(),
ns: Some(ns),
db: Some(db),
sc: Some(sc),
id: Some(rid.to_raw()),
..Claims::default()
};
// Create the authentication token
match encode(&*HEADER, &val, &key) {
// The auth token was created successfully
Ok(tk) => Ok(tk),
// There was an error creating the token
_ => Err(Error::InvalidAuth),
}
}
// No record was returned
_ => Err(Error::InvalidAuth),
},
// The signin query failed
_ => Err(Error::InvalidAuth),
}
}
// This scope does not allow signin
_ => Err(Error::InvalidAuth),
}
}
// The scope does not exists
_ => Err(Error::InvalidAuth),
}
}

View file

@ -1,8 +1,8 @@
use crate::dbs::DB;
use crate::err::Error;
use crate::net::head;
use crate::net::output;
use crate::net::session;
use crate::net::DB;
use bytes::Bytes;
use futures::{SinkExt, StreamExt};
use surrealdb::Session;