Compare commits

...

3 Commits

7 changed files with 259 additions and 128 deletions

View File

@ -222,7 +222,7 @@ async function socket(request, response, client) {
} else if (message.action == 'setActiveIdentity') {
process.setActiveIdentity(message.identity);
} else if (message.action == 'createIdentity') {
process.createIdentity();
await process.createIdentity();
} else if (message.message == 'tfrpc') {
if (message.id && g_calls[message.id]) {
if (message.error !== undefined) {

View File

@ -472,7 +472,7 @@ async function getProcessBlob(blobId, key, options) {
process.credentials.session.name &&
process.credentials.session.name !== 'guest'
) {
let id = ssb.createIdentity(process.credentials.session.name);
let id = await ssb.createIdentity(process.credentials.session.name);
await process.sendIdentities();
broadcastAppEventToUser(
process?.credentials?.session?.name,

View File

@ -320,31 +320,90 @@ static JSValue _database_remove(JSContext* context, JSValueConst this_val, int a
return JS_UNDEFINED;
}
typedef struct _database_get_all_t
{
const char* id;
const char* key;
size_t key_length;
char** out_values;
size_t* out_lengths;
int out_values_length;
JSValue promise[2];
} database_get_all_t;
static void _database_get_all_work(tf_ssb_t* ssb, void* user_data)
{
database_get_all_t* work = user_data;
sqlite3_stmt* statement;
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key FROM properties WHERE id = ?", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, work->id, -1, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
work->out_values = tf_resize_vec(work->out_values, sizeof(char*) * (work->out_values_length + 1));
work->out_lengths = tf_resize_vec(work->out_lengths, sizeof(size_t) * (work->out_values_length + 1));
size_t length = sqlite3_column_bytes(statement, 0);
char* data = tf_malloc(length + 1);
memcpy(data, sqlite3_column_text(statement, 0), length);
data[length] = '\0';
work->out_values[work->out_values_length] = data;
work->out_lengths[work->out_values_length] = length;
work->out_values_length++;
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
}
static void _database_get_all_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
database_get_all_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_NewArray(context);
;
for (int i = 0; i < work->out_values_length; i++)
{
JS_SetPropertyUint32(context, result, i, JS_NewStringLen(context, work->out_values[i], work->out_lengths[i]));
tf_free((void*)work->out_values[i]);
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work->out_values);
tf_free(work->out_lengths);
tf_free(work);
}
static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue array = JS_UNDEFINED;
JSValue result = JS_UNDEFINED;
database_t* database = JS_GetOpaque(this_val, _database_class_id);
if (database)
{
sqlite3_stmt* statement;
tf_ssb_t* ssb = tf_task_get_ssb(database->task);
sqlite3* db = tf_ssb_acquire_db_reader(ssb);
if (sqlite3_prepare(db, "SELECT key, value FROM properties WHERE id = ?1", -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK)
{
array = JS_NewArray(context);
uint32_t index = 0;
while (sqlite3_step(statement) == SQLITE_ROW)
{
JS_SetPropertyUint32(context, array, index++, JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)));
}
}
sqlite3_finalize(statement);
}
tf_ssb_release_db_reader(ssb, db);
size_t length;
const char* key = JS_ToCStringLen(context, &length, argv[0]);
database_get_all_t* work = tf_malloc(sizeof(database_get_all_t) + strlen(database->id) + 1 + length + 1);
*work = (database_get_all_t) {
.id = (const char*)(work + 1),
.key = (const char*)(work + 1) + strlen(database->id) + 1,
.key_length = length,
};
memcpy((char*)work->id, database->id, strlen(database->id) + 1);
memcpy((char*)work->key, key, length + 1);
JS_FreeCString(context, key);
tf_ssb_run_work(ssb, _database_get_all_work, _database_get_all_after_work, work);
result = JS_NewPromiseCapability(context, work->promise);
}
return array;
return result;
}
static JSValue _database_get_like(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)

View File

@ -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)

View File

@ -30,35 +30,80 @@ static JSClassID _tf_ssb_classId;
static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
typedef struct _create_identity_t
{
char id[k_id_base64_len];
bool error_add;
bool error_too_many;
JSValue promise[2];
char user[];
} create_identity_t;
static void _tf_ssb_create_identity_work(tf_ssb_t* ssb, void* user_data)
{
create_identity_t* work = user_data;
int count = tf_ssb_db_identity_get_count_for_user(ssb, work->user);
if (count < 16)
{
char public[k_id_base64_len - 1];
char private[512];
tf_ssb_generate_keys_buffer(public, sizeof(public), private, sizeof(private));
if (tf_ssb_db_identity_add(ssb, work->user, public, private))
{
snprintf(work->id, sizeof(work->id), "@%s", public);
}
else
{
work->error_add = true;
}
}
else
{
work->error_too_many = true;
}
}
static void _tf_ssb_create_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_UNDEFINED;
create_identity_t* work = user_data;
if (work->error_too_many)
{
result = JS_ThrowInternalError(context, "Too many identities for user.");
}
else if (work->error_add)
{
result = JS_ThrowInternalError(context, "Unable to add identity.");
}
else
{
result = JS_NewString(context, work->id);
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
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_createIdentity(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)
{
const char* user = JS_ToCString(context, argv[0]);
int count = tf_ssb_db_identity_get_count_for_user(ssb, user);
if (count < 16)
{
char public[512];
char private[512];
tf_ssb_generate_keys_buffer(public, sizeof(public), private, sizeof(private));
if (tf_ssb_db_identity_add(ssb, user, public, private))
{
char id[513];
snprintf(id, sizeof(id), "@%s", public);
result = JS_NewString(context, id);
}
else
{
result = JS_ThrowInternalError(context, "Unable to add identity.");
}
}
else
{
result = JS_ThrowInternalError(context, "Too many identities for user.");
}
size_t length = 0;
const char* user = JS_ToCStringLen(context, &length, argv[0]);
create_identity_t* work = tf_malloc(sizeof(create_identity_t) + length + 1);
*work = (create_identity_t) { 0 };
memcpy(work->user, user, length + 1);
JS_FreeCString(context, user);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_create_identity_work, _tf_ssb_create_identity_after_work, work);
}
return result;
}

View File

@ -279,7 +279,7 @@ static void _test_database(const tf_test_options_t* options)
" await db.set('c', 3);\n"
"\n"
" var expected = ['a', 'b', 'c'];\n"
" var have = db.getAll();\n"
" var have = await db.getAll();\n"
" for (var i = 0; i < have.length; i++) {\n"
" var item = have[i];\n"
" if (expected.indexOf(item) == -1) {\n"

View File

@ -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: