From fa00a41fe0ab8c665ea4abf198be8eee7f027ba3 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Fri, 25 Oct 2024 13:58:06 -0400 Subject: [PATCH] js: Move app delete to C. --- core/core.js | 32 +------------------ src/httpd.js.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- src/ssb.db.c | 38 ++++++++++++++++++++++ src/ssb.db.h | 19 +++++++++++ 4 files changed, 144 insertions(+), 32 deletions(-) diff --git a/core/core.js b/core/core.js index 5a508b4e..de1ba131 100644 --- a/core/core.js +++ b/core/core.js @@ -995,36 +995,6 @@ async function blobHandler(request, response, blobId, uri) { response.writeHead(400, {'Content-Type': 'text/plain; charset=utf-8'}); response.end('Invalid name.'); } - } else if (uri == '/delete') { - let match; - if ((match = /^\/\~(\w+)\/(\w+)$/.exec(blobId))) { - let user = match[1]; - let appName = match[2]; - let credentials = await httpd.auth_query(request.headers); - if ( - credentials && - credentials.session && - (credentials.session.name == user || - (credentials.permissions.administration && user == 'core')) - ) { - let database = new Database(user); - let apps = new Set(); - try { - apps = new Set(JSON.parse(await database.get('apps'))); - } catch {} - if (apps.delete(appName)) { - await database.set('apps', JSON.stringify([...apps].sort())); - } - database.remove('path:' + appName); - } else { - response.writeHead(401, {'Content-Type': 'text/plain; charset=utf-8'}); - response.end('401 Unauthorized'); - return; - } - } - - response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'}); - response.end('OK'); } else { let data; let match; @@ -1175,7 +1145,7 @@ loadSettings() (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) ) { return blobHandler(request, response, match[1], match[2]); - } else if ((match = /^(.*)(\/(?:save|delete)?)$/.exec(request.uri))) { + } else if ((match = /^(.*)(\/(?:save)?)$/.exec(request.uri))) { return blobHandler(request, response, match[1], match[2]); } }); diff --git a/src/httpd.js.c b/src/httpd.js.c index 6a11ed1d..33be5d83 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -39,6 +39,7 @@ const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000; static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt); static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv); +static bool _is_name_valid(const char* name); static const char* _make_session_jwt(JSContext* context, tf_ssb_t* ssb, const char* name); static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie); const char** _form_data_decode(const char* data, int length); @@ -1081,6 +1082,89 @@ static void _httpd_endpoint_view(tf_http_request_t* request) tf_ssb_run_work(ssb, _httpd_endpoint_view_work, _httpd_endpoint_view_after_work, view); } +typedef struct _delete_t +{ + tf_http_request_t* request; + const char* session; + int response; +} delete_t; + +static void _httpd_endpoint_delete_work(tf_ssb_t* ssb, void* user_data) +{ + delete_t* delete = user_data; + tf_http_request_t* request = delete->request; + + JSMallocFunctions funcs = { 0 }; + tf_get_js_malloc_functions(&funcs); + JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL); + JSContext* context = JS_NewContext(runtime); + + JSValue jwt = _authenticate_jwt(ssb, context, delete->session); + JSValue user = JS_GetPropertyStr(context, jwt, "name"); + const char* user_string = JS_ToCString(context, user); + if (user_string && _is_name_valid(user_string)) + { + size_t length = strlen(user_string); + if (request->path && request->path[0] == '/' && request->path[1] == '~' && + /* TODO: admin users used to be able to delete core apps */ + strncmp(request->path + 2, user_string, length) == 0 && request->path[2 + length] == '/') + { + char* app_name = tf_strdup(request->path + 2 + length + 1); + if (app_name) + { + if (strlen(app_name) > strlen("/delete") && strcmp(app_name + strlen(app_name) - strlen("/delete"), "/delete") == 0) + { + app_name[strlen(app_name) - strlen("/delete")] = '\0'; + } + } + + size_t path_length = strlen("path:") + strlen(app_name) + 1; + char* app_path = tf_malloc(path_length); + snprintf(app_path, path_length, "path:%s", app_name); + + bool changed = false; + changed = tf_ssb_db_remove_value_from_array_property(ssb, user_string, "apps", app_name) || changed; + changed = tf_ssb_db_remove_property(ssb, user_string, app_path) || changed; + delete->response = changed ? 200 : 404; + tf_free(app_name); + tf_free(app_path); + } + } + else + { + delete->response = 401; + } + + JS_FreeCString(context, user_string); + JS_FreeValue(context, user); + JS_FreeValue(context, jwt); + JS_FreeContext(context); + JS_FreeRuntime(runtime); +} + +static void _httpd_endpoint_delete_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + delete_t* delete = user_data; + const char* k_payload = tf_http_status_text(delete->response ? delete->response : 404); + tf_http_respond(delete->request, delete->response ? delete->response : 404, NULL, 0, k_payload, strlen(k_payload)); + tf_http_request_unref(delete->request); + tf_free((void*)delete->session); + tf_free(delete); +} + +static void _httpd_endpoint_delete(tf_http_request_t* request) +{ + tf_http_request_ref(request); + tf_task_t* task = request->user_data; + tf_ssb_t* ssb = tf_task_get_ssb(task); + delete_t* delete = tf_malloc(sizeof(delete_t)); + *delete = (delete_t) { + .request = request, + .session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"), + }; + tf_ssb_run_work(ssb, _httpd_endpoint_delete_work, _httpd_endpoint_delete_after_work, delete); +} + static void _httpd_endpoint_root_callback(const char* path, void* user_data) { tf_http_request_t* request = user_data; @@ -1402,7 +1486,7 @@ static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* static bool _session_is_authenticated_as_user(JSContext* context, JSValue session) { bool result = false; - JSValue user = JS_GetPropertyStr(context, session, "user"); + JSValue user = JS_GetPropertyStr(context, session, "name"); const char* user_string = JS_ToCString(context, user); result = user_string && strcmp(user_string, "guest") != 0; JS_FreeCString(context, user_string); @@ -1813,6 +1897,7 @@ void tf_httpd_register(JSContext* context) tf_http_add_handler(http, "/~*/*/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/&*.sha256/", _httpd_endpoint_static, NULL, task); tf_http_add_handler(http, "/*/view", _httpd_endpoint_view, NULL, task); + tf_http_add_handler(http, "/~*/*/delete", _httpd_endpoint_delete, NULL, task); tf_http_add_handler(http, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL); tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task); diff --git a/src/ssb.db.c b/src/ssb.db.c index cacb4a88..ab9d1376 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -1787,6 +1787,44 @@ bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, cons return result; } +bool tf_ssb_db_remove_property(tf_ssb_t* ssb, const char* id, const char* key) +{ + bool result = false; + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, "DELETE FROM properties WHERE id = ? AND key = ?", -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK) + { + result = sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) != 0; + } + sqlite3_finalize(statement); + } + tf_ssb_release_db_writer(ssb, db); + return result; +} + +bool tf_ssb_db_remove_value_from_array_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value) +{ + bool result = false; + sqlite3* db = tf_ssb_acquire_db_writer(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, + "UPDATE properties SET value = json_remove(properties.value, entry.fullkey) FROM json_each(properties.value) AS entry WHERE properties.id = ? AND properties.key = ? " + "AND entry.value = ?", + -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, key, -1, NULL) == SQLITE_OK && + sqlite3_bind_text(statement, 3, value, -1, NULL) == SQLITE_OK) + { + result = sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(db) != 0; + } + sqlite3_finalize(statement); + } + tf_ssb_release_db_writer(ssb, db); + return result; +} + bool tf_ssb_db_identity_get_active(sqlite3* db, const char* user, const char* package_owner, const char* package_name, char* out_identity, size_t out_identity_size) { sqlite3_stmt* statement = NULL; diff --git a/src/ssb.db.h b/src/ssb.db.h index 36b8b2f6..417504c7 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -399,6 +399,25 @@ const char* tf_ssb_db_get_property(tf_ssb_t* ssb, const char* id, const char* ke */ bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value); +/** +** Remove an entry in the properties table. +** @param ssb The SSB instance. +** @param id The user. +** @param key The property key. +** @return true if the property was removed successfully. +*/ +bool tf_ssb_db_remove_property(tf_ssb_t* ssb, const char* id, const char* key); + +/** +** Remove a value from an entry in the properties table that is a JSON array. +** @param ssb The SSB instance. +** @param id The user. +** @param key The property key. +** @param value The value to remove from the JSON array. +** @return true if the property was updated. +*/ +bool tf_ssb_db_remove_value_from_array_property(tf_ssb_t* ssb, const char* id, const char* key, const char* value); + /** ** Resolve a hostname to its index path by global settings. ** @param ssb The SSB instance.