mirror of
https://github.com/P-Aimon-Pen/ROFMAD
synced 2026-06-21 13:45:02 +00:00
164 lines
5.5 KiB
Rust
Executable File
164 lines
5.5 KiB
Rust
Executable File
// src/sql/users.rs - User management for role-based authentication
|
|
|
|
use std::error::Error;
|
|
use sqlx::Row;
|
|
use crate::sql::ProjectContext;
|
|
use crate::web::auth::{UserRole, hash_password};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct User {
|
|
pub id: i64,
|
|
pub username: String,
|
|
pub role: UserRole,
|
|
pub enabled: bool,
|
|
pub created_at: String,
|
|
}
|
|
|
|
impl ProjectContext {
|
|
// Context: Creates new player user account
|
|
// Operation: Inserts new user with hashed password into database
|
|
pub async fn create_player_user(
|
|
&self,
|
|
username: &str,
|
|
password: &str,
|
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
let password_hash = hash_password(password)?;
|
|
|
|
sqlx::query(
|
|
"INSERT INTO users (username, password_hash, role, enabled) VALUES (?1, ?2, ?3, TRUE)"
|
|
)
|
|
.bind(username)
|
|
.bind(password_hash)
|
|
.bind(UserRole::Player.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Context: Retrieves all player users for management interface
|
|
// Operation: Returns list of player accounts with their status
|
|
pub async fn get_player_users(&self) -> Result<Vec<User>, Box<dyn Error + Send + Sync>> {
|
|
let rows = sqlx::query(
|
|
"SELECT id, username, role, enabled, created_at FROM users WHERE role = ?1 ORDER BY created_at DESC"
|
|
)
|
|
.bind(UserRole::Player.as_str())
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let mut users = Vec::new();
|
|
for row in rows {
|
|
let role_str: String = row.get("role");
|
|
let role = UserRole::from_str(&role_str).unwrap_or(UserRole::Player);
|
|
|
|
users.push(User {
|
|
id: row.get("id"),
|
|
username: row.get("username"),
|
|
role,
|
|
enabled: row.get("enabled"),
|
|
created_at: row.get("created_at"),
|
|
});
|
|
}
|
|
|
|
Ok(users)
|
|
}
|
|
|
|
// Context: Enables or disables player user account
|
|
// Operation: Updates enabled status for specified user
|
|
pub async fn set_user_enabled(
|
|
&self,
|
|
username: &str,
|
|
enabled: bool,
|
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
sqlx::query("UPDATE users SET enabled = ?1 WHERE username = ?2 AND role = ?3")
|
|
.bind(enabled)
|
|
.bind(username)
|
|
.bind(UserRole::Player.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Context: Resets player user password
|
|
// Operation: Updates password hash for specified user
|
|
pub async fn reset_user_password(
|
|
&self,
|
|
username: &str,
|
|
new_password: &str,
|
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
let password_hash = hash_password(new_password)?;
|
|
|
|
sqlx::query("UPDATE users SET password_hash = ?1 WHERE username = ?2 AND role = ?3")
|
|
.bind(password_hash)
|
|
.bind(username)
|
|
.bind(UserRole::Player.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Context: Removes player user account
|
|
// Operation: Deletes user from database (admin cannot be deleted)
|
|
pub async fn delete_user(&self, username: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
sqlx::query("DELETE FROM users WHERE username = ?1 AND role = ?2")
|
|
.bind(username)
|
|
.bind(UserRole::Player.as_str())
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Context: Checks if username already exists
|
|
// Operation: Returns true if username is already taken
|
|
pub async fn username_exists(&self, username: &str) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
|
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users WHERE username = ?1")
|
|
.bind(username)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(count > 0)
|
|
}
|
|
|
|
// Context: First-run check — determines whether admin setup is required
|
|
// Operation: Returns true if an admin account already exists in the database
|
|
pub async fn admin_exists(&self) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
|
let count: i64 = sqlx::query_scalar(
|
|
"SELECT COUNT(*) FROM users WHERE role = 'admin'"
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
Ok(count > 0)
|
|
}
|
|
|
|
// Context: First-run admin account creation
|
|
// Operation: Creates the admin user with a bcrypt-hashed password; fails if admin already exists
|
|
pub async fn create_admin_user(&self, password: &str) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
let password_hash = hash_password(password)?;
|
|
sqlx::query(
|
|
"INSERT INTO users (username, password_hash, role, enabled) VALUES ('admin', ?1, 'admin', TRUE)"
|
|
)
|
|
.bind(password_hash)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
// Context: Admin login authentication
|
|
// Operation: Retrieves admin password hash and verifies it with bcrypt
|
|
pub async fn verify_admin_password(&self, password: &str) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
|
use crate::web::auth::verify_password;
|
|
let hash: Option<String> = sqlx::query_scalar(
|
|
"SELECT password_hash FROM users WHERE username = 'admin' AND role = 'admin' AND enabled = TRUE"
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match hash {
|
|
Some(h) => Ok(verify_password(password, &h)),
|
|
None => Ok(false),
|
|
}
|
|
}
|
|
} |