Add an option to disable account registation, and fix use of a JSContext from the wrong thread along the way.

This commit is contained in:
Cory McWilliams 2024-08-21 20:56:21 -04:00
parent e38ff99607
commit bfb3d8b8a2
4 changed files with 92 additions and 51 deletions

View File

@ -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 = {

View File

@ -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.";
}

View File

@ -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;
}

View File

@ -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.