diff --git a/core/core.js b/core/core.js index 6b79f930..e833963d 100644 --- a/core/core.js +++ b/core/core.js @@ -515,21 +515,6 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) { ); } }; - if (process.credentials?.permissions?.administration) { - imports.ssb.swapWithServerIdentity = function (id) { - if ( - process.credentials && - process.credentials.session && - process.credentials.session.name - ) { - return ssb.swapWithServerIdentity( - process.credentials.session.name, - id - ); - } - }; - } - if ( process.credentials && process.credentials.session && diff --git a/src/api.js.c b/src/api.js.c index e1afc8ee..8393187a 100644 --- a/src/api.js.c +++ b/src/api.js.c @@ -1158,6 +1158,77 @@ static JSValue _tf_ssb_delete_user(JSContext* context, JSValueConst this_val, in return result; } +typedef struct _swap_with_server_identity_t +{ + char server_id[k_id_base64_len]; + char user_id[k_id_base64_len]; + JSValue promise[2]; + char* error; + char user[]; +} swap_with_server_identity_t; + +static void _tf_ssb_swap_with_server_identity_work(tf_ssb_t* ssb, void* user_data) +{ + swap_with_server_identity_t* work = user_data; + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + work->error = tf_ssb_db_swap_with_server_identity(db, work->user, work->user_id, work->server_id); + tf_ssb_release_db_writer(ssb, db); +} + +static void _tf_ssb_swap_with_server_identity_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + swap_with_server_identity_t* work = user_data; + JSContext* context = tf_ssb_get_context(ssb); + JSValue error = JS_UNDEFINED; + if (work->error) + { + JSValue arg = JS_ThrowInternalError(context, "%s", work->error); + JSValue exception = JS_GetException(context); + error = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &exception); + tf_free(work->error); + JS_FreeValue(context, exception); + JS_FreeValue(context, arg); + } + else + { + error = JS_Call(context, work->promise[0], JS_UNDEFINED, 0, NULL); + } + tf_util_report_error(context, error); + JS_FreeValue(context, error); + JS_FreeValue(context, work->promise[0]); + JS_FreeValue(context, work->promise[1]); + tf_free(work); +} + +static void _tf_ssb_swap_with_server_identity_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data) +{ + swap_with_server_identity_t* work = user_data; + tf_task_t* task = tf_task_get(context); + tf_ssb_t* ssb = tf_task_get_ssb(task); + tf_ssb_run_work(ssb, _tf_ssb_swap_with_server_identity_work, _tf_ssb_swap_with_server_identity_after_work, work); +} + +static JSValue _tf_ssb_swap_with_server_identity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data) +{ + tf_task_t* task = tf_task_get(context); + tf_ssb_t* ssb = tf_task_get_ssb(task); + const char* user = _tf_ssb_get_process_credentials_session_name(context, data[0]); + size_t user_length = user ? strlen(user) : 0; + const char* id = JS_ToCString(context, argv[0]); + swap_with_server_identity_t* work = tf_malloc(sizeof(swap_with_server_identity_t) + user_length + 1); + *work = (swap_with_server_identity_t) { 0 }; + tf_ssb_whoami(ssb, work->server_id, sizeof(work->server_id)); + tf_string_set(work->user_id, sizeof(work->user_id), id); + memcpy(work->user, user, user_length + 1); + JSValue result = JS_NewPromiseCapability(context, work->promise); + char description[1024]; + snprintf(description, sizeof(description), "Swap identity %s with %s.", work->user_id, work->server_id); + _tf_ssb_permission_test(context, data[0], "delete_user", description, _tf_ssb_swap_with_server_identity_permission_callback, work); + JS_FreeCString(context, id); + tf_free((void*)user); + return result; +} + static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue imports = argv[0]; @@ -1200,6 +1271,8 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va JS_SetPropertyStr(context, ssb, "addBlock", JS_NewCFunctionData(context, _tf_ssb_add_block, 1, 0, 1, &process)); JS_SetPropertyStr(context, ssb, "removeBlock", JS_NewCFunctionData(context, _tf_ssb_remove_block, 1, 0, 1, &process)); JS_SetPropertyStr(context, ssb, "getBlocks", JS_NewCFunctionData(context, _tf_ssb_get_blocks, 0, 0, 1, &process)); + + JS_SetPropertyStr(context, ssb, "swapWithServerIdentity", JS_NewCFunctionData(context, _tf_ssb_swap_with_server_identity, 1, 0, 1, &process)); } JS_FreeValue(context, administration); JS_FreeValue(context, permissions); diff --git a/src/ssb.db.c b/src/ssb.db.c index 835655e3..de648ce5 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -2953,3 +2953,52 @@ void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double t sqlite3_finalize(statement); } } + +char* tf_ssb_db_swap_with_server_identity(sqlite3* db, const char* user, const char* user_id, const char* server_id) +{ + tf_printf("SWAP user=%s user_id=%s server_id=%s\n", user, user_id, server_id); + char* result = NULL; + char* error = NULL; + if (sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &error) == SQLITE_OK) + { + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare_v2(db, "UPDATE identities SET user = ? WHERE user = ? AND '@' || public_key = ?", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, ":admin", -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 3, server_id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) == 1 && + sqlite3_reset(statement) == SQLITE_OK && sqlite3_bind_text(statement, 1, ":admin", -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 2, user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 3, user_id, -1, NULL) == SQLITE_OK && + sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) == 1) + { + char* commit_error = NULL; + if (sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, &commit_error) != SQLITE_OK) + { + result = commit_error ? tf_strdup(commit_error) : tf_strdup(sqlite3_errmsg(db)); + } + if (commit_error) + { + sqlite3_free(commit_error); + } + } + else + { + result = tf_strdup(sqlite3_errmsg(db) ? sqlite3_errmsg(db) : "swap failed"); + } + sqlite3_finalize(statement); + } + else + { + result = tf_strdup(sqlite3_errmsg(db)); + } + } + else + { + result = error ? tf_strdup(error) : tf_strdup(sqlite3_errmsg(db)); + } + if (error) + { + sqlite3_free(error); + } + + return result; +} diff --git a/src/ssb.db.h b/src/ssb.db.h index 31f3401f..f64d18c1 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -647,4 +647,14 @@ bool tf_ssb_db_is_blocked(sqlite3* db, const char* id); */ void tf_ssb_db_get_blocks(sqlite3* db, void (*callback)(const char* id, double timestamp, void* user_data), void* user_data); +/** +** Swap a user's identity with the server identity. +** @param db The database. +** @param user The user. +** @param user_id The user identity. +** @param server_id The server identity. +** @return Null on success or an error message on error. Free with tf_free(). +*/ +char* tf_ssb_db_swap_with_server_identity(sqlite3* db, const char* user, const char* user_id, const char* server_id); + /** @} */ diff --git a/src/ssb.js.c b/src/ssb.js.c index eb192729..7756559a 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -252,117 +252,6 @@ static JSValue _tf_ssb_deleteIdentity(JSContext* context, JSValueConst this_val, return result; } -typedef struct _swap_with_server_identity_t -{ - char server_id[k_id_base64_len]; - char id[k_id_base64_len]; - JSValue promise[2]; - char* error; - char user[]; -} swap_with_server_identity_t; - -static void _tf_ssb_swap_with_server_identity_work(tf_ssb_t* ssb, void* user_data) -{ - swap_with_server_identity_t* work = user_data; - sqlite3* db = tf_ssb_acquire_db_writer(ssb); - if (tf_ssb_db_user_has_permission(ssb, db, work->user, "administration")) - { - char* error = NULL; - if (sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &error) == SQLITE_OK) - { - sqlite3_stmt* statement = NULL; - if (sqlite3_prepare_v2(db, "UPDATE identities SET user = ? WHERE user = ? AND '@' || public_key = ?", -1, &statement, NULL) == SQLITE_OK) - { - if (sqlite3_bind_text(statement, 1, work->user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, ":admin", -1, NULL) == SQLITE_OK && - sqlite3_bind_text(statement, 3, work->server_id, -1, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) == 1 && - sqlite3_reset(statement) == SQLITE_OK && sqlite3_bind_text(statement, 1, ":admin", -1, NULL) == SQLITE_OK && - sqlite3_bind_text(statement, 2, work->user, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 3, work->id, -1, NULL) == SQLITE_OK && - sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) == 1) - { - char* commit_error = NULL; - if (sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, &commit_error) != SQLITE_OK) - { - work->error = commit_error ? tf_strdup(commit_error) : tf_strdup(sqlite3_errmsg(db)); - } - if (commit_error) - { - sqlite3_free(commit_error); - } - } - else - { - work->error = tf_strdup(sqlite3_errmsg(db) ? sqlite3_errmsg(db) : "swap failed"); - } - sqlite3_finalize(statement); - } - else - { - work->error = tf_strdup(sqlite3_errmsg(db)); - } - } - else - { - work->error = error ? tf_strdup(error) : tf_strdup(sqlite3_errmsg(db)); - } - if (error) - { - sqlite3_free(error); - } - } - else - { - work->error = tf_strdup("not administrator"); - } - tf_ssb_release_db_writer(ssb, db); -} - -static void _tf_ssb_swap_with_server_identity_after_work(tf_ssb_t* ssb, int status, void* user_data) -{ - swap_with_server_identity_t* work = user_data; - JSContext* context = tf_ssb_get_context(ssb); - JSValue error = JS_UNDEFINED; - if (work->error) - { - JSValue arg = JS_ThrowInternalError(context, "%s", work->error); - JSValue exception = JS_GetException(context); - error = JS_Call(context, work->promise[1], JS_UNDEFINED, 1, &exception); - tf_free(work->error); - JS_FreeValue(context, exception); - JS_FreeValue(context, arg); - } - else - { - error = JS_Call(context, work->promise[0], JS_UNDEFINED, 0, NULL); - } - tf_util_report_error(context, error); - JS_FreeValue(context, error); - JS_FreeValue(context, work->promise[0]); - JS_FreeValue(context, work->promise[1]); - tf_free(work); -} - -static JSValue _tf_ssb_swap_with_server_identity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) -{ - tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); - JSValue result = JS_UNDEFINED; - if (ssb) - { - size_t user_length = 0; - const char* user = JS_ToCStringLen(context, &user_length, argv[0]); - const char* id = JS_ToCString(context, argv[1]); - swap_with_server_identity_t* work = tf_malloc(sizeof(swap_with_server_identity_t) + user_length + 1); - *work = (swap_with_server_identity_t) { 0 }; - tf_ssb_whoami(ssb, work->server_id, sizeof(work->server_id)); - tf_string_set(work->id, sizeof(work->id), id); - memcpy(work->user, user, user_length + 1); - result = JS_NewPromiseCapability(context, work->promise); - tf_ssb_run_work(ssb, _tf_ssb_swap_with_server_identity_work, _tf_ssb_swap_with_server_identity_after_work, work); - JS_FreeCString(context, user); - JS_FreeCString(context, id); - } - return result; -} - typedef struct _get_private_key_t { JSContext* context; @@ -2196,7 +2085,6 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1)); JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2)); JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2)); - JS_SetPropertyStr(context, object, "swapWithServerIdentity", JS_NewCFunction(context, _tf_ssb_swap_with_server_identity, "swapWithServerIdentity", 2)); JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2)); JS_SetPropertyStr(context, object, "privateMessageEncrypt", JS_NewCFunction(context, _tf_ssb_private_message_encrypt, "privateMessageEncrypt", 4)); JS_SetPropertyStr(context, object, "privateMessageDecrypt", JS_NewCFunction(context, _tf_ssb_private_message_decrypt, "privateMessageDecrypt", 3));