surrealdb_core/sql/statements/define/
user.rs

1use crate::ctx::Context;
2use crate::dbs::Options;
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::iam::{Action, ResourceKind};
6use crate::sql::statements::info::InfoStructure;
7use crate::sql::{
8	escape::quote_str, fmt::Fmt, user::UserDuration, Base, Duration, Ident, Strand, Value,
9};
10use argon2::{
11	password_hash::{PasswordHasher, SaltString},
12	Argon2,
13};
14
15use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
16use revision::revisioned;
17use serde::{Deserialize, Serialize};
18use std::fmt::{self, Display};
19
20#[revisioned(revision = 4)]
21#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize, Hash)]
22#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
23#[non_exhaustive]
24pub struct DefineUserStatement {
25	pub name: Ident,
26	pub base: Base,
27	pub hash: String,
28	pub code: String,
29	pub roles: Vec<Ident>,
30	#[revision(start = 3)]
31	pub duration: UserDuration,
32	pub comment: Option<Strand>,
33	#[revision(start = 2)]
34	pub if_not_exists: bool,
35	#[revision(start = 4)]
36	pub overwrite: bool,
37}
38
39impl From<(Base, &str, &str, &str)> for DefineUserStatement {
40	fn from((base, user, pass, role): (Base, &str, &str, &str)) -> Self {
41		DefineUserStatement {
42			base,
43			name: user.into(),
44			hash: Argon2::default()
45				.hash_password(pass.as_ref(), &SaltString::generate(&mut OsRng))
46				.unwrap()
47				.to_string(),
48			code: rand::thread_rng()
49				.sample_iter(&Alphanumeric)
50				.take(128)
51				.map(char::from)
52				.collect::<String>(),
53			roles: vec![role.into()],
54			duration: UserDuration::default(),
55			comment: None,
56			if_not_exists: false,
57			overwrite: false,
58		}
59	}
60}
61
62impl DefineUserStatement {
63	pub(crate) fn from_parsed_values(
64		name: Ident,
65		base: Base,
66		roles: Vec<Ident>,
67		duration: UserDuration,
68	) -> Self {
69		DefineUserStatement {
70			name,
71			base,
72			roles,
73			duration,
74			code: rand::thread_rng()
75				.sample_iter(&Alphanumeric)
76				.take(128)
77				.map(char::from)
78				.collect::<String>(),
79			..Default::default()
80		}
81	}
82
83	pub(crate) fn set_password(&mut self, password: &str) {
84		self.hash = Argon2::default()
85			.hash_password(password.as_bytes(), &SaltString::generate(&mut OsRng))
86			.unwrap()
87			.to_string()
88	}
89
90	pub(crate) fn set_passhash(&mut self, passhash: String) {
91		self.hash = passhash;
92	}
93
94	pub(crate) fn set_token_duration(&mut self, duration: Option<Duration>) {
95		self.duration.token = duration;
96	}
97
98	pub(crate) fn set_session_duration(&mut self, duration: Option<Duration>) {
99		self.duration.session = duration;
100	}
101
102	/// Process this type returning a computed simple Value
103	pub(crate) async fn compute(
104		&self,
105		ctx: &Context,
106		opt: &Options,
107		_doc: Option<&CursorDoc>,
108	) -> Result<Value, Error> {
109		// Allowed to run?
110		opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?;
111		// Check the statement type
112		match self.base {
113			Base::Root => {
114				// Fetch the transaction
115				let txn = ctx.tx();
116				// Check if the definition exists
117				if txn.get_root_user(&self.name).await.is_ok() {
118					if self.if_not_exists {
119						return Ok(Value::None);
120					} else if !self.overwrite {
121						return Err(Error::UserRootAlreadyExists {
122							name: self.name.to_string(),
123						});
124					}
125				}
126				// Process the statement
127				let key = crate::key::root::us::new(&self.name);
128				txn.set(
129					key,
130					revision::to_vec(&DefineUserStatement {
131						// Don't persist the `IF NOT EXISTS` clause to schema
132						if_not_exists: false,
133						overwrite: false,
134						..self.clone()
135					})?,
136					None,
137				)
138				.await?;
139				// Clear the cache
140				txn.clear();
141				// Ok all good
142				Ok(Value::None)
143			}
144			Base::Ns => {
145				// Fetch the transaction
146				let txn = ctx.tx();
147				// Check if the definition exists
148				if txn.get_ns_user(opt.ns()?, &self.name).await.is_ok() {
149					if self.if_not_exists {
150						return Ok(Value::None);
151					} else if !self.overwrite {
152						return Err(Error::UserNsAlreadyExists {
153							name: self.name.to_string(),
154							ns: opt.ns()?.into(),
155						});
156					}
157				}
158				// Process the statement
159				let key = crate::key::namespace::us::new(opt.ns()?, &self.name);
160				txn.get_or_add_ns(opt.ns()?, opt.strict).await?;
161				txn.set(
162					key,
163					revision::to_vec(&DefineUserStatement {
164						// Don't persist the `IF NOT EXISTS` clause to schema
165						if_not_exists: false,
166						overwrite: false,
167						..self.clone()
168					})?,
169					None,
170				)
171				.await?;
172				// Clear the cache
173				txn.clear();
174				// Ok all good
175				Ok(Value::None)
176			}
177			Base::Db => {
178				// Fetch the transaction
179				let txn = ctx.tx();
180				// Check if the definition exists
181				let (ns, db) = opt.ns_db()?;
182				if txn.get_db_user(ns, db, &self.name).await.is_ok() {
183					if self.if_not_exists {
184						return Ok(Value::None);
185					} else if !self.overwrite {
186						return Err(Error::UserDbAlreadyExists {
187							name: self.name.to_string(),
188							ns: ns.into(),
189							db: db.into(),
190						});
191					}
192				}
193				// Process the statement
194				let key = crate::key::database::us::new(ns, db, &self.name);
195				txn.get_or_add_ns(ns, opt.strict).await?;
196				txn.get_or_add_db(ns, db, opt.strict).await?;
197				txn.set(
198					key,
199					revision::to_vec(&DefineUserStatement {
200						// Don't persist the `IF NOT EXISTS` clause to schema
201						if_not_exists: false,
202						overwrite: false,
203						..self.clone()
204					})?,
205					None,
206				)
207				.await?;
208				// Clear the cache
209				txn.clear();
210				// Ok all good
211				Ok(Value::None)
212			}
213			// Other levels are not supported
214			_ => Err(Error::InvalidLevel(self.base.to_string())),
215		}
216	}
217}
218
219impl Display for DefineUserStatement {
220	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221		write!(f, "DEFINE USER")?;
222		if self.if_not_exists {
223			write!(f, " IF NOT EXISTS")?
224		}
225		if self.overwrite {
226			write!(f, " OVERWRITE")?
227		}
228		write!(
229			f,
230			" {} ON {} PASSHASH {} ROLES {}",
231			self.name,
232			self.base,
233			quote_str(&self.hash),
234			Fmt::comma_separated(
235				&self.roles.iter().map(|r| r.to_string().to_uppercase()).collect::<Vec<String>>()
236			),
237		)?;
238		// Always print relevant durations so defaults can be changed in the future
239		// If default values were not printed, exports would not be forward compatible
240		// None values need to be printed, as they are different from the default values
241		write!(f, " DURATION")?;
242		write!(
243			f,
244			" FOR TOKEN {},",
245			match self.duration.token {
246				Some(dur) => format!("{}", dur),
247				None => "NONE".to_string(),
248			}
249		)?;
250		write!(
251			f,
252			" FOR SESSION {}",
253			match self.duration.session {
254				Some(dur) => format!("{}", dur),
255				None => "NONE".to_string(),
256			}
257		)?;
258		if let Some(ref v) = self.comment {
259			write!(f, " COMMENT {v}")?
260		}
261		Ok(())
262	}
263}
264
265impl InfoStructure for DefineUserStatement {
266	fn structure(self) -> Value {
267		Value::from(map! {
268			"name".to_string() => self.name.structure(),
269			"base".to_string() => self.base.structure(),
270			"hash".to_string() => self.hash.into(),
271			"roles".to_string() => self.roles.into_iter().map(Ident::structure).collect(),
272			"duration".to_string() => Value::from(map! {
273				"token".to_string() => self.duration.token.into(),
274				"session".to_string() => self.duration.session.into(),
275			}),
276			"comment".to_string(), if let Some(v) = self.comment => v.into(),
277		})
278	}
279}