From ddc4603f13ae22601eef6af0421fd55cef78c462 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Thu, 27 Nov 2025 14:33:57 -0500 Subject: [PATCH] ssb: Add some plausible API and a table for storing instance-wide blocks. --- core/core.js | 36 +++++++++++++++++++++--------- src/ssb.db.c | 38 +++++++++++++++++++++++++++++++- src/ssb.db.h | 14 ++++++++++++ src/ssb.js.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 11 deletions(-) diff --git a/core/core.js b/core/core.js index d2e9acfe..fdde1e7c 100644 --- a/core/core.js +++ b/core/core.js @@ -494,18 +494,34 @@ 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 + 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 + ); + } + }; + imports.ssb.addBlock = async function (id) { + await imports.core.permissionTest( + 'modify_blocks', + `Block ${id}.` ); + await ssb_internal.addBlock(id); } - }; + imports.ssb.removeBlock = async function (id) { + await imports.core.permissionTest( + 'modify_blocks', + `Unblock ${id}.` + ); + await ssb_internal.removeBlock(id); + } + } if ( process.credentials && diff --git a/src/ssb.db.c b/src/ssb.db.c index a16f64eb..c8bb917b 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -271,7 +271,6 @@ void tf_ssb_db_init(tf_ssb_t* ssb) ")"); _tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS identities_user ON identities (user, public_key)"); _tf_ssb_db_exec(db, "DELETE FROM identities WHERE user = ':auth'"); - _tf_ssb_db_exec(db, "CREATE TABLE IF NOT EXISTS invites (" " invite_public_key TEXT PRIMARY KEY," @@ -279,6 +278,11 @@ void tf_ssb_db_init(tf_ssb_t* ssb) " use_count INTEGER," " expires INTEGER" ")"); + _tf_ssb_db_exec(db, + "CREATE TABLE IF NOT EXISTS blocks (" + " id TEXT PRIMARY KEY," + " timestamp REAL" + ")"); bool populate_fts = false; if (!_tf_ssb_db_has_rows(db, "PRAGMA table_list('messages_fts')")) @@ -2868,3 +2872,35 @@ tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* u tf_free(info); return copy; } + +void tf_ssb_db_add_block(sqlite3* db, const char* id) +{ + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare_v2(db, "INSERT INTO blocks (id, timestamp) VALUES (?, ?) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK) + { + if (sqlite3_step(statement) != SQLITE_DONE) + { + tf_printf("add block: %s\n", sqlite3_errmsg(db)); + } + } + sqlite3_finalize(statement); + } +} + +void tf_ssb_db_remove_block(sqlite3* db, const char* id) +{ + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare_v2(db, "DELETE FROM blocks WHERE id = ?", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK) + { + if (sqlite3_step(statement) != SQLITE_DONE) + { + tf_printf("remove block: %s\n", sqlite3_errmsg(db)); + } + } + sqlite3_finalize(statement); + } +} diff --git a/src/ssb.db.h b/src/ssb.db.h index badc9a9b..5aef1a43 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -617,4 +617,18 @@ tf_ssb_identity_info_t* tf_ssb_db_get_identity_info(tf_ssb_t* ssb, const char* u */ void tf_ssb_db_add_blob_wants(sqlite3* db, const char* id); +/** +** Add an instance-wide block. +** @param db The database. +** @param id The account, message, or blob ID to block. +*/ +void tf_ssb_db_add_block(sqlite3* db, const char* id); + +/** +** Remove an instance-wide block. +** @param db The database. +** @param id The account, message, or blob ID to unblock. +*/ +void tf_ssb_db_remove_block(sqlite3* db, const char* id); + /** @} */ diff --git a/src/ssb.js.c b/src/ssb.js.c index eb192729..db5d466c 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -2172,6 +2172,66 @@ static JSValue _tf_ssb_port(JSContext* context, JSValueConst this_val, int argc, return JS_NewInt32(context, tf_ssb_server_get_port(ssb)); } +typedef struct _modify_block_t +{ + char id[k_id_base64_len]; + bool add; + JSValue promise[2]; +} modify_block_t; + +static void _tf_ssb_modify_block_work(tf_ssb_t* ssb, void* user_data) +{ + modify_block_t* work = user_data; + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + if (work->add) + { + tf_ssb_db_add_block(db, work->id); + } + else + { + tf_ssb_db_remove_block(db, work->id); + } + tf_ssb_release_db_writer(ssb, db); +} + +static void _tf_ssb_modify_block_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + modify_block_t* request = user_data; + JSContext* context = tf_ssb_get_context(ssb); + JSValue error = JS_Call(context, request->promise[0], JS_UNDEFINED, 0, NULL); + tf_util_report_error(context, error); + JS_FreeValue(context, error); + JS_FreeValue(context, request->promise[0]); + JS_FreeValue(context, request->promise[1]); + tf_free(request); +} + +static JSValue _tf_ssb_add_block(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); + const char* id = JS_ToCString(context, argv[0]); + modify_block_t* work = tf_malloc(sizeof(modify_block_t)); + *work = (modify_block_t) { .add = true }; + tf_string_set(work->id, sizeof(work->id), id); + JSValue result = JS_NewPromiseCapability(context, work->promise); + JS_FreeCString(context, id); + tf_ssb_run_work(ssb, _tf_ssb_modify_block_work, _tf_ssb_modify_block_after_work, work); + return result; +} + +static JSValue _tf_ssb_remove_block(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) +{ + tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId); + const char* id = JS_ToCString(context, argv[0]); + modify_block_t* work = tf_malloc(sizeof(modify_block_t)); + *work = (modify_block_t) { .add = false }; + tf_string_set(work->id, sizeof(work->id), id); + JSValue result = JS_NewPromiseCapability(context, work->promise); + JS_FreeCString(context, id); + tf_ssb_run_work(ssb, _tf_ssb_modify_block_work, _tf_ssb_modify_block_after_work, work); + return result; +} + void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) { JS_NewClassID(&_tf_ssb_classId); @@ -2227,6 +2287,8 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb) JS_SetPropertyStr(context, object_internal, "getIdentityInfo", JS_NewCFunction(context, _tf_ssb_getIdentityInfo, "getIdentityInfo", 3)); JS_SetPropertyStr(context, object_internal, "addEventListener", JS_NewCFunction(context, _tf_ssb_add_event_listener, "addEventListener", 2)); JS_SetPropertyStr(context, object_internal, "removeEventListener", JS_NewCFunction(context, _tf_ssb_remove_event_listener, "removeEventListener", 2)); + JS_SetPropertyStr(context, object_internal, "addBlock", JS_NewCFunction(context, _tf_ssb_add_block, "addBlock", 1)); + JS_SetPropertyStr(context, object_internal, "removeBlock", JS_NewCFunction(context, _tf_ssb_remove_block, "removeBlock", 1)); JS_FreeValue(context, global); }