surrealdb_core/sql/statements/define/
user.rs1use 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 pub(crate) async fn compute(
104 &self,
105 ctx: &Context,
106 opt: &Options,
107 _doc: Option<&CursorDoc>,
108 ) -> Result<Value, Error> {
109 opt.is_allowed(Action::Edit, ResourceKind::Actor, &self.base)?;
111 match self.base {
113 Base::Root => {
114 let txn = ctx.tx();
116 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 let key = crate::key::root::us::new(&self.name);
128 txn.set(
129 key,
130 revision::to_vec(&DefineUserStatement {
131 if_not_exists: false,
133 overwrite: false,
134 ..self.clone()
135 })?,
136 None,
137 )
138 .await?;
139 txn.clear();
141 Ok(Value::None)
143 }
144 Base::Ns => {
145 let txn = ctx.tx();
147 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 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 if_not_exists: false,
166 overwrite: false,
167 ..self.clone()
168 })?,
169 None,
170 )
171 .await?;
172 txn.clear();
174 Ok(Value::None)
176 }
177 Base::Db => {
178 let txn = ctx.tx();
180 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 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 if_not_exists: false,
202 overwrite: false,
203 ..self.clone()
204 })?,
205 None,
206 )
207 .await?;
208 txn.clear();
210 Ok(Value::None)
212 }
213 _ => 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 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}