parse_rust_server/routes/
users.rs1use axum::extract::State;
14use axum::response::{IntoResponse, Response};
15use axum::Json;
16use parse_rust_core::{ErrorCode, ParseError, ParseMap, ParseValue};
17use parse_rust_rest::AclScope;
18use serde_json::{json, Value as Json_};
19
20use crate::auth::Authority;
21use crate::response::ParseErrorResponse;
22use crate::state::AppState;
23
24const USER_CLASS: &str = "_User";
25const HASHED_PASSWORD: &str = "_hashed_password";
27
28fn err(e: ParseError) -> Response {
29 ParseErrorResponse(e).into_response()
30}
31
32fn strip_sensitive(mut row: ParseMap) -> ParseMap {
38 for key in [
39 HASHED_PASSWORD,
40 "password",
41 "_perishable_token",
42 "_email_verify_token",
43 ] {
44 row.shift_remove(key);
45 }
46 row
47}
48
49fn body_of(row: &ParseMap) -> Json_ {
51 let row = parse_rust_rest::to_response_body(row);
52 serde_json::from_str(&ParseValue::Object(row).to_json()).unwrap_or(Json_::Null)
53}
54
55fn take_string(body: &ParseMap, key: &str) -> Option<String> {
56 match body.get(key) {
57 Some(ParseValue::String(s)) => Some(s.clone()),
58 _ => None,
59 }
60}
61
62pub(crate) fn hash_user_password(body: &mut ParseMap) -> Result<(), ParseError> {
67 let password = match body.get("password") {
68 Some(ParseValue::String(password)) => password.clone(),
69 _ => return Ok(()),
72 };
73 let hash = parse_rust_auth::password::hash(&password)?;
74 body.shift_remove("password");
75 body.insert(HASHED_PASSWORD.to_string(), ParseValue::String(hash));
76 Ok(())
77}
78
79pub(crate) fn ensure_user_identity_and_acl(body: &mut ParseMap) -> String {
90 let object_id = match body.get("objectId") {
91 Some(ParseValue::String(id)) => id.clone(),
92 _ => {
93 let id = parse_rust_core::new_object_id();
94 body.insert("objectId".to_string(), ParseValue::String(id.clone()));
95 id
96 }
97 };
98
99 let mut permissions = ParseMap::new();
100 permissions.insert("read".to_string(), ParseValue::Bool(true));
101 permissions.insert("write".to_string(), ParseValue::Bool(true));
102
103 match body.get_mut("ACL") {
104 Some(ParseValue::Object(acl)) => {
105 acl.insert(object_id.clone(), ParseValue::Object(permissions));
106 }
107 Some(_) => {}
108 None => {
109 let mut acl = ParseMap::new();
110 acl.insert(object_id.clone(), ParseValue::Object(permissions));
111 body.insert("ACL".to_string(), ParseValue::Object(acl));
112 }
113 }
114 object_id
115}
116
117pub async fn signup(
119 State(state): State<AppState>,
120 _authority: Authority,
121 Json(body): Json<Json_>,
122) -> Response {
123 let mut body = match parse_rust_core::classify(body) {
124 Ok(ParseValue::Object(m)) => m,
125 Ok(_) => return err(ParseError::invalid_json("body must be an object")),
126 Err(e) => return err(e),
127 };
128 if let Err(e) = parse_rust_rest::reject_reserved_keys(&body) {
131 return err(e);
132 }
133
134 let Some(username) = take_string(&body, "username") else {
135 return err(ParseError::new(
136 ErrorCode::UsernameMissing,
137 "bad or missing username",
138 ));
139 };
140 let Some(password) = take_string(&body, "password") else {
141 return err(ParseError::new(
142 ErrorCode::PasswordMissing,
143 "password is required.",
144 ));
145 };
146 if username.is_empty() {
147 return err(ParseError::new(
148 ErrorCode::UsernameMissing,
149 "bad or missing username",
150 ));
151 }
152 if password.is_empty() {
153 return err(ParseError::new(
154 ErrorCode::PasswordMissing,
155 "password is required.",
156 ));
157 }
158
159 if let Err(e) = hash_user_password(&mut body) {
160 return err(e);
161 }
162
163 ensure_user_identity_and_acl(&mut body);
171
172 let created = match parse_rust_rest::create(
173 state.storage(),
174 USER_CLASS,
175 body.clone(),
176 &AclScope::Unrestricted,
177 )
178 .await
179 {
180 Ok(c) => c,
181 Err(e) => return err(map_duplicate(e)),
182 };
183
184 let token = state.sessions().create(&created.object_id);
185 (
186 axum::http::StatusCode::CREATED,
187 Json(json!({
188 "objectId": created.object_id,
189 "createdAt": created.created_at.to_iso(),
190 "sessionToken": token,
191 })),
192 )
193 .into_response()
194}
195
196fn map_duplicate(e: ParseError) -> ParseError {
201 if e.code != ErrorCode::DuplicateValue {
202 return e;
203 }
204 if e.message.contains("username_1") {
205 return ParseError::new(
206 ErrorCode::UsernameTaken,
207 "Account already exists for this username.",
208 );
209 }
210 if e.message.contains("email_1") {
211 return ParseError::new(
212 ErrorCode::EmailTaken,
213 "Account already exists for this email address.",
214 );
215 }
216 e
217}
218
219pub async fn login(
221 State(state): State<AppState>,
222 _authority: Authority,
223 Json(body): Json<Json_>,
224) -> Response {
225 let body = match parse_rust_core::classify(body) {
226 Ok(ParseValue::Object(m)) => m,
227 Ok(_) => return err(ParseError::invalid_json("body must be an object")),
228 Err(e) => return err(e),
229 };
230
231 let (Some(username), Some(password)) = (
232 take_string(&body, "username"),
233 take_string(&body, "password"),
234 ) else {
235 return err(ParseError::new(
236 ErrorCode::UsernameMissing,
237 "username/email is required.",
238 ));
239 };
240
241 let rows = match parse_rust_rest::find(
242 state.storage(),
243 USER_CLASS,
244 vec![parse_rust_storage::Constraint::equal(
245 "username",
246 ParseValue::String(username),
247 )],
248 parse_rust_storage::QueryOptions {
249 limit: Some(1),
250 ..Default::default()
251 },
252 &AclScope::Unrestricted,
253 )
254 .await
255 {
256 Ok(r) => r,
257 Err(e) => return err(e),
258 };
259
260 let invalid = || ParseError::new(ErrorCode::ObjectNotFound, "Invalid username/password.");
263
264 let Some(row) = rows.into_iter().next() else {
265 return err(invalid());
266 };
267 let Some(ParseValue::String(hash)) = row.get(HASHED_PASSWORD) else {
268 return err(invalid());
269 };
270 if !parse_rust_auth::password::verify(&password, hash) {
271 return err(invalid());
272 }
273
274 let Some(ParseValue::String(object_id)) = row.get("objectId") else {
275 return err(ParseError::new(
276 ErrorCode::InternalServerError,
277 "stored user has no objectId",
278 ));
279 };
280
281 let token = state.sessions().create(object_id);
282 let mut out = strip_sensitive(row.clone());
283 out.insert("sessionToken".to_string(), ParseValue::String(token));
284 Json(body_of(&out)).into_response()
285}
286
287pub async fn me(State(state): State<AppState>, authority: Authority) -> Response {
289 let Authority::Client {
290 session_token: Some(token),
291 } = &authority
292 else {
293 return err(ParseError::new(
294 ErrorCode::InvalidSessionToken,
295 "Invalid session token",
296 ));
297 };
298 let Some(object_id) = state.sessions().user_for(token) else {
299 return err(ParseError::new(
300 ErrorCode::InvalidSessionToken,
301 "Invalid session token",
302 ));
303 };
304
305 match parse_rust_rest::get(
306 state.storage(),
307 USER_CLASS,
308 &object_id,
309 &AclScope::Unrestricted,
310 )
311 .await
312 {
313 Ok(row) => {
314 let mut out = strip_sensitive(row);
315 out.insert(
316 "sessionToken".to_string(),
317 ParseValue::String(token.clone()),
318 );
319 Json(body_of(&out)).into_response()
320 }
321 Err(e) => err(e),
322 }
323}
324
325pub async fn logout(State(state): State<AppState>, authority: Authority) -> Response {
327 if let Authority::Client {
328 session_token: Some(token),
329 } = &authority
330 {
331 state.sessions().revoke(token);
332 }
333 Json(json!({})).into_response()
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn shared_user_transform_removes_plaintext_and_writes_a_bcrypt_hash() {
342 let mut body = ParseMap::new();
343 body.insert(
344 "password".to_string(),
345 ParseValue::String("hunter2".to_string()),
346 );
347
348 hash_user_password(&mut body).expect("password hashes");
349
350 assert!(!body.contains_key("password"));
351 let Some(ParseValue::String(hash)) = body.get(HASHED_PASSWORD) else {
352 panic!("hash missing");
353 };
354 assert!(parse_rust_auth::password::verify("hunter2", hash));
355 }
356
357 #[test]
358 fn user_owner_acl_is_added_without_discarding_a_master_supplied_acl() {
359 let mut public = ParseMap::new();
360 public.insert("read".to_string(), ParseValue::Bool(true));
361 let mut acl = ParseMap::new();
362 acl.insert("*".to_string(), ParseValue::Object(public));
363
364 let mut body = ParseMap::new();
365 body.insert(
366 "objectId".to_string(),
367 ParseValue::String("user123456".to_string()),
368 );
369 body.insert("ACL".to_string(), ParseValue::Object(acl));
370
371 let object_id = ensure_user_identity_and_acl(&mut body);
372
373 assert_eq!(object_id, "user123456");
374 let Some(ParseValue::Object(acl)) = body.get("ACL") else {
375 panic!("ACL missing");
376 };
377 assert!(acl.contains_key("*"));
378 let Some(ParseValue::Object(owner)) = acl.get("user123456") else {
379 panic!("owner ACL missing");
380 };
381 assert!(matches!(owner.get("read"), Some(ParseValue::Bool(true))));
382 assert!(matches!(owner.get("write"), Some(ParseValue::Bool(true))));
383 }
384}