124 lines
3.9 KiB
Rust
124 lines
3.9 KiB
Rust
use anyhow::{Context, Result as AnyhowResult};
|
|
use bcrypt::{hash, verify, DEFAULT_COST}; // Add bcrypt dependency for password hashing
|
|
use rusqlite::{params, Connection, OptionalExtension};
|
|
use uuid::Uuid; // UUID for generating unique IDs // Import anyhow
|
|
|
|
pub fn init_db() -> AnyhowResult<Connection> {
|
|
let conn = Connection::open("form_data.db").context("Failed to open the database")?;
|
|
|
|
// Create tables
|
|
conn.execute(
|
|
"CREATE TABLE IF NOT EXISTS forms (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
fields TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)",
|
|
[],
|
|
)?;
|
|
|
|
conn.execute(
|
|
"CREATE TABLE IF NOT EXISTS submissions (
|
|
id TEXT PRIMARY KEY,
|
|
form_id TEXT NOT NULL,
|
|
data TEXT NOT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (form_id) REFERENCES forms (id) ON DELETE CASCADE
|
|
)",
|
|
[],
|
|
)?;
|
|
|
|
// 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
|
|
)",
|
|
[],
|
|
)?;
|
|
|
|
// Setup initial admin after creating the tables
|
|
setup_initial_admin(&conn)?;
|
|
|
|
Ok(conn)
|
|
}
|
|
|
|
pub fn setup_initial_admin(conn: &Connection) -> AnyhowResult<()> {
|
|
add_admin_user(conn)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_admin_user(conn: &Connection) -> AnyhowResult<()> {
|
|
// Check if admin user already exists
|
|
let mut stmt = conn
|
|
.prepare("SELECT id FROM users WHERE username = ?1")
|
|
.context("Failed to prepare query for checking admin user")?;
|
|
if stmt.exists(params!["admin"])? {
|
|
return Ok(());
|
|
}
|
|
|
|
// Generate a UUID for the admin user
|
|
let admin_id = Uuid::new_v4().to_string();
|
|
|
|
// Hash the password before storing it
|
|
let hashed_password = hash("admin", DEFAULT_COST).context("Failed to hash password")?;
|
|
|
|
// Add admin user with hashed password
|
|
conn.execute(
|
|
"INSERT INTO users (id, username, password) VALUES (?1, ?2, ?3)",
|
|
params![admin_id, "admin", hashed_password],
|
|
)
|
|
.context("Failed to insert admin user into the database")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Add a function to validate a token
|
|
pub fn validate_token(conn: &Connection, token: &str) -> AnyhowResult<Option<String>> {
|
|
let mut stmt = conn
|
|
.prepare("SELECT id FROM users WHERE token = ?1")
|
|
.context("Failed to prepare query for validating token")?;
|
|
let user_id: Option<String> = stmt
|
|
.query_row(params![token], |row| row.get(0))
|
|
.optional()
|
|
.context("Failed to retrieve user ID for the given token")?;
|
|
Ok(user_id)
|
|
}
|
|
|
|
// Add a function to authenticate users (for login)
|
|
pub fn authenticate_user(
|
|
conn: &Connection,
|
|
username: &str,
|
|
password: &str,
|
|
) -> AnyhowResult<Option<String>> {
|
|
let mut stmt = conn
|
|
.prepare("SELECT id, password FROM users WHERE username = ?1")
|
|
.context("Failed to prepare query for authenticating user")?;
|
|
let mut rows = stmt
|
|
.query(params![username])
|
|
.context("Failed to execute query for authenticating user")?;
|
|
|
|
if let Some(row) = rows.next()? {
|
|
let user_id: String = row.get(0)?;
|
|
let stored_password: String = row.get(1)?;
|
|
|
|
// Use bcrypt to verify the hashed password
|
|
if verify(password, &stored_password).context("Failed to verify 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) -> AnyhowResult<()> {
|
|
conn.execute(
|
|
"UPDATE users SET token = ?1 WHERE id = ?2",
|
|
params![token, user_id],
|
|
)
|
|
.context("Failed to update token for user")?;
|
|
Ok(())
|
|
}
|