Compare commits

..

2 Commits

Author SHA1 Message Date
0e7d2a8b0e ssb: The identity app now lets you switch out the server identity if you are an administrator.
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
2024-10-13 14:40:14 -04:00
3743543ef8 welcome: prettier 2024-10-13 14:19:55 -04:00
6 changed files with 169 additions and 29 deletions

View File

@ -1,5 +1,5 @@
{ {
"type": "tildefriends-app", "type": "tildefriends-app",
"emoji": "🪪", "emoji": "🪪",
"previous": "&zxsmzdLKsiG/WZt/Gw7JOxepgypoktNNbIyWiyFiJVc=.sha256" "previous": "&5kw/2PgcySwOYCmAkjHTR2xTkIx3i7UjQmtQ8MfgWw8=.sha256"
} }

View File

@ -1,5 +1,7 @@
import * as tfrpc from '/tfrpc.js'; import * as tfrpc from '/tfrpc.js';
const is_admin = core.user?.credentials?.permissions?.administration;
tfrpc.register(async function get_private_key(id) { tfrpc.register(async function get_private_key(id) {
return bip39Words(await ssb.getPrivateKey(id)); return bip39Words(await ssb.getPrivateKey(id));
}); });
@ -15,6 +17,9 @@ tfrpc.register(async function delete_id(id) {
tfrpc.register(async function reload() { tfrpc.register(async function reload() {
await main(); await main();
}); });
tfrpc.register(async function make_server(id) {
return await ssb.swapWithServerIdentity(id);
});
async function main() { async function main() {
let ids = await ssb.getIdentities(); let ids = await ssb.getIdentities();
@ -99,6 +104,16 @@ async function main() {
alert('Error deleting ID: ' + e); 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);
}
}
</script> </script>
<header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header> <header class="w3-theme w3-padding"><h1>SSB Identity Management</h1></header>
<div class="w3-card-4 w3-margin"> <div class="w3-card-4 w3-margin">
@ -117,13 +132,14 @@ async function main() {
<div class="w3-card-4 w3-margin"> <div class="w3-card-4 w3-margin">
<header class="w3-container w3-theme-l2"><h2>Identities</h2></header> <header class="w3-container w3-theme-l2"><h2>Identities</h2></header>
<ul class="w3-ul">` + <ul class="w3-ul">` +
ids (ids ?? [])
.map( .map(
( (
id id
) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis"> ) => `<li style="overflow: hidden; text-wrap: nowrap; text-overflow: ellipsis">
<button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button> <button onclick="handler.export_id(event)" data-id="${id}" class="w3-button w3-theme">Export Identity</button>
<button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button> <button onclick="handler.delete_id(event)" data-id="${id}" class="w3-button w3-theme">Delete Identity</button>
${is_admin && id != server_id ? `<button onclick="handler.make_server(event)" data-id="${id}" class="w3-button w3-theme">Make Server Identity</button>` : ''}
${id}${id == server_id ? ' <div class="w3-tag w3-theme-l4 w3-round">🖥 local server</div>' : ''} ${id}${id == server_id ? ' <div class="w3-tag w3-theme-l4 w3-round">🖥 local server</div>' : ''}
</li>` </li>`
) )

View File

@ -72,16 +72,21 @@
<a <a
class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/" href="https://f-droid.org/en/packages/com.unprompted.tildefriends.fdroid/"
><img src="f-droid.svg" style="height: 2em; margin: 0" ><img src="f-droid.svg" style="height: 2em; margin: 0" /> Get it
/> Get it on F-Droid</a> on F-Droid</a
<a class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" >
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"> <a
<img src="appimage.svg" style="height: 2em; margin: 0"> class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
href="https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage"
>
<img src="appimage.svg" style="height: 2em; margin: 0" />
Get Linux 64-bit AppImage Get Linux 64-bit AppImage
</a> </a>
<a class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top" <a
href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"> class="w3-button w3-round-large w3-padding w3-blue-gray w3-margin-top"
<img src="googleplay.svg" style="height: 2em; margin: 0"> href="https://play.google.com/store/apps/details?id=com.unprompted.tildefriends"
>
<img src="googleplay.svg" style="height: 2em; margin: 0" />
Get it on Google Play (Open Testing) Get it on Google Play (Open Testing)
</a> </a>
</p> </p>
@ -110,9 +115,7 @@
>https://www.tildefriends.net/</a >https://www.tildefriends.net/</a
>. >.
</li> </li>
<li> <li>Create an account to identify yourself with that instance.</li>
Create an account to identify yourself with that instance.
</li>
<li> <li>
Describe yourself in your profile in the <b>ssb</b> app. Give Describe yourself in your profile in the <b>ssb</b> app. Give
yourself a name and an avatar if you like. yourself a name and an avatar if you like.
@ -121,10 +124,16 @@
Connect to others. Connect to others.
<ul> <ul>
<li>Automatically discover peers on the same network.</li> <li>Automatically discover peers on the same network.</li>
<li>Manually connect to rooms and pubs, including <a href="https://www.tildefriends.net/~cory/room/" <li>
Manually connect to rooms and pubs, including
<a href="https://www.tildefriends.net/~cory/room/"
>tildefriends.net itself</a >tildefriends.net itself</a
>.</li> >.
<li>Enable <b>Peer Exchange</b> in the <b>admin</b> to discover internet peers.</li> </li>
<li>
Enable <b>Peer Exchange</b> in the <b>admin</b> to discover
internet peers.
</li>
</ul> </ul>
</li> </li>
<li>Follow people to grow your network.</li> <li>Follow people to grow your network.</li>
@ -216,10 +225,11 @@
<div class="w3-container w3-padding-64 w3-light-grey w3-center"> <div class="w3-container w3-padding-64 w3-light-grey w3-center">
<h1 class="w3-jumbo"><b>Boring Technology</b></h1> <h1 class="w3-jumbo"><b>Boring Technology</b></h1>
<p> <p>
Tilde Friends is built using boring, trusted tech. Unless a better reason Tilde Friends is built using boring, trusted tech. Unless a better
presents itself, it strives to use only simple and reason presents itself, it strives to use only simple and widely adopted
widely adopted dependencies in order to keep it easy to build for dependencies in order to keep it easy to build for all sorts of
all sorts of platforms and maintainable for a very long time.</p> platforms and maintainable for a very long time.
</p>
<p> <p>
Though of course for building Tilde Friends apps, you are free to use Though of course for building Tilde Friends apps, you are free to use
whatever fits. whatever fits.

View File

@ -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.addEventListener = undefined;
imports.ssb.removeEventListener = undefined; imports.ssb.removeEventListener = undefined;
imports.ssb.getIdentityInfo = undefined; imports.ssb.getIdentityInfo = undefined;

View File

@ -360,6 +360,109 @@ static JSValue _tf_ssb_set_server_following_me(JSContext* context, JSValueConst
return result; 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 typedef struct _identities_visit_t
{ {
JSContext* context; 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, "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, "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, "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, "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, "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, "privateMessageEncrypt", JS_NewCFunction(context, _tf_ssb_private_message_encrypt, "privateMessageEncrypt", 4));

View File

@ -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; tf_ssb_t* ssb = user_data;
if (tunnel && if (tunnel && (tf_ssb_connection_get_flags(tunnel) & k_tf_ssb_connect_flag_one_shot) != 0 && !tf_ssb_connection_get_tunnel(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 target_id[k_id_base64_len] = { 0 };
char portal_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) && 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_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); tf_ssb_tunnel_create(ssb, portal_id, target_id, k_tf_ssb_connect_flag_one_shot);
} }