basic backend auth fr
This commit is contained in:
parent
d3f829880a
commit
793e136087
59
backend/Cargo.lock
generated
59
backend/Cargo.lock
generated
@ -543,6 +543,7 @@ dependencies = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"log",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
@ -550,12 +551,65 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
@ -574,8 +628,13 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
|
@ -13,3 +13,4 @@ actix-files = "0.6"
|
||||
actix-cors = "0.6"
|
||||
env_logger = "0.10" # Check for the latest version
|
||||
log = "0.4"
|
||||
futures = "0.3"
|
36
backend/src/auth.rs
Normal file
36
backend/src/auth.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use actix_web::{dev::Payload, http::header::AUTHORIZATION, web, Error, FromRequest, HttpRequest};
|
||||
use futures::future::{ready, Ready};
|
||||
use rusqlite::Connection;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub struct Auth {
|
||||
pub user_id: String,
|
||||
}
|
||||
|
||||
impl FromRequest for Auth {
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
let db = req
|
||||
.app_data::<web::Data<Arc<Mutex<Connection>>>>()
|
||||
.expect("Database connection missing");
|
||||
|
||||
if let Some(auth_header) = req.headers().get(AUTHORIZATION) {
|
||||
if let Ok(auth_str) = auth_header.to_str() {
|
||||
if auth_str.starts_with("Bearer ") {
|
||||
let token = &auth_str[7..];
|
||||
let conn = db.lock().unwrap();
|
||||
|
||||
match super::db::validate_token(&conn, token) {
|
||||
Ok(Some(user_id)) => return ready(Ok(Auth { user_id })),
|
||||
Ok(None) | Err(_) => {
|
||||
return ready(Err(actix_web::error::ErrorUnauthorized("Invalid token")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ready(Err(actix_web::error::ErrorUnauthorized("Missing token")))
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use rusqlite::{Connection, Result};
|
||||
use rusqlite::{params, Connection, OptionalExtension, Result};
|
||||
|
||||
pub fn init_db() -> Result<Connection> {
|
||||
let conn = Connection::open("form_data.db")?;
|
||||
@ -24,5 +24,55 @@ pub fn init_db() -> Result<Connection> {
|
||||
[],
|
||||
)?;
|
||||
|
||||
// Add a table for users
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL, -- Store a hashed password
|
||||
token TEXT UNIQUE -- Optional: For token-based auth
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
// Add a function to validate a token
|
||||
pub fn validate_token(conn: &Connection, token: &str) -> Result<Option<String>> {
|
||||
let mut stmt = conn.prepare("SELECT id FROM users WHERE token = ?1")?;
|
||||
let user_id: Option<String> = stmt
|
||||
.query_row(params![token], |row| row.get(0))
|
||||
.optional()?;
|
||||
Ok(user_id)
|
||||
}
|
||||
|
||||
// Add a function to authenticate users (for login)
|
||||
pub fn authenticate_user(
|
||||
conn: &Connection,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let mut stmt = conn.prepare("SELECT id, password FROM users WHERE username = ?1")?;
|
||||
let mut rows = stmt.query(params![username])?;
|
||||
|
||||
if let Some(row) = rows.next()? {
|
||||
let user_id: String = row.get(0)?;
|
||||
let stored_password: String = row.get(1)?;
|
||||
|
||||
// Replace this with a secure password hashing and verification mechanism
|
||||
if stored_password == password {
|
||||
return Ok(Some(user_id));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Add a function to generate and save a token for a user
|
||||
pub fn generate_token_for_user(conn: &Connection, user_id: &str, token: &str) -> Result<()> {
|
||||
conn.execute(
|
||||
"UPDATE users SET token = ?1 WHERE id = ?2",
|
||||
params![token, user_id],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,17 +1,73 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use rusqlite::{params, Connection};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::{Form, Submission};
|
||||
use crate::auth::Auth;
|
||||
|
||||
// Create a new form
|
||||
// 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>>>,
|
||||
form: web::Json<Form>,
|
||||
auth: Auth,
|
||||
form: web::Json<crate::models::Form>,
|
||||
) -> impl Responder {
|
||||
println!("Received form: {:?}", form);
|
||||
let conn = db.lock().unwrap(); // Lock the Mutex to access the database
|
||||
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();
|
||||
|
||||
@ -24,9 +80,10 @@ pub async fn create_form(
|
||||
}
|
||||
}
|
||||
|
||||
// Get all forms
|
||||
pub async fn get_forms(db: web::Data<Arc<Mutex<Connection>>>) -> impl Responder {
|
||||
let conn = db.lock().unwrap(); // Lock the Mutex to access the database
|
||||
// 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,
|
||||
@ -39,41 +96,22 @@ pub async fn get_forms(db: web::Data<Arc<Mutex<Connection>>>) -> impl Responder
|
||||
let name: String = row.get(1)?;
|
||||
let fields: String = row.get(2)?;
|
||||
let fields = serde_json::from_str(&fields).unwrap();
|
||||
Ok(Form { id, name, fields })
|
||||
Ok(crate::models::Form { id, name, fields })
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let forms: Vec<Form> = forms_iter.filter_map(|f| f.ok()).collect();
|
||||
let forms: Vec<crate::models::Form> = forms_iter.filter_map(|f| f.ok()).collect();
|
||||
HttpResponse::Ok().json(forms)
|
||||
}
|
||||
|
||||
// Submit a form
|
||||
pub async fn submit_form(
|
||||
db: web::Data<Arc<Mutex<rusqlite::Connection>>>,
|
||||
path: web::Path<String>,
|
||||
submission: web::Form<serde_json::Value>,
|
||||
) -> impl Responder {
|
||||
let conn = db.lock().unwrap(); // Lock the Mutex to access the database
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
||||
// Get submissions for a form
|
||||
// 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 {
|
||||
let conn = db.lock().unwrap(); // Lock the Mutex to access the database
|
||||
println!("Authenticated user: {}", auth.user_id);
|
||||
let conn = db.lock().unwrap();
|
||||
let form_id = path.into_inner();
|
||||
|
||||
let mut stmt =
|
||||
@ -88,10 +126,11 @@ pub async fn get_submissions(
|
||||
let form_id: String = row.get(1)?;
|
||||
let data: String = row.get(2)?;
|
||||
let data = serde_json::from_str(&data).unwrap();
|
||||
Ok(Submission { id, form_id, data })
|
||||
Ok(crate::models::Submission { id, form_id, data })
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let submissions: Vec<Submission> = submissions_iter.filter_map(|s| s.ok()).collect();
|
||||
let submissions: Vec<crate::models::Submission> =
|
||||
submissions_iter.filter_map(|s| s.ok()).collect();
|
||||
HttpResponse::Ok().json(submissions)
|
||||
}
|
||||
|
@ -1,22 +1,19 @@
|
||||
use actix_cors::Cors;
|
||||
// use actix_files as fs;
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
mod auth;
|
||||
mod db;
|
||||
mod handlers;
|
||||
mod models;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
// Initialize the database connection
|
||||
env_logger::init();
|
||||
|
||||
let db = Arc::new(Mutex::new(
|
||||
db::init_db().expect("Failed to initialize the database"),
|
||||
));
|
||||
|
||||
// Start the Actix-Web server
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(
|
||||
@ -26,16 +23,16 @@ async fn main() -> std::io::Result<()> {
|
||||
.allow_any_method(),
|
||||
)
|
||||
.app_data(web::Data::new(db.clone()))
|
||||
// .service(fs::Files::new("/", "./frontend/public").index_file("index.html"))
|
||||
.route("/forms", web::post().to(handlers::create_form))
|
||||
.route("/forms", web::get().to(handlers::get_forms))
|
||||
.route("/login", web::post().to(handlers::login)) // Public: Login
|
||||
.route(
|
||||
"/forms/{id}/submissions",
|
||||
web::post().to(handlers::submit_form),
|
||||
web::post().to(handlers::submit_form), // Public: Submit form
|
||||
)
|
||||
.route("/forms", web::post().to(handlers::create_form)) // Protected
|
||||
.route("/forms", web::get().to(handlers::get_forms)) // Protected
|
||||
.route(
|
||||
"/forms/{id}/submissions",
|
||||
web::get().to(handlers::get_submissions),
|
||||
web::get().to(handlers::get_submissions), // Protected
|
||||
)
|
||||
})
|
||||
.bind("127.0.0.1:8080")?
|
||||
|
Loading…
Reference in New Issue
Block a user