| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407 |
- /*
- * auth_db.cpp - SQLite database layer for authentication
- */
- #include "auth_db.h"
- #include <sqlite3.h>
- #include <cstring>
- #include <cstdio>
- #include <mutex>
- namespace auth {
- static std::mutex db_mutex;
- AuthDB::AuthDB() {}
- AuthDB::~AuthDB() {
- close();
- }
- bool AuthDB::exec(const std::string &sql) {
- if (!db_) return false;
- char *err = nullptr;
- int rc = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &err);
- if (rc != SQLITE_OK) {
- fprintf(stderr, "[auth_db] SQL error: %s (sql: %.100s...)\n",
- err ? err : "unknown", sql.c_str());
- sqlite3_free(err);
- return false;
- }
- return true;
- }
- bool AuthDB::init(const std::string &db_path) {
- std::lock_guard<std::mutex> lock(db_mutex);
- int rc = sqlite3_open(db_path.c_str(), &db_);
- if (rc != SQLITE_OK) {
- fprintf(stderr, "[auth_db] Cannot open database: %s\n", db_path.c_str());
- return false;
- }
- // Set WAL mode for better concurrent performance
- exec("PRAGMA journal_mode=WAL");
- exec("PRAGMA busy_timeout=5000");
- // Create tables
- const char *schema = R"SQL(
- CREATE TABLE IF NOT EXISTS users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- username TEXT UNIQUE NOT NULL,
- password_hash TEXT NOT NULL,
- role INTEGER DEFAULT 0,
- enabled INTEGER DEFAULT 1,
- failed_attempts INTEGER DEFAULT 0,
- locked_until INTEGER DEFAULT 0,
- created_at INTEGER NOT NULL,
- updated_at INTEGER NOT NULL
- );
- CREATE TABLE IF NOT EXISTS sessions (
- session_id TEXT PRIMARY KEY,
- csrf_token TEXT NOT NULL,
- user_id INTEGER NOT NULL,
- username TEXT NOT NULL,
- remote_ip TEXT DEFAULT '',
- created_at INTEGER NOT NULL,
- last_active INTEGER NOT NULL,
- expires_at INTEGER NOT NULL,
- remember INTEGER DEFAULT 0,
- FOREIGN KEY (user_id) REFERENCES users(id)
- );
- CREATE TABLE IF NOT EXISTS audit_log (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- timestamp INTEGER NOT NULL,
- event_type TEXT NOT NULL,
- username TEXT DEFAULT '',
- remote_ip TEXT DEFAULT '',
- details TEXT DEFAULT '',
- success INTEGER DEFAULT 0
- );
- CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
- CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
- CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
- CREATE INDEX IF NOT EXISTS idx_audit_event ON audit_log(event_type);
- )SQL";
- if (!exec(schema)) {
- fprintf(stderr, "[auth_db] Failed to create schema\n");
- return false;
- }
- // Create default admin user if no users exist
- sqlite3_stmt *stmt;
- rc = sqlite3_prepare_v2(db_, "SELECT COUNT(*) FROM users", -1, &stmt, nullptr);
- if (rc == SQLITE_OK) {
- if (sqlite3_step(stmt) == SQLITE_ROW) {
- int count = sqlite3_column_int(stmt, 0);
- sqlite3_finalize(stmt);
- if (count == 0) {
- // Default admin will be created by the caller
- // (they need to hash the password first)
- printf("[auth_db] No users found, admin setup needed\n");
- }
- } else {
- sqlite3_finalize(stmt);
- }
- }
- printf("[auth_db] Database initialized: %s\n", db_path.c_str());
- return true;
- }
- void AuthDB::close() {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (db_) {
- sqlite3_close(db_);
- db_ = nullptr;
- }
- }
- bool AuthDB::create_user(const std::string &username, const std::string &password_hash,
- int role) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "INSERT INTO users (username, password_hash, role, enabled, "
- "failed_attempts, locked_until, created_at, updated_at) "
- "VALUES (?, ?, ?, 1, 0, 0, ?, ?)";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- time_t now = time(nullptr);
- sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_text(stmt, 2, password_hash.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_int(stmt, 3, role);
- sqlite3_bind_int64(stmt, 4, now);
- sqlite3_bind_int64(stmt, 5, now);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- bool AuthDB::get_user_by_name(const std::string &username, User &user) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "SELECT id, username, password_hash, role, enabled, "
- "failed_attempts, locked_until, created_at, updated_at "
- "FROM users WHERE username = ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
- bool found = false;
- if (sqlite3_step(stmt) == SQLITE_ROW) {
- user.id = sqlite3_column_int(stmt, 0);
- user.username = (const char*)sqlite3_column_text(stmt, 1);
- user.password_hash = (const char*)sqlite3_column_text(stmt, 2);
- user.role = sqlite3_column_int(stmt, 3);
- user.enabled = sqlite3_column_int(stmt, 4);
- user.failed_attempts = sqlite3_column_int(stmt, 5);
- user.locked_until = sqlite3_column_int64(stmt, 6);
- user.created_at = sqlite3_column_int64(stmt, 7);
- user.updated_at = sqlite3_column_int64(stmt, 8);
- found = true;
- }
- sqlite3_finalize(stmt);
- return found;
- }
- bool AuthDB::update_user_password(int user_id, const std::string &new_hash) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "UPDATE users SET password_hash = ?, updated_at = ?, "
- "failed_attempts = 0, locked_until = 0 WHERE id = ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_text(stmt, 1, new_hash.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_int64(stmt, 2, time(nullptr));
- sqlite3_bind_int(stmt, 3, user_id);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- bool AuthDB::update_failed_attempts(int user_id, int count, time_t locked_until) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "UPDATE users SET failed_attempts = ?, locked_until = ?, "
- "updated_at = ? WHERE id = ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_int(stmt, 1, count);
- sqlite3_bind_int64(stmt, 2, locked_until);
- sqlite3_bind_int64(stmt, 3, time(nullptr));
- sqlite3_bind_int(stmt, 4, user_id);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- bool AuthDB::reset_failed_attempts(int user_id) {
- return update_failed_attempts(user_id, 0, 0);
- }
- bool AuthDB::create_session(const Session &sess) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "INSERT OR REPLACE INTO sessions "
- "(session_id, csrf_token, user_id, username, remote_ip, "
- "created_at, last_active, expires_at, remember) "
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_text(stmt, 1, sess.session_id.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_text(stmt, 2, sess.csrf_token.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_int(stmt, 3, sess.user_id);
- sqlite3_bind_text(stmt, 4, sess.username.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_text(stmt, 5, sess.remote_ip.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_int64(stmt, 6, sess.created_at);
- sqlite3_bind_int64(stmt, 7, sess.last_active);
- sqlite3_bind_int64(stmt, 8, sess.expires_at);
- sqlite3_bind_int(stmt, 9, sess.remember);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- bool AuthDB::get_session(const std::string &session_id, Session &sess) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "SELECT session_id, csrf_token, user_id, username, remote_ip, "
- "created_at, last_active, expires_at, remember "
- "FROM sessions WHERE session_id = ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_text(stmt, 1, session_id.c_str(), -1, SQLITE_TRANSIENT);
- bool found = false;
- if (sqlite3_step(stmt) == SQLITE_ROW) {
- sess.session_id = (const char*)sqlite3_column_text(stmt, 0);
- sess.csrf_token = (const char*)sqlite3_column_text(stmt, 1);
- sess.user_id = sqlite3_column_int(stmt, 2);
- sess.username = (const char*)sqlite3_column_text(stmt, 3);
- sess.remote_ip = (const char*)sqlite3_column_text(stmt, 4);
- sess.created_at = sqlite3_column_int64(stmt, 5);
- sess.last_active = sqlite3_column_int64(stmt, 6);
- sess.expires_at = sqlite3_column_int64(stmt, 7);
- sess.remember = sqlite3_column_int(stmt, 8);
- found = true;
- }
- sqlite3_finalize(stmt);
- return found;
- }
- bool AuthDB::update_session_activity(const std::string &session_id, time_t now) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "UPDATE sessions SET last_active = ? WHERE session_id = ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_int64(stmt, 1, now);
- sqlite3_bind_text(stmt, 2, session_id.c_str(), -1, SQLITE_TRANSIENT);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- bool AuthDB::delete_session(const std::string &session_id) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "DELETE FROM sessions WHERE session_id = ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_text(stmt, 1, session_id.c_str(), -1, SQLITE_TRANSIENT);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- bool AuthDB::delete_user_sessions(int user_id) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "DELETE FROM sessions WHERE user_id = ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_int(stmt, 1, user_id);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- bool AuthDB::cleanup_expired_sessions(time_t now) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- char sql[128];
- snprintf(sql, sizeof(sql), "DELETE FROM sessions WHERE expires_at < %ld", (long)now);
- return exec(sql);
- }
- bool AuthDB::add_audit_log(const AuditEntry &entry) {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return false;
- sqlite3_stmt *stmt;
- const char *sql = "INSERT INTO audit_log (timestamp, event_type, username, "
- "remote_ip, details, success) VALUES (?, ?, ?, ?, ?, ?)";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return false;
- sqlite3_bind_int64(stmt, 1, entry.timestamp);
- sqlite3_bind_text(stmt, 2, entry.event_type.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_text(stmt, 3, entry.username.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_text(stmt, 4, entry.remote_ip.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_text(stmt, 5, entry.details.c_str(), -1, SQLITE_TRANSIENT);
- sqlite3_bind_int(stmt, 6, entry.success);
- rc = sqlite3_step(stmt);
- sqlite3_finalize(stmt);
- return rc == SQLITE_DONE;
- }
- std::vector<AuditEntry> AuthDB::get_audit_logs(int limit, int offset) {
- std::lock_guard<std::mutex> lock(db_mutex);
- std::vector<AuditEntry> result;
- if (!db_) return result;
- sqlite3_stmt *stmt;
- const char *sql = "SELECT id, timestamp, event_type, username, remote_ip, "
- "details, success FROM audit_log ORDER BY timestamp DESC "
- "LIMIT ? OFFSET ?";
- int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return result;
- sqlite3_bind_int(stmt, 1, limit);
- sqlite3_bind_int(stmt, 2, offset);
- while (sqlite3_step(stmt) == SQLITE_ROW) {
- AuditEntry e;
- e.id = sqlite3_column_int(stmt, 0);
- e.timestamp = sqlite3_column_int64(stmt, 1);
- e.event_type = (const char*)sqlite3_column_text(stmt, 2);
- e.username = (const char*)sqlite3_column_text(stmt, 3);
- e.remote_ip = (const char*)sqlite3_column_text(stmt, 4);
- e.details = (const char*)sqlite3_column_text(stmt, 5);
- e.success = sqlite3_column_int(stmt, 6);
- result.push_back(e);
- }
- sqlite3_finalize(stmt);
- return result;
- }
- int AuthDB::get_audit_log_count() {
- std::lock_guard<std::mutex> lock(db_mutex);
- if (!db_) return 0;
- sqlite3_stmt *stmt;
- int rc = sqlite3_prepare_v2(db_, "SELECT COUNT(*) FROM audit_log", -1, &stmt, nullptr);
- if (rc != SQLITE_OK) return 0;
- int count = 0;
- if (sqlite3_step(stmt) == SQLITE_ROW) {
- count = sqlite3_column_int(stmt, 0);
- }
- sqlite3_finalize(stmt);
- return count;
- }
- } // namespace auth
|