Create controllers functions
This commit is contained in:
106
server/src/controllers/auth.rs
Normal file
106
server/src/controllers/auth.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use crate::controllers::users::UsersController;
|
||||
use crate::utils::{error::*, models::*, DbPool};
|
||||
use chrono::{Duration, Utc};
|
||||
use sqlx::Row;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct AuthController;
|
||||
|
||||
impl AuthController {
|
||||
pub async fn login(pool: &DbPool, username: &str, password: &str) -> AppResult<LoginResponse> {
|
||||
let user = UsersController::verify_user_credentials(pool, username, password).await?;
|
||||
|
||||
let session = Self::create_session(pool, user.id).await?;
|
||||
|
||||
Ok(LoginResponse {
|
||||
token: session.token,
|
||||
role: user.role,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn logout(pool: &DbPool, user_id: i64) -> AppResult<()> {
|
||||
sqlx::query("DELETE FROM sessions WHERE user_id = ?")
|
||||
.bind(user_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_session(pool: &DbPool, user_id: i64) -> AppResult<Session> {
|
||||
let token = Self::generate_session_token();
|
||||
let expires_at = Utc::now() + Duration::days(30);
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO sessions (user_id, token, expires_at)
|
||||
VALUES (?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(&token)
|
||||
.bind(expires_at)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(Session {
|
||||
id: result.last_insert_rowid(),
|
||||
user_id,
|
||||
token,
|
||||
created_at: Utc::now(),
|
||||
expires_at,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_session_by_token(pool: &DbPool, token: &str) -> AppResult<Option<Session>> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT id, user_id, token, created_at, expires_at
|
||||
FROM sessions WHERE token = ? AND expires_at > datetime('now')
|
||||
"#,
|
||||
)
|
||||
.bind(token)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = row {
|
||||
Ok(Some(Session {
|
||||
id: row.get("id"),
|
||||
user_id: row.get("user_id"),
|
||||
token: row.get("token"),
|
||||
created_at: row.get("created_at"),
|
||||
expires_at: row.get("expires_at"),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn authenticate_user(pool: &DbPool, token: &str) -> AppResult<User> {
|
||||
let session = Self::get_session_by_token(pool, token)
|
||||
.await?
|
||||
.ok_or_else(|| auth_error("Invalid or expired session"))?;
|
||||
|
||||
let user = UsersController::get_user_by_id(pool, session.user_id).await?;
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn cleanup_expired_sessions(pool: &DbPool) -> AppResult<()> {
|
||||
sqlx::query("DELETE FROM sessions WHERE expires_at <= datetime('now')")
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_session_token() -> String {
|
||||
Uuid::new_v4().to_string()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn delete_session_by_token(pool: &DbPool, token: &str) -> AppResult<()> {
|
||||
sqlx::query("DELETE FROM sessions WHERE token = ?")
|
||||
.bind(token)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
208
server/src/controllers/machines.rs
Normal file
208
server/src/controllers/machines.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
use crate::utils::{error::*, models::*, DbPool};
|
||||
use chrono::Utc;
|
||||
use sqlx::Row;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct MachinesController;
|
||||
|
||||
impl MachinesController {
|
||||
pub async fn register_machine(
|
||||
pool: &DbPool,
|
||||
code: &str,
|
||||
uuid: &Uuid,
|
||||
name: &str,
|
||||
) -> AppResult<Machine> {
|
||||
Self::validate_machine_input(name)?;
|
||||
|
||||
let provisioning_code = Self::get_provisioning_code(pool, code)
|
||||
.await?
|
||||
.ok_or_else(|| validation_error("Invalid provisioning code"))?;
|
||||
|
||||
if provisioning_code.used {
|
||||
return Err(validation_error("Provisioning code already used"));
|
||||
}
|
||||
|
||||
if provisioning_code.expires_at < Utc::now() {
|
||||
return Err(validation_error("Provisioning code expired"));
|
||||
}
|
||||
|
||||
if Self::machine_exists_by_uuid(pool, uuid).await? {
|
||||
return Err(conflict_error("Machine with this UUID already exists"));
|
||||
}
|
||||
|
||||
let machine = Self::create_machine(pool, provisioning_code.user_id, uuid, name).await?;
|
||||
|
||||
Self::mark_provisioning_code_used(pool, code).await?;
|
||||
|
||||
Ok(machine)
|
||||
}
|
||||
|
||||
pub async fn get_machines_for_user(pool: &DbPool, user: &User) -> AppResult<Vec<Machine>> {
|
||||
if user.role == UserRole::Admin {
|
||||
Self::get_all_machines(pool).await
|
||||
} else {
|
||||
Self::get_machines_by_user_id(pool, user.id).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_machine(pool: &DbPool, machine_id: i64, user: &User) -> AppResult<()> {
|
||||
let machine = Self::get_machine_by_id(pool, machine_id).await?;
|
||||
|
||||
if user.role != UserRole::Admin && machine.user_id != user.id {
|
||||
return Err(forbidden_error("Access denied"));
|
||||
}
|
||||
|
||||
Self::delete_machine_by_id(pool, machine_id).await
|
||||
}
|
||||
|
||||
pub async fn get_machine_by_id(pool: &DbPool, id: i64) -> AppResult<Machine> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT id, user_id, uuid, name, created_at
|
||||
FROM machines WHERE id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(Machine {
|
||||
id: row.get("id"),
|
||||
user_id: row.get("user_id"),
|
||||
uuid: Uuid::parse_str(&row.get::<String, _>("uuid")).unwrap(),
|
||||
name: row.get("name"),
|
||||
created_at: row.get("created_at"),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_all_machines(pool: &DbPool) -> AppResult<Vec<Machine>> {
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT id, user_id, uuid, name, created_at
|
||||
FROM machines ORDER BY created_at DESC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let mut machines = Vec::new();
|
||||
for row in rows {
|
||||
machines.push(Machine {
|
||||
id: row.get("id"),
|
||||
user_id: row.get("user_id"),
|
||||
uuid: Uuid::parse_str(&row.get::<String, _>("uuid")).unwrap(),
|
||||
name: row.get("name"),
|
||||
created_at: row.get("created_at"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(machines)
|
||||
}
|
||||
|
||||
async fn get_machines_by_user_id(pool: &DbPool, user_id: i64) -> AppResult<Vec<Machine>> {
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT id, user_id, uuid, name, created_at
|
||||
FROM machines WHERE user_id = ? ORDER BY created_at DESC
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let mut machines = Vec::new();
|
||||
for row in rows {
|
||||
machines.push(Machine {
|
||||
id: row.get("id"),
|
||||
user_id: row.get("user_id"),
|
||||
uuid: Uuid::parse_str(&row.get::<String, _>("uuid")).unwrap(),
|
||||
name: row.get("name"),
|
||||
created_at: row.get("created_at"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(machines)
|
||||
}
|
||||
|
||||
async fn create_machine(
|
||||
pool: &DbPool,
|
||||
user_id: i64,
|
||||
uuid: &Uuid,
|
||||
name: &str,
|
||||
) -> AppResult<Machine> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO machines (user_id, uuid, name)
|
||||
VALUES (?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(uuid.to_string())
|
||||
.bind(name)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Self::get_machine_by_id(pool, result.last_insert_rowid()).await
|
||||
}
|
||||
|
||||
async fn machine_exists_by_uuid(pool: &DbPool, uuid: &Uuid) -> AppResult<bool> {
|
||||
let row = sqlx::query("SELECT COUNT(*) as count FROM machines WHERE uuid = ?")
|
||||
.bind(uuid.to_string())
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
let count: i64 = row.get("count");
|
||||
Ok(count > 0)
|
||||
}
|
||||
|
||||
async fn delete_machine_by_id(pool: &DbPool, id: i64) -> AppResult<()> {
|
||||
sqlx::query("DELETE FROM machines WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_provisioning_code(
|
||||
pool: &DbPool,
|
||||
code: &str,
|
||||
) -> AppResult<Option<ProvisioningCode>> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT id, user_id, code, created_at, expires_at, used
|
||||
FROM provisioning_codes WHERE code = ?
|
||||
"#,
|
||||
)
|
||||
.bind(code)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = row {
|
||||
Ok(Some(ProvisioningCode {
|
||||
id: row.get("id"),
|
||||
user_id: row.get("user_id"),
|
||||
code: row.get("code"),
|
||||
created_at: row.get("created_at"),
|
||||
expires_at: row.get("expires_at"),
|
||||
used: row.get("used"),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn mark_provisioning_code_used(pool: &DbPool, code: &str) -> AppResult<()> {
|
||||
sqlx::query("UPDATE provisioning_codes SET used = 1 WHERE code = ?")
|
||||
.bind(code)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_machine_input(name: &str) -> AppResult<()> {
|
||||
if name.trim().is_empty() {
|
||||
return Err(validation_error("Machine name cannot be empty"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
3
server/src/controllers/mod.rs
Normal file
3
server/src/controllers/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod auth;
|
||||
pub mod machines;
|
||||
pub mod users;
|
202
server/src/controllers/users.rs
Normal file
202
server/src/controllers/users.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
use crate::utils::{error::*, models::*, DbPool};
|
||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||
use sqlx::Row;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub struct UsersController;
|
||||
|
||||
impl UsersController {
|
||||
pub async fn create_user(
|
||||
pool: &DbPool,
|
||||
username: &str,
|
||||
password: &str,
|
||||
role: UserRole,
|
||||
storage_limit_gb: i64,
|
||||
) -> AppResult<User> {
|
||||
Self::validate_user_input(username, password)?;
|
||||
|
||||
let password_hash = hash(password, DEFAULT_COST)?;
|
||||
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO users (username, password_hash, role, storage_limit_gb)
|
||||
VALUES (?, ?, ?, ?)
|
||||
"#,
|
||||
)
|
||||
.bind(username)
|
||||
.bind(&password_hash)
|
||||
.bind(role.to_string())
|
||||
.bind(storage_limit_gb)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Self::get_user_by_id(pool, result.last_insert_rowid()).await
|
||||
}
|
||||
|
||||
pub async fn get_user_by_username(pool: &DbPool, username: &str) -> AppResult<Option<User>> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT id, username, password_hash, role, storage_limit_gb, created_at
|
||||
FROM users WHERE username = ?
|
||||
"#,
|
||||
)
|
||||
.bind(username)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = row {
|
||||
Ok(Some(User {
|
||||
id: row.get("id"),
|
||||
username: row.get("username"),
|
||||
password_hash: row.get("password_hash"),
|
||||
role: UserRole::from_str(&row.get::<String, _>("role")).unwrap(),
|
||||
storage_limit_gb: row.get("storage_limit_gb"),
|
||||
created_at: row.get("created_at"),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_user_by_id(pool: &DbPool, id: i64) -> AppResult<User> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT id, username, password_hash, role, storage_limit_gb, created_at
|
||||
FROM users WHERE id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(User {
|
||||
id: row.get("id"),
|
||||
username: row.get("username"),
|
||||
password_hash: row.get("password_hash"),
|
||||
role: UserRole::from_str(&row.get::<String, _>("role")).unwrap(),
|
||||
storage_limit_gb: row.get("storage_limit_gb"),
|
||||
created_at: row.get("created_at"),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_all_users(pool: &DbPool) -> AppResult<Vec<User>> {
|
||||
let rows = sqlx::query(
|
||||
r#"
|
||||
SELECT id, username, password_hash, role, storage_limit_gb, created_at
|
||||
FROM users ORDER BY created_at DESC
|
||||
"#,
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let mut users = Vec::new();
|
||||
for row in rows {
|
||||
users.push(User {
|
||||
id: row.get("id"),
|
||||
username: row.get("username"),
|
||||
password_hash: row.get("password_hash"),
|
||||
role: UserRole::from_str(&row.get::<String, _>("role")).unwrap(),
|
||||
storage_limit_gb: row.get("storage_limit_gb"),
|
||||
created_at: row.get("created_at"),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
pub async fn update_user(
|
||||
pool: &DbPool,
|
||||
id: i64,
|
||||
request: UpdateUserRequest,
|
||||
) -> AppResult<User> {
|
||||
let current_user = Self::get_user_by_id(pool, id).await?;
|
||||
|
||||
let username = request.username.clone().unwrap_or(current_user.username);
|
||||
let role = request.role.unwrap_or(current_user.role);
|
||||
let storage_limit_gb = request
|
||||
.storage_limit_gb
|
||||
.unwrap_or(current_user.storage_limit_gb);
|
||||
|
||||
if request.username.is_some() || request.password.is_some() {
|
||||
Self::validate_user_input(
|
||||
&username,
|
||||
&request.password.as_deref().unwrap_or("validpassword"),
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(password) = request.password {
|
||||
let password_hash = hash(&password, DEFAULT_COST)?;
|
||||
sqlx::query(
|
||||
r#"
|
||||
UPDATE users
|
||||
SET username = ?, password_hash = ?, role = ?, storage_limit_gb = ?
|
||||
WHERE id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(&username)
|
||||
.bind(&password_hash)
|
||||
.bind(role.to_string())
|
||||
.bind(storage_limit_gb)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query(
|
||||
r#"
|
||||
UPDATE users
|
||||
SET username = ?, role = ?, storage_limit_gb = ?
|
||||
WHERE id = ?
|
||||
"#,
|
||||
)
|
||||
.bind(&username)
|
||||
.bind(role.to_string())
|
||||
.bind(storage_limit_gb)
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Self::get_user_by_id(pool, id).await
|
||||
}
|
||||
|
||||
pub async fn delete_user(pool: &DbPool, id: i64) -> AppResult<()> {
|
||||
Self::get_user_by_id(pool, id).await?;
|
||||
|
||||
sqlx::query("DELETE FROM users WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn verify_user_credentials(
|
||||
pool: &DbPool,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> AppResult<User> {
|
||||
let user = Self::get_user_by_username(pool, username)
|
||||
.await?
|
||||
.ok_or_else(|| auth_error("Invalid credentials"))?;
|
||||
|
||||
let is_valid = verify(password, &user.password_hash)?;
|
||||
if !is_valid {
|
||||
return Err(auth_error("Invalid credentials"));
|
||||
}
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
fn validate_user_input(username: &str, password: &str) -> AppResult<()> {
|
||||
if username.trim().is_empty() {
|
||||
return Err(validation_error("Username cannot be empty"));
|
||||
}
|
||||
|
||||
if password.len() < 8 {
|
||||
return Err(validation_error(
|
||||
"Password must be at least 8 characters long",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user