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)
|
||||
}
|
||||
|
@ -1,10 +1,18 @@
|
||||
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 std::sync::{Arc, Mutex};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::{Form, Submission};
|
||||
|
||||
// Create a new form
|
||||
pub async fn create_form(
|
||||
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();
|
||||
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 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