auth_db.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. /*
  2. * auth_db.cpp - SQLite database layer for authentication
  3. */
  4. #include "auth_db.h"
  5. #include <sqlite3.h>
  6. #include <cstring>
  7. #include <cstdio>
  8. #include <mutex>
  9. namespace auth {
  10. static std::mutex db_mutex;
  11. AuthDB::AuthDB() {}
  12. AuthDB::~AuthDB() {
  13. close();
  14. }
  15. bool AuthDB::exec(const std::string &sql) {
  16. if (!db_) return false;
  17. char *err = nullptr;
  18. int rc = sqlite3_exec(db_, sql.c_str(), nullptr, nullptr, &err);
  19. if (rc != SQLITE_OK) {
  20. fprintf(stderr, "[auth_db] SQL error: %s (sql: %.100s...)\n",
  21. err ? err : "unknown", sql.c_str());
  22. sqlite3_free(err);
  23. return false;
  24. }
  25. return true;
  26. }
  27. bool AuthDB::init(const std::string &db_path) {
  28. std::lock_guard<std::mutex> lock(db_mutex);
  29. int rc = sqlite3_open(db_path.c_str(), &db_);
  30. if (rc != SQLITE_OK) {
  31. fprintf(stderr, "[auth_db] Cannot open database: %s\n", db_path.c_str());
  32. return false;
  33. }
  34. // Set WAL mode for better concurrent performance
  35. exec("PRAGMA journal_mode=WAL");
  36. exec("PRAGMA busy_timeout=5000");
  37. // Create tables
  38. const char *schema = R"SQL(
  39. CREATE TABLE IF NOT EXISTS users (
  40. id INTEGER PRIMARY KEY AUTOINCREMENT,
  41. username TEXT UNIQUE NOT NULL,
  42. password_hash TEXT NOT NULL,
  43. role INTEGER DEFAULT 0,
  44. enabled INTEGER DEFAULT 1,
  45. failed_attempts INTEGER DEFAULT 0,
  46. locked_until INTEGER DEFAULT 0,
  47. created_at INTEGER NOT NULL,
  48. updated_at INTEGER NOT NULL
  49. );
  50. CREATE TABLE IF NOT EXISTS sessions (
  51. session_id TEXT PRIMARY KEY,
  52. csrf_token TEXT NOT NULL,
  53. user_id INTEGER NOT NULL,
  54. username TEXT NOT NULL,
  55. remote_ip TEXT DEFAULT '',
  56. created_at INTEGER NOT NULL,
  57. last_active INTEGER NOT NULL,
  58. expires_at INTEGER NOT NULL,
  59. remember INTEGER DEFAULT 0,
  60. FOREIGN KEY (user_id) REFERENCES users(id)
  61. );
  62. CREATE TABLE IF NOT EXISTS audit_log (
  63. id INTEGER PRIMARY KEY AUTOINCREMENT,
  64. timestamp INTEGER NOT NULL,
  65. event_type TEXT NOT NULL,
  66. username TEXT DEFAULT '',
  67. remote_ip TEXT DEFAULT '',
  68. details TEXT DEFAULT '',
  69. success INTEGER DEFAULT 0
  70. );
  71. CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
  72. CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
  73. CREATE INDEX IF NOT EXISTS idx_audit_timestamp ON audit_log(timestamp);
  74. CREATE INDEX IF NOT EXISTS idx_audit_event ON audit_log(event_type);
  75. )SQL";
  76. if (!exec(schema)) {
  77. fprintf(stderr, "[auth_db] Failed to create schema\n");
  78. return false;
  79. }
  80. // Create default admin user if no users exist
  81. sqlite3_stmt *stmt;
  82. rc = sqlite3_prepare_v2(db_, "SELECT COUNT(*) FROM users", -1, &stmt, nullptr);
  83. if (rc == SQLITE_OK) {
  84. if (sqlite3_step(stmt) == SQLITE_ROW) {
  85. int count = sqlite3_column_int(stmt, 0);
  86. sqlite3_finalize(stmt);
  87. if (count == 0) {
  88. // Default admin will be created by the caller
  89. // (they need to hash the password first)
  90. printf("[auth_db] No users found, admin setup needed\n");
  91. }
  92. } else {
  93. sqlite3_finalize(stmt);
  94. }
  95. }
  96. printf("[auth_db] Database initialized: %s\n", db_path.c_str());
  97. return true;
  98. }
  99. void AuthDB::close() {
  100. std::lock_guard<std::mutex> lock(db_mutex);
  101. if (db_) {
  102. sqlite3_close(db_);
  103. db_ = nullptr;
  104. }
  105. }
  106. bool AuthDB::create_user(const std::string &username, const std::string &password_hash,
  107. int role) {
  108. std::lock_guard<std::mutex> lock(db_mutex);
  109. if (!db_) return false;
  110. sqlite3_stmt *stmt;
  111. const char *sql = "INSERT INTO users (username, password_hash, role, enabled, "
  112. "failed_attempts, locked_until, created_at, updated_at) "
  113. "VALUES (?, ?, ?, 1, 0, 0, ?, ?)";
  114. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  115. if (rc != SQLITE_OK) return false;
  116. time_t now = time(nullptr);
  117. sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
  118. sqlite3_bind_text(stmt, 2, password_hash.c_str(), -1, SQLITE_TRANSIENT);
  119. sqlite3_bind_int(stmt, 3, role);
  120. sqlite3_bind_int64(stmt, 4, now);
  121. sqlite3_bind_int64(stmt, 5, now);
  122. rc = sqlite3_step(stmt);
  123. sqlite3_finalize(stmt);
  124. return rc == SQLITE_DONE;
  125. }
  126. bool AuthDB::get_user_by_name(const std::string &username, User &user) {
  127. std::lock_guard<std::mutex> lock(db_mutex);
  128. if (!db_) return false;
  129. sqlite3_stmt *stmt;
  130. const char *sql = "SELECT id, username, password_hash, role, enabled, "
  131. "failed_attempts, locked_until, created_at, updated_at "
  132. "FROM users WHERE username = ?";
  133. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  134. if (rc != SQLITE_OK) return false;
  135. sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT);
  136. bool found = false;
  137. if (sqlite3_step(stmt) == SQLITE_ROW) {
  138. user.id = sqlite3_column_int(stmt, 0);
  139. user.username = (const char*)sqlite3_column_text(stmt, 1);
  140. user.password_hash = (const char*)sqlite3_column_text(stmt, 2);
  141. user.role = sqlite3_column_int(stmt, 3);
  142. user.enabled = sqlite3_column_int(stmt, 4);
  143. user.failed_attempts = sqlite3_column_int(stmt, 5);
  144. user.locked_until = sqlite3_column_int64(stmt, 6);
  145. user.created_at = sqlite3_column_int64(stmt, 7);
  146. user.updated_at = sqlite3_column_int64(stmt, 8);
  147. found = true;
  148. }
  149. sqlite3_finalize(stmt);
  150. return found;
  151. }
  152. bool AuthDB::update_user_password(int user_id, const std::string &new_hash) {
  153. std::lock_guard<std::mutex> lock(db_mutex);
  154. if (!db_) return false;
  155. sqlite3_stmt *stmt;
  156. const char *sql = "UPDATE users SET password_hash = ?, updated_at = ?, "
  157. "failed_attempts = 0, locked_until = 0 WHERE id = ?";
  158. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  159. if (rc != SQLITE_OK) return false;
  160. sqlite3_bind_text(stmt, 1, new_hash.c_str(), -1, SQLITE_TRANSIENT);
  161. sqlite3_bind_int64(stmt, 2, time(nullptr));
  162. sqlite3_bind_int(stmt, 3, user_id);
  163. rc = sqlite3_step(stmt);
  164. sqlite3_finalize(stmt);
  165. return rc == SQLITE_DONE;
  166. }
  167. bool AuthDB::update_failed_attempts(int user_id, int count, time_t locked_until) {
  168. std::lock_guard<std::mutex> lock(db_mutex);
  169. if (!db_) return false;
  170. sqlite3_stmt *stmt;
  171. const char *sql = "UPDATE users SET failed_attempts = ?, locked_until = ?, "
  172. "updated_at = ? WHERE id = ?";
  173. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  174. if (rc != SQLITE_OK) return false;
  175. sqlite3_bind_int(stmt, 1, count);
  176. sqlite3_bind_int64(stmt, 2, locked_until);
  177. sqlite3_bind_int64(stmt, 3, time(nullptr));
  178. sqlite3_bind_int(stmt, 4, user_id);
  179. rc = sqlite3_step(stmt);
  180. sqlite3_finalize(stmt);
  181. return rc == SQLITE_DONE;
  182. }
  183. bool AuthDB::reset_failed_attempts(int user_id) {
  184. return update_failed_attempts(user_id, 0, 0);
  185. }
  186. bool AuthDB::create_session(const Session &sess) {
  187. std::lock_guard<std::mutex> lock(db_mutex);
  188. if (!db_) return false;
  189. sqlite3_stmt *stmt;
  190. const char *sql = "INSERT OR REPLACE INTO sessions "
  191. "(session_id, csrf_token, user_id, username, remote_ip, "
  192. "created_at, last_active, expires_at, remember) "
  193. "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
  194. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  195. if (rc != SQLITE_OK) return false;
  196. sqlite3_bind_text(stmt, 1, sess.session_id.c_str(), -1, SQLITE_TRANSIENT);
  197. sqlite3_bind_text(stmt, 2, sess.csrf_token.c_str(), -1, SQLITE_TRANSIENT);
  198. sqlite3_bind_int(stmt, 3, sess.user_id);
  199. sqlite3_bind_text(stmt, 4, sess.username.c_str(), -1, SQLITE_TRANSIENT);
  200. sqlite3_bind_text(stmt, 5, sess.remote_ip.c_str(), -1, SQLITE_TRANSIENT);
  201. sqlite3_bind_int64(stmt, 6, sess.created_at);
  202. sqlite3_bind_int64(stmt, 7, sess.last_active);
  203. sqlite3_bind_int64(stmt, 8, sess.expires_at);
  204. sqlite3_bind_int(stmt, 9, sess.remember);
  205. rc = sqlite3_step(stmt);
  206. sqlite3_finalize(stmt);
  207. return rc == SQLITE_DONE;
  208. }
  209. bool AuthDB::get_session(const std::string &session_id, Session &sess) {
  210. std::lock_guard<std::mutex> lock(db_mutex);
  211. if (!db_) return false;
  212. sqlite3_stmt *stmt;
  213. const char *sql = "SELECT session_id, csrf_token, user_id, username, remote_ip, "
  214. "created_at, last_active, expires_at, remember "
  215. "FROM sessions WHERE session_id = ?";
  216. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  217. if (rc != SQLITE_OK) return false;
  218. sqlite3_bind_text(stmt, 1, session_id.c_str(), -1, SQLITE_TRANSIENT);
  219. bool found = false;
  220. if (sqlite3_step(stmt) == SQLITE_ROW) {
  221. sess.session_id = (const char*)sqlite3_column_text(stmt, 0);
  222. sess.csrf_token = (const char*)sqlite3_column_text(stmt, 1);
  223. sess.user_id = sqlite3_column_int(stmt, 2);
  224. sess.username = (const char*)sqlite3_column_text(stmt, 3);
  225. sess.remote_ip = (const char*)sqlite3_column_text(stmt, 4);
  226. sess.created_at = sqlite3_column_int64(stmt, 5);
  227. sess.last_active = sqlite3_column_int64(stmt, 6);
  228. sess.expires_at = sqlite3_column_int64(stmt, 7);
  229. sess.remember = sqlite3_column_int(stmt, 8);
  230. found = true;
  231. }
  232. sqlite3_finalize(stmt);
  233. return found;
  234. }
  235. bool AuthDB::update_session_activity(const std::string &session_id, time_t now) {
  236. std::lock_guard<std::mutex> lock(db_mutex);
  237. if (!db_) return false;
  238. sqlite3_stmt *stmt;
  239. const char *sql = "UPDATE sessions SET last_active = ? WHERE session_id = ?";
  240. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  241. if (rc != SQLITE_OK) return false;
  242. sqlite3_bind_int64(stmt, 1, now);
  243. sqlite3_bind_text(stmt, 2, session_id.c_str(), -1, SQLITE_TRANSIENT);
  244. rc = sqlite3_step(stmt);
  245. sqlite3_finalize(stmt);
  246. return rc == SQLITE_DONE;
  247. }
  248. bool AuthDB::delete_session(const std::string &session_id) {
  249. std::lock_guard<std::mutex> lock(db_mutex);
  250. if (!db_) return false;
  251. sqlite3_stmt *stmt;
  252. const char *sql = "DELETE FROM sessions WHERE session_id = ?";
  253. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  254. if (rc != SQLITE_OK) return false;
  255. sqlite3_bind_text(stmt, 1, session_id.c_str(), -1, SQLITE_TRANSIENT);
  256. rc = sqlite3_step(stmt);
  257. sqlite3_finalize(stmt);
  258. return rc == SQLITE_DONE;
  259. }
  260. bool AuthDB::delete_user_sessions(int user_id) {
  261. std::lock_guard<std::mutex> lock(db_mutex);
  262. if (!db_) return false;
  263. sqlite3_stmt *stmt;
  264. const char *sql = "DELETE FROM sessions WHERE user_id = ?";
  265. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  266. if (rc != SQLITE_OK) return false;
  267. sqlite3_bind_int(stmt, 1, user_id);
  268. rc = sqlite3_step(stmt);
  269. sqlite3_finalize(stmt);
  270. return rc == SQLITE_DONE;
  271. }
  272. bool AuthDB::cleanup_expired_sessions(time_t now) {
  273. std::lock_guard<std::mutex> lock(db_mutex);
  274. if (!db_) return false;
  275. char sql[128];
  276. snprintf(sql, sizeof(sql), "DELETE FROM sessions WHERE expires_at < %ld", (long)now);
  277. return exec(sql);
  278. }
  279. bool AuthDB::add_audit_log(const AuditEntry &entry) {
  280. std::lock_guard<std::mutex> lock(db_mutex);
  281. if (!db_) return false;
  282. sqlite3_stmt *stmt;
  283. const char *sql = "INSERT INTO audit_log (timestamp, event_type, username, "
  284. "remote_ip, details, success) VALUES (?, ?, ?, ?, ?, ?)";
  285. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  286. if (rc != SQLITE_OK) return false;
  287. sqlite3_bind_int64(stmt, 1, entry.timestamp);
  288. sqlite3_bind_text(stmt, 2, entry.event_type.c_str(), -1, SQLITE_TRANSIENT);
  289. sqlite3_bind_text(stmt, 3, entry.username.c_str(), -1, SQLITE_TRANSIENT);
  290. sqlite3_bind_text(stmt, 4, entry.remote_ip.c_str(), -1, SQLITE_TRANSIENT);
  291. sqlite3_bind_text(stmt, 5, entry.details.c_str(), -1, SQLITE_TRANSIENT);
  292. sqlite3_bind_int(stmt, 6, entry.success);
  293. rc = sqlite3_step(stmt);
  294. sqlite3_finalize(stmt);
  295. return rc == SQLITE_DONE;
  296. }
  297. std::vector<AuditEntry> AuthDB::get_audit_logs(int limit, int offset) {
  298. std::lock_guard<std::mutex> lock(db_mutex);
  299. std::vector<AuditEntry> result;
  300. if (!db_) return result;
  301. sqlite3_stmt *stmt;
  302. const char *sql = "SELECT id, timestamp, event_type, username, remote_ip, "
  303. "details, success FROM audit_log ORDER BY timestamp DESC "
  304. "LIMIT ? OFFSET ?";
  305. int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
  306. if (rc != SQLITE_OK) return result;
  307. sqlite3_bind_int(stmt, 1, limit);
  308. sqlite3_bind_int(stmt, 2, offset);
  309. while (sqlite3_step(stmt) == SQLITE_ROW) {
  310. AuditEntry e;
  311. e.id = sqlite3_column_int(stmt, 0);
  312. e.timestamp = sqlite3_column_int64(stmt, 1);
  313. e.event_type = (const char*)sqlite3_column_text(stmt, 2);
  314. e.username = (const char*)sqlite3_column_text(stmt, 3);
  315. e.remote_ip = (const char*)sqlite3_column_text(stmt, 4);
  316. e.details = (const char*)sqlite3_column_text(stmt, 5);
  317. e.success = sqlite3_column_int(stmt, 6);
  318. result.push_back(e);
  319. }
  320. sqlite3_finalize(stmt);
  321. return result;
  322. }
  323. int AuthDB::get_audit_log_count() {
  324. std::lock_guard<std::mutex> lock(db_mutex);
  325. if (!db_) return 0;
  326. sqlite3_stmt *stmt;
  327. int rc = sqlite3_prepare_v2(db_, "SELECT COUNT(*) FROM audit_log", -1, &stmt, nullptr);
  328. if (rc != SQLITE_OK) return 0;
  329. int count = 0;
  330. if (sqlite3_step(stmt) == SQLITE_ROW) {
  331. count = sqlite3_column_int(stmt, 0);
  332. }
  333. sqlite3_finalize(stmt);
  334. return count;
  335. }
  336. } // namespace auth