js: Move app delete to C.

This commit is contained in:
Cory McWilliams 2024-10-25 13:58:06 -04:00
parent 2e66666bdf
commit fa00a41fe0
4 changed files with 144 additions and 32 deletions

View File

@ -995,36 +995,6 @@ async function blobHandler(request, response, blobId, uri) {
response.writeHead(400, {'Content-Type': 'text/plain; charset=utf-8'}); response.writeHead(400, {'Content-Type': 'text/plain; charset=utf-8'});
response.end('Invalid name.'); 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 { } else {
let data; let data;
let match; let match;
@ -1175,7 +1145,7 @@ loadSettings()
(match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri)) (match = /^\/([&\%][^\.]{44}(?:\.\w+)?)(\/?.*)/.exec(request.uri))
) { ) {
return blobHandler(request, response, match[1], match[2]); 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]); return blobHandler(request, response, match[1], match[2]);
} }
}); });

View File

@ -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 _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 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_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); 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); 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); 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) static void _httpd_endpoint_root_callback(const char* path, void* user_data)
{ {
tf_http_request_t* request = 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) static bool _session_is_authenticated_as_user(JSContext* context, JSValue session)
{ {
bool result = false; 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); const char* user_string = JS_ToCString(context, user);
result = user_string && strcmp(user_string, "guest") != 0; result = user_string && strcmp(user_string, "guest") != 0;
JS_FreeCString(context, user_string); 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, "/~*/*/", _httpd_endpoint_static, NULL, task);
tf_http_add_handler(http, "/&*.sha256/", _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, "/*/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, "/robots.txt", _httpd_endpoint_robots_txt, NULL, NULL);
tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task); tf_http_add_handler(http, "/debug", _httpd_endpoint_debug, NULL, task);

View File

@ -1787,6 +1787,44 @@ bool tf_ssb_db_set_property(tf_ssb_t* ssb, const char* id, const char* key, cons
return result; 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) 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; sqlite3_stmt* statement = NULL;

View File

@ -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); 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. ** Resolve a hostname to its index path by global settings.
** @param ssb The SSB instance. ** @param ssb The SSB instance.