From 0e7d2a8b0e7954c773b31dbf879727a73a9b4dd1 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Sun, 13 Oct 2024 14:40:14 -0400 Subject: [PATCH] ssb: The identity app now lets you switch out the server identity if you are an administrator. --- apps/identity.json | 2 +- apps/identity/app.js | 18 +++++++- core/core.js | 12 +++++ src/ssb.js.c | 104 +++++++++++++++++++++++++++++++++++++++++++ src/ssb.rpc.c | 10 ++--- 5 files changed, 138 insertions(+), 8 deletions(-) diff --git a/apps/identity.json b/apps/identity.json index 4f2a7b1d..cbae76b4 100644 --- a/apps/identity.json +++ b/apps/identity.json @@ -1,5 +1,5 @@ { "type": "tildefriends-app", "emoji": "🪪", - "previous": "&zxsmzdLKsiG/WZt/Gw7JOxepgypoktNNbIyWiyFiJVc=.sha256" + "previous": "&5kw/2PgcySwOYCmAkjHTR2xTkIx3i7UjQmtQ8MfgWw8=.sha256" } diff --git a/apps/identity/app.js b/apps/identity/app.js index 6b0eedf7..e45c32d1 100644 --- a/apps/identity/app.js +++ b/apps/identity/app.js @@ -1,5 +1,7 @@ import * as tfrpc from '/tfrpc.js'; +const is_admin = core.user?.credentials?.permissions?.administration; + tfrpc.register(async function get_private_key(id) { return bip39Words(await ssb.getPrivateKey(id)); }); @@ -15,6 +17,9 @@ tfrpc.register(async function delete_id(id) { tfrpc.register(async function reload() { await main(); }); +tfrpc.register(async function make_server(id) { + return await ssb.swapWithServerIdentity(id); +}); async function main() { let ids = await ssb.getIdentities(); @@ -99,6 +104,16 @@ async function main() { alert('Error deleting ID: ' + e); } } + handler.make_server = async function make_server(event) { + let id = event.srcElement.dataset.id; + try { + if (confirm('Are you sure you want to make "' + id + '" the server identity?\\n\\nFor it to take effect, you will need to both sign in again and restart Tilde Friends.')) { + await tfrpc.rpc.make_server(id); + } + } catch (e) { + alert('Error making server ID: ' + e); + } + }

SSB Identity Management

@@ -117,13 +132,14 @@ async function main() {

Identities

    ` + - ids + (ids ?? []) .map( ( id ) => `
  • + ${is_admin && id != server_id ? `` : ''} ${id}${id == server_id ? '
    🖥 local server
    ' : ''}
  • ` ) diff --git a/core/core.js b/core/core.js index 89ae8d81..6a986dbe 100644 --- a/core/core.js +++ b/core/core.js @@ -692,6 +692,18 @@ async function getProcessBlob(blobId, key, options) { ); } }; + imports.ssb.swapWithServerIdentity = function (id) { + if ( + process.credentials && + process.credentials.session && + process.credentials.session.name + ) { + return ssb.swapWithServerIdentity( + process.credentials.session.name, + id + ); + } + }; imports.ssb.addEventListener = undefined; imports.ssb.removeEventListener = undefined; imports.ssb.getIdentityInfo = undefined; diff --git a/src/ssb.js.c b/src/ssb.js.c index 8f037eef..66c0614c 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -360,6 +360,109 @@ static JSValue _tf_ssb_set_server_following_me(JSContext* context, JSValueConst 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; + if (tf_ssb_db_user_has_permission(ssb, work->user, "administration")) + { + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + char* error = NULL; + if (sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &error) == SQLITE_OK) + { + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(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) + { + error = NULL; + if (sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, &error) != SQLITE_OK) + { + work->error = error ? tf_strdup(error) : tf_strdup(sqlite3_errmsg(db)); + } + } + 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)); + } + tf_ssb_release_db_writer(ssb, db); + } + else + { + work->error = tf_strdup("not administrator"); + } +} + +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)); + snprintf(work->id, sizeof(work->id), "%s", 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 _identities_visit_t { JSContext* context; @@ -2312,6 +2415,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) 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, "setServerFollowingMe", JS_NewCFunction(context, _tf_ssb_set_server_following_me, "setServerFollowingMe", 3)); + JS_SetPropertyStr(context, object, "swapWithServerIdentity", JS_NewCFunction(context, _tf_ssb_swap_with_server_identity, "swapWithServerIdentity", 2)); JS_SetPropertyStr(context, object, "getIdentities", JS_NewCFunction(context, _tf_ssb_getIdentities, "getIdentities", 1)); 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)); diff --git a/src/ssb.rpc.c b/src/ssb.rpc.c index da63ec22..2c95dd29 100644 --- a/src/ssb.rpc.c +++ b/src/ssb.rpc.c @@ -1295,17 +1295,15 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang } } -static void _tf_ssb_rpc_broadcasts_changed_visit(const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data) +static void _tf_ssb_rpc_broadcasts_changed_visit( + const char* host, const struct sockaddr_in* addr, tf_ssb_broadcast_origin_t origin, tf_ssb_connection_t* tunnel, const uint8_t* pub, void* user_data) { tf_ssb_t* ssb = user_data; - if (tunnel && - (tf_ssb_connection_get_flags(tunnel) & k_tf_ssb_connect_flag_one_shot) != 0 && - !tf_ssb_connection_get_tunnel(tunnel)) + if (tunnel && (tf_ssb_connection_get_flags(tunnel) & k_tf_ssb_connect_flag_one_shot) != 0 && !tf_ssb_connection_get_tunnel(tunnel)) { char target_id[k_id_base64_len] = { 0 }; char portal_id[k_id_base64_len] = { 0 }; - if (tf_ssb_id_bin_to_str(target_id, sizeof(target_id), pub) && - tf_ssb_connection_get_id(tunnel, portal_id, sizeof(portal_id))) + if (tf_ssb_id_bin_to_str(target_id, sizeof(target_id), pub) && tf_ssb_connection_get_id(tunnel, portal_id, sizeof(portal_id))) { tf_ssb_tunnel_create(ssb, portal_id, target_id, k_tf_ssb_connect_flag_one_shot); }