forked from cory/tildefriends
		
	ssb: The identity app now lets you switch out the server identity if you are an administrator.
This commit is contained in:
		| @@ -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); | ||||||
| 		} | 		} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user