surrealdb_core/iam/
verify.rs

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	// TODO(gguillemas): This keeps the existing behavior as of SurrealDB 2.0.0-alpha.9.
62	// Up to that point, a fork of the "jsonwebtoken" crate in version 8.3.0 was being used.
63	// Now that the audience claim is validated by default, we could allow users to leverage this.
64	// This will most likely involve defining an audience string via "DEFINE ACCESS ... TYPE JWT".
65	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	// Log the authentication type
90	trace!("Attempting basic authentication");
91	// Check if the parameters exist
92	match (ns, db) {
93		// DB signin
94		(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		// NS signin
105		(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		// Root signin
115		(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	// Log the authentication type
135	trace!("Attempting token authentication");
136	// Decode the token without verifying
137	let token_data = decode::<Claims>(token, &KEY, &DUD)?;
138	// Convert the token to a SurrealQL object value
139	let value = (&token_data.claims).into();
140	// Check if the auth token can be used
141	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	// Check if the auth token has expired
148	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	// Check the token authentication claims
155	match &token_data.claims {
156		// Check if this is record access
157		Claims {
158			ns: Some(ns),
159			db: Some(db),
160			ac: Some(ac),
161			id: Some(id),
162			..
163		} => {
164			// Log the decoded authentication claims
165			trace!("Authenticating with record access method `{}`", ac);
166			// Create a new readonly transaction
167			let tx = kvs.transaction(Read, Optimistic).await?;
168			// Parse the record id
169			let mut rid = syn::thing(id)?;
170			// Get the database access method
171			let de = tx.get_db_access(ns, db, ac).await?;
172			// Ensure that the transaction is cancelled
173			tx.cancel().await?;
174			// Obtain the configuration to verify the token based on the access method
175			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 the token
192			verify_token(token, &cf.0, &cf.1)?;
193			// AUTHENTICATE clause
194			if let Some(au) = &de.authenticate {
195				// Setup the system session for finding the signin record
196				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			// Log the success
204			debug!("Authenticated with record access method `{}`", ac);
205			// Set the session
206			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		// Check if this is database access
220		// This can also be record access with an authenticate clause
221		Claims {
222			ns: Some(ns),
223			db: Some(db),
224			ac: Some(ac),
225			..
226		} => {
227			// Log the decoded authentication claims
228			trace!("Authenticating to database `{}` with access method `{}`", db, ac);
229			// Create a new readonly transaction
230			let tx = kvs.transaction(Read, Optimistic).await?;
231			// Get the database access method
232			let de = tx.get_db_access(ns, db, ac).await?;
233			// Ensure that the transaction is cancelled
234			tx.cancel().await?;
235			// Obtain the configuration to verify the token based on the access method
236			match &de.kind {
237				// If the access type is Jwt or Bearer, this is database access
238				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 the token
253					verify_token(token, &cf.0, &cf.1)?;
254					// AUTHENTICATE clause
255					if let Some(au) = &de.authenticate {
256						// Setup the system session for executing the clause
257						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					// Parse the roles
264					let roles = match &token_data.claims.roles {
265						// If no role is provided, grant the viewer role
266						None => vec![Role::Viewer],
267						// If roles are provided, parse them
268						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					// Log the success
276					debug!("Authenticated to database `{}` with access method `{}`", db, ac);
277					// Set the session
278					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				// If the access type is Record, this is record access
290				// Record access without an "id" claim is only possible if there is an AUTHENTICATE clause
291				// The clause can make up for the missing "id" claim by resolving other claims to a specific record
292				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 the token
310						verify_token(token, &cf.0, &cf.1)?;
311						// AUTHENTICATE clause
312						// Setup the system session for finding the signin record
313						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						// Log the success
319						debug!("Authenticated with record access method `{}`", ac);
320						// Set the session
321						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		// Check if this is database authentication with user credentials
339		Claims {
340			ns: Some(ns),
341			db: Some(db),
342			id: Some(id),
343			..
344		} => {
345			// Log the decoded authentication claims
346			trace!("Authenticating to database `{}` with user `{}`", db, id);
347			// Create a new readonly transaction
348			let tx = kvs.transaction(Read, Optimistic).await?;
349			// Get the database user
350			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			// Ensure that the transaction is cancelled
355			tx.cancel().await?;
356			// Check the algorithm
357			let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
358			// Verify the token
359			verify_token(token, &cf.0, &cf.1)?;
360			// Log the success
361			debug!("Authenticated to database `{}` with user `{}` using token", db, id);
362			// Set the session
363			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		// Check if this is namespace access
375		Claims {
376			ns: Some(ns),
377			ac: Some(ac),
378			..
379		} => {
380			// Log the decoded authentication claims
381			trace!("Authenticating to namespace `{}` with access method `{}`", ns, ac);
382			// Create a new readonly transaction
383			let tx = kvs.transaction(Read, Optimistic).await?;
384			// Get the namespace access method
385			let de = tx.get_ns_access(ns, ac).await?;
386			// Ensure that the transaction is cancelled
387			tx.cancel().await?;
388			// Obtain the configuration to verify the token based on the access method
389			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 the token
406			verify_token(token, &cf.0, &cf.1)?;
407			// AUTHENTICATE clause
408			if let Some(au) = &de.authenticate {
409				// Setup the system session for executing the clause
410				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			// Parse the roles
417			let roles = match &token_data.claims.roles {
418				// If no role is provided, grant the viewer role
419				None => vec![Role::Viewer],
420				// If roles are provided, parse them
421				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			// Log the success
429			debug!("Authenticated to namespace `{}` with access method `{}`", ns, ac);
430			// Set the session
431			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		// Check if this is namespace authentication with user credentials
443		Claims {
444			ns: Some(ns),
445			id: Some(id),
446			..
447		} => {
448			// Log the decoded authentication claims
449			trace!("Authenticating to namespace `{}` with user `{}`", ns, id);
450			// Create a new readonly transaction
451			let tx = kvs.transaction(Read, Optimistic).await?;
452			// Get the namespace user
453			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			// Ensure that the transaction is cancelled
458			tx.cancel().await?;
459			// Check the algorithm
460			let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
461			// Verify the token
462			verify_token(token, &cf.0, &cf.1)?;
463			// Log the success
464			debug!("Authenticated to namespace `{}` with user `{}` using token", ns, id);
465			// Set the session
466			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		// Check if this is root access
477		Claims {
478			ac: Some(ac),
479			..
480		} => {
481			// Log the decoded authentication claims
482			trace!("Authenticating to root with access method `{}`", ac);
483			// Create a new readonly transaction
484			let tx = kvs.transaction(Read, Optimistic).await?;
485			// Get the namespace access method
486			let de = tx.get_root_access(ac).await?;
487			// Ensure that the transaction is cancelled
488			tx.cancel().await?;
489			// Obtain the configuration to verify the token based on the access method
490			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 the token
507			verify_token(token, &cf.0, &cf.1)?;
508			// AUTHENTICATE clause
509			if let Some(au) = &de.authenticate {
510				// Setup the system session for executing the clause
511				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			// Parse the roles
518			let roles = match &token_data.claims.roles {
519				// If no role is provided, grant the viewer role
520				None => vec![Role::Viewer],
521				// If roles are provided, parse them
522				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			// Log the success
530			debug!("Authenticated to root with access method `{}`", ac);
531			// Set the session
532			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		// Check if this is root authentication with user credentials
539		Claims {
540			id: Some(id),
541			..
542		} => {
543			// Log the decoded authentication claims
544			trace!("Authenticating to root level with user `{}`", id);
545			// Create a new readonly transaction
546			let tx = kvs.transaction(Read, Optimistic).await?;
547			// Get the namespace user
548			let de = tx.get_root_user(id).await.map_err(|e| {
549				debug!("Error while authenticating to root: {e}");
550				Error::InvalidAuth
551			})?;
552			// Ensure that the transaction is cancelled
553			tx.cancel().await?;
554			// Check the algorithm
555			let cf = config(Algorithm::Hs512, de.code.as_bytes())?;
556			// Verify the token
557			verify_token(token, &cf.0, &cf.1)?;
558			// Log the success
559			debug!("Authenticated to root level with user `{}` using token", id);
560			// Set the session
561			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		// There was an auth error
571		_ => 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	// Create a new readonly transaction
581	let tx = ds.transaction(Read, Optimistic).await?;
582	// Fetch the specified user from storage
583	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	// Ensure that the transaction is cancelled
588	tx.cancel().await?;
589	// Verify the specified password for the user
590	verify_pass(pass, user.hash.as_ref())?;
591	// Clone the cached user object
592	let user = (*user).clone();
593	// Return the verified user object
594	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	// Create a new readonly transaction
604	let tx = ds.transaction(Read, Optimistic).await?;
605	// Fetch the specified user from storage
606	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	// Ensure that the transaction is cancelled
611	tx.cancel().await?;
612	// Verify the specified password for the user
613	verify_pass(pass, user.hash.as_ref())?;
614	// Clone the cached user object
615	let user = (*user).clone();
616	// Return the verified user object
617	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	// Create a new readonly transaction
628	let tx = ds.transaction(Read, Optimistic).await?;
629	// Fetch the specified user from storage
630	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	// Ensure that the transaction is cancelled
635	tx.cancel().await?;
636	// Verify the specified password for the user
637	verify_pass(pass, user.hash.as_ref())?;
638	// Clone the cached user object
639	let user = (*user).clone();
640	// Return the verified user object
641	Ok(user)
642}
643
644fn verify_pass(pass: &str, hash: &str) -> Result<(), Error> {
645	// Compute the hash and verify the password
646	let hash = PasswordHash::new(hash).unwrap();
647	// Attempt to verify the password using Argon2
648	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			// Only transparently return certain token verification errors
659			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					// Check auth level
786					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					// Check roles
796					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					// Check expiration
803					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				// This is the Argon2id hash for "pass" with a random salt.
864				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			// Use pre-parsed definition, which bypasses the existent role check during parsing.
875			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			// Basic authentication using the newly defined user.
886			let res = basic(&ds, &mut sess, "user", "pass", level.ns, level.db).await;
887			match res {
888				Err(Error::IamError(IamError::InvalidRole(_))) => {} // ok
889				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			// Define the access token for that level
970			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				// Prepare the claims object
990				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				// Create the token
997				let key = EncodingKey::from_secret(case.key.as_ref());
998				let enc = encode(&HEADER, &claims, &key).unwrap();
999
1000				// Authenticate with the token
1001				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					// Check roles
1013					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					// Ensure that the expiration is set correctly
1020					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				// Prepare the claims object
1153				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				// Create the token
1159				let key = EncodingKey::from_secret(case.key.as_ref());
1160				let enc = encode(&HEADER, &claims, &key).unwrap();
1161
1162				// Authenticate with the token
1163				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					// Ensure record users do not have roles
1175					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					// Ensure that the expiration is set correctly
1185					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		//
1227		// Token with valid custom claims of different types
1228		//
1229		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			// Create the token
1264			let enc = match encode(&HEADER, &claims, &key) {
1265				Ok(enc) => enc,
1266				Err(err) => panic!("Failed to encode token: {:?}", err),
1267			};
1268			// Signin with the token
1269			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			// Session expiration has been set explicitly
1284			let exp = sess.exp.unwrap();
1285			// Expiration should match the current time plus session duration with some margin
1286			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		// Use unique path to prevent accidental cache reuse
1328		fn random_path() -> String {
1329			let rng = rand::thread_rng();
1330			rng.sample_iter(&Alphanumeric).take(8).map(char::from).collect()
1331		}
1332
1333		// Key identifier used in both JWT and JWT
1334		let kid = "test_kid";
1335		// Secret used to both sign and verify with HMAC
1336		let secret = "jwt_secret";
1337
1338		// JWKS object with single JWK object providing the HS512 secret used to verify
1339		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		// We allow requests to the local server serving the JWKS object
1372		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		// Use custom JWT header that includes the key identifier
1396		let header_with_kid = jsonwebtoken::Header {
1397			kid: Some(kid.to_string()),
1398			alg: jsonwebtoken::Algorithm::HS512,
1399			..jsonwebtoken::Header::default()
1400		};
1401
1402		// Sign the JWT with the same secret specified in the JWK
1403		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		//
1418		// Test without roles defined
1419		// Roles should be ignored in record access
1420		//
1421		{
1422			// Prepare the claims object
1423			let mut claims = claims.clone();
1424			claims.roles = None;
1425			// Create the token
1426			let enc = encode(&header_with_kid, &claims, &key).unwrap();
1427			// Signin with the token
1428			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		//
1446		// Test with invalid signature
1447		//
1448		{
1449			// Prepare the claims object
1450			let claims = claims.clone();
1451			// Create the token
1452			let key = EncodingKey::from_secret("invalid".as_ref());
1453			let enc = encode(&header_with_kid, &claims, &key).unwrap();
1454			// Signin with the token
1455			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 with the matching password
1468		verify_pass("test", &hash).unwrap();
1469
1470		// Verify with a non matching password
1471		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		// Reject invalid ROOT credentials
1481		{
1482			assert!(verify_root_creds(&ds, "test", "test").await.is_err());
1483		}
1484
1485		// Reject invalid NS credentials
1486		{
1487			assert!(verify_ns_creds(&ds, &ns, "test", "test").await.is_err());
1488		}
1489
1490		// Reject invalid DB credentials
1491		{
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		// Define users
1503		{
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		// Accept ROOT user
1517		{
1518			let res = verify_root_creds(&ds, "root", "root").await;
1519			res.unwrap();
1520		}
1521
1522		// Accept NS user
1523		{
1524			let res = verify_ns_creds(&ds, &ns, "ns", "ns").await;
1525			res.unwrap();
1526		}
1527
1528		// Accept DB user
1529		{
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			// Token was issued two hours ago and expired one hour ago
1542			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		// Prepare the claims object
1563		let mut claims = claims.clone();
1564		claims.roles = None;
1565		// Create the token
1566		let enc = encode(&HEADER, &claims, &key).unwrap();
1567		// Signin with the token
1568		let mut sess = Session::default();
1569		let res = token(&ds, &mut sess, &enc).await;
1570
1571		match res {
1572			Err(Error::ExpiredToken) => {} // ok
1573			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				// Prepare the claims object
1695				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				// Create the token
1702				let enc = encode(&HEADER, &claims, &key).unwrap();
1703
1704				// Signin with the token
1705				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					// Check auth level
1725					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					// Check roles
1735					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					// Check expiration
1749					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		// Test with an "id" claim
1767		{
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			// Prepare the claims object
1807			let mut claims = claims.clone();
1808			claims.roles = None;
1809			// Create the token
1810			let enc = encode(&HEADER, &claims, &key).unwrap();
1811			// Signin with the token
1812			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			// Record users should not have roles
1825			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			// Expiration should match the defined duration
1829			let exp = sess.exp.unwrap();
1830			// Expiration should match the current time plus session duration with some margin
1831			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		// Test without an "id" claim
1840		{
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			// Create the token
1888			let enc = match encode(&HEADER, &claims, &key) {
1889				Ok(enc) => enc,
1890				Err(err) => panic!("Failed to encode token: {:?}", err),
1891			};
1892			// Signin with the token
1893			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			// Record users should not have roles
1906			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			// Expiration should match the defined duration
1910			let exp = sess.exp.unwrap();
1911			// Expiration should match the current time plus session duration with some margin
1912			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		// Test being able to fail authentication
1921		{
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			// Prepare the claims object
1963			let mut claims = claims.clone();
1964			claims.roles = None;
1965			// Create the token
1966			let enc = encode(&HEADER, &claims, &key).unwrap();
1967			// Signin with the token
1968			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" => {} // ok
1973				res => panic!(
1974				    "Expected authentication to failed due to user not being enabled, but instead received: {:?}",
1975					res
1976				),
1977			}
1978		}
1979
1980		// Test AUTHENTICATE clause not returning a value
1981		{
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			// Prepare the claims object
2018			let mut claims = claims.clone();
2019			claims.roles = None;
2020			// Create the token
2021			let enc = encode(&HEADER, &claims, &key).unwrap();
2022			// Signin with the token
2023			let mut sess = Session::default();
2024			let res = token(&ds, &mut sess, &enc).await;
2025
2026			match res {
2027				Err(Error::InvalidAuth) => {} // ok
2028				res => panic!(
2029					"Expected authentication to generally fail, but instead received: {:?}",
2030					res
2031				),
2032			}
2033		}
2034	}
2035}