Add tests for the signin
and signup
methods (#3689)
This commit is contained in:
parent
2e27000c8e
commit
900fc58328
2 changed files with 491 additions and 0 deletions
|
@ -362,3 +362,375 @@ pub async fn root(
|
|||
_ => Err(Error::InvalidAuth),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::iam::Role;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signin_scope() {
|
||||
// Test with correct credentials
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
SIGNIN (
|
||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||
)
|
||||
SIGNUP (
|
||||
CREATE user CONTENT {
|
||||
name: $user,
|
||||
pass: crypto::argon2::generate($pass)
|
||||
}
|
||||
);
|
||||
|
||||
CREATE user:test CONTENT {
|
||||
name: 'user',
|
||||
pass: crypto::argon2::generate('pass')
|
||||
}
|
||||
"#,
|
||||
&sess,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||
vars.insert("user", "user".into());
|
||||
vars.insert("pass", "pass".into());
|
||||
let res = sc(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
vars.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "user:test");
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||
assert_eq!(sess.au.level().db(), Some("test"));
|
||||
// Scope users should not have roles
|
||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||
// Expiration should always be set for tokens issued by SurrealDB
|
||||
let exp = sess.exp.unwrap();
|
||||
// Expiration should match the current time plus session duration with some margin
|
||||
let min_exp = (Utc::now() + Duration::hours(1) - Duration::seconds(10)).timestamp();
|
||||
let max_exp = (Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
||||
assert!(
|
||||
exp > min_exp && exp < max_exp,
|
||||
"Session expiration is expected to follow scope duration"
|
||||
);
|
||||
}
|
||||
|
||||
// Test with incorrect credentials
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
SIGNIN (
|
||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||
)
|
||||
SIGNUP (
|
||||
CREATE user CONTENT {
|
||||
name: $user,
|
||||
pass: crypto::argon2::generate($pass)
|
||||
}
|
||||
);
|
||||
|
||||
CREATE user:test CONTENT {
|
||||
name: 'user',
|
||||
pass: crypto::argon2::generate('pass')
|
||||
}
|
||||
"#,
|
||||
&sess,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||
vars.insert("user", "user".into());
|
||||
vars.insert("pass", "incorrect".into());
|
||||
let res = sc(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
vars.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signin_db() {
|
||||
//
|
||||
// Test without roles defined
|
||||
//
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute("DEFINE USER user ON DB PASSWORD 'pass'", &sess, None).await.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let res = db(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
"pass".to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "user");
|
||||
assert!(sess.au.is_db());
|
||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||
assert_eq!(sess.au.level().db(), Some("test"));
|
||||
assert!(sess.au.has_role(&Role::Viewer), "Auth user expected to have Viewer role");
|
||||
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||
assert_eq!(sess.exp, None, "Default system user expiration is expected to be None");
|
||||
}
|
||||
|
||||
//
|
||||
// Test with roles defined
|
||||
//
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute("DEFINE USER user ON DB PASSWORD 'pass' ROLES EDITOR, OWNER", &sess, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let res = db(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
"pass".to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "user");
|
||||
assert!(sess.au.is_db());
|
||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||
assert_eq!(sess.au.level().db(), Some("test"));
|
||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||
assert!(sess.au.has_role(&Role::Editor), "Auth user expected to have Editor role");
|
||||
assert!(sess.au.has_role(&Role::Owner), "Auth user expected to have Owner role");
|
||||
assert_eq!(sess.exp, None, "Default system user expiration is expected to be None");
|
||||
}
|
||||
|
||||
// Test invalid password
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute("DEFINE USER user ON DB PASSWORD 'pass'", &sess, None).await.unwrap();
|
||||
|
||||
let mut sess = Session {
|
||||
..Default::default()
|
||||
};
|
||||
let res = db(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
"invalid".to_string(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signin_ns() {
|
||||
//
|
||||
// Test without roles defined
|
||||
//
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test");
|
||||
ds.execute("DEFINE USER user ON NS PASSWORD 'pass'", &sess, None).await.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let res =
|
||||
ns(&ds, &mut sess, "test".to_string(), "user".to_string(), "pass".to_string())
|
||||
.await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "user");
|
||||
assert!(sess.au.is_ns());
|
||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||
assert!(sess.au.has_role(&Role::Viewer), "Auth user expected to have Viewer role");
|
||||
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||
assert_eq!(sess.exp, None, "Default system user expiration is expected to be None");
|
||||
}
|
||||
|
||||
//
|
||||
// Test with roles defined
|
||||
//
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test");
|
||||
ds.execute("DEFINE USER user ON NS PASSWORD 'pass' ROLES EDITOR, OWNER", &sess, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let res =
|
||||
ns(&ds, &mut sess, "test".to_string(), "user".to_string(), "pass".to_string())
|
||||
.await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.au.id(), "user");
|
||||
assert!(sess.au.is_ns());
|
||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||
assert!(sess.au.has_role(&Role::Editor), "Auth user expected to have Editor role");
|
||||
assert!(sess.au.has_role(&Role::Owner), "Auth user expected to have Owner role");
|
||||
assert_eq!(sess.exp, None, "Default system user expiration is expected to be None");
|
||||
}
|
||||
|
||||
// Test invalid password
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test");
|
||||
ds.execute("DEFINE USER user ON NS PASSWORD 'pass'", &sess, None).await.unwrap();
|
||||
|
||||
let mut sess = Session {
|
||||
..Default::default()
|
||||
};
|
||||
let res =
|
||||
ns(&ds, &mut sess, "test".to_string(), "user".to_string(), "invalid".to_string())
|
||||
.await;
|
||||
|
||||
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signin_root() {
|
||||
//
|
||||
// Test without roles defined
|
||||
//
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner();
|
||||
ds.execute("DEFINE USER user ON ROOT PASSWORD 'pass'", &sess, None).await.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
..Default::default()
|
||||
};
|
||||
let res = root(&ds, &mut sess, "user".to_string(), "pass".to_string()).await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
assert_eq!(sess.au.id(), "user");
|
||||
assert!(sess.au.is_root());
|
||||
assert!(sess.au.has_role(&Role::Viewer), "Auth user expected to have Viewer role");
|
||||
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||
assert_eq!(sess.exp, None, "Default system user expiration is expected to be None");
|
||||
}
|
||||
|
||||
//
|
||||
// Test with roles defined
|
||||
//
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner();
|
||||
ds.execute("DEFINE USER user ON ROOT PASSWORD 'pass' ROLES EDITOR, OWNER", &sess, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
..Default::default()
|
||||
};
|
||||
let res = root(&ds, &mut sess, "user".to_string(), "pass".to_string()).await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signin with credentials: {:?}", res);
|
||||
assert_eq!(sess.au.id(), "user");
|
||||
assert!(sess.au.is_root());
|
||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||
assert!(sess.au.has_role(&Role::Editor), "Auth user expected to have Editor role");
|
||||
assert!(sess.au.has_role(&Role::Owner), "Auth user expected to have Owner role");
|
||||
assert_eq!(sess.exp, None, "Default system user expiration is expected to be None");
|
||||
}
|
||||
|
||||
// Test invalid password
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test");
|
||||
ds.execute("DEFINE USER user ON ROOT PASSWORD 'pass'", &sess, None).await.unwrap();
|
||||
|
||||
let mut sess = Session {
|
||||
..Default::default()
|
||||
};
|
||||
let res = root(&ds, &mut sess, "user".to_string(), "invalid".to_string()).await;
|
||||
|
||||
assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,3 +141,122 @@ pub async fn sc(
|
|||
_ => Err(Error::NoScopeFound),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::iam::Role;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_scope_signup() {
|
||||
// Test with valid parameters
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
SIGNIN (
|
||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||
)
|
||||
SIGNUP (
|
||||
CREATE user CONTENT {
|
||||
name: $user,
|
||||
pass: crypto::argon2::generate($pass)
|
||||
}
|
||||
);
|
||||
"#,
|
||||
&sess,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||
vars.insert("user", "user".into());
|
||||
vars.insert("pass", "pass".into());
|
||||
let res = sc(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
vars.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_ok(), "Failed to signup: {:?}", res);
|
||||
assert_eq!(sess.ns, Some("test".to_string()));
|
||||
assert_eq!(sess.db, Some("test".to_string()));
|
||||
assert!(sess.au.id().starts_with("user:"));
|
||||
assert!(sess.au.is_scope());
|
||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||
assert_eq!(sess.au.level().db(), Some("test"));
|
||||
// Scope users should not have roles.
|
||||
assert!(!sess.au.has_role(&Role::Viewer), "Auth user expected to not have Viewer role");
|
||||
assert!(!sess.au.has_role(&Role::Editor), "Auth user expected to not have Editor role");
|
||||
assert!(!sess.au.has_role(&Role::Owner), "Auth user expected to not have Owner role");
|
||||
// Expiration should always be set for tokens issued by SurrealDB
|
||||
let exp = sess.exp.unwrap();
|
||||
// Expiration should match the current time plus session duration with some margin
|
||||
let min_exp = (Utc::now() + Duration::hours(1) - Duration::seconds(10)).timestamp();
|
||||
let max_exp = (Utc::now() + Duration::hours(1) + Duration::seconds(10)).timestamp();
|
||||
assert!(
|
||||
exp > min_exp && exp < max_exp,
|
||||
"Session expiration is expected to follow scope duration"
|
||||
);
|
||||
}
|
||||
|
||||
// Test with invalid parameters
|
||||
{
|
||||
let ds = Datastore::new("memory").await.unwrap();
|
||||
let sess = Session::owner().with_ns("test").with_db("test");
|
||||
ds.execute(
|
||||
r#"
|
||||
DEFINE SCOPE user SESSION 1h
|
||||
SIGNIN (
|
||||
SELECT * FROM user WHERE name = $user AND crypto::argon2::compare(pass, $pass)
|
||||
)
|
||||
SIGNUP (
|
||||
CREATE user CONTENT {
|
||||
name: $user,
|
||||
pass: crypto::argon2::generate($pass)
|
||||
}
|
||||
);
|
||||
"#,
|
||||
&sess,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Signin with the user
|
||||
let mut sess = Session {
|
||||
ns: Some("test".to_string()),
|
||||
db: Some("test".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let mut vars: HashMap<&str, Value> = HashMap::new();
|
||||
// Password is missing
|
||||
vars.insert("user", "user".into());
|
||||
let res = sc(
|
||||
&ds,
|
||||
&mut sess,
|
||||
"test".to_string(),
|
||||
"test".to_string(),
|
||||
"user".to_string(),
|
||||
vars.into(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.is_err(), "Unexpected successful signup: {:?}", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue