require 'json' require 'net/http' require 'uri' require 'base64' USER = "pdp8" WWW_DOMAIN = "pdp8.info" WWW_URL = "https://#{WWW_DOMAIN}" SOCIAL_DOMAIN = "social.#{WWW_DOMAIN}" ACCOUNT = "#{USER}@#{SOCIAL_DOMAIN}" SOCIAL_URL = "https://#{SOCIAL_DOMAIN}" ACTOR = URI.join(SOCIAL_URL, USER) MATRIX = "@#{USER}:matrix.#{WWW_DOMAIN}" class Application def call(env) code = 404 type = "application/activity+json" case env['REQUEST_METHOD'] when 'POST' case env["REQUEST_URI"] when "/inbox" type = "text/plain" signature_header = {} if env["HTTP_SIGNATURE"].split(',') env["HTTP_SIGNATURE"].split(',').each do |pair| k, v = pair.split('=') signature_header[k] = v.gsub('"', '') end key_id = signature_header['keyId'] headers = signature_header['headers'] signature = Base64.urlsafe_decode64(signature_header['signature'].encode("ascii-8bit")) uri = URI(key_id) res = Net::HTTP.get_response(uri) actor = JSON.parse(res.body) key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem']) comparison_string = headers.split(' ').map do |signed_header_name| if signed_header_name == '(request-target)' '(request-target): post /inbox' else "#{signed_header_name}: #{env["HTTP_" + signed_header_name.upcase]}" end end.join("\n") if key.verify(OpenSSL::Digest::SHA256.new, signature, comparison_string.encode("ascii-8bit")) input = JSON.parse(env["rack.input"].gets) # p input code = 200 response = "OK" else code = 401 response = 'Request signature could not be verified' end else code = 401 response = 'Request signature could not be verified' end end when 'GET' case env["REQUEST_URI"] when "/.well-known/webfinger?resource=acct:#{ACCOUNT}" code = 200 type = "application/jrd+json" response = { "subject" => "acct:#{ACCOUNT}", "links" => [ { "rel" => "self", "type" => "application/activity+json", "href" => ACTOR } ] } when "/#{USER}" code = 200 response = { "@context" => ["https://www.w3.org/ns/activitystreams"], "id" => ACTOR, "type" => "Person", "preferredUsername" => USER, "name" => USER, "inbox" => URI.join(SOCIAL_URL, "inbox"), "outbox" => URI.join(SOCIAL_URL, "outbox"), "following" => URI.join(SOCIAL_URL, "following"), "followers" => URI.join(SOCIAL_URL, "followers"), "liked" => URI.join(SOCIAL_URL, "liked"), "icon" => { "type" => "Image", "url" => "https://pdp8.info/pdp8.png" }, "attachment": [ { "type": "PropertyValue", "name": "Web", "value": "#{WWW_DOMAIN}" }, { "type": "PropertyValue", "name": "Fediverse", "value": "@#{ACCOUNT}" }, { "type": "PropertyValue", "name": "Matrix", "value": "#{MATRIX}" } ], "publicKey" => { "@context" => "https://w3id.org/security/v1", "@type" => "Key", "id" => "#{ACTOR}#main-key", "owner" => ACTOR, "publicKeyPem" => File.read("public.pem") } } when "/outbox" code = 200 type = "application/activity+json" response = { "@context" => "https://www.w3.org/ns/activitystreams", "summary" => "", "type" => "OrderedCollection", "totalItems" => 2, # TODO generate items from src/www "orderedItems" => [ { "type" => "Note", "name" => "A Simple Note", "tag" => [ { "type" => "Hashtag", "name" => "#activitypub", "href" => "https://s3lph.me/activitypub/tags/activitypub" }, ] }, { "type" => "Note", "name" => "Another Simple Note" } ] } when "/following" when "/followers" when "/liked" end end [code, { "Content-Type" => type }, [response.to_json]] end end