summaryrefslogtreecommitdiff
path: root/activitypub.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activitypub.rb')
-rw-r--r--activitypub.rb255
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 %>&nbsp;<b><%= mention item['attributedTo'] %></b>&nbsp;<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