241 lines
8.1 KiB
Rust
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)),
|
|
}
|
|
}
|