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
Some checks failed
Build Tilde Friends / Build-All (push) Has been cancelled
This commit is contained in:
parent
3743543ef8
commit
0e7d2a8b0e
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"type": "tildefriends-app",
|
"type": "tildefriends-app",
|
||||||
"emoji": "🪪",
|
"emoji": "🪪",
|
||||||
"previous": "&zxsmzdLKsiG/WZt/Gw7JOxepgypoktNNbIyWiyFiJVc=.sha256"
|
"previous": "&5kw/2PgcySwOYCmAkjHTR2xTkIx3i7UjQmtQ8MfgWw8=.sha256"
|
||||||
}
|
}
|
||||||
|
@ -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>`
|
||||||
)
|
)
|
||||||
|
12
core/core.js
12
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.addEventListener = undefined;
|
||||||
imports.ssb.removeEventListener = undefined;
|
imports.ssb.removeEventListener = undefined;
|
||||||
imports.ssb.getIdentityInfo = undefined;
|
imports.ssb.getIdentityInfo = undefined;
|
||||||
|
104
src/ssb.js.c
104
src/ssb.js.c
@ -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));
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user