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-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
@ -550,12 +551,65 @@ dependencies = [
|
|||||||
"uuid",
|
"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]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
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]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@ -574,8 +628,13 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
"slab",
|
"slab",
|
||||||
|
@ -12,4 +12,5 @@ uuid = { version = "1.0", features = ["v4"] }
|
|||||||
actix-files = "0.6"
|
actix-files = "0.6"
|
||||||
actix-cors = "0.6"
|
actix-cors = "0.6"
|
||||||
env_logger = "0.10" # Check for the latest version
|
env_logger = "0.10" # Check for the latest version
|
||||||
log = "0.4"
|
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> {
|
pub fn init_db() -> Result<Connection> {
|
||||||
let conn = Connection::open("form_data.db")?;
|
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)
|
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 actix_web::{web, HttpResponse, Responder};
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use uuid::Uuid;
|
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(
|
pub async fn create_form(
|
||||||
db: web::Data<Arc<Mutex<Connection>>>,
|
db: web::Data<Arc<Mutex<Connection>>>,
|
||||||
form: web::Json<Form>,
|
auth: Auth,
|
||||||
|
form: web::Json<crate::models::Form>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
println!("Received form: {:?}", form);
|
println!("Authenticated user: {}", auth.user_id);
|
||||||
let conn = db.lock().unwrap(); // Lock the Mutex to access the database
|
let conn = db.lock().unwrap();
|
||||||
let form_id = Uuid::new_v4().to_string();
|
let form_id = Uuid::new_v4().to_string();
|
||||||
let form_json = serde_json::to_string(&form.fields).unwrap();
|
let form_json = serde_json::to_string(&form.fields).unwrap();
|
||||||
|
|
||||||
@ -24,9 +80,10 @@ pub async fn create_form(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all forms
|
// Protected: Get all forms
|
||||||
pub async fn get_forms(db: web::Data<Arc<Mutex<Connection>>>) -> impl Responder {
|
pub async fn get_forms(db: web::Data<Arc<Mutex<Connection>>>, auth: Auth) -> 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 mut stmt = match conn.prepare("SELECT id, name, fields FROM forms") {
|
let mut stmt = match conn.prepare("SELECT id, name, fields FROM forms") {
|
||||||
Ok(stmt) => stmt,
|
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 name: String = row.get(1)?;
|
||||||
let fields: String = row.get(2)?;
|
let fields: String = row.get(2)?;
|
||||||
let fields = serde_json::from_str(&fields).unwrap();
|
let fields = serde_json::from_str(&fields).unwrap();
|
||||||
Ok(Form { id, name, fields })
|
Ok(crate::models::Form { id, name, fields })
|
||||||
})
|
})
|
||||||
.unwrap();
|
.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)
|
HttpResponse::Ok().json(forms)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit a form
|
// Protected: Get submissions for 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
|
|
||||||
pub async fn get_submissions(
|
pub async fn get_submissions(
|
||||||
db: web::Data<Arc<Mutex<Connection>>>,
|
db: web::Data<Arc<Mutex<Connection>>>,
|
||||||
|
auth: Auth,
|
||||||
path: web::Path<String>,
|
path: web::Path<String>,
|
||||||
) -> impl Responder {
|
) -> 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 form_id = path.into_inner();
|
||||||
|
|
||||||
let mut stmt =
|
let mut stmt =
|
||||||
@ -88,10 +126,11 @@ pub async fn get_submissions(
|
|||||||
let form_id: String = row.get(1)?;
|
let form_id: String = row.get(1)?;
|
||||||
let data: String = row.get(2)?;
|
let data: String = row.get(2)?;
|
||||||
let data = serde_json::from_str(&data).unwrap();
|
let data = serde_json::from_str(&data).unwrap();
|
||||||
Ok(Submission { id, form_id, data })
|
Ok(crate::models::Submission { id, form_id, data })
|
||||||
})
|
})
|
||||||
.unwrap();
|
.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)
|
HttpResponse::Ok().json(submissions)
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
// use actix_files as fs;
|
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
mod auth;
|
||||||
mod db;
|
mod db;
|
||||||
mod handlers;
|
mod handlers;
|
||||||
mod models;
|
mod models;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
// Initialize the database connection
|
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let db = Arc::new(Mutex::new(
|
let db = Arc::new(Mutex::new(
|
||||||
db::init_db().expect("Failed to initialize the database"),
|
db::init_db().expect("Failed to initialize the database"),
|
||||||
));
|
));
|
||||||
|
|
||||||
// Start the Actix-Web server
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(
|
.wrap(
|
||||||
@ -26,16 +23,16 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.allow_any_method(),
|
.allow_any_method(),
|
||||||
)
|
)
|
||||||
.app_data(web::Data::new(db.clone()))
|
.app_data(web::Data::new(db.clone()))
|
||||||
// .service(fs::Files::new("/", "./frontend/public").index_file("index.html"))
|
.route("/login", web::post().to(handlers::login)) // Public: Login
|
||||||
.route("/forms", web::post().to(handlers::create_form))
|
|
||||||
.route("/forms", web::get().to(handlers::get_forms))
|
|
||||||
.route(
|
.route(
|
||||||
"/forms/{id}/submissions",
|
"/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(
|
.route(
|
||||||
"/forms/{id}/submissions",
|
"/forms/{id}/submissions",
|
||||||
web::get().to(handlers::get_submissions),
|
web::get().to(handlers::get_submissions), // Protected
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.bind("127.0.0.1:8080")?
|
.bind("127.0.0.1:8080")?
|
||||||
|
Loading…
Reference in New Issue
Block a user