From bfb3d8b8a2b355a947a8580fd1385d5ff5fdcbbe Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Wed, 21 Aug 2024 20:56:21 -0400 Subject: [PATCH] Add an option to disable account registation, and fix use of a JSContext from the wrong thread along the way. --- core/core.js | 5 +++ src/httpd.js.c | 34 +++++++++++++------ src/ssb.db.c | 92 ++++++++++++++++++++++++++++++-------------------- src/ssb.db.h | 12 ++++--- 4 files changed, 92 insertions(+), 51 deletions(-) diff --git a/core/core.js b/core/core.js index 8bcd9773..8c415922 100644 --- a/core/core.js +++ b/core/core.js @@ -79,6 +79,11 @@ const k_global_settings = { default_value: false, description: 'Enable discovery of, sharing of, and connecting to internet peer strangers, including announcing this instance.', }, + account_registration: { + type: 'boolean', + default_value: true, + description: 'Allow registration of new accounts.', + }, }; let gGlobalSettings = { diff --git a/src/httpd.js.c b/src/httpd.js.c index c34366de..aa849733 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -1484,27 +1484,39 @@ static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data) if (form_register && strcmp(form_register, "1") == 0) { - if (!have_account && _is_name_valid(account_name) && password && confirm && strcmp(password, confirm) == 0 && - tf_ssb_db_register_account(ssb, account_name, password)) + bool registered = false; + if (!have_account && _is_name_valid(account_name) && password && confirm && strcmp(password, confirm) == 0) { - tf_free((void*)send_session); - send_session = _make_session_jwt(ssb, account_name); - may_become_first_admin = true; + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + registered = tf_ssb_db_register_account(tf_ssb_get_loop(ssb), db, context, account_name, password); + tf_ssb_release_db_writer(ssb, db); + if (registered) + { + tf_free((void*)send_session); + send_session = _make_session_jwt(ssb, account_name); + may_become_first_admin = true; + } } - else + if (!registered) { login_error = "Error registering account."; } } else if (change && strcmp(change, "1") == 0) { - if (have_account && _is_name_valid(account_name) && new_password && confirm && strcmp(new_password, confirm) == 0 && _verify_password(password, account_passwd) && - tf_ssb_db_set_account_password(ssb, account_name, new_password)) + bool set = false; + if (have_account && _is_name_valid(account_name) && new_password && confirm && strcmp(new_password, confirm) == 0 && _verify_password(password, account_passwd)) { - tf_free((void*)send_session); - send_session = _make_session_jwt(ssb, account_name); + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + set = tf_ssb_db_set_account_password(tf_ssb_get_loop(ssb), db, context, account_name, new_password); + tf_ssb_release_db_writer(ssb, db); + if (set) + { + tf_free((void*)send_session); + send_session = _make_session_jwt(ssb, account_name); + } } - else + if (!set) { login_error = "Error changing password."; } diff --git a/src/ssb.db.c b/src/ssb.db.c index cdb14c6b..029e91e8 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -1644,14 +1644,13 @@ bool tf_ssb_db_get_account_password_hash(tf_ssb_t* ssb, const char* name, char* return result; } -bool tf_ssb_db_set_account_password(tf_ssb_t* ssb, const char* name, const char* password) +bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password) { - JSContext* context = tf_ssb_get_context(ssb); bool result = false; static const int k_salt_length = 12; char buffer[16]; - size_t bytes = uv_random(tf_ssb_get_loop(ssb), &(uv_random_t) { 0 }, buffer, sizeof(buffer), 0, NULL) == 0 ? sizeof(buffer) : 0; + size_t bytes = uv_random(loop, &(uv_random_t) { 0 }, buffer, sizeof(buffer), 0, NULL) == 0 ? sizeof(buffer) : 0; char output[7 + 22 + 1]; char* salt = crypt_gensalt_rn("$2b$", k_salt_length, buffer, bytes, output, sizeof(output)); char hash_output[7 + 22 + 31 + 1]; @@ -1663,7 +1662,6 @@ bool tf_ssb_db_set_account_password(tf_ssb_t* ssb, const char* name, const char* size_t user_length = 0; const char* user_string = JS_ToCStringLen(context, &user_length, user_json); - sqlite3* db = tf_ssb_acquire_db_writer(ssb); sqlite3_stmt* statement = NULL; if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'user:' || ?, ?)", -1, &statement, NULL) == SQLITE_OK) { @@ -1673,7 +1671,6 @@ bool tf_ssb_db_set_account_password(tf_ssb_t* ssb, const char* name, const char* } sqlite3_finalize(statement); } - tf_ssb_release_db_writer(ssb, db); JS_FreeCString(context, user_string); JS_FreeValue(context, user_json); @@ -1681,47 +1678,70 @@ bool tf_ssb_db_set_account_password(tf_ssb_t* ssb, const char* name, const char* return result; } -bool tf_ssb_db_register_account(tf_ssb_t* ssb, const char* name, const char* password) +static bool _tf_ssb_db_get_global_setting_bool(sqlite3* db, const char* name, bool default_value) +{ + bool result = default_value; + sqlite3_stmt* statement; + if (sqlite3_prepare(db, "SELECT json_extract(value, '$.' || ?) FROM properties WHERE id = 'core' AND key = 'settings'", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, name, -1, NULL) == SQLITE_OK) + { + if (sqlite3_step(statement) == SQLITE_ROW && sqlite3_column_type(statement, 0) != SQLITE_NULL) + { + result = sqlite3_column_int(statement, 0) != 0; + } + } + sqlite3_finalize(statement); + } + else + { + tf_printf("prepare failed: %s\n", sqlite3_errmsg(db)); + } + return result; +} + +bool tf_ssb_db_register_account(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password) { bool result = false; - JSContext* context = tf_ssb_get_context(ssb); JSValue users_array = JS_UNDEFINED; - sqlite3* db = tf_ssb_acquire_db_writer(ssb); - sqlite3_stmt* statement = NULL; - if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'auth' AND key = 'users'", -1, &statement, NULL) == SQLITE_OK) + bool registration_allowed = _tf_ssb_db_get_global_setting_bool(db, "account_registration", true); + if (registration_allowed) { - if (sqlite3_step(statement) == SQLITE_ROW) + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = 'auth' AND key = 'users'", -1, &statement, NULL) == SQLITE_OK) { - users_array = JS_ParseJSON(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0), NULL); + if (sqlite3_step(statement) == SQLITE_ROW) + { + users_array = JS_ParseJSON(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0), NULL); + } + sqlite3_finalize(statement); } - sqlite3_finalize(statement); - } - if (JS_IsUndefined(users_array)) - { - users_array = JS_NewArray(context); - } - int length = tf_util_get_length(context, users_array); - JS_SetPropertyUint32(context, users_array, length, JS_NewString(context, name)); - - JSValue json = JS_JSONStringify(context, users_array, JS_NULL, JS_NULL); - JS_FreeValue(context, users_array); - size_t value_length = 0; - const char* value = JS_ToCStringLen(context, &value_length, json); - if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'users', ?)", -1, &statement, NULL) == SQLITE_OK) - { - if (sqlite3_bind_text(statement, 1, value, value_length, NULL) == SQLITE_OK) + if (JS_IsUndefined(users_array)) { - tf_printf("added user to properties\n"); - result = sqlite3_step(statement) == SQLITE_DONE; + users_array = JS_NewArray(context); } - sqlite3_finalize(statement); - } - JS_FreeCString(context, value); - JS_FreeValue(context, json); - tf_ssb_release_db_writer(ssb, db); + int length = tf_util_get_length(context, users_array); + JS_SetPropertyUint32(context, users_array, length, JS_NewString(context, name)); - result = result && tf_ssb_db_set_account_password(ssb, name, password); + JSValue json = JS_JSONStringify(context, users_array, JS_NULL, JS_NULL); + JS_FreeValue(context, users_array); + size_t value_length = 0; + const char* value = JS_ToCStringLen(context, &value_length, json); + if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ('auth', 'users', ?)", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, value, value_length, NULL) == SQLITE_OK) + { + tf_printf("added user to properties\n"); + result = sqlite3_step(statement) == SQLITE_DONE; + } + sqlite3_finalize(statement); + } + JS_FreeCString(context, value); + JS_FreeValue(context, json); + } + + result = result && tf_ssb_db_set_account_password(loop, db, context, name, password); return result; } diff --git a/src/ssb.db.h b/src/ssb.db.h index d86a55a7..2b8d59b8 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -360,21 +360,25 @@ bool tf_ssb_db_get_account_password_hash(tf_ssb_t* ssb, const char* name, char* /** ** Insert or update a user's hashed password in the database. -** @param ssb The SSB instance. +** @param loop The event loop. +** @param db A DB writer. +** @param context A JS context. ** @param name The username. ** @param password The raw password. ** @return true if the hash of the password was successfully stored. */ -bool tf_ssb_db_set_account_password(tf_ssb_t* ssb, const char* name, const char* password); +bool tf_ssb_db_set_account_password(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password); /** ** Add a user account to the database. -** @param ssb The SSB instance. +** @param loop The event loop. +** @param db A DB writer. +** @param context A JS context. ** @param name The username to add. ** @param password The user's raw password. ** @return true If the user was added successfully. */ -bool tf_ssb_db_register_account(tf_ssb_t* ssb, const char* name, const char* password); +bool tf_ssb_db_register_account(uv_loop_t* loop, sqlite3* db, JSContext* context, const char* name, const char* password); /** ** Get an entry from the properties table.