formies/backend/src/handlers.rs
Mohamad 92c85ebe40
Some checks failed
Build and Deploy / build (push) Failing after 7s
Merge branch 'working'
2025-01-02 14:42:06 +01:00

241 lines
8.1 KiB
Rust

use crate::models::{AdminUser, Claims, Form, LoginCredentials, Submission};
use actix_web::{web, HttpResponse, Responder};
use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
use jsonwebtoken::{encode, EncodingKey, Header};
use rusqlite::{params, Connection};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use uuid::Uuid;
use crate::auth::Auth;
// Structs for requests and responses
#[derive(Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Serialize)]
pub struct LoginResponse {
pub token: String,
}
// Public: Login handler
pub async fn login(
db: web::Data<Arc<Mutex<Connection>>>,
login_request: web::Json<LoginRequest>,
) -> impl Responder {
let conn = db.lock().unwrap();
let user_id =
match crate::db::authenticate_user(&conn, &login_request.username, &login_request.password)
{
Ok(Some(user_id)) => user_id,
Ok(None) => return HttpResponse::Unauthorized().body("Invalid username or password"),
Err(_) => return HttpResponse::InternalServerError().body("Database error"),
};
let token = Uuid::new_v4().to_string();
if let Err(_) = crate::db::generate_token_for_user(&conn, &user_id, &token) {
return HttpResponse::InternalServerError().body("Failed to generate token");
}
HttpResponse::Ok().json(LoginResponse { token })
}
// Public: Submit a form
pub async fn submit_form(
db: web::Data<Arc<Mutex<Connection>>>,
path: web::Path<String>,
submission: web::Form<serde_json::Value>,
) -> impl Responder {
let conn = db.lock().unwrap();
let submission_id = Uuid::new_v4().to_string();
let form_id = path.into_inner();
let submission_json = serde_json::to_string(&submission.into_inner()).unwrap();
match conn.execute(
"INSERT INTO submissions (id, form_id, data) VALUES (?1, ?2, ?3)",
params![submission_id, form_id, submission_json],
) {
Ok(_) => HttpResponse::Ok().json(submission_id),
Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)),
}
}
// Protected: Create a new form
pub async fn create_form(
db: web::Data<Arc<Mutex<Connection>>>,
auth: Auth,
form: web::Json<crate::models::Form>,
) -> impl Responder {
println!("Authenticated user: {}", auth.user_id);
let conn = db.lock().unwrap();
let form_id = Uuid::new_v4().to_string();
let form_json = serde_json::to_string(&form.fields).unwrap();
match conn.execute(
"INSERT INTO forms (id, name, fields) VALUES (?1, ?2, ?3)",
params![form_id, form.name, form_json],
) {
Ok(_) => HttpResponse::Ok().json(form_id),
Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)),
}
}
// Protected: Get all forms
pub async fn get_forms(db: web::Data<Arc<Mutex<Connection>>>, auth: Auth) -> impl Responder {
println!("Authenticated user: {}", auth.user_id);
let conn = db.lock().unwrap();
let mut stmt = match conn.prepare("SELECT id, name, fields FROM forms") {
Ok(stmt) => stmt,
Err(e) => return HttpResponse::InternalServerError().body(format!("Error: {}", e)),
};
let forms_iter = stmt
.query_map([], |row| {
let id: Option<String> = row.get(0)?;
let name: String = row.get(1)?;
let fields: String = row.get(2)?;
let fields = serde_json::from_str(&fields).unwrap();
Ok(crate::models::Form { id, name, fields })
})
.unwrap();
let forms: Vec<crate::models::Form> = forms_iter.filter_map(|f| f.ok()).collect();
HttpResponse::Ok().json(forms)
}
// Protected: Get submissions for a form
pub async fn get_submissions(
db: web::Data<Arc<Mutex<Connection>>>,
auth: Auth,
path: web::Path<String>,
) -> impl Responder {
println!("Authenticated user: {}", auth.user_id);
let conn = db.lock().unwrap();
let form_id = path.into_inner();
let mut stmt =
match conn.prepare("SELECT id, form_id, data FROM submissions WHERE form_id = ?1") {
Ok(stmt) => stmt,
Err(e) => return HttpResponse::InternalServerError().body(format!("Error: {}", e)),
};
let submissions_iter = stmt
.query_map([form_id], |row| {
let id: String = row.get(0)?;
let form_id: String = row.get(1)?;
let data: String = row.get(2)?;
let data = serde_json::from_str(&data).unwrap();
Ok(crate::models::Submission { id, form_id, data })
})
.unwrap();
let submissions: Vec<crate::models::Submission> =
submissions_iter.filter_map(|s| s.ok()).collect();
HttpResponse::Ok().json(submissions)
}
pub async fn admin_login(
db: web::Data<Arc<Mutex<Connection>>>,
credentials: web::Json<LoginCredentials>,
) -> impl Responder {
let conn = match db.lock() {
Ok(conn) => conn,
Err(_) => return HttpResponse::InternalServerError().body("Database lock error"),
};
let mut stmt =
match conn.prepare("SELECT username, password_hash FROM admin_users WHERE username = ?1") {
Ok(stmt) => stmt,
Err(e) => {
return HttpResponse::InternalServerError().body(format!("Database error: {}", e))
}
};
let admin: Option<AdminUser> = match stmt.query_row([&credentials.username], |row| {
Ok(AdminUser {
username: row.get(0)?,
password_hash: row.get(1)?,
})
}) {
Ok(admin) => Some(admin),
Err(rusqlite::Error::QueryReturnedNoRows) => None, // No user found
Err(e) => return HttpResponse::InternalServerError().body(format!("Query error: {}", e)),
};
match admin {
Some(user) => {
let parsed_hash = match PasswordHash::new(&user.password_hash) {
Ok(hash) => hash,
Err(_) => {
return HttpResponse::InternalServerError()
.body("Invalid password hash format in database")
}
};
let argon2 = Argon2::default();
let is_valid = argon2
.verify_password(credentials.password.as_bytes(), &parsed_hash)
.is_ok();
if is_valid {
let expiration = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(duration) => duration.as_secs() as usize + 24 * 3600,
Err(_) => return HttpResponse::InternalServerError().body("System time error"),
};
let claims = Claims {
sub: user.username,
exp: expiration,
};
let token = match encode(
&Header::default(),
&claims,
&EncodingKey::from_secret("your-secret-key".as_ref()),
) {
Ok(token) => token,
Err(_) => {
return HttpResponse::InternalServerError().body("Token generation error")
}
};
HttpResponse::Ok().json(json!({ "token": token }))
} else {
HttpResponse::Unauthorized().json(json!({ "error": "Invalid credentials" }))
}
}
None => HttpResponse::Unauthorized().json(json!({ "error": "Invalid credentials" })),
}
}
pub async fn create_admin(
db: web::Data<Arc<Mutex<Connection>>>,
user: web::Json<LoginCredentials>,
) -> impl Responder {
let conn = db.lock().unwrap();
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(user.password.as_bytes(), &salt)
.unwrap()
.to_string();
match conn.execute(
"INSERT INTO admin_users (username, password_hash) VALUES (?1, ?2)",
params![user.username, password_hash],
) {
Ok(_) => HttpResponse::Ok().json(json!({
"message": "Admin user created successfully"
})),
Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)),
}
}