diff --git a/Slon/Api/V1/Accounts.HC b/Slon/Api/V1/Accounts.HC index 5602235..72031dd 100644 --- a/Slon/Api/V1/Accounts.HC +++ b/Slon/Api/V1/Accounts.HC @@ -1,3 +1,174 @@ +U0 @slon_api_v1_accounts_follow_request(U8* this_actor, U8* remote_actor) +{ + + U8 scratch_buffer[2048]; + + StrPrint(scratch_buffer, "%s/follow/%d", this_actor, Now); + JsonObject* follow_object = Json.CreateObject(); + follow_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING); + follow_object->set("id", scratch_buffer, JSON_STRING); + follow_object->set("type", "Follow", JSON_STRING); + follow_object->set("actor", this_actor, JSON_STRING); + follow_object->set("object", remote_actor, JSON_STRING); + + U8* follow_object_s = Json.Stringify(follow_object); + + U8 content_hash[512]; + calc_sha_256(content_hash, follow_object_s, StrLen(follow_object_s)); + U8* computed_digest = @base64_encode(content_hash, 32); + + JsonObject* http_headers = Json.CreateObject(); + + StrPrint(scratch_buffer, "%s/inbox", remote_actor); + HttpUrl* url = @http_parse_url(scratch_buffer); + + CDateStruct ds; + Date2Struct(&ds, Now + 1043910000); + StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS", + ds.year, ds.hour, ds.min, ds.sec); + http_headers->set("Date", scratch_buffer, JSON_STRING); + + StrPrint(scratch_buffer, "SHA-256=%s", computed_digest); + http_headers->set("Digest", scratch_buffer, JSON_STRING); + + http_headers->set("Content-Type", "application/activity+json", JSON_STRING); + + StrPrint(scratch_buffer, ""); + String.Append(scratch_buffer, "(request-target): post %s\n", url->path); + String.Append(scratch_buffer, "host: %s\n", url->host); + String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date")); + String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest")); + String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type")); + + AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer); + + calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer)); + + U8* user = StrFind("/users/", this_actor) + 7; + JsonObject* private_key_binary = db->o("private_keys_binary")->o(user); + if (!private_key_binary) { + I64 private_key_binary_size = 0; + private_key_binary = Json.CreateObject(); + private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT); + private_key_binary->set("size", private_key_binary_size, JSON_NUMBER); + db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT); + } + + I64 res; + + // Import RSA key + U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task); + res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key); + AdamLog("@rsa_import: res: %d\n", res); + + U8 sig[256]; + U64 siglen = 256; + res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key); + AdamLog("@rsa_create_signature: res: %d\n", res); + U8* computed_sig = @base64_encode(sig, 256); + + StrCpy(scratch_buffer, ""); + String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor); + String.Append(scratch_buffer, "algorithm=\"rsa-sha256\","); + String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\","); + String.Append(scratch_buffer, "signature=\"%s\"", computed_sig); + http_headers->set("Signature", scratch_buffer, JSON_STRING); + + U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task); + @http_response* resp = Http.Post(url, fetch_buffer, follow_object_s, http_headers); + + if (!resp) { + @slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server"); + Free(fetch_buffer); + return; + } + + while (resp->state != HTTP_STATE_DONE) { + Sleep(1); + } + + AdamLog("code: %d\n", resp->status.code); + + Free(fetch_buffer); +} + +U0 @slon_api_v1_accounts_post(SlonHttpSession* session) +{ + if (!@slon_api_authorized(session)) { + @slon_http_set_status_code(session, 401); + return; + } + + SLON_AUTH_ACCOUNT_ID + JsonObject* my_acct = @slon_api_account_by_id(account_id); + + I64 i; + + U8* path = @slon_strnew(session, @slon_http_request_path(session)); + I64 path_segments_count = 0; + U8** path_segments = String.Split(path, '/', &path_segments_count); + + if (2 == 3) { + // placeholder for other methods + } else { + // Work with account :id + U8* some_account_id = path_segments[3]; + JsonObject* acct = @slon_api_account_by_id(some_account_id); + if (!acct) { + @slon_http_set_status_code(session, 404); + goto slon_api_v1_accounts_post_return; + } + if (path_segments_count > 5) { + U8* method = path_segments[4]; + if (!StrICmp("follow", method)) { + if (!acct->@("remote_actor")) { + @slon_http_set_status_code(session, 404); + goto slon_api_v1_accounts_post_return; + } + // add to my following + if (!db->o("following")->a(my_acct->@("username"))) { + db->o("following")->set(my_acct->@("username"), Json.CreateArray(), JSON_ARRAY); + } + db->o("following")->a(my_acct->@("username"))->append(Json.CreateItem(acct->@("remote_actor"), JSON_STRING)); + @slon_db_save_to_disk; + // send Follow request + @slon_api_v1_accounts_follow_request(db->o("actors")->o((my_acct->@("username")))->@("id"), acct->@("remote_actor")); + + Bool followed_by = FALSE; + JsonArray* my_followers = db->o("followers")->a(my_acct->@("username")); + if (my_followers) { + for (i = 0; i < my_followers->length; i++) { + if (my_followers->@(i) && !StrICmp(my_followers->@(i), acct->@("remote_actor"))) { + followed_by = TRUE; + break; + } + } + } + JsonObject* relationship = Json.CreateObject(); + relationship->set("id", acct->@("id"), JSON_STRING); + relationship->set("following", TRUE, JSON_BOOLEAN); + relationship->set("showing_reblogs", TRUE, JSON_BOOLEAN); + relationship->set("notifying", FALSE, JSON_BOOLEAN); + relationship->set("followed_by", followed_by, JSON_BOOLEAN); + relationship->set("blocking", FALSE, JSON_BOOLEAN); + relationship->set("blocked_by", FALSE, JSON_BOOLEAN); + relationship->set("muting", FALSE, JSON_BOOLEAN); + relationship->set("muting_notifications", FALSE, JSON_BOOLEAN); + relationship->set("requested", FALSE, JSON_BOOLEAN); + relationship->set("domain_blocking", FALSE, JSON_BOOLEAN); + relationship->set("endorsed", FALSE, JSON_BOOLEAN); + @slon_http_send_json(session, relationship); + goto slon_api_v1_accounts_post_return; + } + @slon_http_set_status_code(session, 404); + } else { + @slon_http_set_status_code(session, 404); + } + } +slon_api_v1_accounts_post_return: + @slon_free(session, path); +} + U0 @slon_api_v1_accounts_get(SlonHttpSession* session) { SLON_SCRATCH_BUFFER_AND_REQUEST_JSON diff --git a/Slon/Endpoints/Post/Accounts.HC b/Slon/Endpoints/Post/Accounts.HC new file mode 100644 index 0000000..c5a708d --- /dev/null +++ b/Slon/Endpoints/Post/Accounts.HC @@ -0,0 +1,4 @@ +if (String.BeginsWith("/api/v1/accounts", @slon_http_request_path(session))) { + @slon_api_v1_accounts_post(session); + return; +} diff --git a/Slon/Http/Server.HC b/Slon/Http/Server.HC index ff04a99..13471f1 100644 --- a/Slon/Http/Server.HC +++ b/Slon/Http/Server.HC @@ -459,6 +459,7 @@ U0 @slon_http_handle_post_request(SlonHttpSession* session) /* clang-format off */ + #include "Endpoints/Post/Accounts"; #include "Endpoints/Post/ActivityPub"; #include "Endpoints/Post/Apps"; #include "Endpoints/Post/OAuth"; diff --git a/Slon/Modules/ActivityPub.HC b/Slon/Modules/ActivityPub.HC index a85247b..abf21b9 100644 --- a/Slon/Modules/ActivityPub.HC +++ b/Slon/Modules/ActivityPub.HC @@ -367,102 +367,6 @@ U0 @slon_activitypub_async_accept_request(JsonObject* o) Free(fetch_buffer); } -U0 @slon_activitypub_follow_request() -{ - - U8 scratch_buffer[2048]; - - U8* this_actor = "https://error.checksum.fail/users/alec"; - - StrPrint(scratch_buffer, "%s/follow/%d", this_actor, Now); - JsonObject* follow_object = Json.CreateObject(); - follow_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING); - follow_object->set("id", scratch_buffer, JSON_STRING); - follow_object->set("type", "Follow", JSON_STRING); - follow_object->set("actor", this_actor, JSON_STRING); - follow_object->set("object", "https://techhub.social/users/ryeucrvtexw3", JSON_STRING); - - U8* follow_object_s = Json.Stringify(follow_object); - - U8 content_hash[512]; - calc_sha_256(content_hash, follow_object_s, StrLen(follow_object_s)); - U8* computed_digest = @base64_encode(content_hash, 32); - - JsonObject* http_headers = Json.CreateObject(); - - StrPrint(scratch_buffer, "%s/inbox", "https://techhub.social/users/ryeucrvtexw3"); - HttpUrl* url = @http_parse_url(scratch_buffer); - - CDateStruct ds; - Date2Struct(&ds, Now + 1043910000); - StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS", - ds.year, ds.hour, ds.min, ds.sec); - http_headers->set("Date", scratch_buffer, JSON_STRING); - - StrPrint(scratch_buffer, "SHA-256=%s", computed_digest); - http_headers->set("Digest", scratch_buffer, JSON_STRING); - - http_headers->set("Content-Type", "application/activity+json", JSON_STRING); - - StrPrint(scratch_buffer, ""); - String.Append(scratch_buffer, "(request-target): post %s\n", url->path); - String.Append(scratch_buffer, "host: %s\n", url->host); - String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date")); - String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest")); - String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type")); - - AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer); - - calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer)); - - U8* user = StrFind("/users/", this_actor) + 7; - JsonObject* private_key_binary = db->o("private_keys_binary")->o(user); - if (!private_key_binary) { - I64 private_key_binary_size = 0; - private_key_binary = Json.CreateObject(); - private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT); - private_key_binary->set("size", private_key_binary_size, JSON_NUMBER); - db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT); - } - - I64 res; - - // Import RSA key - U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task); - res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key); - AdamLog("@rsa_import: res: %d\n", res); - - U8 sig[256]; - U64 siglen = 256; - res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key); - AdamLog("@rsa_create_signature: res: %d\n", res); - U8* computed_sig = @base64_encode(sig, 256); - - StrCpy(scratch_buffer, ""); - String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor); - String.Append(scratch_buffer, "algorithm=\"rsa-sha256\","); - String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\","); - String.Append(scratch_buffer, "signature=\"%s\"", computed_sig); - http_headers->set("Signature", scratch_buffer, JSON_STRING); - - U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task); - @http_response* resp = Http.Post(url, fetch_buffer, follow_object_s, http_headers); - - if (!resp) { - @slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server"); - Free(fetch_buffer); - return; - } - - while (resp->state != HTTP_STATE_DONE) { - Sleep(1); - } - - AdamLog("code: %d\n", resp->status.code); - - Free(fetch_buffer); -} - U0 @slon_activitypub_async_create_status(JsonObject* status) { Sleep(2000);