Skip to main content

quorum_public/db/queries/
auth.rs

1//! Authentication related database queries
2//!
3//! This file contains functions for Creating an account (signup), login, token management and deleteing a user account.
4
5use crate::db::queries::tokens;
6use crate::models::user::UpdateUserProfileRequest;
7use crate::models::user::User;
8use crate::utility::auth_common::check_email_address;
9use axum::http::StatusCode;
10use quorum_core::db::DB;
11use std::error::Error;
12
13/// Creates a new user account in the database
14///
15/// # Arguments
16///* `db` - A reference to the database connection
17/// * `username` - The desired username for the new account
18/// * `email` - An optional email address for the new account
19/// * `password_hash` - The hashed password for the new account
20///
21/// # Returns
22/// * `Ok(User)` - The newly created user object if the operation was successful
23/// * `Err(Box<dyn Error>)` - An error if the operation failed, such as if the username is already taken or if there was a database error
24///
25/// # Errors
26/// * "Failed to create user" - If the database query did not return a user object, indicating that the user creation failed
27///
28/// # Example
29/// ```rust
30/// use crate::db::DB;
31/// use crate::db::queries::auth;
32/// async fn example_signup(db: &DB) {
33///     let username = "new_user";
34///     let email = Some("123@example.com");
35///     let password_hash = "hashed_password";
36///     match auth::signup_user(db, username, email, password_hash).await {
37///         Ok(user) => println!("User created: {:?}", user),
38///         Err(e) => eprintln!("Error creating user: {}", e),
39///     }
40/// }
41///```
42pub async fn signup_user(
43    db: &DB,
44    username: &str,
45    email: Option<&str>,
46    password: &str,
47    email_backup_codes: Option<Vec<String>>,
48) -> Result<User, Box<dyn Error + Send + Sync>> {
49    if password.len() < 12 || password.len() > 35 {
50        return Err("Invalid password length".into());
51    }
52
53    if let Some(email) = email
54        && !check_email_address(email)
55    {
56        return Err("Invalid email address".into());
57    }
58
59    let password_hash = crate::utility::auth_common::hash(password)
60        .map_err(|e| format!("Failed to hash password: {}", e))?;
61
62    let mut response = db
63        .query(
64            "CREATE users SET
65            username = $username,
66            email = $email,
67            password_hash = $password_hash,
68            email_backup_codes = $email_backup_codes",
69        )
70        .bind(("username", username.to_string()))
71        .bind(("email", email.map(|e| e.to_string())))
72        .bind(("password_hash", password_hash))
73        .bind(("email_backup_codes", email_backup_codes))
74        .await?;
75
76    let user: Vec<User> = response.take(0)?;
77    user.into_iter()
78        .next()
79        .ok_or("Failed to create user".into())
80}
81
82/// Deletes a user from the database by their ID
83///
84/// # Arguments
85/// * `db` - A reference to the database connection
86/// * `user_id` - The ID of the user to delete
87///
88/// # Returns
89/// * `Ok(())` - If the user was successfully deleted
90/// * `Err(Box<dyn Error>)` - An error if the operation failed, such as if there was a database error
91///
92/// # Errors
93/// * "Failed to delete user" - If the database query did not execute successfully, indicating that the user could not be deleted
94///
95/// # Example
96/// ```rust
97/// use crate::db::DB;
98/// use crate::db::queries::auth;
99/// async fn example_delete_user(db: &DB) {
100///     let user_id = "user_id_to_delete";
101///     match auth::delete_user_by_id(db, user_id).await {
102///         Ok(()) => println!("User deleted successfully"),
103///         Err(e) => eprintln!("Error deleting user: {}", e),
104///     }
105/// }
106///```
107pub async fn delete_user_by_id(db: &DB, user_id: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
108    tokens::delete_all_user_tokens(db, user_id).await?;
109
110    let query = format!("DELETE FROM users:{}", user_id);
111    db.query(&query).await?;
112    Ok(())
113}
114
115/// Stores a refresh token in the database for a specific user
116///
117/// # Arguments
118/// * `db` - A reference to the database connection
119/// * `user_id` - The ID of the user for whom the refresh token is being stored
120/// * `refresh_token` - The refresh token string to store
121/// * `expires_at` - The expiration time of the refresh token as a Unix timestamp
122///
123/// # Returns
124/// * `Ok(())` - If the refresh token was successfully stored
125/// * `Err(Box<dyn Error>)` - An error if the operation failed, such as if there was a database error
126///
127/// # Errors
128/// * "Failed to store refresh token" - If the database query did not execute successfully, indicating that the refresh token could not be stored
129///
130/// # Example
131/// ```rust
132/// use crate::db::DB;
133/// use crate::db::queries::auth;
134/// async fn example_store_refresh_token(db: &DB) {
135///     let user_id = "user_id";
136///     let refresh_token = "refresh_token_string";
137///     let expires_at = 1700000000; // Example expiration timestamp
138///     match auth::store_refresh_token(db, user_id, refresh_token, expires_at).await {
139///         Ok(()) => println!("Refresh token stored successfully"),
140///         Err(e) => eprintln!("Error storing refresh token: {}", e),
141///     }
142/// }
143///```
144pub async fn store_refresh_token(
145    db: &DB,
146    user_id: &str,
147    refresh_token: &str,
148    expires_at: i64,
149) -> Result<(), Box<dyn Error + Send + Sync>> {
150    tokens::store_refresh_token(db, user_id, refresh_token, expires_at).await
151}
152
153/// Revokes a refresh token, preventing it from being used for future authentication
154///
155/// # Arguments
156/// * `db` - A reference to the database connection
157/// * `refresh_token` - The refresh token string to revoke
158///
159/// # Returns
160/// * `Ok(())` - If the refresh token was successfully revoked
161/// * `Err(Box<dyn Error>)` - An error if the operation failed, such as if there was a database error
162///
163/// # Errors
164/// * "Failed to revoke refresh token" - If the database query did not execute successfully, indicating that the refresh token could not be revoked
165///
166/// # Example
167/// ```rust
168/// use crate::db::DB;
169/// use crate::db::queries::auth;
170/// async fn example_revoke_refresh_token(db: &DB) {
171///     let refresh_token = "refresh_token_string";
172///     match auth::revoke_refresh_token(db, refresh_token).await {
173///         Ok(()) => println!("Refresh token revoked successfully"),
174///         Err(e) => eprintln!("Error revoking refresh token: {}", e),
175///     }
176/// }
177///```
178pub async fn revoke_refresh_token(
179    db: &DB,
180    refresh_token: &str,
181) -> Result<(), Box<dyn Error + Send + Sync>> {
182    tokens::revoke_refresh_token(db, refresh_token).await
183}
184
185/// Get's user's data to allow them to be verified
186///
187/// # Arguments
188/// * `db` - A reference to the database connection
189/// * `username` - The username of the user to retrieve
190/// * `password` - The password of the user to retrieve
191///
192/// # Returns
193/// * `Ok(User)` - The user object if the operation was successful
194/// * `Err(Box<dyn Error>)` - An error if the operation failed, such
195///
196/// # Example
197/// ```rust
198/// use crate::db::DB;
199/// use crate::db::queries::auth;
200/// async fn example_get_user(db: &DB) {
201///     let username = "existing_user";
202///     let password = "user_password";
203///     match auth::get_user(db, username, password).await {
204///         Ok(user) => println!("User retrieved: {:?}", user),
205///         Err(e) => eprintln!("Error retrieving user: {}", e),
206///     }
207/// }
208///```
209pub async fn verify_user_credentials(
210    db: &DB,
211    username_or_email: &str,
212    password: &str,
213) -> Result<User, (StatusCode, String)> {
214    let mut response = db
215        .query("SELECT * FROM users WHERE (username = $value OR email = $value) AND is_banned = false AND is_deleted = false LIMIT 1")
216        .bind(("value", username_or_email.to_string()))
217        .await
218        .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error".to_string()))?;
219
220    let user: Option<User> = response.take::<Option<User>>(0).map_err(|_| {
221        (
222            StatusCode::INTERNAL_SERVER_ERROR,
223            "Database error".to_string(),
224        )
225    })?;
226
227    match user {
228        Some(u) => {
229            if let Some(ref hash) = u.password_hash {
230                if crate::utility::auth_common::verify(password, hash).unwrap_or(false) {
231                    Ok(u)
232                } else {
233                    Err((
234                        StatusCode::UNAUTHORIZED,
235                        "Invalid username or password".to_string(),
236                    ))
237                }
238            } else {
239                Err((
240                    StatusCode::UNAUTHORIZED,
241                    "Invalid username or password".to_string(),
242                ))
243            }
244        }
245        None => Err((
246            StatusCode::UNAUTHORIZED,
247            "Invalid username or password".to_string(),
248        )),
249    }
250}
251
252/// Updates a user's profile information in the database
253///
254/// # Arguments
255/// * `db` - A reference to the database connection
256/// * `user_id` - The ID of the user whose profile is being updated
257/// * `email` - The new email address for the user
258/// * `username` - The new username for the user
259///
260/// # Returns
261/// * `Ok(User)` - The updated user object if the operation was successful
262/// * `Err((StatusCode, String))` - An error if the operation failed, containing an HTTP status code and an error message
263///
264/// # Example
265/// ```rust
266/// use crate::db::DB;
267/// use crate::db::queries::auth;
268/// async fn example_update_user_profile(db: &DB) {
269///     let user_id = "user_id";
270///     let new_email = "test@example.com";
271///     let new_username = "new_username";
272///     match auth::update_user_profile(db, user_id, new_email, new_username).await {
273///         Ok(user) => println!("User profile updated: {:?}", user),
274///         Err((status, message)) => eprintln!("Error updating user profile ({}): {}", status, message),
275///     }
276/// }
277///```
278pub async fn update_user_profile(
279    db: &DB,
280    payload: &UpdateUserProfileRequest,
281) -> Result<User, (StatusCode, String)> {
282    let mut response = db
283        .query(format!(
284            "UPDATE ONLY users:{} SET email = $email, username = $username RETURN AFTER",
285            payload.user_id
286        ))
287        .bind(("email", payload.email.clone()))
288        .bind(("username", payload.username.clone()))
289        .await
290        .map_err(|_| {
291            (
292                StatusCode::INTERNAL_SERVER_ERROR,
293                "Database error".to_string(),
294            )
295        })?;
296
297    let user: Option<User> = response.take(0).map_err(|_| {
298        (
299            StatusCode::INTERNAL_SERVER_ERROR,
300            "Failed to parse user".to_string(),
301        )
302    })?;
303
304    user.ok_or_else(|| (StatusCode::NOT_FOUND, "User not found".to_string()))
305}
306
307/// Promotes a user to admin status in the database
308///
309/// This function uses `#[allow(dead_code)]` due to rust providing a false positive warning
310/// that this is dead code and not used, despite obvious usage in `cli/server.rs` -> called from `cli/mod.rs`
311/// # Arguments
312/// * `db` - A reference to the database connection
313/// * `username` - The username of the user to promote to admin
314///
315/// # Returns
316/// * `Ok(())` - If the user was successfully promoted to admin
317/// * `Err(Box<dyn Error>)` - An error if the operation failed, such as if an admin already exists or if there was a database error
318///
319/// # Examples
320/// ```rust
321/// use crate::db::DB;
322/// use crate::db::queries::auth;
323/// async fn example_make_admin(db: &DB) {
324///     let username = "user_to_promote";
325///     match auth::make_admin(db, username).await {
326///         Ok(()) => println!("User promoted to admin successfully"),
327///         Err(e) => eprintln!("Error promoting user to admin: {}", e),
328///     }
329/// }
330///```
331#[allow(dead_code)]
332pub async fn make_admin(db: &DB, username: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
333    // Check bootstrap flag first
334    let mut response = db
335        .query("SELECT first_admin_bootstrapped FROM server_config LIMIT 1")
336        .await?;
337
338    let bootstrapped: Option<bool> = response.take("first_admin_bootstrapped")?;
339    if bootstrapped.unwrap_or(false) {
340        return Err("An admin already exists. This command is one time only.".into());
341    }
342
343    // Promote the user
344    db.query("UPDATE users SET is_admin = true WHERE username = $username")
345        .bind(("username", username))
346        .await?;
347
348    // Lock the bootstrap
349    db.query("UPDATE server_config SET first_admin_bootstrapped = true")
350        .await?;
351
352    Ok(())
353}
354
355pub async fn validate_refresh_token(
356    db: &DB,
357    user_id: &str,
358    refresh_token: &str,
359) -> Result<(), Box<dyn Error + Send + Sync>> {
360    tokens::validate_refresh_token(db, user_id, refresh_token).await
361}