1use crate::dbs::Session;
2use crate::err::Error;
3use crate::iam::access::{authenticate_generic, authenticate_record};
4#[cfg(feature = "jwks")]
5use crate::iam::jwks;
6use crate::iam::{issue::expiration, token::Claims, Actor, Auth, Level, Role};
7use crate::kvs::{Datastore, LockType::*, TransactionType::*};
8use crate::sql::access_type::{AccessType, Jwt, JwtAccessVerify};
9use crate::sql::{statements::DefineUserStatement, Algorithm, Value};
10use crate::syn;
11use argon2::{Argon2, PasswordHash, PasswordVerifier};
12use chrono::Utc;
13use jsonwebtoken::{decode, DecodingKey, Validation};
14use std::str::{self, FromStr};
15use std::sync::Arc;
16use std::sync::LazyLock;
17
18fn config(alg: Algorithm, key: &[u8]) -> Result<(DecodingKey, Validation), Error> {
19 let (dec, mut val) = match alg {
20 Algorithm::Hs256 => {
21 (DecodingKey::from_secret(key), Validation::new(jsonwebtoken::Algorithm::HS256))
22 }
23 Algorithm::Hs384 => {
24 (DecodingKey::from_secret(key), Validation::new(jsonwebtoken::Algorithm::HS384))
25 }
26 Algorithm::Hs512 => {
27 (DecodingKey::from_secret(key), Validation::new(jsonwebtoken::Algorithm::HS512))
28 }
29 Algorithm::EdDSA => {
30 (DecodingKey::from_ed_pem(key)?, Validation::new(jsonwebtoken::Algorithm::EdDSA))
31 }
32 Algorithm::Es256 => {
33 (DecodingKey::from_ec_pem(key)?, Validation::new(jsonwebtoken::Algorithm::ES256))
34 }
35 Algorithm::Es384 => {
36 (DecodingKey::from_ec_pem(key)?, Validation::new(jsonwebtoken::Algorithm::ES384))
37 }
38 Algorithm::Es512 => {
39 (DecodingKey::from_ec_pem(key)?, Validation::new(jsonwebtoken::Algorithm::ES384))
40 }
41 Algorithm::Ps256 => {
42 (DecodingKey::from_rsa_pem(key)?, Validation::new(jsonwebtoken::Algorithm::PS256))
43 }
44 Algorithm::Ps384 => {
45 (DecodingKey::from_rsa_pem(key)?, Validation::new(jsonwebtoken::Algorithm::PS384))
46 }
47 Algorithm::Ps512 => {
48 (DecodingKey::from_rsa_pem(key)?, Validation::new(jsonwebtoken::Algorithm::PS512))
49 }
50 Algorithm::Rs256 => {
51 (DecodingKey::from_rsa_pem(key)?, Validation::new(jsonwebtoken::Algorithm::RS256))
52 }
53 Algorithm::Rs384 => {
54 (DecodingKey::from_rsa_pem(key)?, Validation::new(jsonwebtoken::Algorithm::RS384))
55 }
56 Algorithm::Rs512 => {
57 (DecodingKey::from_rsa_pem(key)?, Validation::new(jsonwebtoken::Algorithm::RS512))
58 }
59 };
60
61 val.validate_aud = false;
66
67 Ok((dec, val))
68}
69
70static KEY: LazyLock<DecodingKey> = LazyLock::new(|| DecodingKey::from_secret(&[]));
71
72static DUD: LazyLock<Validation> = LazyLock::new(|| {
73 let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256);
74 validation.insecure_disable_signature_validation();
75 validation.validate_nbf = false;
76 validation.validate_exp = false;
77 validation.validate_aud = false;
78 validation
79});
80
81pub async fn basic(
82 kvs: &Datastore,
83 session: &mut Session,
84 user: &str,
85 pass: &str,
86 ns: Option<&str>,
87 db: Option<&str>,
88) -> Result<(), Error> {
89 trace!("Attempting basic authentication");
91 match (ns, db) {
93 (Some(ns), Some(db)) => match verify_db_creds(kvs, ns, db, user, pass).await {
95 Ok(u) => {
96 debug!("Authenticated as database user '{}'", user);
97 session.exp = expiration(u.duration.session)?;
98 session.au =
99 Arc::new((&u, Level::Database(ns.to_owned(), db.to_owned())).try_into()?);
100 Ok(())
101 }
102 Err(err) => Err(err),
103 },
104 (Some(ns), None) => match verify_ns_creds(kvs, ns, user, pass).await {
106 Ok(u) => {
107 debug!("Authenticated as namespace user '{}'", user);
108 session.exp = expiration(u.duration.session)?;
109 session.au = Arc::new((&u, Level::Namespace(ns.to_owned())).try_into()?);
110 Ok(())
111 }
112 Err(err) => Err(err),
113 },
114 (None, None) => match verify_root_creds(kvs, user, pass).await {
116 Ok(u) => {
117 debug!("Authenticated as root user '{}'", user);
118 session.exp = expiration(u.duration.session)?;
119 session.au = Arc::new((&u, Level::Root).try_into()?);
120 Ok(())
121 }
122 Err(err) => Err(err),
123 },
124 (None, Some(db)) => {
125 debug!(
126 "Attempted basic authentication in database '{db}' without specifying a namespace"
127 );
128 Err(Error::InvalidAuth)
129 }
130 }
131}
132
133pub async fn token(kvs: &Datastore, session: &mut Session, token: &str) -> Result<(), Error> {
134 trace!("Attempting token authentication");
136 let token_data = decode::<Claims>(token, &KEY, &DUD)?;
138 let value = (&token_data.claims).into();
140 if let Some(nbf) = token_data.claims.nbf {
142 if nbf > Utc::now().timestamp() {
143 debug!("Token verification failed due to the 'nbf' claim containing a future time");
144 return Err(Error::InvalidAuth);
145 }
146 }
147 if let Some(exp) = token_data.claims.exp {
149 if exp < Utc::now().timestamp() {
150 debug!("Token verification failed due to the 'exp' claim containing a past time");
151 return Err(Error::ExpiredToken);
152 }
153 }
154 match &token_data.claims {
156 Claims {
158 ns: Some(ns),
159 db: Some(db),
160 ac: Some(ac),
161 id: Some(id),
162 ..
163 } => {
164 trace!("Authenticating with record access method `{}`", ac);
166 let tx = kvs.transaction(Read, Optimistic).await?;
168 let mut rid = syn::thing(id)?;
170 let de = tx.get_db_access(ns, db, ac).await?;
172 tx.cancel().await?;
174 let cf = match &de.kind {
176 AccessType::Record(at) => match &at.jwt.verify {
177 JwtAccessVerify::Key(key) => config(key.alg, key.key.as_bytes()),
178 #[cfg(feature = "jwks")]
179 JwtAccessVerify::Jwks(jwks) => {
180 if let Some(kid) = token_data.header.kid {
181 jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
182 } else {
183 Err(Error::MissingTokenHeader("kid".to_string()))
184 }
185 }
186 #[cfg(not(feature = "jwks"))]
187 _ => return Err(Error::AccessMethodMismatch),
188 }?,
189 _ => return Err(Error::AccessMethodMismatch),
190 };
191 verify_token(token, &cf.0, &cf.1)?;
193 if let Some(au) = &de.authenticate {
195 let mut sess = Session::editor().with_ns(ns).with_db(db);
197 sess.rd = Some(rid.clone().into());
198 sess.tk = Some((&token_data.claims).into());
199 sess.ip.clone_from(&session.ip);
200 sess.or.clone_from(&session.or);
201 rid = authenticate_record(kvs, &sess, au).await?;
202 }
203 debug!("Authenticated with record access method `{}`", ac);
205 session.tk = Some(value);
207 session.ns = Some(ns.to_owned());
208 session.db = Some(db.to_owned());
209 session.ac = Some(ac.to_owned());
210 session.rd = Some(Value::from(rid.to_owned()));
211 session.exp = expiration(de.duration.session)?;
212 session.au = Arc::new(Auth::new(Actor::new(
213 rid.to_string(),
214 Default::default(),
215 Level::Record(ns.to_string(), db.to_string(), rid.to_string()),
216 )));
217 Ok(())
218 }
219 Claims {
222 ns: Some(ns),
223 db: Some(db),
224 ac: Some(ac),
225 ..
226 } => {
227 trace!("Authenticating to database `{}` with access method `{}`", db, ac);
229 let tx = kvs.transaction(Read, Optimistic).await?;
231 let de = tx.get_db_access(ns, db, ac).await?;
233 tx.cancel().await?;
235 match &de.kind {
237 AccessType::Jwt(_) | AccessType::Bearer(_) => {
239 let cf = match &de.kind.jwt().verify {
240 JwtAccessVerify::Key(key) => config(key.alg, key.key.as_bytes()),
241 #[cfg(feature = "jwks")]
242 JwtAccessVerify::Jwks(jwks) => {
243 if let Some(kid) = token_data.header.kid {
244 jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
245 } else {
246 Err(Error::MissingTokenHeader("kid".to_string()))
247 }
248 }
249 #[cfg(not(feature = "jwks"))]
250 _ => return Err(Error::AccessMethodMismatch),
251 }?;
252 verify_token(token, &cf.0, &cf.1)?;
254 if let Some(au) = &de.authenticate {
256 let mut sess = Session::editor().with_ns(ns).with_db(db);
258 sess.tk = Some((&token_data.claims).into());
259 sess.ip.clone_from(&session.ip);
260 sess.or.clone_from(&session.or);
261 authenticate_generic(kvs, &sess, au).await?;
262 }
263 let roles = match &token_data.claims.roles {
265 None => vec![Role::Viewer],
267 Some(roles) => roles
269 .iter()
270 .map(|r| -> Result<Role, Error> {
271 Role::from_str(r.as_str()).map_err(Error::IamError)
272 })
273 .collect::<Result<Vec<_>, _>>()?,
274 };
275 debug!("Authenticated to database `{}` with access method `{}`", db, ac);
277 session.tk = Some(value);
279 session.ns = Some(ns.to_owned());
280 session.db = Some(db.to_owned());
281 session.ac = Some(ac.to_owned());
282 session.exp = expiration(de.duration.session)?;
283 session.au = Arc::new(Auth::new(Actor::new(
284 de.name.to_string(),
285 roles,
286 Level::Database(ns.to_string(), db.to_string()),
287 )));
288 }
289 AccessType::Record(at) => match &de.authenticate {
293 Some(au) => {
294 trace!("Access method `{}` is record access with AUTHENTICATE clause", ac);
295 let cf = match &at.jwt.verify {
296 JwtAccessVerify::Key(key) => config(key.alg, key.key.as_bytes()),
297 #[cfg(feature = "jwks")]
298 JwtAccessVerify::Jwks(jwks) => {
299 if let Some(kid) = token_data.header.kid {
300 jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
301 } else {
302 Err(Error::MissingTokenHeader("kid".to_string()))
303 }
304 }
305 #[cfg(not(feature = "jwks"))]
306 _ => return Err(Error::AccessMethodMismatch),
307 }?;
308
309 verify_token(token, &cf.0, &cf.1)?;
311 let mut sess = Session::editor().with_ns(ns).with_db(db);
314 sess.tk = Some((&token_data.claims).into());
315 sess.ip.clone_from(&session.ip);
316 sess.or.clone_from(&session.or);
317 let rid = authenticate_record(kvs, &sess, au).await?;
318 debug!("Authenticated with record access method `{}`", ac);
320 session.tk = Some(value);
322 session.ns = Some(ns.to_owned());
323 session.db = Some(db.to_owned());
324 session.ac = Some(ac.to_owned());
325 session.rd = Some(Value::from(rid.to_owned()));
326 session.exp = expiration(de.duration.session)?;
327 session.au = Arc::new(Auth::new(Actor::new(
328 rid.to_string(),
329 Default::default(),
330 Level::Record(ns.to_string(), db.to_string(), rid.to_string()),
331 )));
332 }
333 _ => return Err(Error::AccessMethodMismatch),
334 },
335 };
336 Ok(())
337 }
338 Claims {
340 ns: Some(ns),
341 db: Some(db),
342 id: Some(id),
343 ..
344 } => {
345 trace!("Authenticating to database `{}` with user `{}`", db, id);
347 let tx = kvs.transaction(Read, Optimistic).await?;
349 let de = tx.get_db_user(ns, db, id).await.map_err(|e| {
351 debug!("Error while authenticating to database `{db}`: {e}");
352 Error::InvalidAuth
353 })?;
354 tx.cancel().await?;
356 let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
358 verify_token(token, &cf.0, &cf.1)?;
360 debug!("Authenticated to database `{}` with user `{}` using token", db, id);
362 session.tk = Some(value);
364 session.ns = Some(ns.to_owned());
365 session.db = Some(db.to_owned());
366 session.exp = expiration(de.duration.session)?;
367 session.au = Arc::new(Auth::new(Actor::new(
368 id.to_string(),
369 de.roles.iter().map(Role::try_from).collect::<Result<_, _>>()?,
370 Level::Database(ns.to_string(), db.to_string()),
371 )));
372 Ok(())
373 }
374 Claims {
376 ns: Some(ns),
377 ac: Some(ac),
378 ..
379 } => {
380 trace!("Authenticating to namespace `{}` with access method `{}`", ns, ac);
382 let tx = kvs.transaction(Read, Optimistic).await?;
384 let de = tx.get_ns_access(ns, ac).await?;
386 tx.cancel().await?;
388 let cf = match &de.kind {
390 AccessType::Jwt(_) | AccessType::Bearer(_) => match &de.kind.jwt().verify {
391 JwtAccessVerify::Key(key) => config(key.alg, key.key.as_bytes()),
392 #[cfg(feature = "jwks")]
393 JwtAccessVerify::Jwks(jwks) => {
394 if let Some(kid) = token_data.header.kid {
395 jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
396 } else {
397 Err(Error::MissingTokenHeader("kid".to_string()))
398 }
399 }
400 #[cfg(not(feature = "jwks"))]
401 _ => return Err(Error::AccessMethodMismatch),
402 },
403 _ => return Err(Error::AccessMethodMismatch),
404 }?;
405 verify_token(token, &cf.0, &cf.1)?;
407 if let Some(au) = &de.authenticate {
409 let mut sess = Session::editor().with_ns(ns);
411 sess.tk = Some((&token_data.claims).into());
412 sess.ip.clone_from(&session.ip);
413 sess.or.clone_from(&session.or);
414 authenticate_generic(kvs, &sess, au).await?;
415 }
416 let roles = match &token_data.claims.roles {
418 None => vec![Role::Viewer],
420 Some(roles) => roles
422 .iter()
423 .map(|r| -> Result<Role, Error> {
424 Role::from_str(r.as_str()).map_err(Error::IamError)
425 })
426 .collect::<Result<Vec<_>, _>>()?,
427 };
428 debug!("Authenticated to namespace `{}` with access method `{}`", ns, ac);
430 session.tk = Some(value);
432 session.ns = Some(ns.to_owned());
433 session.ac = Some(ac.to_owned());
434 session.exp = expiration(de.duration.session)?;
435 session.au = Arc::new(Auth::new(Actor::new(
436 de.name.to_string(),
437 roles,
438 Level::Namespace(ns.to_string()),
439 )));
440 Ok(())
441 }
442 Claims {
444 ns: Some(ns),
445 id: Some(id),
446 ..
447 } => {
448 trace!("Authenticating to namespace `{}` with user `{}`", ns, id);
450 let tx = kvs.transaction(Read, Optimistic).await?;
452 let de = tx.get_ns_user(ns, id).await.map_err(|e| {
454 debug!("Error while authenticating to namespace `{ns}`: {e}");
455 Error::InvalidAuth
456 })?;
457 tx.cancel().await?;
459 let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
461 verify_token(token, &cf.0, &cf.1)?;
463 debug!("Authenticated to namespace `{}` with user `{}` using token", ns, id);
465 session.tk = Some(value);
467 session.ns = Some(ns.to_owned());
468 session.exp = expiration(de.duration.session)?;
469 session.au = Arc::new(Auth::new(Actor::new(
470 id.to_string(),
471 de.roles.iter().map(Role::try_from).collect::<Result<_, _>>()?,
472 Level::Namespace(ns.to_string()),
473 )));
474 Ok(())
475 }
476 Claims {
478 ac: Some(ac),
479 ..
480 } => {
481 trace!("Authenticating to root with access method `{}`", ac);
483 let tx = kvs.transaction(Read, Optimistic).await?;
485 let de = tx.get_root_access(ac).await?;
487 tx.cancel().await?;
489 let cf = match &de.kind {
491 AccessType::Jwt(_) | AccessType::Bearer(_) => match &de.kind.jwt().verify {
492 JwtAccessVerify::Key(key) => config(key.alg, key.key.as_bytes()),
493 #[cfg(feature = "jwks")]
494 JwtAccessVerify::Jwks(jwks) => {
495 if let Some(kid) = token_data.header.kid {
496 jwks::config(kvs, &kid, &jwks.url, token_data.header.alg).await
497 } else {
498 Err(Error::MissingTokenHeader("kid".to_string()))
499 }
500 }
501 #[cfg(not(feature = "jwks"))]
502 _ => return Err(Error::AccessMethodMismatch),
503 },
504 _ => return Err(Error::AccessMethodMismatch),
505 }?;
506 verify_token(token, &cf.0, &cf.1)?;
508 if let Some(au) = &de.authenticate {
510 let mut sess = Session::editor();
512 sess.tk = Some((&token_data.claims).into());
513 sess.ip.clone_from(&session.ip);
514 sess.or.clone_from(&session.or);
515 authenticate_generic(kvs, &sess, au).await?;
516 }
517 let roles = match &token_data.claims.roles {
519 None => vec![Role::Viewer],
521 Some(roles) => roles
523 .iter()
524 .map(|r| -> Result<Role, Error> {
525 Role::from_str(r.as_str()).map_err(Error::IamError)
526 })
527 .collect::<Result<Vec<_>, _>>()?,
528 };
529 debug!("Authenticated to root with access method `{}`", ac);
531 session.tk = Some(value);
533 session.ac = Some(ac.to_owned());
534 session.exp = expiration(de.duration.session)?;
535 session.au = Arc::new(Auth::new(Actor::new(de.name.to_string(), roles, Level::Root)));
536 Ok(())
537 }
538 Claims {
540 id: Some(id),
541 ..
542 } => {
543 trace!("Authenticating to root level with user `{}`", id);
545 let tx = kvs.transaction(Read, Optimistic).await?;
547 let de = tx.get_root_user(id).await.map_err(|e| {
549 debug!("Error while authenticating to root: {e}");
550 Error::InvalidAuth
551 })?;
552 tx.cancel().await?;
554 let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
556 verify_token(token, &cf.0, &cf.1)?;
558 debug!("Authenticated to root level with user `{}` using token", id);
560 session.tk = Some(value);
562 session.exp = expiration(de.duration.session)?;
563 session.au = Arc::new(Auth::new(Actor::new(
564 id.to_string(),
565 de.roles.iter().map(Role::try_from).collect::<Result<_, _>>()?,
566 Level::Root,
567 )));
568 Ok(())
569 }
570 _ => Err(Error::InvalidAuth),
572 }
573}
574
575pub async fn verify_root_creds(
576 ds: &Datastore,
577 user: &str,
578 pass: &str,
579) -> Result<DefineUserStatement, Error> {
580 let tx = ds.transaction(Read, Optimistic).await?;
582 let user = tx.get_root_user(user).await.map_err(|e| {
584 debug!("Error retrieving user for authentication to root: {e}");
585 Error::InvalidAuth
586 })?;
587 tx.cancel().await?;
589 verify_pass(pass, user.hash.as_ref())?;
591 let user = (*user).clone();
593 Ok(user)
595}
596
597pub async fn verify_ns_creds(
598 ds: &Datastore,
599 ns: &str,
600 user: &str,
601 pass: &str,
602) -> Result<DefineUserStatement, Error> {
603 let tx = ds.transaction(Read, Optimistic).await?;
605 let user = tx.get_ns_user(ns, user).await.map_err(|e| {
607 debug!("Error retrieving user for authentication to namespace `{ns}`: {e}");
608 Error::InvalidAuth
609 })?;
610 tx.cancel().await?;
612 verify_pass(pass, user.hash.as_ref())?;
614 let user = (*user).clone();
616 Ok(user)
618}
619
620pub async fn verify_db_creds(
621 ds: &Datastore,
622 ns: &str,
623 db: &str,
624 user: &str,
625 pass: &str,
626) -> Result<DefineUserStatement, Error> {
627 let tx = ds.transaction(Read, Optimistic).await?;
629 let user = tx.get_db_user(ns, db, user).await.map_err(|e| {
631 debug!("Error retrieving user for authentication to database `{ns}/{db}`: {e}");
632 Error::InvalidAuth
633 })?;
634 tx.cancel().await?;
636 verify_pass(pass, user.hash.as_ref())?;
638 let user = (*user).clone();
640 Ok(user)
642}
643
644fn verify_pass(pass: &str, hash: &str) -> Result<(), Error> {
645 let hash = PasswordHash::new(hash).unwrap();
647 match Argon2::default().verify_password(pass.as_ref(), &hash) {
649 Ok(_) => Ok(()),
650 _ => Err(Error::InvalidPass),
651 }
652}
653
654fn verify_token(token: &str, key: &DecodingKey, validation: &Validation) -> Result<(), Error> {
655 match decode::<Claims>(token, key, validation) {
656 Ok(_) => Ok(()),
657 Err(err) => {
658 match err.kind() {
660 jsonwebtoken::errors::ErrorKind::ExpiredSignature => Err(Error::ExpiredToken),
661 _ => {
662 debug!("Error verifying authentication token: {err}");
663 Err(Error::InvalidAuth)
664 }
665 }
666 }
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673 use crate::iam::token::{Audience, HEADER};
674 use argon2::password_hash::{PasswordHasher, SaltString};
675 use chrono::Duration;
676 use jsonwebtoken::{encode, EncodingKey};
677
678 struct TestLevel {
679 level: &'static str,
680 ns: Option<&'static str>,
681 db: Option<&'static str>,
682 }
683
684 const AVAILABLE_ROLES: [Role; 3] = [Role::Viewer, Role::Editor, Role::Owner];
685
686 #[tokio::test]
687 async fn test_basic() {
688 #[derive(Debug)]
689 struct TestCase {
690 title: &'static str,
691 password: &'static str,
692 roles: Vec<Role>,
693 expiration: Option<Duration>,
694 expect_ok: bool,
695 }
696
697 let test_cases = vec![
698 TestCase {
699 title: "without roles or expiration",
700 password: "pass",
701 roles: vec![Role::Viewer],
702 expiration: None,
703 expect_ok: true,
704 },
705 TestCase {
706 title: "with roles and expiration",
707 password: "pass",
708 roles: vec![Role::Editor, Role::Owner],
709 expiration: Some(Duration::days(1)),
710 expect_ok: true,
711 },
712 TestCase {
713 title: "with invalid password",
714 password: "invalid",
715 roles: vec![],
716 expiration: None,
717 expect_ok: false,
718 },
719 ];
720
721 let test_levels = vec![
722 TestLevel {
723 level: "ROOT",
724 ns: None,
725 db: None,
726 },
727 TestLevel {
728 level: "NS",
729 ns: Some("test"),
730 db: None,
731 },
732 TestLevel {
733 level: "DB",
734 ns: Some("test"),
735 db: Some("test"),
736 },
737 ];
738
739 for level in &test_levels {
740 for case in &test_cases {
741 println!("Test case: {} level {}", level.level, case.title);
742 let ds = Datastore::new("memory").await.unwrap();
743 let sess = Session::owner().with_ns("test").with_db("test");
744
745 let roles_clause = if case.roles.is_empty() {
746 String::new()
747 } else {
748 let roles: Vec<&str> = case
749 .roles
750 .iter()
751 .map(|r| match r {
752 Role::Viewer => "VIEWER",
753 Role::Editor => "EDITOR",
754 Role::Owner => "OWNER",
755 })
756 .collect();
757 format!("ROLES {}", roles.join(", "))
758 };
759
760 let duration_clause = if let Some(duration) = case.expiration {
761 format!("DURATION FOR SESSION {}s", duration.num_seconds())
762 } else {
763 String::new()
764 };
765
766 let define_user_query = format!(
767 "DEFINE USER user ON {} PASSWORD 'pass' {} {}",
768 level.level, roles_clause, duration_clause,
769 );
770
771 ds.execute(&define_user_query, &sess, None).await.unwrap();
772
773 let mut sess = Session {
774 ns: level.ns.map(String::from),
775 db: level.db.map(String::from),
776 ..Default::default()
777 };
778
779 let res = basic(&ds, &mut sess, "user", case.password, level.ns, level.db).await;
780
781 if case.expect_ok {
782 assert!(res.is_ok(), "Failed to signin: {:?}", res);
783 assert_eq!(sess.au.id(), "user");
784
785 assert_eq!(sess.au.level().ns(), level.ns);
787 assert_eq!(sess.au.level().db(), level.db);
788 match level.level {
789 "ROOT" => assert!(sess.au.is_root()),
790 "NS" => assert!(sess.au.is_ns()),
791 "DB" => assert!(sess.au.is_db()),
792 _ => panic!("Unsupported level"),
793 }
794
795 for role in AVAILABLE_ROLES {
797 let has_role = sess.au.has_role(role);
798 let should_have_role = case.roles.contains(&role);
799 assert_eq!(has_role, should_have_role, "Role {:?} check failed", role);
800 }
801
802 if let Some(exp_duration) = case.expiration {
804 let exp = sess.exp.unwrap();
805 let min_exp =
806 (Utc::now() + exp_duration - Duration::seconds(10)).timestamp();
807 let max_exp =
808 (Utc::now() + exp_duration + Duration::seconds(10)).timestamp();
809 assert!(
810 exp > min_exp && exp < max_exp,
811 "Session expiration is expected to match the defined duration"
812 );
813 } else {
814 assert_eq!(sess.exp, None, "Expiration is expected to be None");
815 }
816 } else {
817 assert!(res.is_err(), "Unexpected successful signin: {:?}", res);
818 }
819 }
820 }
821 }
822
823 #[tokio::test]
824 async fn test_basic_nonexistent_role() {
825 use crate::iam::Error as IamError;
826 use crate::sql::{
827 statements::{define::DefineStatement, DefineUserStatement},
828 user::UserDuration,
829 Base, Statement,
830 };
831 let test_levels = vec![
832 TestLevel {
833 level: "ROOT",
834 ns: None,
835 db: None,
836 },
837 TestLevel {
838 level: "NS",
839 ns: Some("test"),
840 db: None,
841 },
842 TestLevel {
843 level: "DB",
844 ns: Some("test"),
845 db: Some("test"),
846 },
847 ];
848
849 for level in &test_levels {
850 let ds = Datastore::new("memory").await.unwrap();
851 let sess = Session::owner().with_ns("test").with_db("test");
852
853 let base = match level.level {
854 "ROOT" => Base::Root,
855 "NS" => Base::Ns,
856 "DB" => Base::Db,
857 _ => panic!("Unsupported level"),
858 };
859
860 let user = DefineUserStatement {
861 base,
862 name: "user".into(),
863 hash: "$argon2id$v=19$m=16,t=2,p=1$VUlHTHVOYjc5d0I1dGE3OQ$sVtmRNH+Xtiijk0uXL2+4w"
865 .to_string(),
866 code: "dummy".to_string(),
867 roles: vec!["nonexistent".into()],
868 duration: UserDuration::default(),
869 comment: None,
870 if_not_exists: false,
871 overwrite: false,
872 };
873
874 ds.process(Statement::Define(DefineStatement::User(user)).into(), &sess, None)
876 .await
877 .unwrap();
878
879 let mut sess = Session {
880 ns: level.ns.map(String::from),
881 db: level.db.map(String::from),
882 ..Default::default()
883 };
884
885 let res = basic(&ds, &mut sess, "user", "pass", level.ns, level.db).await;
887 match res {
888 Err(Error::IamError(IamError::InvalidRole(_))) => {} res => {
890 panic!("Expected an invalid role IAM error, but instead received: {:?}", res)
891 }
892 }
893 }
894 }
895
896 #[tokio::test]
897 async fn test_token() {
898 #[derive(Debug)]
899 struct TestCase {
900 title: &'static str,
901 roles: Option<Vec<&'static str>>,
902 key: &'static str,
903 expect_roles: Vec<Role>,
904 expect_error: bool,
905 }
906
907 let test_cases = vec![
908 TestCase {
909 title: "with no roles",
910 roles: None,
911 key: "secret",
912 expect_roles: vec![Role::Viewer],
913 expect_error: false,
914 },
915 TestCase {
916 title: "with roles",
917 roles: Some(vec!["editor", "owner"]),
918 key: "secret",
919 expect_roles: vec![Role::Editor, Role::Owner],
920 expect_error: false,
921 },
922 TestCase {
923 title: "with nonexistent roles",
924 roles: Some(vec!["viewer", "nonexistent"]),
925 key: "secret",
926 expect_roles: vec![],
927 expect_error: true,
928 },
929 TestCase {
930 title: "with invalid token signature",
931 roles: None,
932 key: "invalid",
933 expect_roles: vec![],
934 expect_error: true,
935 },
936 ];
937
938 let test_levels = vec![
939 TestLevel {
940 level: "ROOT",
941 ns: None,
942 db: None,
943 },
944 TestLevel {
945 level: "NS",
946 ns: Some("test"),
947 db: None,
948 },
949 TestLevel {
950 level: "DB",
951 ns: Some("test"),
952 db: Some("test"),
953 },
954 ];
955
956 let claims = Claims {
957 iss: Some("surrealdb-test".to_string()),
958 iat: Some(Utc::now().timestamp()),
959 nbf: Some(Utc::now().timestamp()),
960 exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
961 ac: Some("token".to_string()),
962 ..Claims::default()
963 };
964
965 let ds = Datastore::new("memory").await.unwrap();
966 let sess = Session::owner().with_ns("test").with_db("test");
967
968 for level in &test_levels {
969 ds.execute(
971 format!(
972 r#"
973 DEFINE ACCESS token ON {} TYPE JWT
974 ALGORITHM HS512 KEY 'secret' DURATION FOR SESSION 30d
975 ;
976 "#,
977 level.level
978 )
979 .as_str(),
980 &sess,
981 None,
982 )
983 .await
984 .unwrap();
985
986 for case in &test_cases {
987 println!("Test case: {} level {}", level.level, case.title);
988
989 let mut claims = claims.clone();
991 claims.ns = level.ns.map(|s| s.to_string());
992 claims.db = level.db.map(|s| s.to_string());
993 claims.roles =
994 case.roles.clone().map(|roles| roles.into_iter().map(String::from).collect());
995
996 let key = EncodingKey::from_secret(case.key.as_ref());
998 let enc = encode(&HEADER, &claims, &key).unwrap();
999
1000 let mut sess = Session::default();
1002 let res = token(&ds, &mut sess, &enc).await;
1003
1004 if case.expect_error {
1005 assert!(res.is_err(), "Unexpected success for case: {:?}", case);
1006 } else {
1007 assert!(res.is_ok(), "Failed to sign in with token for case: {:?}", case);
1008 assert_eq!(sess.ns, level.ns.map(|s| s.to_string()));
1009 assert_eq!(sess.db, level.db.map(|s| s.to_string()));
1010 assert_eq!(sess.au.id(), "token");
1011
1012 for role in AVAILABLE_ROLES {
1014 let has_role = sess.au.has_role(role);
1015 let should_have_role = case.expect_roles.contains(&role);
1016 assert_eq!(has_role, should_have_role, "Role {:?} check failed", role);
1017 }
1018
1019 let exp = sess.exp.unwrap();
1021 let min_exp =
1022 (Utc::now() + Duration::days(30) - Duration::seconds(10)).timestamp();
1023 let max_exp =
1024 (Utc::now() + Duration::days(30) + Duration::seconds(10)).timestamp();
1025 assert!(
1026 exp > min_exp && exp < max_exp,
1027 "Session expiration is expected to match the defined duration in case: {:?}",
1028 case
1029 );
1030 }
1031 }
1032 }
1033 }
1034
1035 #[tokio::test]
1036 async fn test_token_record() {
1037 #[derive(Debug)]
1038 struct TestCase {
1039 title: &'static str,
1040 ids: Vec<&'static str>,
1041 roles: Option<Vec<&'static str>>,
1042 key: &'static str,
1043 expect_error: bool,
1044 }
1045
1046 let test_cases = vec![
1047 TestCase {
1048 title: "with no roles",
1049 ids: vec!["user:test"],
1050 roles: None,
1051 key: "secret",
1052 expect_error: false,
1053 },
1054 TestCase {
1055 title: "with roles",
1056 ids: vec!["user:test"],
1057 roles: Some(vec!["editor", "owner"]),
1058 key: "secret",
1059 expect_error: false,
1060 },
1061 TestCase {
1062 title: "with invalid token signature",
1063 ids: vec!["user:test"],
1064 roles: None,
1065 key: "invalid",
1066 expect_error: true,
1067 },
1068 TestCase {
1069 title: "with invalid id",
1070 ids: vec!["invalid"],
1071 roles: None,
1072 key: "invalid",
1073 expect_error: true,
1074 },
1075 TestCase {
1076 title: "with generic id",
1077 ids: vec!["user:2k9qnabxuxh8k4d5gfto"],
1078 roles: None,
1079 key: "secret",
1080 expect_error: false,
1081 },
1082 TestCase {
1083 title: "with numeric ids",
1084 ids: vec!["user:1", "user:2", "user:100", "user:10000000"],
1085 roles: None,
1086 key: "secret",
1087 expect_error: false,
1088 },
1089 TestCase {
1090 title: "with alphanumeric ids",
1091 ids: vec!["user:username", "user:username1", "user:username10", "user:username100"],
1092 roles: None,
1093 key: "secret",
1094 expect_error: false,
1095 },
1096 TestCase {
1097 title: "with ids including special characters",
1098 ids: vec![
1099 "user:⟨user.name⟩",
1100 "user:⟨user.name1⟩",
1101 "user:⟨user.name10⟩",
1102 "user:⟨user.name100⟩",
1103 ],
1104 roles: None,
1105 key: "secret",
1106 expect_error: false,
1107 },
1108 TestCase {
1109 title: "with UUID ids",
1110 ids: vec!["user:⟨83149446-95f5-4c0d-9f42-136e7b272456⟩"],
1111 roles: None,
1112 key: "secret",
1113 expect_error: false,
1114 },
1115 ];
1116
1117 let secret = "secret";
1118 let claims = Claims {
1119 iss: Some("surrealdb-test".to_string()),
1120 iat: Some(Utc::now().timestamp()),
1121 nbf: Some(Utc::now().timestamp()),
1122 exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
1123 ns: Some("test".to_string()),
1124 db: Some("test".to_string()),
1125 ac: Some("token".to_string()),
1126 ..Claims::default()
1127 };
1128
1129 let ds = Datastore::new("memory").await.unwrap();
1130 let sess = Session::owner().with_ns("test").with_db("test");
1131 ds.execute(
1132 format!(
1133 r#"
1134 DEFINE ACCESS token ON DATABASE TYPE RECORD
1135 WITH JWT ALGORITHM HS512 KEY '{secret}'
1136 DURATION FOR SESSION 30d;
1137
1138 CREATE user:test;
1139 "#
1140 )
1141 .as_str(),
1142 &sess,
1143 None,
1144 )
1145 .await
1146 .unwrap();
1147
1148 for case in &test_cases {
1149 println!("Test case: {}", case.title);
1150
1151 for id in &case.ids {
1152 let mut claims = claims.clone();
1154 claims.id = Some(id.to_string());
1155 claims.roles =
1156 case.roles.clone().map(|roles| roles.into_iter().map(String::from).collect());
1157
1158 let key = EncodingKey::from_secret(case.key.as_ref());
1160 let enc = encode(&HEADER, &claims, &key).unwrap();
1161
1162 let mut sess = Session::default();
1164 let res = token(&ds, &mut sess, &enc).await;
1165
1166 if case.expect_error {
1167 assert!(res.is_err(), "Unexpected success for case: {:?}", case);
1168 } else {
1169 assert!(res.is_ok(), "Failed to sign in with token for case: {:?}", case);
1170 assert_eq!(sess.ns, Some("test".to_string()));
1171 assert_eq!(sess.db, Some("test".to_string()));
1172 assert_eq!(sess.au.id(), *id);
1173
1174 for role in AVAILABLE_ROLES {
1176 assert!(
1177 !sess.au.has_role(role),
1178 "Auth user expected to not have role {:?} in case: {:?}",
1179 role,
1180 case
1181 );
1182 }
1183
1184 let exp = sess.exp.unwrap();
1186 let min_exp =
1187 (Utc::now() + Duration::days(30) - Duration::seconds(10)).timestamp();
1188 let max_exp =
1189 (Utc::now() + Duration::days(30) + Duration::seconds(10)).timestamp();
1190 assert!(
1191 exp > min_exp && exp < max_exp,
1192 "Session expiration is expected to match the defined duration in case: {:?}",
1193 case
1194 );
1195 }
1196 }
1197 }
1198 }
1199
1200 #[tokio::test]
1201 async fn test_token_record_custom_claims() {
1202 use std::collections::HashMap;
1203
1204 let secret = "jwt_secret";
1205 let key = EncodingKey::from_secret(secret.as_ref());
1206
1207 let ds = Datastore::new("memory").await.unwrap();
1208 let sess = Session::owner().with_ns("test").with_db("test");
1209 ds.execute(
1210 format!(
1211 r#"
1212 DEFINE ACCESS token ON DATABASE TYPE RECORD
1213 WITH JWT ALGORITHM HS512 KEY '{secret}'
1214 DURATION FOR SESSION 30d;
1215
1216 CREATE user:test;
1217 "#
1218 )
1219 .as_str(),
1220 &sess,
1221 None,
1222 )
1223 .await
1224 .unwrap();
1225
1226 let now = Utc::now().timestamp();
1230 let later = (Utc::now() + Duration::hours(1)).timestamp();
1231 {
1232 let claims_json = format!(
1233 r#"
1234 {{
1235 "iss": "surrealdb-test",
1236 "iat": {now},
1237 "nbf": {now},
1238 "exp": {later},
1239 "ns": "test",
1240 "db": "test",
1241 "ac": "token",
1242 "id": "user:test",
1243
1244 "string_claim": "test",
1245 "bool_claim": true,
1246 "int_claim": 123456,
1247 "float_claim": 123.456,
1248 "array_claim": [
1249 "test_1",
1250 "test_2"
1251 ],
1252 "object_claim": {{
1253 "test_1": "value_1",
1254 "test_2": {{
1255 "test_2_1": "value_2_1",
1256 "test_2_2": "value_2_2"
1257 }}
1258 }}
1259 }}
1260 "#
1261 );
1262 let claims = serde_json::from_str::<Claims>(&claims_json).unwrap();
1263 let enc = match encode(&HEADER, &claims, &key) {
1265 Ok(enc) => enc,
1266 Err(err) => panic!("Failed to encode token: {:?}", err),
1267 };
1268 let mut sess = Session::default();
1270 let res = token(&ds, &mut sess, &enc).await;
1271
1272 assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
1273 assert_eq!(sess.ns, Some("test".to_string()));
1274 assert_eq!(sess.db, Some("test".to_string()));
1275 assert_eq!(sess.ac, Some("token".to_string()));
1276 assert_eq!(sess.au.id(), "user:test");
1277 assert!(sess.au.is_record());
1278 assert_eq!(sess.au.level().ns(), Some("test"));
1279 assert_eq!(sess.au.level().db(), Some("test"));
1280 assert!(!sess.au.has_role(Role::Viewer), "Auth user expected to not have Viewer role");
1281 assert!(!sess.au.has_role(Role::Editor), "Auth user expected to not have Editor role");
1282 assert!(!sess.au.has_role(Role::Owner), "Auth user expected to not have Owner role");
1283 let exp = sess.exp.unwrap();
1285 let min_exp = (Utc::now() + Duration::days(30) - Duration::seconds(10)).timestamp();
1287 let max_exp = (Utc::now() + Duration::days(30) + Duration::seconds(10)).timestamp();
1288 assert!(
1289 exp > min_exp && exp < max_exp,
1290 "Session expiration is expected to match the defined duration"
1291 );
1292 let tk = match sess.tk {
1293 Some(Value::Object(tk)) => tk,
1294 _ => panic!("Session token is not an object"),
1295 };
1296 let string_claim = tk.get("string_claim").unwrap();
1297 assert_eq!(*string_claim, Value::Strand("test".into()));
1298 let bool_claim = tk.get("bool_claim").unwrap();
1299 assert_eq!(*bool_claim, Value::Bool(true));
1300 let int_claim = tk.get("int_claim").unwrap();
1301 assert_eq!(*int_claim, Value::Number(123456.into()));
1302 let float_claim = tk.get("float_claim").unwrap();
1303 assert_eq!(*float_claim, Value::Number(123.456.into()));
1304 let array_claim = tk.get("array_claim").unwrap();
1305 assert_eq!(*array_claim, Value::Array(vec!["test_1", "test_2"].into()));
1306 let object_claim = tk.get("object_claim").unwrap();
1307 let mut test_object: HashMap<String, Value> = HashMap::new();
1308 test_object.insert("test_1".to_string(), Value::Strand("value_1".into()));
1309 let mut test_object_child = HashMap::new();
1310 test_object_child.insert("test_2_1".to_string(), Value::Strand("value_2_1".into()));
1311 test_object_child.insert("test_2_2".to_string(), Value::Strand("value_2_2".into()));
1312 test_object.insert("test_2".to_string(), Value::Object(test_object_child.into()));
1313 assert_eq!(*object_claim, Value::Object(test_object.into()));
1314 }
1315 }
1316
1317 #[cfg(feature = "jwks")]
1318 #[tokio::test]
1319 async fn test_token_record_jwks() {
1320 use crate::dbs::capabilities::{Capabilities, NetTarget, Targets};
1321 use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine};
1322 use jsonwebtoken::jwk::{Jwk, JwkSet};
1323 use rand::{distributions::Alphanumeric, Rng};
1324 use wiremock::matchers::{method, path};
1325 use wiremock::{Mock, MockServer, ResponseTemplate};
1326
1327 fn random_path() -> String {
1329 let rng = rand::thread_rng();
1330 rng.sample_iter(&Alphanumeric).take(8).map(char::from).collect()
1331 }
1332
1333 let kid = "test_kid";
1335 let secret = "jwt_secret";
1337
1338 let jwks = JwkSet {
1340 keys: vec![Jwk {
1341 common: jsonwebtoken::jwk::CommonParameters {
1342 public_key_use: None,
1343 key_operations: None,
1344 key_algorithm: Some(jsonwebtoken::jwk::KeyAlgorithm::HS512),
1345 key_id: Some(kid.to_string()),
1346 x509_url: None,
1347 x509_chain: None,
1348 x509_sha1_fingerprint: None,
1349 x509_sha256_fingerprint: None,
1350 },
1351 algorithm: jsonwebtoken::jwk::AlgorithmParameters::OctetKey(
1352 jsonwebtoken::jwk::OctetKeyParameters {
1353 key_type: jsonwebtoken::jwk::OctetKeyType::Octet,
1354 value: STANDARD_NO_PAD.encode(secret),
1355 },
1356 ),
1357 }],
1358 };
1359
1360 let jwks_path = format!("{}/jwks.json", random_path());
1361 let mock_server = MockServer::start().await;
1362 let response = ResponseTemplate::new(200).set_body_json(jwks);
1363 Mock::given(method("GET"))
1364 .and(path(&jwks_path))
1365 .respond_with(response)
1366 .expect(1)
1367 .mount(&mock_server)
1368 .await;
1369 let server_url = mock_server.uri();
1370
1371 let ds = Datastore::new("memory").await.unwrap().with_capabilities(
1373 Capabilities::default().with_network_targets(Targets::<NetTarget>::Some(
1374 [NetTarget::from_str("127.0.0.1").unwrap()].into(),
1375 )),
1376 );
1377
1378 let sess = Session::owner().with_ns("test").with_db("test");
1379 ds.execute(
1380 format!(
1381 r#"
1382 DEFINE ACCESS token ON DATABASE TYPE RECORD
1383 WITH JWT URL '{server_url}/{jwks_path}';
1384
1385 CREATE user:test;
1386 "#
1387 )
1388 .as_str(),
1389 &sess,
1390 None,
1391 )
1392 .await
1393 .unwrap();
1394
1395 let header_with_kid = jsonwebtoken::Header {
1397 kid: Some(kid.to_string()),
1398 alg: jsonwebtoken::Algorithm::HS512,
1399 ..jsonwebtoken::Header::default()
1400 };
1401
1402 let key = EncodingKey::from_secret(secret.as_ref());
1404 let claims = Claims {
1405 iss: Some("surrealdb-test".to_string()),
1406 iat: Some(Utc::now().timestamp()),
1407 nbf: Some(Utc::now().timestamp()),
1408 aud: Some(Audience::Single("surrealdb-test".to_string())),
1409 exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
1410 ns: Some("test".to_string()),
1411 db: Some("test".to_string()),
1412 ac: Some("token".to_string()),
1413 id: Some("user:test".to_string()),
1414 ..Claims::default()
1415 };
1416
1417 {
1422 let mut claims = claims.clone();
1424 claims.roles = None;
1425 let enc = encode(&header_with_kid, &claims, &key).unwrap();
1427 let mut sess = Session::default();
1429 let res = token(&ds, &mut sess, &enc).await;
1430
1431 assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
1432 assert_eq!(sess.ns, Some("test".to_string()));
1433 assert_eq!(sess.db, Some("test".to_string()));
1434 assert_eq!(sess.ac, Some("token".to_string()));
1435 assert_eq!(sess.au.id(), "user:test");
1436 assert!(sess.au.is_record());
1437 assert_eq!(sess.au.level().ns(), Some("test"));
1438 assert_eq!(sess.au.level().db(), Some("test"));
1439 assert!(!sess.au.has_role(Role::Viewer), "Auth user expected to not have Viewer role");
1440 assert!(!sess.au.has_role(Role::Editor), "Auth user expected to not have Editor role");
1441 assert!(!sess.au.has_role(Role::Owner), "Auth user expected to not have Owner role");
1442 assert_eq!(sess.exp, None, "Default session expiration is expected to be None");
1443 }
1444
1445 {
1449 let claims = claims.clone();
1451 let key = EncodingKey::from_secret("invalid".as_ref());
1453 let enc = encode(&header_with_kid, &claims, &key).unwrap();
1454 let mut sess = Session::default();
1456 let res = token(&ds, &mut sess, &enc).await;
1457
1458 assert!(res.is_err(), "Unexpected success signing in with token: {:?}", res);
1459 }
1460 }
1461
1462 #[test]
1463 fn test_verify_pass() {
1464 let salt = SaltString::generate(&mut rand::thread_rng());
1465 let hash = Argon2::default().hash_password("test".as_bytes(), &salt).unwrap().to_string();
1466
1467 verify_pass("test", &hash).unwrap();
1469
1470 assert!(verify_pass("nonmatching", &hash).is_err());
1472 }
1473
1474 #[tokio::test]
1475 async fn test_verify_creds_invalid() {
1476 let ds = Datastore::new("memory").await.unwrap();
1477 let ns = "N".to_string();
1478 let db = "D".to_string();
1479
1480 {
1482 assert!(verify_root_creds(&ds, "test", "test").await.is_err());
1483 }
1484
1485 {
1487 assert!(verify_ns_creds(&ds, &ns, "test", "test").await.is_err());
1488 }
1489
1490 {
1492 assert!(verify_db_creds(&ds, &ns, &db, "test", "test").await.is_err());
1493 }
1494 }
1495
1496 #[tokio::test]
1497 async fn test_verify_creds_valid() {
1498 let ds = Datastore::new("memory").await.unwrap();
1499 let ns = "N".to_string();
1500 let db = "D".to_string();
1501
1502 {
1504 let sess = Session::owner();
1505
1506 let sql = "DEFINE USER root ON ROOT PASSWORD 'root'";
1507 ds.execute(sql, &sess, None).await.unwrap();
1508
1509 let sql = "USE NS N; DEFINE USER ns ON NS PASSWORD 'ns'";
1510 ds.execute(sql, &sess, None).await.unwrap();
1511
1512 let sql = "USE NS N DB D; DEFINE USER db ON DB PASSWORD 'db'";
1513 ds.execute(sql, &sess, None).await.unwrap();
1514 }
1515
1516 {
1518 let res = verify_root_creds(&ds, "root", "root").await;
1519 res.unwrap();
1520 }
1521
1522 {
1524 let res = verify_ns_creds(&ds, &ns, "ns", "ns").await;
1525 res.unwrap();
1526 }
1527
1528 {
1530 let res = verify_db_creds(&ds, &ns, &db, "db", "db").await;
1531 res.unwrap();
1532 }
1533 }
1534
1535 #[tokio::test]
1536 async fn test_expired_token() {
1537 let secret = "jwt_secret";
1538 let key = EncodingKey::from_secret(secret.as_ref());
1539 let claims = Claims {
1540 iss: Some("surrealdb-test".to_string()),
1541 iat: Some((Utc::now() - Duration::hours(2)).timestamp()),
1543 nbf: Some((Utc::now() - Duration::hours(2)).timestamp()),
1544 exp: Some((Utc::now() - Duration::hours(1)).timestamp()),
1545 ac: Some("token".to_string()),
1546 ns: Some("test".to_string()),
1547 db: Some("test".to_string()),
1548 ..Claims::default()
1549 };
1550
1551 let ds = Datastore::new("memory").await.unwrap();
1552 let sess = Session::owner().with_ns("test").with_db("test");
1553 ds.execute(
1554 format!("DEFINE ACCESS token ON DATABASE TYPE JWT ALGORITHM HS512 KEY '{secret}' DURATION FOR SESSION 30d, FOR TOKEN 30d")
1555 .as_str(),
1556 &sess,
1557 None,
1558 )
1559 .await
1560 .unwrap();
1561
1562 let mut claims = claims.clone();
1564 claims.roles = None;
1565 let enc = encode(&HEADER, &claims, &key).unwrap();
1567 let mut sess = Session::default();
1569 let res = token(&ds, &mut sess, &enc).await;
1570
1571 match res {
1572 Err(Error::ExpiredToken) => {} Err(err) => panic!("Unexpected error signing in with expired token: {:?}", err),
1574 res => panic!("Unexpected success signing in with expired token: {:?}", res),
1575 }
1576 }
1577
1578 #[tokio::test]
1579 async fn test_token_authenticate_clause() {
1580 #[derive(Debug)]
1581 struct TestCase {
1582 title: &'static str,
1583 iss_claim: Option<&'static str>,
1584 aud_claim: Option<Audience>,
1585 error_statement: &'static str,
1586 expected_error: Option<Error>,
1587 }
1588
1589 let test_cases = vec![
1590 TestCase {
1591 title: "with correct 'iss' and 'aud' claims",
1592 iss_claim: Some("surrealdb-test"),
1593 aud_claim: Some(Audience::Single("surrealdb-test".to_string())),
1594 error_statement: "THROW",
1595 expected_error: None,
1596 },
1597 TestCase {
1598 title: "with correct 'iss' and 'aud' claims, multiple audiences",
1599 iss_claim: Some("surrealdb-test"),
1600 aud_claim: Some(Audience::Multiple(vec![
1601 "invalid".to_string(),
1602 "surrealdb-test".to_string(),
1603 ])),
1604 error_statement: "THROW",
1605 expected_error: None,
1606 },
1607 TestCase {
1608 title: "with correct 'iss' claim but invalid 'aud' claim",
1609 iss_claim: Some("surrealdb-test"),
1610 aud_claim: Some(Audience::Single("invalid".to_string())),
1611 error_statement: "THROW",
1612 expected_error: Some(Error::Thrown("Invalid token audience string".to_string())),
1613 },
1614 TestCase {
1615 title: "with correct 'iss' claim but invalid 'aud' claim, multiple audiences",
1616 iss_claim: Some("surrealdb-test"),
1617 aud_claim: Some(Audience::Multiple(vec![
1618 "invalid".to_string(),
1619 "surrealdb-test-different".to_string(),
1620 ])),
1621 error_statement: "THROW",
1622 expected_error: Some(Error::Thrown("Invalid token audience array".to_string())),
1623 },
1624 TestCase {
1625 title: "with correct 'iss' claim but invalid 'aud' claim, generic error",
1626 iss_claim: Some("surrealdb-test"),
1627 aud_claim: Some(Audience::Single("invalid".to_string())),
1628 error_statement: "RETURN",
1629 expected_error: Some(Error::InvalidAuth),
1630 },
1631 ];
1632
1633 let test_levels = vec![
1634 TestLevel {
1635 level: "ROOT",
1636 ns: None,
1637 db: None,
1638 },
1639 TestLevel {
1640 level: "NS",
1641 ns: Some("test"),
1642 db: None,
1643 },
1644 TestLevel {
1645 level: "DB",
1646 ns: Some("test"),
1647 db: Some("test"),
1648 },
1649 ];
1650
1651 let secret = "secret";
1652 let key = EncodingKey::from_secret(secret.as_ref());
1653 let claims = Claims {
1654 iat: Some(Utc::now().timestamp()),
1655 nbf: Some(Utc::now().timestamp()),
1656 exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
1657 ac: Some("user".to_string()),
1658 ..Claims::default()
1659 };
1660
1661 let ds = Datastore::new("memory").await.unwrap();
1662 let sess = Session::owner().with_ns("test").with_db("test");
1663
1664 for level in &test_levels {
1665 for case in &test_cases {
1666 println!("Test case: {} level {}", level.level, case.title);
1667
1668 ds.execute(
1669 format!(
1670 r#"
1671 REMOVE ACCESS IF EXISTS user ON {0};
1672 DEFINE ACCESS user ON {0} TYPE JWT
1673 ALGORITHM HS512 KEY '{1}'
1674 AUTHENTICATE {{
1675 IF $token.iss != "surrealdb-test" {{ {2} "Invalid token issuer" }};
1676 IF type::is::array($token.aud) {{
1677 IF "surrealdb-test" NOT IN $token.aud {{ {2} "Invalid token audience array" }}
1678 }} ELSE {{
1679 IF $token.aud IS NOT "surrealdb-test" {{ {2} "Invalid token audience string" }}
1680 }};
1681 }}
1682 DURATION FOR SESSION 2h
1683 ;
1684 "#,
1685 level.level, secret, case.error_statement,
1686 )
1687 .as_str(),
1688 &sess,
1689 None,
1690 )
1691 .await
1692 .unwrap();
1693
1694 let mut claims = claims.clone();
1696 claims.ns = level.ns.map(|s| s.to_string());
1697 claims.db = level.db.map(|s| s.to_string());
1698 claims.iss = case.iss_claim.map(|s| s.to_string());
1699 claims.aud = case.aud_claim.clone();
1700
1701 let enc = encode(&HEADER, &claims, &key).unwrap();
1703
1704 let mut sess = Session::default();
1706 let res = token(&ds, &mut sess, &enc).await;
1707
1708 if let Some(expected_err) = &case.expected_error {
1709 assert!(res.is_err(), "Unexpected success for case: {:?}", case);
1710 let err = res.unwrap_err();
1711 match (expected_err, &err) {
1712 (Error::InvalidAuth, Error::InvalidAuth) => {}
1713 (Error::Thrown(expected_msg), Error::Thrown(msg))
1714 if expected_msg == msg => {}
1715 _ => panic!("Unexpected error for case: {:?}, got: {:?}", case, err),
1716 }
1717 } else {
1718 assert!(res.is_ok(), "Failed to sign in with token for case: {:?}", case);
1719 assert_eq!(sess.ns, level.ns.map(|s| s.to_string()));
1720 assert_eq!(sess.db, level.db.map(|s| s.to_string()));
1721 assert_eq!(sess.ac, Some("user".to_string()));
1722 assert_eq!(sess.au.id(), "user");
1723
1724 assert_eq!(sess.au.level().ns(), level.ns);
1726 assert_eq!(sess.au.level().db(), level.db);
1727 match level.level {
1728 "ROOT" => assert!(sess.au.is_root()),
1729 "NS" => assert!(sess.au.is_ns()),
1730 "DB" => assert!(sess.au.is_db()),
1731 _ => panic!("Unsupported level"),
1732 }
1733
1734 assert!(
1736 sess.au.has_role(Role::Viewer),
1737 "Auth user expected to have Viewer role"
1738 );
1739 assert!(
1740 !sess.au.has_role(Role::Editor),
1741 "Auth user expected to not have Editor role"
1742 );
1743 assert!(
1744 !sess.au.has_role(Role::Owner),
1745 "Auth user expected to not have Owner role"
1746 );
1747
1748 let exp = sess.exp.unwrap();
1750 let min_exp =
1751 (Utc::now() + Duration::hours(2) - Duration::seconds(10)).timestamp();
1752 let max_exp =
1753 (Utc::now() + Duration::hours(2) + Duration::seconds(10)).timestamp();
1754 assert!(
1755 exp > min_exp && exp < max_exp,
1756 "Session expiration is expected to match the defined duration in case: {:?}",
1757 case
1758 );
1759 }
1760 }
1761 }
1762 }
1763
1764 #[tokio::test]
1765 async fn test_token_record_and_authenticate_clause() {
1766 {
1768 let secret = "jwt_secret";
1769 let key = EncodingKey::from_secret(secret.as_ref());
1770 let claims = Claims {
1771 iss: Some("surrealdb-test".to_string()),
1772 iat: Some(Utc::now().timestamp()),
1773 nbf: Some(Utc::now().timestamp()),
1774 exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
1775 ns: Some("test".to_string()),
1776 db: Some("test".to_string()),
1777 ac: Some("user".to_string()),
1778 id: Some("user:1".to_string()),
1779 ..Claims::default()
1780 };
1781
1782 let ds = Datastore::new("memory").await.unwrap();
1783 let sess = Session::owner().with_ns("test").with_db("test");
1784 ds.execute(
1785 format!(
1786 r#"
1787 DEFINE ACCESS user ON DATABASE TYPE RECORD
1788 WITH JWT ALGORITHM HS512 KEY '{secret}'
1789 AUTHENTICATE (
1790 -- Simple example increasing the record identifier by one
1791 SELECT * FROM type::thing('user', record::id($auth) + 1)
1792 )
1793 DURATION FOR SESSION 2h
1794 ;
1795
1796 CREATE user:1, user:2;
1797 "#
1798 )
1799 .as_str(),
1800 &sess,
1801 None,
1802 )
1803 .await
1804 .unwrap();
1805
1806 let mut claims = claims.clone();
1808 claims.roles = None;
1809 let enc = encode(&HEADER, &claims, &key).unwrap();
1811 let mut sess = Session::default();
1813 let res = token(&ds, &mut sess, &enc).await;
1814
1815 assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
1816 assert_eq!(sess.ns, Some("test".to_string()));
1817 assert_eq!(sess.db, Some("test".to_string()));
1818 assert_eq!(sess.ac, Some("user".to_string()));
1819 assert_eq!(sess.au.id(), "user:2");
1820 assert!(sess.au.is_record());
1821 assert_eq!(sess.au.level().ns(), Some("test"));
1822 assert_eq!(sess.au.level().db(), Some("test"));
1823 assert_eq!(sess.au.level().id(), Some("user:2"));
1824 assert!(!sess.au.has_role(Role::Viewer), "Auth user expected to not have Viewer role");
1826 assert!(!sess.au.has_role(Role::Editor), "Auth user expected to not have Editor role");
1827 assert!(!sess.au.has_role(Role::Owner), "Auth user expected to not have Owner role");
1828 let exp = sess.exp.unwrap();
1830 let min_exp = (Utc::now() + Duration::hours(2) - Duration::seconds(10)).timestamp();
1832 let max_exp = (Utc::now() + Duration::hours(2) + Duration::seconds(10)).timestamp();
1833 assert!(
1834 exp > min_exp && exp < max_exp,
1835 "Session expiration is expected to follow the defined duration"
1836 );
1837 }
1838
1839 {
1841 let secret = "jwt_secret";
1842 let key = EncodingKey::from_secret(secret.as_ref());
1843
1844 let ds = Datastore::new("memory").await.unwrap();
1845 let sess = Session::owner().with_ns("test").with_db("test");
1846 ds.execute(
1847 format!(
1848 r#"
1849 DEFINE ACCESS user ON DATABASE TYPE RECORD
1850 SIGNIN (
1851 SELECT * FROM type::thing('user', $id)
1852 )
1853 WITH JWT ALGORITHM HS512 KEY '{secret}'
1854 AUTHENTICATE (
1855 SELECT id FROM user WHERE email = $token.email
1856 )
1857 DURATION FOR SESSION 2h
1858 ;
1859
1860 CREATE user:1 SET email = "info@surrealdb.com";
1861 "#
1862 )
1863 .as_str(),
1864 &sess,
1865 None,
1866 )
1867 .await
1868 .unwrap();
1869
1870 let now = Utc::now().timestamp();
1871 let later = (Utc::now() + Duration::hours(1)).timestamp();
1872 let claims_json = format!(
1873 r#"
1874 {{
1875 "iss": "surrealdb-test",
1876 "iat": {now},
1877 "nbf": {now},
1878 "exp": {later},
1879 "ns": "test",
1880 "db": "test",
1881 "ac": "user",
1882 "email": "info@surrealdb.com"
1883 }}
1884 "#
1885 );
1886 let claims = serde_json::from_str::<Claims>(&claims_json).unwrap();
1887 let enc = match encode(&HEADER, &claims, &key) {
1889 Ok(enc) => enc,
1890 Err(err) => panic!("Failed to encode token: {:?}", err),
1891 };
1892 let mut sess = Session::default();
1894 let res = token(&ds, &mut sess, &enc).await;
1895
1896 assert!(res.is_ok(), "Failed to signin with token: {:?}", res);
1897 assert_eq!(sess.ns, Some("test".to_string()));
1898 assert_eq!(sess.db, Some("test".to_string()));
1899 assert_eq!(sess.ac, Some("user".to_string()));
1900 assert_eq!(sess.au.id(), "user:1");
1901 assert!(sess.au.is_record());
1902 assert_eq!(sess.au.level().ns(), Some("test"));
1903 assert_eq!(sess.au.level().db(), Some("test"));
1904 assert_eq!(sess.au.level().id(), Some("user:1"));
1905 assert!(!sess.au.has_role(Role::Viewer), "Auth user expected to not have Viewer role");
1907 assert!(!sess.au.has_role(Role::Editor), "Auth user expected to not have Editor role");
1908 assert!(!sess.au.has_role(Role::Owner), "Auth user expected to not have Owner role");
1909 let exp = sess.exp.unwrap();
1911 let min_exp = (Utc::now() + Duration::hours(2) - Duration::seconds(10)).timestamp();
1913 let max_exp = (Utc::now() + Duration::hours(2) + Duration::seconds(10)).timestamp();
1914 assert!(
1915 exp > min_exp && exp < max_exp,
1916 "Session expiration is expected to follow the defined duration"
1917 );
1918 }
1919
1920 {
1922 let secret = "jwt_secret";
1923 let key = EncodingKey::from_secret(secret.as_ref());
1924 let claims = Claims {
1925 iss: Some("surrealdb-test".to_string()),
1926 iat: Some(Utc::now().timestamp()),
1927 nbf: Some(Utc::now().timestamp()),
1928 exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
1929 ns: Some("test".to_string()),
1930 db: Some("test".to_string()),
1931 ac: Some("user".to_string()),
1932 id: Some("user:1".to_string()),
1933 ..Claims::default()
1934 };
1935
1936 let ds = Datastore::new("memory").await.unwrap();
1937 let sess = Session::owner().with_ns("test").with_db("test");
1938 ds.execute(
1939 format!(r#"
1940 DEFINE ACCESS user ON DATABASE TYPE RECORD
1941 WITH JWT ALGORITHM HS512 KEY '{secret}'
1942 AUTHENTICATE {{
1943 -- Not just signin, this clause runs across signin, signup and authenticate, which makes it a nice place to centralize logic
1944 IF !$auth.enabled {{
1945 THROW "This user is not enabled";
1946 }};
1947
1948 -- Always need to return the user id back, otherwise auth generically fails
1949 RETURN $auth;
1950 }}
1951 DURATION FOR SESSION 2h
1952 ;
1953
1954 CREATE user:1 SET enabled = false;
1955 "#).as_str(),
1956 &sess,
1957 None,
1958 )
1959 .await
1960 .unwrap();
1961
1962 let mut claims = claims.clone();
1964 claims.roles = None;
1965 let enc = encode(&HEADER, &claims, &key).unwrap();
1967 let mut sess = Session::default();
1969 let res = token(&ds, &mut sess, &enc).await;
1970
1971 match res {
1972 Err(Error::Thrown(e)) if e == "This user is not enabled" => {} res => panic!(
1974 "Expected authentication to failed due to user not being enabled, but instead received: {:?}",
1975 res
1976 ),
1977 }
1978 }
1979
1980 {
1982 let secret = "jwt_secret";
1983 let key = EncodingKey::from_secret(secret.as_ref());
1984 let claims = Claims {
1985 iss: Some("surrealdb-test".to_string()),
1986 iat: Some(Utc::now().timestamp()),
1987 nbf: Some(Utc::now().timestamp()),
1988 exp: Some((Utc::now() + Duration::hours(1)).timestamp()),
1989 ns: Some("test".to_string()),
1990 db: Some("test".to_string()),
1991 ac: Some("user".to_string()),
1992 id: Some("user:test".to_string()),
1993 ..Claims::default()
1994 };
1995
1996 let ds = Datastore::new("memory").await.unwrap();
1997 let sess = Session::owner().with_ns("test").with_db("test");
1998 ds.execute(
1999 format!(
2000 r#"
2001 DEFINE ACCESS user ON DATABASE TYPE RECORD
2002 WITH JWT ALGORITHM HS512 KEY '{secret}'
2003 AUTHENTICATE {{}}
2004 DURATION FOR SESSION 2h
2005 ;
2006
2007 CREATE user:1;
2008 "#
2009 )
2010 .as_str(),
2011 &sess,
2012 None,
2013 )
2014 .await
2015 .unwrap();
2016
2017 let mut claims = claims.clone();
2019 claims.roles = None;
2020 let enc = encode(&HEADER, &claims, &key).unwrap();
2022 let mut sess = Session::default();
2024 let res = token(&ds, &mut sess, &enc).await;
2025
2026 match res {
2027 Err(Error::InvalidAuth) => {} res => panic!(
2029 "Expected authentication to generally fail, but instead received: {:?}",
2030 res
2031 ),
2032 }
2033 }
2034 }
2035}