summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--application.rb135
1 files changed, 98 insertions, 37 deletions
diff --git a/application.rb b/application.rb
index 6e700fd..cd8c2ea 100644
--- a/application.rb
+++ b/application.rb
@@ -1,3 +1,4 @@
+# TODO check POST to outbox with mastinator.com
require 'json'
require 'net/http'
require 'uri'
@@ -14,9 +15,6 @@ 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
@@ -29,8 +27,7 @@ class Application
when "/inbox" # receive from server
if verify(env)
- input = JSON.parse(env["rack.input"].gets)
- # p input["type"]
+ save JSON.parse(env["rack.input"].gets), "inbox"
code = 200
response = "OK"
else
@@ -38,10 +35,28 @@ class Application
response = "not verified"
end
- when "/outbox"
- input = JSON.parse(env["rack.input"].gets)
- case input["type"]
- when "Create"
+ when "/outbox" # receive from client
+ # TODO auth
+ if auth(env)
+ input = JSON.parse(env["rack.input"].gets)
+ input["type"] ? activity = input : activity = activity(input) # expand object to create activity
+ save activity, "outbox"
+ deliver ["to", "bto", "cc", "bcc", "audience"].collect { |d| activity[d] }.flatten.uniq
+ case activity["type"]
+ when "Create"
+ when "Update"
+ when "Delete"
+ when "Follow"
+ when "Remove"
+ when "Like"
+ when "Block"
+ when "Undo"
+ end
+ code = 200
+ response = "OK"
+ else
+ code = 403
+ response = "forbidden"
end
end
@@ -56,67 +71,113 @@ class Application
code = 200
when "/#{USER}"
- response = File.read("pdp8")
+ response = File.read(USER)
code = 200
- when "/outbox"
- response = ordered_collection OUTBOX
+ when %r{/[inbox|outbox|following|followers|likes|shares]}
+ response = ordered_collection env["REQUEST_URI"]
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 activity object
+ date = Time.now.strftime("%Y-%m-%dT%H:%M:%S.%3N")
+ object["id"] = "TODO"
+ object["attributedTo"] = ACTOR
+ object["published"] = date
+ {
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "id" => "https://example.net/~mallory/87374",
+ "actor" => ACTOR,
+ "object" => object,
+ "published" => date,
+ "to" => object["to"],
+ "cc" => object["cc"]
+ }
+ end
+
+ def save activity, dir
+ date = Time.now.strftime("%Y-%m-%dT%H:%M:%S.%3N")
+ File.open(File.join(dir, date), "w+") { |f| f.puts activity.to_json }
+ end
+
+ def deliver addr
+ p addr
+ keypair = OpenSSL::PKey::RSA.new(File.read('private.pem'))
+ addr.each do |url|
+ date = Time.now.utc.httpdate
+ uri = URI.parse(url)
+ signed_string = "(request-target): post /inbox\nhost: #{uri.host}\ndate: #{date}"
+ signed_string = keypair.sign(OpenSSL::Digest::SHA256.new, signed_string)
+ signature = Base64.urlsafe_encode64(signed_string).encode("UTF-8")
+ signed_header = 'keyId="#{url}",headers="(request-target) host date",signature="' + signature + '"'
+
+ inbox_url = JSON.parse(Net::HTTP.get(uri,
+ { 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' }))["inbox"]
+ inbox = URI.parse(inbox_url)
+ http = Net::HTTP.new(inbox.host, inbox.port)
+ http.use_ssl = true
+ # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ header = {
+ 'Accept' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
+ 'Host' => inbox.host,
+ 'Date' => date,
+ 'Signature' => signed_header,
+ }
+ request = Net::HTTP::Post.new(inbox.request_uri, header)
+ request.body = document.to_json
+
+ response = http.request(request)
+ puts(response.body, response.code)
+ end
+ end
+
def ordered_collection dir
- posts = Dir[File.join(dir, "*.json")].sort.reverse.collect { |f| p f; JSON.parse(File.read f) }
+ collection = dir.sub(/^\//, "")
+ posts = Dir[File.join(collection, "*.json")].sort.reverse.collect { |f| p f; JSON.parse(File.read f) }
{
"@context" => "https://www.w3.org/ns/activitystreams",
- "summary" => "pdp8 outbox",
+ "summary" => "#{USER} #{collection}",
"type" => "OrderedCollection",
"totalItems" => posts.size,
"orderedItems" => posts,
}.to_json
end
- def verify(env)
+ 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_header = env["HTTP_SIGNATURE"].split(',').each do |pair|
+ pair.gsub('"', '').split('=')
+ end.to_h
+ 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|
+ comparison = 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
+ end.join("\n").encode("ascii-8bit")
+
+ key.verify(OpenSSL::Digest::SHA256.new, signature, comparison)
rescue
false
end
end
+
+ def auth
+ true
+ end
end