diff options
Diffstat (limited to 'activitypub.rb')
-rw-r--r-- | activitypub.rb | 255 |
1 files changed, 101 insertions, 154 deletions
diff --git a/activitypub.rb b/activitypub.rb index c019d6e..05ed6e0 100644 --- a/activitypub.rb +++ b/activitypub.rb @@ -20,7 +20,8 @@ ACCOUNT = "#{USER}@#{SOCIAL_DOMAIN}" SOCIAL_URL = "https://#{SOCIAL_DOMAIN}" ACTOR = File.join(SOCIAL_URL, USER) -use Rack::Reloader +enable :sessions +set :session_secret, File.read(".secret").chomp set :default_content_type, 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' set :port, 9292 @@ -33,24 +34,30 @@ end post "/inbox" do # server-server request.body.rewind # in case someone already read it body = request.body.read + p body action = JSON.parse body case action["type"] + when "Create" create action["object"] + when "Delete" delete action["object"] + when "Update" delete action["object"] create action["object"] + when "Follow" File.open(File.join("public", "followers", mention(action["actor"]) + ".json"), "w+") { |f| f.puts body } accept = { "@context" => "https://www.w3.org/ns/activitystreams", "id" => File.join(SOCIAL_URL + "#accepts", SecureRandom.uuid), "type" => "Accept", "actor" => ACTOR, - "object" => object } - send_signed accept, accept["object"]["actor"] + "object" => action["object"] } + send_signed accept, action["actor"] + when "Undo" o = action["object"] case o["type"] @@ -59,12 +66,14 @@ post "/inbox" do # server-server FileUtils.rm follower if JSON.parse(File.read(follower))["actor"] == o["actor"] end end + else p body end end post "/outbox" do # client-server + protected! request.body.rewind # in case someone already read it body = request.body.read date = Time.now.strftime("%Y-%m-%dT%H:%M:%S") @@ -107,54 +116,75 @@ post "/outbox" do # client-server recipients.each { |r| send_signed create, r } end +post "/delete/*" do + protected! + FileUtils.rm params['splat'][0] + redirect to("/inbox") +end + +post "/follow/*" do + protected! + mention = params['splat'][0] + actor = actor(mention) + p actor + following_path = File.join("public", "following", mention + ".json") + follow = { "@context" => "https://www.w3.org/ns/activitystreams", + "id" => File.join(SOCIAL_URL, following_path), + "type" => "Follow", + "actor" => ACTOR, + "object" => actor } + send_signed follow, actor + File.open(following_path, "w+") { |f| f.puts follow.to_json } + redirect to("/inbox") +end + +post "/unfollow/*" do + protected! + mention = params['splat'][0] + actor = actor(mention) + following_path = File.join("public", "following", mention + ".json") + if File.exists?(following_path) + undo = { "@context" => "https://www.w3.org/ns/activitystreams", + "id" => File.join(SOCIAL_URL + "#undo", SecureRandom.uuid), + "type" => "Undo", + "actor" => ACTOR, + "object" => JSON.parse(File.read(following_path)) } + send_signed undo, actor + FileUtils.rm following_path + redirect to("/inbox") + end +end + +post "/login" do + session["client"] = true if params["secret"] == File.read(".pwd").chomp + redirect to("/inbox") +end + get "/.well-known/webfinger" do if request["resource"] == "acct:#{ACCOUNT}" send_file "./webfinger", :type => "application/jrd+json" else - 404 + halt 404 end end get "/inbox", :provides => 'html' do - erb "<!DOCTYPE html> - <html lang='en'> - <body> - <% Dir['./inbox/*'].sort.each_with_index do |file,i| %> - <% item = JSON.parse(File.read(file)) %> - <%= i+1 %> <b><%= mention item['attributedTo'] %></b> <i><%= item['published'].sub('T', ' ') %></i> - <p><%= item['content'] %> - <% if item['attachment'] - item['attachment'].each do |att| - case att['mediaType'] - when /audio/ %> - <br><audio controls=''><source src='<%= att['url'] %>' type='<%= att['mediaType'] %>'></audio> - <% when /image/ %> - <br><a href='<%= att['url'] %>'><img src='<%= att['url'] %>'></a> - <% when /video/ %> - <br><video controls=''><source src='<%= att['url'] %>' type='<%= att['mediaType'] %>'></video> - <% else %> - <%= att %><br> - <a href='<%= att['url'] %>'><%= att['url'] %></a> - <% end %> - <% end %> - <% end %> - <p> - <form action='<%= File.join 'delete', file %>' method='post'> - <button>Delete</button> - </form> - <form action='<%= File.join 'boost', file %>' method='post'> - <button>Boost</button> - </form> - <form action='<%= File.join 'archive', file %>' method='post'> - <button>Archive</button> - </form> - <form action='<%= File.join 'reply', file %>' method='post'> - <button>Reply</button> - </form> - <hr> - <% end %> - </body> - </html>" + protected! + @inbox = Dir['./inbox/*'].sort + p @inbox + erb :inbox +end + +["/outbox","/following","/followers"].each do |path| + get path do + ordered_collection(path).to_json + end +end + +helpers do + def protected! + redirect("/login.html") unless session['client'] + end end def delete object @@ -183,7 +213,7 @@ def get url, accept = 'application/ld+json; profile="https://www.w3.org/ns/activ end def ordered_collection dir - posts = Dir[File.join(dir, "*.json")].collect { |f| JSON.parse(File.read f) }.sort_by { |o| o["published"] } + posts = Dir[File.join("public",dir, "*.json")].collect { |f| JSON.parse(File.read f) }.sort_by { |o| o["published"] } { "@context" => "https://www.w3.org/ns/activitystreams", "summary" => "#{USER} #{dir}", @@ -224,36 +254,43 @@ def send_signed object, url end def inbox uri + p "INBOX" + p uri + p get(uri) URI(get(uri)["inbox"]).request_uri end def verify_signature env # https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb # TODO verify digest - signature_params = {} - env["HTTP_SIGNATURE"].split(',').each do |pair| - k, v = pair.split('=') - signature_params[k] = v.gsub('"', '') - end + begin + signature_params = {} + env["HTTP_SIGNATURE"].split(',').each do |pair| + k, v = pair.split('=') + signature_params[k] = v.gsub('"', '') + end - key_id = signature_params['keyId'] - headers = signature_params['headers'] - signature = Base64.decode64(signature_params['signature']) + key_id = signature_params['keyId'] + headers = signature_params['headers'] + signature = Base64.decode64(signature_params['signature']) - actor = get key_id - key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem']) + actor = get key_id + key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem']) - comparison = headers.split(' ').map do |signed_params_name| - if signed_params_name == '(request-target)' - '(request-target): post /inbox' - elsif signed_params_name == 'content-type' - "#{signed_params_name}: #{env["CONTENT_TYPE"]}" - else - "#{signed_params_name}: #{env["HTTP_" + signed_params_name.upcase]}" - end - end.join("\n") + comparison = headers.split(' ').map do |signed_params_name| + if signed_params_name == '(request-target)' + '(request-target): post /inbox' + elsif signed_params_name == 'content-type' + "#{signed_params_name}: #{env["CONTENT_TYPE"]}" + else + "#{signed_params_name}: #{env["HTTP_" + signed_params_name.upcase]}" + end + end.join("\n") - key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison) + key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison) + rescue + false + end end def actor mention @@ -264,93 +301,3 @@ def actor mention l["rel"] == "self" }[0]["href"] end - -=begin - -class Application - def call(env) - code = 404 - type = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' - response = "Not found." - - case env['REQUEST_METHOD'] - - when 'POST' - input = env["rack.input"].read - case env["REQUEST_PATH"] - - when %r{/delete} # receive from client - if auth(env) - FileUtils.rm env["REQUEST_URI"].sub("/delete/", "") - return [302, { "Location" => "/inbox" }, []] - end - - when "/outbox" # receive from client - p "outbox" - if auth(env) - p "OK" - else - code = 403 - response = "You are not allowed to POST to #{env["REQUEST_URI"]}." - end - - when "/follow" # receive from client - if auth(env) - input.split.each do |mention| - actor = actor(mention) - follow = { "@context" => "https://www.w3.org/ns/activitystreams", - "id" => File.join(SOCIAL_URL, "following", SecureRandom.uuid + ".json"), - "type" => "Follow", - "actor" => ACTOR, - "object" => actor } - save follow - puts(send follow, [actor]) - code = 200 - response = "OK" - end - else - code = 403 - response = "You are not allowed to POST to #{env["REQUEST_URI"]}." - end - - when "/unfollow" # receive from client - if auth(env) - input.split.each do |mention| - actor = actor(mention) - Dir["following/*.json"].each do |f| - follow = JSON.parse(File.read(f)) - puts follow - if follow["object"] == actor - undo = { "@context" => "https://www.w3.org/ns/activitystreams", - "id" => File.join(SOCIAL_URL + "#undo", SecureRandom.uuid), - "type" => "Undo", - "actor" => ACTOR, - "object" => follow } - send undo, [actor] - FileUtils.rm f - end - end - end - end - end - - - def path object - object["id"].sub(SOCIAL_URL, '').sub('/', '') - end - - def save object - path = path object - FileUtils.mkdir_p File.dirname(path) - File.open(path, "w+") { |f| f.puts object.to_json } - end - - def auth env - auth = Rack::Auth::Basic::Request.new(env) - usr = File.read(".usr").chomp - pwd = File.read(".pwd").chomp - auth.provided? && auth.basic? && auth.credentials && auth.credentials == [usr, pwd] - true - end -end -=end |