webui: rename auth_store → webui_store
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 <noreply@anthropic.com>
This commit is contained in:
parent
ab51774554
commit
1b79fe8079
5 changed files with 85 additions and 84 deletions
|
|
@ -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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <jansson.h>
|
||||
|
||||
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 */
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include "naut/naut_plugin.h"
|
||||
#include "naut/http_client.h"
|
||||
#include "auth_store.h"
|
||||
#include "webui_store.h"
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -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;
|
||||
47
plugins/webui/webui_store.h
Normal file
47
plugins/webui/webui_store.h
Normal file
|
|
@ -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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <jansson.h>
|
||||
|
||||
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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue