Files
P-Aimon-Pen-ROFMAD/src/sql/users.rs
T
2026-06-18 15:03:00 +02:00

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),
}
}
}