ssb: Invite support progress. Now the pub accepts the invite, tracks its use, and follows. The client still needs to react.

This commit is contained in:
Cory McWilliams 2025-01-19 17:02:08 -05:00
parent faca63946c
commit 616f3ad76d
3 changed files with 163 additions and 6 deletions

View File

@ -2164,7 +2164,6 @@ bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, in
char public[k_id_base64_len];
tf_ssb_id_bin_to_str(public, sizeof(public), public_key);
tf_printf("invite is for public key %s\n", public);
char seed_b64[64];
tf_base64_encode(seed, sizeof(seed), seed_b64, sizeof(seed_b64));
@ -2188,3 +2187,22 @@ bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, in
snprintf(out_invite, size, "%s:%d:%s~%s", host, port, id, seed_b64);
return inserted;
}
bool tf_ssb_db_use_invite(sqlite3* db, const char* id)
{
bool used = false;
sqlite3_stmt* statement;
if (sqlite3_prepare(db,
"UPDATE invites SET use_count = use_count - 1 WHERE invite_public_key = ? AND expires > ? AND (use_count > 0 OR use_count = -1)",
-1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
sqlite3_bind_int64(statement, 2, (int64_t)time(NULL)) == SQLITE_OK)
{
used = sqlite3_step(statement) == SQLITE_DONE &&
sqlite3_changes(db) > 0;
}
sqlite3_finalize(statement);
}
return used;
}

View File

