added basic auth
This commit is contained in:
parent
d3f829880a
commit
5214b6b88a
@ -24,5 +24,13 @@ pub fn init_db() -> Result<Connection> {
|
|||||||
[],
|
[],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS admin_users (
|
||||||
|
username TEXT PRIMARY KEY,
|
||||||
|
password_hash TEXT NOT NULL
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(conn)
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
|
use crate::models::{AdminUser, Claims, Form, LoginCredentials, Submission};
|
||||||
use actix_web::{web, HttpResponse, Responder};
|
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 rusqlite::{params, Connection};
|
||||||
use std::sync::{Arc, Mutex};
|
use serde_json::json;
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::models::{Form, Submission};
|
|
||||||
|
|
||||||
// Create a new form
|
// 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>>>,
|
||||||
@ -95,3 +103,89 @@ pub async fn get_submissions(
|
|||||||
let submissions: Vec<Submission> = submissions_iter.filter_map(|s| s.ok()).collect();
|
let submissions: Vec<Submission> = submissions_iter.filter_map(|s| s.ok()).collect();
|
||||||
HttpResponse::Ok().json(submissions)
|
HttpResponse::Ok().json(submissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn admin_login(
|
||||||
|
db: web::Data<Arc<Mutex<Connection>>>,
|
||||||
|
credentials: web::Json<LoginCredentials>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let conn = db.lock().unwrap();
|
||||||
|
|
||||||
|
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!("Error: {}", e)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let admin: Option<AdminUser> = stmt
|
||||||
|
.query_row([&credentials.username], |row| {
|
||||||
|
Ok(AdminUser {
|
||||||
|
username: row.get(0)?,
|
||||||
|
password_hash: row.get(1)?,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
match admin {
|
||||||
|
Some(user) => {
|
||||||
|
let parsed_hash = PasswordHash::new(&user.password_hash).unwrap();
|
||||||
|
let argon2 = Argon2::default();
|
||||||
|
|
||||||
|
let is_valid = argon2
|
||||||
|
.verify_password(credentials.password.as_bytes(), &parsed_hash)
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if is_valid {
|
||||||
|
let expiration = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs() as usize
|
||||||
|
+ 24 * 3600;
|
||||||
|
|
||||||
|
let claims = Claims {
|
||||||
|
sub: user.username,
|
||||||
|
exp: expiration,
|
||||||
|
};
|
||||||
|
|
||||||
|
let token = encode(
|
||||||
|
&Header::default(),
|
||||||
|
&claims,
|
||||||
|
&EncodingKey::from_secret("your-secret-key".as_ref()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
87
backend/src/middleware.rs
Normal file
87
backend/src/middleware.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use crate::models::Claims;
|
||||||
|
use actix_web::body::{BoxBody, MessageBody};
|
||||||
|
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
|
||||||
|
use actix_web::{Error, HttpResponse};
|
||||||
|
use futures::future::{ok, Ready};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
pub struct AuthMiddleware;
|
||||||
|
|
||||||
|
impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<BoxBody>; // Changed to BoxBody
|
||||||
|
type Error = Error;
|
||||||
|
type Transform = AuthMiddlewareService<S>;
|
||||||
|
type InitError = ();
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ok(AuthMiddlewareService { service })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AuthMiddlewareService<S> {
|
||||||
|
service: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> Service<ServiceRequest> for AuthMiddlewareService<S>
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: MessageBody + 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<BoxBody>; // Changed to BoxBody
|
||||||
|
type Error = Error;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
|
|
||||||
|
forward_ready!(service);
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
if req.path() == "/admin/login" || req.path() == "/admin/create" {
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
return Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
Ok(res.map_into_boxed_body()) // Convert the response body to BoxBody
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let auth_header = req.headers().get("Authorization");
|
||||||
|
match auth_header {
|
||||||
|
Some(header) => {
|
||||||
|
let token = header.to_str().unwrap_or("").replace("Bearer ", "");
|
||||||
|
if verify_token(&token) {
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
Ok(res.map_into_boxed_body()) // Convert the response body to BoxBody
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let (request, _) = req.into_parts();
|
||||||
|
let response = HttpResponse::Unauthorized()
|
||||||
|
.json(json!({"error": "Invalid token"}))
|
||||||
|
.map_into_boxed_body();
|
||||||
|
Box::pin(async move { Ok(ServiceResponse::new(request, response)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let (request, _) = req.into_parts();
|
||||||
|
let response = HttpResponse::Unauthorized()
|
||||||
|
.json(json!({"error": "No authorization token"}))
|
||||||
|
.map_into_boxed_body();
|
||||||
|
Box::pin(async move { Ok(ServiceResponse::new(request, response)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_token(token: &str) -> bool {
|
||||||
|
let validation = jsonwebtoken::Validation::default();
|
||||||
|
let key = jsonwebtoken::DecodingKey::from_secret("your-secret-key".as_ref());
|
||||||
|
jsonwebtoken::decode::<Claims>(token, &key, &validation).is_ok()
|
||||||
|
}
|
@ -13,3 +13,21 @@ pub struct Submission {
|
|||||||
pub form_id: String,
|
pub form_id: String,
|
||||||
pub data: serde_json::Value, // JSON of submission data
|
pub data: serde_json::Value, // JSON of submission data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct AdminUser {
|
||||||
|
pub username: String,
|
||||||
|
pub password_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct LoginCredentials {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Claims {
|
||||||
|
pub sub: String,
|
||||||
|
pub(crate) exp: usize,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user