From 0423ed7fb49471b828a163c75815dffd254897df Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Wed, 12 Jun 2024 20:12:35 -0400 Subject: [PATCH] Login without hitting the DB from the main thread. --- src/httpd.js.c | 188 ++++++++++++++++++++++++++-------------------- tools/autotest.py | 9 +-- 2 files changed, 112 insertions(+), 85 deletions(-) diff --git a/src/httpd.js.c b/src/httpd.js.c index f15f4c1f..8ce608b1 100644 --- a/src/httpd.js.c +++ b/src/httpd.js.c @@ -37,7 +37,7 @@ const int64_t k_refresh_interval = 1ULL * 7 * 24 * 60 * 60 * 1000; -static JSValue _authenticate_jwt(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 const char* _make_session_jwt(tf_ssb_t* ssb, const char* name); static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie); @@ -330,7 +330,7 @@ static JSValue _httpd_websocket_upgrade(JSContext* context, JSValueConst this_va tf_ssb_t* ssb = tf_task_get_ssb(tf_task_get(context)); const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"); - JSValue jwt = _authenticate_jwt(context, session); + JSValue jwt = _authenticate_jwt(ssb, context, session); tf_free((void*)session); JSValue name = !JS_IsUndefined(jwt) ? JS_GetPropertyStr(context, jwt, "name") : JS_UNDEFINED; const char* name_string = !JS_IsUndefined(name) ? JS_ToCString(context, name) : NULL; @@ -508,7 +508,7 @@ static JSValue _httpd_auth_query(JSContext* context, JSValueConst this_val, int JSValue cookie = JS_GetPropertyStr(context, headers, "cookie"); const char* cookie_string = JS_ToCString(context, cookie); const char* session = tf_http_get_cookie(cookie_string, "session"); - JSValue entry = _authenticate_jwt(context, session); + JSValue entry = _authenticate_jwt(ssb, context, session); tf_free((void*)session); JS_FreeCString(context, cookie_string); JS_FreeValue(context, cookie); @@ -1094,14 +1094,17 @@ const char* _form_data_get(const char** form_data, const char* key) typedef struct _login_request_t { tf_http_request_t* request; - const char* session_cookie; - JSValue jwt; const char* name; const char* error; const char* settings; const char* code_of_conduct; bool have_administrator; bool session_is_new; + + char location_header[1024]; + const char* set_cookie_header; + + int pending; } login_request_t; static const char* _make_set_session_cookie_header(tf_http_request_t* request, const char* session_cookie) @@ -1116,18 +1119,29 @@ static const char* _make_set_session_cookie_header(tf_http_request_t* request, c return cookie; } +static void _login_release(login_request_t* login) +{ + int ref_count = --login->pending; + if (ref_count == 0) + { + tf_free((void*)login->name); + tf_free((void*)login->code_of_conduct); + tf_free((void*)login->set_cookie_header); + tf_free(login); + } +} + static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char* path, int result, const void* data, void* user_data) { login_request_t* login = user_data; tf_http_request_t* request = login->request; if (result >= 0) { - const char* cookie = _make_set_session_cookie_header(request, login->session_cookie); const char* headers[] = { "Content-Type", "text/html; charset=utf-8", "Set-Cookie", - cookie ? cookie : "", + login->set_cookie_header ? login->set_cookie_header : "", }; const char* replace_me = "$AUTH_DATA"; const char* auth = strstr(data, replace_me); @@ -1161,7 +1175,6 @@ static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char { tf_http_respond(request, 200, headers, tf_countof(headers) / 2, data, result); } - tf_free((void*)cookie); } else { @@ -1169,10 +1182,7 @@ static void _httpd_endpoint_login_file_read_callback(tf_task_t* task, const char tf_http_respond(request, 404, NULL, 0, k_payload, strlen(k_payload)); } tf_http_request_unref(request); - tf_free((void*)login->name); - tf_free((void*)login->code_of_conduct); - tf_free((void*)login->session_cookie); - tf_free(login); + _login_release(login); } static bool _string_property_equals(JSContext* context, JSValue object, const char* name, const char* value) @@ -1185,7 +1195,7 @@ static bool _string_property_equals(JSContext* context, JSValue object, const ch return equals; } -static JSValue _authenticate_jwt(JSContext* context, const char* jwt) +static JSValue _authenticate_jwt(tf_ssb_t* ssb, JSContext* context, const char* jwt) { if (!jwt) { @@ -1226,8 +1236,6 @@ static JSValue _authenticate_jwt(JSContext* context, const char* jwt) return JS_UNDEFINED; } - tf_task_t* task = tf_task_get(context); - tf_ssb_t* ssb = tf_task_get_ssb(task); char public_key_b64[k_id_base64_len] = { 0 }; tf_ssb_whoami(ssb, public_key_b64, sizeof(public_key_b64)); @@ -1343,32 +1351,6 @@ static bool _verify_password(const char* password, const char* hash) return out_hash && strcmp(hash, out_hash) == 0; } -static void _httpd_endpoint_login_get_code_of_conduct_work(tf_ssb_t* ssb, void* user_data) -{ - login_request_t* login = user_data; - login->settings = tf_ssb_db_get_property(ssb, "core", "settings"); -} - -static void _httpd_endpoint_login_get_code_of_conduct_after_work(tf_ssb_t* ssb, int status, void* user_data) -{ - login_request_t* login = user_data; - if (login->settings) - { - JSContext* context = tf_ssb_get_context(ssb); - JSValue settings_value = JS_ParseJSON(context, login->settings, strlen(login->settings), NULL); - JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct"); - const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value); - const char* result = tf_strdup(code_of_conduct); - JS_FreeCString(context, code_of_conduct); - JS_FreeValue(context, code_of_conduct_value); - JS_FreeValue(context, settings_value); - tf_free((void*)login->settings); - login->settings = NULL; - login->code_of_conduct = result; - } - tf_file_read(login->request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login); -} - static bool _make_administrator_if_first(tf_ssb_t* ssb, const char* account_name_copy, bool may_become_first_admin) { JSContext* context = tf_ssb_get_context(ssb); @@ -1442,30 +1424,32 @@ static bool _make_administrator_if_first(tf_ssb_t* ssb, const char* account_name return have_administrator; } -static void _httpd_endpoint_login(tf_http_request_t* request) +static void _httpd_endpoint_login_work(tf_ssb_t* ssb, void* user_data) { - tf_task_t* task = request->user_data; - JSContext* context = tf_task_get_context(task); - tf_ssb_t* ssb = tf_task_get_ssb(task); + login_request_t* login = user_data; + tf_http_request_t* request = login->request; + + JSMallocFunctions funcs = { 0 }; + tf_get_js_malloc_functions(&funcs); + JSRuntime* runtime = JS_NewRuntime2(&funcs, NULL); + JSContext* context = JS_NewContext(runtime); + const char* session = tf_http_get_cookie(tf_http_request_get_header(request, "cookie"), "session"); const char** form_data = _form_data_decode(request->query, request->query ? strlen(request->query) : 0); const char* account_name_copy = NULL; - JSValue jwt = _authenticate_jwt(context, session); + JSValue jwt = _authenticate_jwt(ssb, context, session); if (_session_is_authenticated_as_user(context, jwt)) { const char* return_url = _form_data_get(form_data, "return"); - char url[1024]; - if (!return_url) + if (return_url) { - snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host")); - return_url = url; + snprintf(login->location_header, sizeof(login->location_header), "%s", return_url); + } + else + { + snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host")); } - const char* headers[] = { - "Location", - return_url, - }; - tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); goto done; } @@ -1544,40 +1528,46 @@ static void _httpd_endpoint_login(tf_http_request_t* request) if (session_is_new && _form_data_get(form_data, "return") && !login_error) { const char* return_url = _form_data_get(form_data, "return"); - char url[1024]; - if (!return_url) + if (return_url) { - snprintf(url, sizeof(url), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host")); - return_url = url; + snprintf(login->location_header, sizeof(login->location_header), "%s", return_url); } - const char* cookie = _make_set_session_cookie_header(request, send_session); - const char* headers[] = { - "Location", - return_url, - "Set-Cookie", - cookie ? cookie : "", - }; - tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); - tf_free((void*)cookie); + else + { + snprintf(login->location_header, sizeof(login->location_header), "%s%s/", request->is_tls ? "https://" : "http://", tf_http_request_get_header(request, "host")); + } + login->set_cookie_header = _make_set_session_cookie_header(request, send_session); tf_free((void*)send_session); } else { tf_http_request_ref(request); - login_request_t* login = tf_malloc(sizeof(login_request_t)); - *login = (login_request_t) { - .request = request, - .name = account_name_copy, - .jwt = jwt, - .error = login_error, - .session_cookie = send_session, - .session_is_new = session_is_new, - .have_administrator = have_administrator, - }; + login->name = account_name_copy; + login->error = login_error; + login->set_cookie_header = _make_set_session_cookie_header(request, send_session); + tf_free((void*)send_session); + login->session_is_new = session_is_new; + login->have_administrator = have_administrator; + login->settings = tf_ssb_db_get_property(ssb, "core", "settings"); + + if (login->settings) + { + JSValue settings_value = JS_ParseJSON(context, login->settings, strlen(login->settings), NULL); + JSValue code_of_conduct_value = JS_GetPropertyStr(context, settings_value, "code_of_conduct"); + const char* code_of_conduct = JS_ToCString(context, code_of_conduct_value); + const char* result = tf_strdup(code_of_conduct); + JS_FreeCString(context, code_of_conduct); + JS_FreeValue(context, code_of_conduct_value); + JS_FreeValue(context, settings_value); + tf_free((void*)login->settings); + login->settings = NULL; + login->code_of_conduct = result; + } + + login->pending++; + tf_file_read(login->request->user_data, "core/auth.html", _httpd_endpoint_login_file_read_callback, login); - tf_ssb_run_work(ssb, _httpd_endpoint_login_get_code_of_conduct_work, _httpd_endpoint_login_get_code_of_conduct_after_work, login); - jwt = JS_UNDEFINED; account_name_copy = NULL; } @@ -1586,6 +1576,44 @@ done: tf_free(form_data); tf_free((void*)account_name_copy); JS_FreeValue(context, jwt); + + JS_FreeContext(context); + JS_FreeRuntime(runtime); +} + +static void _httpd_endpoint_login_after_work(tf_ssb_t* ssb, int status, void* user_data) +{ + login_request_t* login = user_data; + if (login->pending == 1) + { + tf_http_request_t* request = login->request; + if (*login->location_header) + { + const char* headers[] = { + "Location", + login->location_header, + "Set-Cookie", + login->set_cookie_header ? login->set_cookie_header : "", + }; + tf_http_respond(request, 303, headers, tf_countof(headers) / 2, NULL, 0); + } + tf_http_request_unref(request); + } + _login_release(login); +} + +static void _httpd_endpoint_login(tf_http_request_t* request) +{ + tf_task_t* task = request->user_data; + tf_http_request_ref(request); + + tf_ssb_t* ssb = tf_task_get_ssb(task); + login_request_t* login = tf_malloc(sizeof(login_request_t)); + *login = (login_request_t) { + .request = request, + }; + login->pending++; + tf_ssb_run_work(ssb, _httpd_endpoint_login_work, _httpd_endpoint_login_after_work, login); } static void _httpd_endpoint_logout(tf_http_request_t* request) diff --git a/tools/autotest.py b/tools/autotest.py index bcd28680..fd67a2d4 100755 --- a/tools/autotest.py +++ b/tools/autotest.py @@ -42,20 +42,19 @@ try: driver.switch_to.frame(driver.find_element(By.ID, 'document')) wait.until(expected_conditions.presence_of_element_located((By.LINK_TEXT, 'identity'))).click() - driver.switch_to.default_content() - - wait.until(expected_conditions.presence_of_element_located((By.ID, 'content'))) # StaleElementReferenceException while True: try: + driver.switch_to.default_content() + wait.until(expected_conditions.presence_of_element_located((By.ID, 'content'))) driver.switch_to.frame(wait.until(expected_conditions.presence_of_element_located((By.ID, 'document')))) + wait.until(expected_conditions.presence_of_element_located((By.ID, 'create_id'))).click() + driver.switch_to.alert.accept() break except: pass - wait.until(expected_conditions.presence_of_element_located((By.ID, 'create_id'))).click() - driver.switch_to.alert.accept() # StaleElementReferenceException while True: try: