Check for single and multiple token audiences (#4493)
This commit is contained in:
parent
89f1de825a
commit
a8b96936f4
1 changed files with 461 additions and 16 deletions
|
@ -2266,9 +2266,89 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ THROW "Invalid token audience" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
|
;
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = None;
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
|
assert_eq!(sess.db, Some("test".to_string()));
|
||||||
|
assert_eq!(sess.ac, Some("user".to_string()));
|
||||||
|
assert!(sess.au.is_db());
|
||||||
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
|
assert_eq!(sess.au.level().db(), Some("test"));
|
||||||
|
// Record users should not have roles
|
||||||
|
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");
|
||||||
|
// Expiration should match the defined duration
|
||||||
|
let exp = sess.exp.unwrap();
|
||||||
|
// Expiration should match the current time plus session duration with some margin
|
||||||
|
let min_exp = (Utc::now() + Duration::hours(2) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_exp = (Utc::now() + Duration::hours(2) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_exp && exp < max_exp,
|
||||||
|
"Session expiration is expected to follow the defined duration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with correct "iss" and "aud" claims, with multiple audiences
|
||||||
|
{
|
||||||
|
let secret = "jwt_secret";
|
||||||
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
|
let claims = Claims {
|
||||||
|
iss: Some("surrealdb-test".to_string()),
|
||||||
|
aud: Some(Audience::Multiple(vec![
|
||||||
|
"invalid".to_string(),
|
||||||
|
"surrealdb-test".to_string(),
|
||||||
|
])),
|
||||||
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
|
ns: Some("test".to_string()),
|
||||||
|
db: Some("test".to_string()),
|
||||||
|
ac: Some("user".to_string()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
|
ds.execute(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON DATABASE TYPE JWT
|
||||||
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
|
AUTHENTICATE {{
|
||||||
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
|
@ -2335,10 +2415,14 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ THROW "Invalid token audience" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
|
@ -2358,7 +2442,7 @@ mod tests {
|
||||||
let res = token(&ds, &mut sess, &enc).await;
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(Error::Thrown(e)) if e == "Invalid token audience" => {} // ok
|
Err(Error::Thrown(e)) if e == "Invalid token audience string" => {} // ok
|
||||||
res => panic!(
|
res => panic!(
|
||||||
"Expected authentication to failed due to invalid token audience, but instead received: {:?}",
|
"Expected authentication to failed due to invalid token audience, but instead received: {:?}",
|
||||||
res
|
res
|
||||||
|
@ -2366,6 +2450,68 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with correct "iss" claim but incorrect "aud" claim, with multiple audiences
|
||||||
|
{
|
||||||
|
let secret = "jwt_secret";
|
||||||
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
|
let claims = Claims {
|
||||||
|
iss: Some("surrealdb-test".to_string()),
|
||||||
|
aud: Some(Audience::Multiple(vec![
|
||||||
|
"surrealdb-test-different".to_string(),
|
||||||
|
"invalid".to_string(),
|
||||||
|
])),
|
||||||
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
|
ns: Some("test".to_string()),
|
||||||
|
db: Some("test".to_string()),
|
||||||
|
ac: Some("user".to_string()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner().with_ns("test").with_db("test");
|
||||||
|
ds.execute(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON DATABASE TYPE JWT
|
||||||
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
|
AUTHENTICATE {{
|
||||||
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
DURATION FOR SESSION 2h
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = None;
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Err(Error::Thrown(e)) if e == "Invalid token audience array" => {} // ok
|
||||||
|
res => panic!(
|
||||||
|
"Expected authentication to failed due to invalid token audience array, but instead received: {:?}",
|
||||||
|
res
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test with correct "iss" claim but incorrect "aud" claim
|
// Test with correct "iss" claim but incorrect "aud" claim
|
||||||
// In this case, something is returned by the clause, which returns a generic error
|
// In this case, something is returned by the clause, which returns a generic error
|
||||||
{
|
{
|
||||||
|
@ -2392,7 +2538,11 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ RETURN "FAIL" }};
|
IF $token.iss != "surrealdb-test" {{ RETURN "FAIL" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ RETURN "FAIL" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ RETURN "FAIL" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ RETURN "FAIL" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
|
@ -2450,9 +2600,87 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ THROW "Invalid token audience" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
|
;
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = None;
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
|
assert_eq!(sess.ac, Some("user".to_string()));
|
||||||
|
assert!(sess.au.is_ns());
|
||||||
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
|
assert_eq!(sess.au.level().db(), None);
|
||||||
|
// Record users should not have roles
|
||||||
|
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");
|
||||||
|
// Expiration should match the defined duration
|
||||||
|
let exp = sess.exp.unwrap();
|
||||||
|
// Expiration should match the current time plus session duration with some margin
|
||||||
|
let min_exp = (Utc::now() + Duration::hours(2) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_exp = (Utc::now() + Duration::hours(2) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_exp && exp < max_exp,
|
||||||
|
"Session expiration is expected to follow the defined duration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with correct "iss" and "aud" claims, with multiple audiences
|
||||||
|
{
|
||||||
|
let secret = "jwt_secret";
|
||||||
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
|
let claims = Claims {
|
||||||
|
iss: Some("surrealdb-test".to_string()),
|
||||||
|
aud: Some(Audience::Multiple(vec![
|
||||||
|
"invalid".to_string(),
|
||||||
|
"surrealdb-test".to_string(),
|
||||||
|
])),
|
||||||
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
|
ns: Some("test".to_string()),
|
||||||
|
ac: Some("user".to_string()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner().with_ns("test");
|
||||||
|
ds.execute(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON NAMESPACE TYPE JWT
|
||||||
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
|
AUTHENTICATE {{
|
||||||
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
|
@ -2474,6 +2702,7 @@ mod tests {
|
||||||
|
|
||||||
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
assert_eq!(sess.ns, Some("test".to_string()));
|
assert_eq!(sess.ns, Some("test".to_string()));
|
||||||
|
assert_eq!(sess.db, None);
|
||||||
assert_eq!(sess.ac, Some("user".to_string()));
|
assert_eq!(sess.ac, Some("user".to_string()));
|
||||||
assert!(sess.au.is_ns());
|
assert!(sess.au.is_ns());
|
||||||
assert_eq!(sess.au.level().ns(), Some("test"));
|
assert_eq!(sess.au.level().ns(), Some("test"));
|
||||||
|
@ -2517,9 +2746,13 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ THROW "Invalid token audience" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
|
@ -2540,7 +2773,7 @@ mod tests {
|
||||||
let res = token(&ds, &mut sess, &enc).await;
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(Error::Thrown(e)) if e == "Invalid token audience" => {} // ok
|
Err(Error::Thrown(e)) if e == "Invalid token audience string" => {} // ok
|
||||||
res => panic!(
|
res => panic!(
|
||||||
"Expected authentication to failed due to invalid token audience, but instead received: {:?}",
|
"Expected authentication to failed due to invalid token audience, but instead received: {:?}",
|
||||||
res
|
res
|
||||||
|
@ -2548,6 +2781,68 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with correct "iss" claim but incorrect "aud" claim, with multiple audiences
|
||||||
|
{
|
||||||
|
let secret = "jwt_secret";
|
||||||
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
|
let claims = Claims {
|
||||||
|
iss: Some("surrealdb-test".to_string()),
|
||||||
|
aud: Some(Audience::Multiple(vec![
|
||||||
|
"surrealdb-test-different".to_string(),
|
||||||
|
"invalid".to_string(),
|
||||||
|
])),
|
||||||
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
|
ns: Some("test".to_string()),
|
||||||
|
ac: Some("user".to_string()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner().with_ns("test");
|
||||||
|
ds.execute(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON NAMESPACE TYPE JWT
|
||||||
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
|
AUTHENTICATE {{
|
||||||
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
DURATION FOR SESSION 2h
|
||||||
|
;
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = None;
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Err(Error::Thrown(e)) if e == "Invalid token audience array" => {} // ok
|
||||||
|
res => panic!(
|
||||||
|
"Expected authentication to failed due to invalid token audience array, but instead received: {:?}",
|
||||||
|
res
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test with correct "iss" claim but incorrect "aud" claim
|
// Test with correct "iss" claim but incorrect "aud" claim
|
||||||
// In this case, something is returned by the clause, which returns a generic error
|
// In this case, something is returned by the clause, which returns a generic error
|
||||||
{
|
{
|
||||||
|
@ -2573,7 +2868,11 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ RETURN "FAIL" }};
|
IF $token.iss != "surrealdb-test" {{ RETURN "FAIL" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ RETURN "FAIL" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ RETURN "FAIL" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ RETURN "FAIL" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
|
@ -2630,7 +2929,11 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ THROW "Invalid token audience" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
|
@ -2672,6 +2975,80 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with correct "iss" and "aud" claims, with multiple audiences
|
||||||
|
{
|
||||||
|
let secret = "jwt_secret";
|
||||||
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
|
let claims = Claims {
|
||||||
|
iss: Some("surrealdb-test".to_string()),
|
||||||
|
aud: Some(Audience::Multiple(vec![
|
||||||
|
"invalid".to_string(),
|
||||||
|
"surrealdb-test".to_string(),
|
||||||
|
])),
|
||||||
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
|
ac: Some("user".to_string()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner();
|
||||||
|
ds.execute(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON ROOT TYPE JWT
|
||||||
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
|
AUTHENTICATE {{
|
||||||
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
DURATION FOR SESSION 2h
|
||||||
|
;
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = None;
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
|
||||||
|
assert_eq!(sess.ns, None);
|
||||||
|
assert_eq!(sess.db, None);
|
||||||
|
assert_eq!(sess.ac, Some("user".to_string()));
|
||||||
|
assert!(sess.au.is_root());
|
||||||
|
assert_eq!(sess.au.level().ns(), None);
|
||||||
|
assert_eq!(sess.au.level().db(), None);
|
||||||
|
// Record users should not have roles
|
||||||
|
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");
|
||||||
|
// Expiration should match the defined duration
|
||||||
|
let exp = sess.exp.unwrap();
|
||||||
|
// Expiration should match the current time plus session duration with some margin
|
||||||
|
let min_exp = (Utc::now() + Duration::hours(2) - Duration::seconds(10)).timestamp();
|
||||||
|
let max_exp = (Utc::now() + Duration::hours(2) + Duration::seconds(10)).timestamp();
|
||||||
|
assert!(
|
||||||
|
exp > min_exp && exp < max_exp,
|
||||||
|
"Session expiration is expected to follow the defined duration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Test with correct "iss" claim but incorrect "aud" claim
|
// Test with correct "iss" claim but incorrect "aud" claim
|
||||||
{
|
{
|
||||||
let secret = "jwt_secret";
|
let secret = "jwt_secret";
|
||||||
|
@ -2695,7 +3072,11 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ THROW "Invalid token audience" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
|
@ -2718,7 +3099,7 @@ mod tests {
|
||||||
let res = token(&ds, &mut sess, &enc).await;
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(Error::Thrown(e)) if e == "Invalid token audience" => {} // ok
|
Err(Error::Thrown(e)) if e == "Invalid token audience string" => {} // ok
|
||||||
res => panic!(
|
res => panic!(
|
||||||
"Expected authentication to failed due to invalid token audience, but instead received: {:?}",
|
"Expected authentication to failed due to invalid token audience, but instead received: {:?}",
|
||||||
res
|
res
|
||||||
|
@ -2726,6 +3107,66 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test with correct "iss" claim but incorrect "aud" claim, with multiple audiences
|
||||||
|
{
|
||||||
|
let secret = "jwt_secret";
|
||||||
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
|
let claims = Claims {
|
||||||
|
iss: Some("surrealdb-test".to_string()),
|
||||||
|
aud: Some(Audience::Multiple(vec![
|
||||||
|
"surrealdb-test-different".to_string(),
|
||||||
|
"invalid".to_string(),
|
||||||
|
])),
|
||||||
|
iat: Some(Utc::now().timestamp()),
|
||||||
|
nbf: Some(Utc::now().timestamp()),
|
||||||
|
exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
|
||||||
|
ac: Some("user".to_string()),
|
||||||
|
..Claims::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let ds = Datastore::new("memory").await.unwrap();
|
||||||
|
let sess = Session::owner();
|
||||||
|
ds.execute(
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
DEFINE ACCESS user ON ROOT TYPE JWT
|
||||||
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
|
AUTHENTICATE {{
|
||||||
|
IF $token.iss != "surrealdb-test" {{ THROW "Invalid token issuer" }};
|
||||||
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ THROW "Invalid token audience array" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ THROW "Invalid token audience string" }}
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
DURATION FOR SESSION 2h
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&sess,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Prepare the claims object
|
||||||
|
let mut claims = claims.clone();
|
||||||
|
claims.roles = None;
|
||||||
|
// Create the token
|
||||||
|
let enc = encode(&HEADER, &claims, &key).unwrap();
|
||||||
|
// Signin with the token
|
||||||
|
let mut sess = Session::default();
|
||||||
|
let res = token(&ds, &mut sess, &enc).await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Err(Error::Thrown(e)) if e == "Invalid token audience array" => {} // ok
|
||||||
|
res => panic!(
|
||||||
|
"Expected authentication to failed due to invalid token audience array, but instead received: {:?}",
|
||||||
|
res
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test with correct "iss" claim but incorrect "aud" claim
|
// Test with correct "iss" claim but incorrect "aud" claim
|
||||||
// In this case, something is returned by the clause, which returns a generic error
|
// In this case, something is returned by the clause, which returns a generic error
|
||||||
{
|
{
|
||||||
|
@ -2750,7 +3191,11 @@ mod tests {
|
||||||
ALGORITHM HS512 KEY '{secret}'
|
ALGORITHM HS512 KEY '{secret}'
|
||||||
AUTHENTICATE {{
|
AUTHENTICATE {{
|
||||||
IF $token.iss != "surrealdb-test" {{ RETURN "FAIL" }};
|
IF $token.iss != "surrealdb-test" {{ RETURN "FAIL" }};
|
||||||
IF $token.aud != "surrealdb-test" {{ RETURN "FAIL" }};
|
IF type::is::array($token.aud) {{
|
||||||
|
IF "surrealdb-test" NOT IN $token.aud {{ RETURN "FAIL" }}
|
||||||
|
}} ELSE {{
|
||||||
|
IF $token.aud IS NOT "surrealdb-test" {{ RETURN "FAIL" }}
|
||||||
|
}};
|
||||||
}}
|
}}
|
||||||
DURATION FOR SESSION 2h
|
DURATION FOR SESSION 2h
|
||||||
;
|
;
|
||||||
|
|
Loading…
Reference in a new issue