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}" OUTBOX = File.join(File.dirname(__FILE__), "outbox") INBOX = File.join(File.dirname(__FILE__), "inbox") class Application def call(env) code = 404 type = "application/activity+json" case env['REQUEST_METHOD'] when 'POST' case env["REQUEST_URI"] when "/inbox" # receive from server if verify(env) input = JSON.parse(env["rack.input"].gets) # p input["type"] code = 200 response = "OK" else code = 401 response = "not verified" end when "/outbox" input = JSON.parse(env["rack.input"].gets) case input["type"] when "Create" end end when 'GET' case env["REQUEST_URI"] when "/.well-known/webfinger?resource=acct:#{ACCOUNT}" type = "application/jrd+json" response = File.read("webfinger") code = 200 when "/#{USER}" response = File.read("pdp8") code = 200 when "/outbox" response = ordered_collection OUTBOX code = 200 when "/inbox" type = "application/activity+json" response = ordered_collection INBOX code = 200 # when "/following" # when "/followers" # when "/liked" end end [code, { "Content-Type" => type }, [response]] end def ordered_collection dir posts = Dir[File.join(dir, "*.json")].sort.reverse.collect { |f| p f; JSON.parse(File.read f) } { "@context" => "https://www.w3.org/ns/activitystreams", "summary" => "pdp8 outbox", "type" => "OrderedCollection", "totalItems" => posts.size, "orderedItems" => posts, }.to_json end def verify(env) begin signature_header = {} 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")) true else false end rescue false end end end