From 1b79fe8079835a7aa24427300c7bf36aabf4ef71 Mon Sep 17 00:00:00 2001 From: ookami125 Date: Tue, 23 Jun 2026 21:59:30 -0400 Subject: [PATCH] =?UTF-8?q?webui:=20rename=20auth=5Fstore=20=E2=86=92=20we?= =?UTF-8?q?bui=5Fstore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin's SQLite store will own more than accounts (taxonomy, RSS), so give it a general name. Mechanical rename of files + symbols; default DB filename is now webui.db. No behavior change. Co-Authored-By: Claude Opus 4.8 --- CMakeLists.txt | 2 +- plugins/webui/auth_store.h | 46 ------------------ plugins/webui/webui.c | 40 ++++++++-------- plugins/webui/{auth_store.c => webui_store.c} | 34 +++++++------- plugins/webui/webui_store.h | 47 +++++++++++++++++++ 5 files changed, 85 insertions(+), 84 deletions(-) delete mode 100644 plugins/webui/auth_store.h rename plugins/webui/{auth_store.c => webui_store.c} (90%) create mode 100644 plugins/webui/webui_store.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c31cf1e..a1185e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -224,7 +224,7 @@ set_target_properties(naut_example PROPERTIES PREFIX "") find_package(PkgConfig REQUIRED) pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3) -add_library(naut_webui MODULE plugins/webui/webui.c plugins/webui/auth_store.c) +add_library(naut_webui MODULE plugins/webui/webui.c plugins/webui/webui_store.c) target_include_directories(naut_webui PRIVATE ${CMAKE_SOURCE_DIR}/include) target_link_libraries(naut_webui PRIVATE ${NAUT_JANSSON_TARGET} naut_net PkgConfig::SQLITE3 OpenSSL::Crypto pthread) diff --git a/plugins/webui/auth_store.h b/plugins/webui/auth_store.h deleted file mode 100644 index c3097fe..0000000 --- a/plugins/webui/auth_store.h +++ /dev/null @@ -1,46 +0,0 @@ -/* auth_store.h — SQLite-backed user account store for the web UI. - * - * Owned entirely by the webui plugin. Passwords are stored as PBKDF2-HMAC- - * SHA256 hashes with a per-user random salt. All calls are thread-safe (the - * store serializes access to its single SQLite connection internally). */ -#ifndef NAUT_WEBUI_AUTH_STORE_H -#define NAUT_WEBUI_AUTH_STORE_H - -#include -#include -#include - -typedef struct auth_store auth_store; - -/* Open (creating if needed) the account database at `path`. Returns NULL on - * failure. The schema is created/migrated on open. */ -auth_store *auth_store_open(const char *path); -void auth_store_close(auth_store *s); - -/* Number of accounts, or -1 on error. */ -int auth_store_user_count(auth_store *s); -/* Number of admin accounts, or -1 on error. */ -int auth_store_admin_count(auth_store *s); -bool auth_store_user_exists(auth_store *s, const char *username); - -/* Verify a username/password pair (constant-time). On success, copies the - * account's role ("admin"/"user") into role_out. */ -bool auth_store_verify(auth_store *s, const char *username, - const char *password, char *role_out, size_t role_sz); - -/* Create an account. `role` must be "admin" or "user" (defaults to "user" if - * NULL/invalid). Returns false if the username already exists or on error. */ -bool auth_store_create_user(auth_store *s, const char *username, - const char *password, const char *role); - -bool auth_store_set_password(auth_store *s, const char *username, - const char *password); -/* Change an account's role ("admin"/"user"). */ -bool auth_store_set_role(auth_store *s, const char *username, const char *role); -bool auth_store_delete_user(auth_store *s, const char *username); - -/* Append {username, role, createdAt} objects (sorted by username) to the - * json array `out`. Returns false on error. */ -bool auth_store_list_users(auth_store *s, json_t *out); - -#endif /* NAUT_WEBUI_AUTH_STORE_H */ diff --git a/plugins/webui/webui.c b/plugins/webui/webui.c index bebc989..040b202 100644 --- a/plugins/webui/webui.c +++ b/plugins/webui/webui.c @@ -1,6 +1,6 @@ #include "naut/naut_plugin.h" #include "naut/http_client.h" -#include "auth_store.h" +#include "webui_store.h" #include @@ -59,7 +59,7 @@ typedef struct { char host_name[64]; char auth_user[64]; /* bootstrap admin name (for startup banner) */ char auth_password[64]; /* generated bootstrap password (banner only) */ - auth_store *auth; /* SQLite-backed account store */ + webui_store *auth; /* SQLite-backed account store */ int port; int listener; bool generated_password; @@ -297,7 +297,7 @@ static bool resolve_auth_db_path(char *out, size_t n) { else if (home && *home) snprintf(dir, sizeof dir, "%s/.local/share/naut", home); else return false; if (mkdir_p(dir, 0700) != 0) return false; - return (size_t)snprintf(out, n, "%s/webui_accounts.db", dir) < n; + return (size_t)snprintf(out, n, "%s/webui.db", dir) < n; } /* Open the account store and, on first run (no accounts), bootstrap an admin @@ -308,7 +308,7 @@ static void init_auth(void) { log_msg(0, "webui: cannot resolve account DB path; set NAUT_WEBUI_DB"); return; } - g_webui.auth = auth_store_open(db_path); + g_webui.auth = webui_store_open(db_path); if (!g_webui.auth) { log_msg(0, "webui: failed to open account database"); return; @@ -318,7 +318,7 @@ static void init_auth(void) { if (!user || !*user) user = "admin"; snprintf(g_webui.auth_user, sizeof g_webui.auth_user, "%s", user); - if (auth_store_user_count(g_webui.auth) > 0) return; /* already set up */ + if (webui_store_user_count(g_webui.auth) > 0) return; /* already set up */ /* No accounts yet — create the initial admin. */ const char *password = getenv("NAUT_AUTH_PASSWORD"); @@ -332,7 +332,7 @@ static void init_auth(void) { log_msg(0, "webui: no CSPRNG; set NAUT_AUTH_PASSWORD to create the admin"); return; } - if (!auth_store_create_user(g_webui.auth, user, password, "admin")) + if (!webui_store_create_user(g_webui.auth, user, password, "admin")) log_msg(0, "webui: failed to create the initial admin account"); } @@ -2722,7 +2722,7 @@ static void serve_cached_torrents(int fd) { /* ============================ account management ========================== * * Admin-only user CRUD plus a self-service password change. The web layer owns - * everything via auth_store; the daemon is not involved. */ + * everything via webui_store; the daemon is not involved. */ static bool valid_username(const char *u) { if (!u || !*u || strlen(u) >= 64) return false; @@ -2736,7 +2736,7 @@ static bool valid_username(const char *u) { /* GET /api/users → [{username, role, createdAt}] (admin only). */ static void api_users_list(int fd) { json_t *users = json_array(); - if (g_webui.auth) auth_store_list_users(g_webui.auth, users); + if (g_webui.auth) webui_store_list_users(g_webui.auth, users); http_json(fd, 200, users); json_decref(users); } @@ -2754,7 +2754,7 @@ static void api_user_create(int fd, const char *body, size_t len) { return; } bool ok = g_webui.auth && - auth_store_create_user(g_webui.auth, user, pass, + webui_store_create_user(g_webui.auth, user, pass, role && *role ? role : "user"); json_decref(req); if (!ok) { http_text(fd, 409, "Conflict", "user already exists"); return; } @@ -2773,18 +2773,18 @@ static void api_user_delete(int fd, const char *body, size_t len, char role[16] = {0}; /* Look up the target's role to guard the last-admin rule. */ json_t *list = json_array(); - if (g_webui.auth) auth_store_list_users(g_webui.auth, list); + if (g_webui.auth) webui_store_list_users(g_webui.auth, list); size_t i; json_t *u; json_array_foreach(list, i, u) if (strcasecmp(json_string_or(u, "username", ""), user) == 0) snprintf(role, sizeof role, "%s", json_string_or(u, "role", "")); json_decref(list); - if (strcmp(role, "admin") == 0 && auth_store_admin_count(g_webui.auth) <= 1) { + if (strcmp(role, "admin") == 0 && webui_store_admin_count(g_webui.auth) <= 1) { json_decref(req); http_text(fd, 409, "Conflict", "cannot delete the last admin"); return; } - bool ok = g_webui.auth && auth_store_delete_user(g_webui.auth, user); + bool ok = g_webui.auth && webui_store_delete_user(g_webui.auth, user); json_decref(req); if (!ok) { http_text(fd, 404, "Not Found", "no such user"); return; } drop_user_sessions(user); @@ -2804,7 +2804,7 @@ static void api_user_set_password(int fd, const char *body, size_t len) { } char user[64]; snprintf(user, sizeof user, "%s", uname); - bool ok = g_webui.auth && auth_store_set_password(g_webui.auth, user, pass); + bool ok = g_webui.auth && webui_store_set_password(g_webui.auth, user, pass); json_decref(req); if (!ok) { http_text(fd, 404, "Not Found", "no such user"); return; } drop_user_sessions(user); /* force re-login with the new password */ @@ -2823,11 +2823,11 @@ static void api_user_set_role(int fd, const char *body, size_t len) { http_text(fd, 400, "Bad Request", "username and role (admin|user) required"); return; } - if (strcmp(role, "user") == 0 && auth_store_admin_count(g_webui.auth) <= 1) { + if (strcmp(role, "user") == 0 && webui_store_admin_count(g_webui.auth) <= 1) { /* Only block if the target is currently the sole admin. */ char cur[16] = {0}; json_t *list = json_array(); - if (g_webui.auth) auth_store_list_users(g_webui.auth, list); + if (g_webui.auth) webui_store_list_users(g_webui.auth, list); size_t i; json_t *u; json_array_foreach(list, i, u) if (strcasecmp(json_string_or(u, "username", ""), user) == 0) @@ -2839,7 +2839,7 @@ static void api_user_set_role(int fd, const char *body, size_t len) { return; } } - bool ok = g_webui.auth && auth_store_set_role(g_webui.auth, user, role); + bool ok = g_webui.auth && webui_store_set_role(g_webui.auth, user, role); json_decref(req); if (!ok) { http_text(fd, 404, "Not Found", "no such user"); return; } api_users_list(fd); @@ -2857,12 +2857,12 @@ static void api_account_password(int fd, const char *body, size_t len, return; } char role[16] = {0}; - if (!g_webui.auth || !auth_store_verify(g_webui.auth, actor, oldp, role, sizeof role)) { + if (!g_webui.auth || !webui_store_verify(g_webui.auth, actor, oldp, role, sizeof role)) { json_decref(req); http_text(fd, 403, "Forbidden", "current password is incorrect"); return; } - bool ok = auth_store_set_password(g_webui.auth, actor, newp); + bool ok = webui_store_set_password(g_webui.auth, actor, newp); json_decref(req); if (!ok) { http_text(fd, 500, "Internal Server Error", "could not update password"); return; } json_t *reply = json_pack("{s:b}", "ok", 1); @@ -2896,7 +2896,7 @@ static void handle_api(int fd, const char *method, char *path, json_string_value(json_object_get(req, "password")); char role[16] = {0}; bool ok = g_webui.auth && user && password && - auth_store_verify(g_webui.auth, user, password, role, sizeof role); + webui_store_verify(g_webui.auth, user, password, role, sizeof role); if (!ok) { json_t *json = json_pack("{s:b,s:s}", "ok", 0, "error", "invalid credentials"); @@ -3373,7 +3373,7 @@ naut_err naut_plugin_shutdown(void) { pthread_cond_destroy(&g_webui.conn_cond); pthread_mutex_destroy(&g_webui.conn_lock); pthread_mutex_destroy(&g_webui.auth_lock); - auth_store_close(g_webui.auth); + webui_store_close(g_webui.auth); g_webui.auth = NULL; return NAUT_OK; } diff --git a/plugins/webui/auth_store.c b/plugins/webui/webui_store.c similarity index 90% rename from plugins/webui/auth_store.c rename to plugins/webui/webui_store.c index bd9ac9a..a3c4796 100644 --- a/plugins/webui/auth_store.c +++ b/plugins/webui/webui_store.c @@ -1,5 +1,5 @@ -/* auth_store.c — SQLite + PBKDF2 implementation of the web-UI account store. */ -#include "auth_store.h" +/* webui_store.c — SQLite + PBKDF2 implementation of the web-UI account store. */ +#include "webui_store.h" #include #include @@ -15,7 +15,7 @@ #define SALT_BYTES 16 #define HASH_BYTES 32 -struct auth_store { +struct webui_store { sqlite3 *db; pthread_mutex_t lock; }; @@ -53,8 +53,8 @@ static bool valid_role(const char *role) { return role && (strcmp(role, "admin") == 0 || strcmp(role, "user") == 0); } -auth_store *auth_store_open(const char *path) { - auth_store *s = calloc(1, sizeof *s); +webui_store *webui_store_open(const char *path) { + webui_store *s = calloc(1, sizeof *s); if (!s) return NULL; if (pthread_mutex_init(&s->lock, NULL) != 0) { free(s); return NULL; } if (sqlite3_open(path, &s->db) != SQLITE_OK) { @@ -77,13 +77,13 @@ auth_store *auth_store_open(const char *path) { char *err = NULL; if (sqlite3_exec(s->db, schema, NULL, NULL, &err) != SQLITE_OK) { sqlite3_free(err); - auth_store_close(s); + webui_store_close(s); return NULL; } return s; } -void auth_store_close(auth_store *s) { +void webui_store_close(webui_store *s) { if (!s) return; if (s->db) sqlite3_close(s->db); pthread_mutex_destroy(&s->lock); @@ -91,7 +91,7 @@ void auth_store_close(auth_store *s) { } /* Run a "SELECT count(*) ... " style query returning a single integer. */ -static int count_query(auth_store *s, const char *sql) { +static int count_query(webui_store *s, const char *sql) { sqlite3_stmt *st = NULL; if (sqlite3_prepare_v2(s->db, sql, -1, &st, NULL) != SQLITE_OK) return -1; int n = -1; @@ -100,7 +100,7 @@ static int count_query(auth_store *s, const char *sql) { return n; } -int auth_store_user_count(auth_store *s) { +int webui_store_user_count(webui_store *s) { if (!s) return -1; pthread_mutex_lock(&s->lock); int n = count_query(s, "SELECT count(*) FROM users;"); @@ -108,7 +108,7 @@ int auth_store_user_count(auth_store *s) { return n; } -int auth_store_admin_count(auth_store *s) { +int webui_store_admin_count(webui_store *s) { if (!s) return -1; pthread_mutex_lock(&s->lock); int n = count_query(s, "SELECT count(*) FROM users WHERE role='admin';"); @@ -116,7 +116,7 @@ int auth_store_admin_count(auth_store *s) { return n; } -bool auth_store_user_exists(auth_store *s, const char *username) { +bool webui_store_user_exists(webui_store *s, const char *username) { if (!s || !username) return false; pthread_mutex_lock(&s->lock); sqlite3_stmt *st = NULL; @@ -131,7 +131,7 @@ bool auth_store_user_exists(auth_store *s, const char *username) { return found; } -bool auth_store_verify(auth_store *s, const char *username, +bool webui_store_verify(webui_store *s, const char *username, const char *password, char *role_out, size_t role_sz) { if (!s || !username || !password) return false; pthread_mutex_lock(&s->lock); @@ -173,7 +173,7 @@ static bool make_hash(const char *password, char salt_hex[SALT_BYTES * 2 + 1], return true; } -bool auth_store_create_user(auth_store *s, const char *username, +bool webui_store_create_user(webui_store *s, const char *username, const char *password, const char *role) { if (!s || !username || !*username || !password || !*password) return false; if (!valid_role(role)) role = "user"; @@ -198,7 +198,7 @@ bool auth_store_create_user(auth_store *s, const char *username, return ok; } -bool auth_store_set_password(auth_store *s, const char *username, +bool webui_store_set_password(webui_store *s, const char *username, const char *password) { if (!s || !username || !password || !*password) return false; char salt_hex[SALT_BYTES * 2 + 1], hash_hex[HASH_BYTES * 2 + 1]; @@ -220,7 +220,7 @@ bool auth_store_set_password(auth_store *s, const char *username, return ok; } -bool auth_store_set_role(auth_store *s, const char *username, const char *role) { +bool webui_store_set_role(webui_store *s, const char *username, const char *role) { if (!s || !username || !valid_role(role)) return false; pthread_mutex_lock(&s->lock); sqlite3_stmt *st = NULL; @@ -236,7 +236,7 @@ bool auth_store_set_role(auth_store *s, const char *username, const char *role) return ok; } -bool auth_store_delete_user(auth_store *s, const char *username) { +bool webui_store_delete_user(webui_store *s, const char *username) { if (!s || !username) return false; pthread_mutex_lock(&s->lock); sqlite3_stmt *st = NULL; @@ -251,7 +251,7 @@ bool auth_store_delete_user(auth_store *s, const char *username) { return ok; } -bool auth_store_list_users(auth_store *s, json_t *out) { +bool webui_store_list_users(webui_store *s, json_t *out) { if (!s || !json_is_array(out)) return false; pthread_mutex_lock(&s->lock); sqlite3_stmt *st = NULL; diff --git a/plugins/webui/webui_store.h b/plugins/webui/webui_store.h new file mode 100644 index 0000000..e183842 --- /dev/null +++ b/plugins/webui/webui_store.h @@ -0,0 +1,47 @@ +/* webui_store.h — SQLite-backed persistence for all web-UI-owned state: + * accounts, the category/tag taxonomy, and RSS feeds/rules/indexers. + * + * Owned entirely by the webui plugin (the daemon persists none of this). + * Account passwords are PBKDF2-HMAC-SHA256 with a per-user random salt. All + * calls are thread-safe (the store serializes access to its SQLite handle). */ +#ifndef NAUT_WEBUI_STORE_H +#define NAUT_WEBUI_STORE_H + +#include +#include +#include + +typedef struct webui_store webui_store; + +/* Open (creating if needed) the account database at `path`. Returns NULL on + * failure. The schema is created/migrated on open. */ +webui_store *webui_store_open(const char *path); +void webui_store_close(webui_store *s); + +/* Number of accounts, or -1 on error. */ +int webui_store_user_count(webui_store *s); +/* Number of admin accounts, or -1 on error. */ +int webui_store_admin_count(webui_store *s); +bool webui_store_user_exists(webui_store *s, const char *username); + +/* Verify a username/password pair (constant-time). On success, copies the + * account's role ("admin"/"user") into role_out. */ +bool webui_store_verify(webui_store *s, const char *username, + const char *password, char *role_out, size_t role_sz); + +/* Create an account. `role` must be "admin" or "user" (defaults to "user" if + * NULL/invalid). Returns false if the username already exists or on error. */ +bool webui_store_create_user(webui_store *s, const char *username, + const char *password, const char *role); + +bool webui_store_set_password(webui_store *s, const char *username, + const char *password); +/* Change an account's role ("admin"/"user"). */ +bool webui_store_set_role(webui_store *s, const char *username, const char *role); +bool webui_store_delete_user(webui_store *s, const char *username); + +/* Append {username, role, createdAt} objects (sorted by username) to the + * json array `out`. Returns false on error. */ +bool webui_store_list_users(webui_store *s, json_t *out); + +#endif /* NAUT_WEBUI_STORE_H */