@ -516,6 +516,14 @@ const char* tf_ssb_db_get_profile_name(sqlite3* db, const char* id);
*/
bool tf_ssb_db_generate_invite(sqlite3* db, const char* id, const char* host, int port, int use_count, int expires_seconds, char* out_invite, size_t size);
/**
** Consume and validate an invite.
** @param db The database.
** @param id The invite public key.
** @return true If the invite was valid and successfully consumed.
*/
bool tf_ssb_db_use_invite(sqlite3* db, const char* id);
/**
** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use.
** @param user_data User data registered with the authorizer.

View File

@ -1202,6 +1202,28 @@ static void _tf_ssb_rpc_ebt_replicate_server(
tf_ssb_connection_add_request(connection, -request_number, "ebt.replicate", _tf_ssb_rpc_ebt_replicate, NULL, NULL, NULL);
}
static void _tf_ssb_rpc_send_invite_use(tf_ssb_connection_t* connection)
{
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_get_context(ssb);
JSValue message = JS_NewObject(context);
JSValue name = JS_NewArray(context);
JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "invite"));
JS_SetPropertyUint32(context, name, 1, JS_NewString(context, "use"));
JS_SetPropertyStr(context, message, "name", name);
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "async"));
JSValue args = JS_NewArray(context);
JSValue object = JS_NewObject(context);
char id[k_id_base64_len] = { 0 };
tf_ssb_whoami(ssb, id, sizeof(id));
JS_SetPropertyStr(context, object, "feed", JS_NewString(context, id));
JS_SetPropertyUint32(context, args, 0, object);
JS_SetPropertyStr(context, message, "args", args);
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_new_request, tf_ssb_connection_next_request_number(connection), "invite.use", message,
_tf_ssb_rpc_connection_tunnel_isRoom_callback, NULL, NULL);
JS_FreeValue(context, message);
}
static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
{
JSContext* context = tf_ssb_get_context(ssb);
@ -1223,6 +1245,11 @@ static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_chang
if (tf_ssb_connection_is_client(connection))
{
if (tf_ssb_connection_get_flags(connection) & k_tf_ssb_connect_flag_use_invite)
{
_tf_ssb_rpc_send_invite_use(connection);
}
JSValue message = JS_NewObject(context);
JSValue name = JS_NewArray(context);
JS_SetPropertyUint32(context, name, 0, JS_NewString(context, "tunnel"));
@ -1608,11 +1635,6 @@ static void _tf_ssb_rpc_send_peers_exchange(tf_ssb_connection_t* connection)
static void _tf_ssb_rpc_peers_exchange(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
if (!tf_ssb_is_peer_exchange(ssb))
{
tf_ssb_connection_rpc_send_error_method_not_allowed(connection, flags, -request_number, "peers.exchange");
return;
}
_tf_ssb_rpc_peers_exchange_internal(connection, flags, request_number, args, message, size, user_data);
@ -1624,6 +1646,114 @@ static void _tf_ssb_rpc_peers_exchange(tf_ssb_connection_t* connection, uint8_t
JS_FreeValue(context, out_message);
}
typedef struct _invite_t
{
tf_ssb_connection_t* connection;
char pub[k_id_base64_len];
char invite_public_key[k_id_base64_len];
char id[k_id_base64_len];
int32_t request_number;
bool accepted;
char previous_id[256];
int64_t previous_sequence;
char* message;
} invite_t;
static void _tf_ssb_rpc_invite_use_work(tf_ssb_connection_t* connection, void* user_data)
{
invite_t* work = user_data;
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
work->accepted = tf_ssb_db_use_invite(db, work->invite_public_key);
tf_ssb_release_db_writer(ssb, db);
if (work->accepted)
{
tf_ssb_db_get_latest_message_by_author(ssb, work->pub, &work->previous_sequence, work->previous_id, sizeof(work->previous_id));
}
}
static void _tf_ssb_invite_use_message_store_callback(const char* id, bool verified, bool is_new, void* user_data)
{
invite_t* work = user_data;
tf_ssb_connection_t* connection = work->connection;
if (verified && is_new)
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error, -work->request_number, NULL,
(const uint8_t*)work->message, strlen(work->message), NULL, NULL, NULL);
}
else
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error, -work->request_number, NULL,
(const uint8_t*)"false", strlen("false"), NULL, NULL, NULL);
}
if (work->message)
{
tf_free(work->message);
}
tf_free(work);
}
static void _tf_ssb_rpc_invite_use_after_work(tf_ssb_connection_t* connection, int status, void* user_data)
{
invite_t* work = user_data;
if (work->accepted)
{
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
JSContext* context = tf_ssb_connection_get_context(connection);
JSValue content = JS_NewObject(context);
JS_SetPropertyStr(context, content, "type", JS_NewString(context, "contact"));
JS_SetPropertyStr(context, content, "contact", JS_NewString(context, work->id));
JS_SetPropertyStr(context, content, "following", JS_TRUE);
JS_SetPropertyStr(context, content, "pub", JS_TRUE);
uint8_t private_key[512] = { 0 };
tf_ssb_get_private_key(ssb, private_key, sizeof(private_key));
JSValue message = tf_ssb_sign_message(ssb, work->pub, private_key, content, work->previous_id, work->previous_sequence);
JSValue json = JS_JSONStringify(context, message, JS_NULL, JS_NULL);
const char* string = JS_ToCString(context, json);
work->message = tf_strdup(string);
JS_FreeCString(context, string);
JS_FreeValue(context, json);
tf_ssb_verify_strip_and_store_message(ssb, message, _tf_ssb_invite_use_message_store_callback, work);
JS_FreeValue(context, message);
JS_FreeValue(context, content);
}
else
{
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_end_error, -work->request_number, NULL,
(const uint8_t*)"false", strlen("false"), NULL, NULL, NULL);
}
}
static void _tf_ssb_rpc_invite_use(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
{
invite_t* work = tf_malloc(sizeof(invite_t));
*work = (invite_t)
{
.connection = connection,
.request_number = request_number,
};
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
tf_ssb_whoami(ssb, work->pub, sizeof(work->pub));
JSContext* context = tf_ssb_connection_get_context(connection);
JSValue array = JS_GetPropertyStr(context, args, "args");
JSValue object = JS_GetPropertyUint32(context, array, 0);
JSValue feed = JS_GetPropertyStr(context, object, "feed");
tf_ssb_connection_get_id(connection, work->invite_public_key, sizeof(work->invite_public_key));
const char* id = JS_ToCString(context, feed);
snprintf(work->id, sizeof(work->id), "%s", id);
tf_printf("PUB = %s\n", work->pub);
tf_printf("FEED = %s\n", work->id);
tf_printf("INVITE_PUBLIC_KEY = %s\n", work->invite_public_key);
JS_FreeCString(context, id);
JS_FreeValue(context, feed);
JS_FreeValue(context, object);
JS_FreeValue(context, array);
tf_ssb_connection_run_work(connection, _tf_ssb_rpc_invite_use_work, _tf_ssb_rpc_invite_use_after_work, work);
}
void tf_ssb_rpc_register(tf_ssb_t* ssb)
{
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_connections_changed_callback, NULL, NULL);
@ -1640,4 +1770,5 @@ void tf_ssb_rpc_register(tf_ssb_t* ssb)
tf_ssb_add_rpc_callback(ssb, "createHistoryStream", _tf_ssb_rpc_createHistoryStream, NULL, NULL); /* SOURCE */
tf_ssb_add_rpc_callback(ssb, "ebt.replicate", _tf_ssb_rpc_ebt_replicate_server, NULL, NULL); /* DUPLEX */
tf_ssb_add_rpc_callback(ssb, "peers.exchange", _tf_ssb_rpc_peers_exchange, NULL, NULL); /* ASYNC */
tf_ssb_add_rpc_callback(ssb, "invite.use", _tf_ssb_rpc_invite_use, NULL, NULL); /* ASYNC */
}