Skip to main content

surrealdb_core/sql/statements/define/
user.rs

1use std::fmt::{self, Display};
2
3use argon2::Argon2;
4use argon2::password_hash::{PasswordHasher, SaltString};
5use rand::Rng;
6use rand::distributions::Alphanumeric;
7use rand::rngs::OsRng;
8
9use super::DefineKind;
10use crate::sql::escape::QuoteStr;
11use crate::sql::fmt::Fmt;
12use crate::sql::{Base, Ident};
13use crate::val::{Duration, Strand};
14
15#[derive(Clone, Debug, Default, Eq, PartialEq)]
16#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
17pub enum PassType {
18	#[default]
19	Unset,
20	Hash(String),
21	Password(String),
22}
23
24#[derive(Clone, Debug, Default, PartialEq, Eq)]
25#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
26pub struct DefineUserStatement {
27	pub kind: DefineKind,
28	pub name: Ident,
29	pub base: Base,
30	pub pass_type: PassType,
31	pub roles: Vec<Ident>,
32	pub token_duration: Option<Duration>,
33	pub session_duration: Option<Duration>,
34
35	pub comment: Option<Strand>,
36}
37
38impl Display for DefineUserStatement {
39	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40		write!(f, "DEFINE USER")?;
41		match self.kind {
42			DefineKind::Default => {}
43			DefineKind::Overwrite => write!(f, " OVERWRITE")?,
44			DefineKind::IfNotExists => write!(f, " IF NOT EXISTS")?,
45		}
46
47		write!(f, " {} ON {}", self.name, self.base,)?;
48
49		match self.pass_type {
50			PassType::Unset => write!(f, "  PASSHASH \"\" ")?,
51			PassType::Hash(ref x) => write!(f, "  PASSHASH {}", QuoteStr(x))?,
52			PassType::Password(ref x) => write!(f, "  PASSWORD {}", QuoteStr(x))?,
53		}
54
55		write!(
56			f,
57			" ROLES {}",
58			Fmt::comma_separated(
59				&self.roles.iter().map(|r| r.to_string().to_uppercase()).collect::<Vec<String>>()
60			),
61		)?;
62		// Always print relevant durations so defaults can be changed in the future
63		// If default values were not printed, exports would not be forward compatible
64		// None values need to be printed, as they are different from the default values
65		write!(f, " DURATION")?;
66		write!(
67			f,
68			" FOR TOKEN {},",
69			match self.token_duration {
70				Some(dur) => format!("{}", dur),
71				None => "NONE".to_string(),
72			}
73		)?;
74		write!(
75			f,
76			" FOR SESSION {}",
77			match self.session_duration {
78				Some(dur) => format!("{}", dur),
79				None => "NONE".to_string(),
80			}
81		)?;
82		if let Some(ref v) = self.comment {
83			write!(f, " COMMENT {v}")?
84		}
85		Ok(())
86	}
87}
88
89#[allow(clippy::fallible_impl_from)]
90impl From<DefineUserStatement> for crate::expr::statements::DefineUserStatement {
91	fn from(v: DefineUserStatement) -> Self {
92		let hash = match v.pass_type {
93			PassType::Unset => String::new(),
94			PassType::Hash(x) => x,
95			// TODO: Move out of AST.
96			PassType::Password(p) => Argon2::default()
97				.hash_password(p.as_bytes(), &SaltString::generate(&mut OsRng))
98				.unwrap()
99				.to_string(),
100		};
101
102		let code = rand::thread_rng()
103			.sample_iter(&Alphanumeric)
104			.take(128)
105			.map(char::from)
106			.collect::<String>();
107
108		Self {
109			kind: v.kind.into(),
110			name: v.name.into(),
111			base: v.base.into(),
112			hash,
113			code,
114			roles: v.roles.into_iter().map(Into::into).collect(),
115			duration: crate::expr::user::UserDuration {
116				token: v.token_duration,
117				session: v.session_duration,
118			},
119			comment: v.comment,
120		}
121	}
122}
123
124impl From<crate::expr::statements::DefineUserStatement> for DefineUserStatement {
125	fn from(v: crate::expr::statements::DefineUserStatement) -> Self {
126		Self {
127			kind: v.kind.into(),
128			name: v.name.into(),
129			base: v.base.into(),
130			pass_type: PassType::Hash(v.hash),
131			roles: v.roles.into_iter().map(Into::into).collect(),
132			token_duration: v.duration.token,
133			session_duration: v.duration.session,
134			comment: v.comment,
135		}
136	}
137}