diff options
-rw-r--r-- | client.rb | 45 | ||||
-rw-r--r-- | helpers.rb | 77 | ||||
-rw-r--r-- | server.rb | 73 | ||||
-rw-r--r-- | views/item.erb | 15 |
4 files changed, 118 insertions, 92 deletions
@@ -2,11 +2,19 @@ post "/outbox" do protected! request.body.rewind # in case someone already read it - body = request.body.read + #body = request.body.read date = Time.now.strftime("%Y-%m-%dT%H:%M:%S") # TODO hashtags, replys outbox_path = File.join("public/outbox", date + ".json") object_path = File.join("public/objects", date + ".json") + + #recipients = ["https://www.w3.org/ns/activitystreams#Public", params[:to]] + recipients = [params[:to]] + #recipients += Dir[File.join("public/followers", "*.json")].collect { |f| JSON.parse(File.read(f))["actor"] } + recipients.delete ACTOR + recipients.uniq! + + p params create = { "@context" => "https://www.w3.org/ns/activitystreams", "id" => File.join(SOCIAL_URL, outbox_path), @@ -16,31 +24,34 @@ post "/outbox" do "id" => File.join(SOCIAL_URL, object_path), "type" => "Note", "attributedTo" => ACTOR, + "inReplyTo" => params[:inReplyTo], "published" => date, - "content" => "", - "to" => ["https://www.w3.org/ns/activitystreams#Public"] + "content" => params[:content], + "to" => recipients }, "published" => date, - "to" => ["https://www.w3.org/ns/activitystreams#Public"] + "to" => recipients } - recipients = [] +=begin if /^@/.match body mentions, body = body.split("\n", 2) mentions.split(/, */).each do |m| recipients << actor(m.chomp) end end - create["object"]["content"] = body.lines.select { |l| !l.empty? }.join("<br>") - recipients += Dir[File.join("public/followers", "*.json")].collect { |f| JSON.parse(File.read(f))["actor"] } - recipients.delete ACTOR - recipients.uniq! - create["object"]["to"] += recipients - create["to"] += recipients +=end + #create["object"]["content"] = body#.lines.select { |l| !l.empty? }.join("<br>") + #create["object"]["to"] += recipients + #create["to"] += recipients + #p params["to"] + p create File.open(outbox_path, "w+") { |f| f.puts create.to_json } File.open(object_path, "w+") { |f| f.puts create["object"].to_json } - recipients.each { |r| send_signed create, r } + #p recipients + create["to"].each { |r| send_signed create, r } + redirect to(params['redirect']) end post "/archive" do @@ -65,8 +76,6 @@ post "/follow/*" do protected! mention = params['splat'][0] actor = actor(mention) - p mention - p actor follow = { "@context" => "https://www.w3.org/ns/activitystreams", "id" => File.join(SOCIAL_URL, "following", mention + ".json"), "type" => "Follow", @@ -115,3 +124,11 @@ end erb :index end end + +helpers do + + def protected! + redirect("/login.html") unless session['client'] + end + +end @@ -1,43 +1,5 @@ helpers do - def protected! - redirect("/login.html") unless session['client'] - end - - # https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb - def verify! - # TODO verify digest - begin - signature_params = {} - request.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']) - - actor = fetch 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}: #{request.env["CONTENT_TYPE"]}" - else - "#{signed_params_name}: #{request.env["HTTP_" + signed_params_name.upcase]}" - end - end.join("\n") - - halt 400 unless key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison) - rescue => e - p request.env["HTTP_SIGNATURE"], e - halt 400 - end - end - def items nr = 0 @items = Dir[File.join(@dir, '*.json')].sort.collect do |file| @@ -82,30 +44,6 @@ helpers do erb :item end - def delete object - Dir["inbox/*.json"].each do |doc| - FileUtils.rm doc if JSON.parse(File.read(doc))["id"] == object["id"] - end - end - - def create object - unless object['type'] == 'Person' - doc = File.join("inbox", "#{Time.now.strftime('%Y-%m-%dT%H:%M:%S.%N')}.json") - File.open(doc, "w+") { |f| f.puts object.to_json } - if object['inReplyTo'] - @dir = 'inbox' - items - if @items.select{|it| it[:id] == object['inReplyTo'] }.empty? - download object['inReplyTo'] - end - end - end - end - - def download object_url - create fetch(object_url) - end - def people File.read('cache/people.tsv').split("\n").collect {|l| l.chomp.split("\t")} end @@ -144,17 +82,6 @@ helpers do JSON.parse(response) end - def ordered_collection dir - 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}", - "type" => "OrderedCollection", - "totalItems" => posts.size, - "orderedItems" => posts, - } - end - # https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb def send_signed object, url @@ -171,7 +98,9 @@ helpers do signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) signed_header = 'keyId="' + ACTOR + '#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="' + signature + '"' - puts `/run/current-system/sw/bin/curl --fail-with-body -sSL -X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}' #{inbox}` + p url + #puts `/run/current-system/sw/bin/curl --fail-with-body -sSL -X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}' #{inbox}` + puts `/run/current-system/sw/bin/curl -iL -X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}' #{inbox}` end end @@ -74,3 +74,76 @@ end ordered_collection(path).to_json end end + +helpers do + + # https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb + def verify! + # TODO verify digest + begin + signature_params = {} + request.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']) + + actor = fetch 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}: #{request.env["CONTENT_TYPE"]}" + else + "#{signed_params_name}: #{request.env["HTTP_" + signed_params_name.upcase]}" + end + end.join("\n") + + halt 400 unless key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison) + rescue => e + p request.env["HTTP_SIGNATURE"], e + halt 400 + end + end + + def create object + unless object['type'] == 'Person' + doc = File.join("inbox", "#{Time.now.strftime('%Y-%m-%dT%H:%M:%S.%N')}.json") + File.open(doc, "w+") { |f| f.puts object.to_json } + if object['inReplyTo'] + @dir = 'inbox' + items + if @items.select{|it| it[:id] == object['inReplyTo'] }.empty? + download object['inReplyTo'] + end + end + end + end + + def download object_url + create fetch(object_url) + end + + def delete object + Dir["inbox/*.json"].each do |doc| + FileUtils.rm doc if JSON.parse(File.read(doc))["id"] == object["id"] + end + end + + def ordered_collection dir + 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}", + "type" => "OrderedCollection", + "totalItems" => posts.size, + "orderedItems" => posts, + } + end + +end diff --git a/views/item.erb b/views/item.erb index aafc4cc..933fe71 100644 --- a/views/item.erb +++ b/views/item.erb @@ -5,15 +5,15 @@ </form> <form action='/delete' method='post'> - <input type='hidden' name='file' id='file' value='<%= @item[:file] %>' /> - <input type='hidden' name='redirect' id='redirect' value='/<%= @dir.sub('inbox','') %>#<%= @item[:nr] %>' /> + <input type='hidden' name='file' value='<%= @item[:file] %>' /> + <input type='hidden' name='redirect' value='/<%= @dir.sub('inbox','') %>#<%= @item[:nr] %>' /> <button>Delete</button> </form> <% if @dir == 'inbox' %> <form action='/archive' method='post'> - <input type='hidden' name='file' id='file' value='<%= @item[:file] %>' /> - <input type='hidden' name='redirect' id='redirect' value='/<%= @dir.sub('inbox','') %>#<%= @item[:nr] %>' /> + <input type='hidden' name='file' value='<%= @item[:file] %>' /> + <input type='hidden' name='redirect' value='/<%= @dir.sub('inbox','') %>#<%= @item[:nr] %>' /> <button>Archive</button> </form> <% end %> @@ -33,6 +33,13 @@ <% end %> <% end %> <% end %> + <form action='/outbox' method='post'> + <input type='hidden' name='to' value='<%= @item[:actor_url] %>' /> + <input type='hidden' name='inReplyTo' value='<%= @item[:id] %>' /> + <input type='hidden' name='redirect' value='/<%= @dir.sub('inbox','') %>#<%= @item[:nr] %>' /> + <textarea name="content"></textarea> + <input type="submit" value="Reply"> + </form> </div> <% @item[:replies].each do |reply| %> <%= html reply %> |