surrealdb_core/iam/
access.rs

1use crate::cnf::INSECURE_FORWARD_ACCESS_ERRORS;
2use crate::ctx::MutableContext;
3use crate::dbs::Session;
4use crate::err::Error;
5use crate::kvs::{Datastore, LockType::*, TransactionType::*};
6use crate::sql::statements::access;
7use crate::sql::{Base, Ident, Thing, Value};
8use reblessive;
9
10// Execute the AUTHENTICATE clause for a record access method
11pub async fn authenticate_record(
12	kvs: &Datastore,
13	session: &Session,
14	authenticate: &Value,
15) -> Result<Thing, Error> {
16	match kvs.evaluate(authenticate, session, None).await {
17		Ok(val) => match val.record() {
18			// If the AUTHENTICATE clause returns a record, authentication continues with that record
19			Some(id) => Ok(id),
20			// If the AUTHENTICATE clause returns anything else, authentication fails generically
21			_ => {
22				debug!("Authentication attempt as record user rejected by AUTHENTICATE clause");
23				Err(Error::InvalidAuth)
24			}
25		},
26		Err(e) => {
27			match e {
28				// If the AUTHENTICATE clause throws a specific error, authentication fails with that error
29				Error::Thrown(_) => Err(e),
30				// If the AUTHENTICATE clause failed due to an unexpected error, be more specific
31				// This allows clients to handle these errors, which may be retryable
32				Error::Tx(_) | Error::TxFailure | Error::TxRetryable => {
33					debug!("Unexpected error found while executing AUTHENTICATE clause: {e}");
34					Err(Error::UnexpectedAuth)
35				}
36				// Otherwise, return a generic error unless it should be forwarded
37				e => {
38					debug!("Authentication attempt failed due to an error in the AUTHENTICATE clause: {e}");
39					if *INSECURE_FORWARD_ACCESS_ERRORS {
40						Err(e)
41					} else {
42						Err(Error::InvalidAuth)
43					}
44				}
45			}
46		}
47	}
48}
49
50// Execute the AUTHENTICATE clause for any other access method
51pub async fn authenticate_generic(
52	kvs: &Datastore,
53	session: &Session,
54	authenticate: &Value,
55) -> Result<(), Error> {
56	match kvs.evaluate(authenticate, session, None).await {
57		Ok(val) => {
58			match val {
59				// If the AUTHENTICATE clause returns nothing, authentication continues
60				Value::None => Ok(()),
61				// If the AUTHENTICATE clause returns anything else, authentication fails generically
62				_ => {
63					debug!("Authentication attempt as system user rejected by AUTHENTICATE clause");
64					Err(Error::InvalidAuth)
65				}
66			}
67		}
68		Err(e) => {
69			match e {
70				// If the AUTHENTICATE clause throws a specific error, authentication fails with that error
71				Error::Thrown(_) => Err(e),
72				// If the AUTHENTICATE clause failed due to an unexpected error, be more specific
73				// This allows clients to handle these errors, which may be retryable
74				Error::Tx(_) | Error::TxFailure | Error::TxRetryable => {
75					debug!("Unexpected error found while executing an AUTHENTICATE clause: {e}");
76					Err(Error::UnexpectedAuth)
77				}
78				// Otherwise, return a generic error unless it should be forwarded
79				e => {
80					debug!("Authentication attempt failed due to an error in the AUTHENTICATE clause: {e}");
81					if *INSECURE_FORWARD_ACCESS_ERRORS {
82						Err(e)
83					} else {
84						Err(Error::InvalidAuth)
85					}
86				}
87			}
88		}
89	}
90}
91
92// Create a bearer key to act as refresh token for a record user
93pub async fn create_refresh_token_record(
94	kvs: &Datastore,
95	ac: Ident,
96	ns: &str,
97	db: &str,
98	rid: Thing,
99) -> Result<String, Error> {
100	let stmt = access::AccessStatementGrant {
101		ac,
102		base: Some(Base::Db),
103		subject: access::Subject::Record(rid),
104	};
105	let sess = Session::owner().with_ns(ns).with_db(db);
106	let opt = kvs.setup_options(&sess);
107	// Create a new context with a writeable transaction
108	let mut ctx = MutableContext::background();
109	let tx = kvs.transaction(Write, Optimistic).await?.enclose();
110	ctx.set_transaction(tx.clone());
111	let ctx = ctx.freeze();
112	// Create a bearer grant to act as the refresh token
113	let grant = access::create_grant(&stmt, &ctx, &opt).await.map_err(|e| {
114		warn!("Unexpected error when attempting to create a refresh token: {e}");
115		Error::UnexpectedAuth
116	})?;
117	tx.commit().await?;
118	// Return the key string from the bearer grant
119	match grant.grant {
120		access::Grant::Bearer(bearer) => Ok(bearer.key.as_string()),
121		_ => Err(Error::AccessMethodMismatch),
122	}
123}
124
125// Revoke a bearer key that acted as a refresh token for a record user
126pub async fn revoke_refresh_token_record(
127	kvs: &Datastore,
128	gr: Ident,
129	ac: Ident,
130	ns: &str,
131	db: &str,
132) -> Result<(), Error> {
133	let stmt = access::AccessStatementRevoke {
134		ac,
135		base: Some(Base::Db),
136		gr: Some(gr),
137		cond: None,
138	};
139	let sess = Session::owner().with_ns(ns).with_db(db);
140	let opt = kvs.setup_options(&sess);
141	// Create a new context with a writeable transaction
142	let mut ctx = MutableContext::background();
143	let tx = kvs.transaction(Write, Optimistic).await?.enclose();
144	ctx.set_transaction(tx.clone());
145	let ctx = ctx.freeze();
146	// Create a bearer grant to act as the refresh token
147	let mut stack = reblessive::tree::TreeStack::new();
148	stack
149		.enter(|stk| async {
150			access::revoke_grant(&stmt, stk, &ctx, &opt).await.map_err(|e| {
151				warn!("Unexpected error when attempting to revoke a refresh token: {e}");
152				Error::UnexpectedAuth
153			})
154		})
155		.finish()
156		.await?;
157	tx.commit().await?;
158	Ok(())
159}