summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpdp8 <pdp8@pdp8.info>2023-06-30 15:42:14 +0200
committerpdp8 <pdp8@pdp8.info>2023-06-30 15:42:14 +0200
commit9d95a49ecd7e2f3e49b3462a281f2b86b406cf41 (patch)
treecd9695f2e22632e88eb710beeb041458e7ac3a71
parente328b59ffc2476262dbd076d2478aaade78e649c (diff)
inbox activities handled by dedicated functions
-rw-r--r--activitypub.rb10
-rw-r--r--client.rb76
-rw-r--r--helpers.rb32
-rw-r--r--public/follow.html15
-rw-r--r--public/pdp8 (renamed from pdp8.json)0
-rw-r--r--server.rb169
-rw-r--r--views/index.erb2
-rw-r--r--views/item.erb6
8 files changed, 182 insertions, 128 deletions
diff --git a/activitypub.rb b/activitypub.rb
index 9b63b46..9a1b9d4 100644
--- a/activitypub.rb
+++ b/activitypub.rb
@@ -7,6 +7,13 @@ USER = "pdp8"
WWW_DOMAIN = "pdp8.info"
WWW_URL = "https://#{WWW_DOMAIN}"
SOCIAL_DOMAIN = "social.#{WWW_DOMAIN}"
+SOCIAL_DIR = '/srv/social/'
+INBOX = File.join(SOCIAL_DIR, 'inbox')
+PUBLIC_DIR = File.join(SOCIAL_DIR, 'public')
+OUTBOX = File.join(PUBLIC_DIR, 'outbox')
+FOLLOWERS = File.join(PUBLIC_DIR, 'followers')
+FOLLOWING = File.join(PUBLIC_DIR, 'following')
+TAGS = File.join(PUBLIC_DIR, 'tags')
ACCOUNT = "#{USER}@#{SOCIAL_DOMAIN}"
SOCIAL_URL = "https://#{SOCIAL_DOMAIN}"
@@ -14,7 +21,8 @@ ACTOR = File.join(SOCIAL_URL, USER)
enable :sessions
set :session_secret, File.read(".secret").chomp
-set :default_content_type, 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
+#set :default_content_type, 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
+set :default_content_type, 'application/activity+json'
set :port, 9292
require_relative 'helpers.rb'
diff --git a/client.rb b/client.rb
index 7c58f85..abf565d 100644
--- a/client.rb
+++ b/client.rb
@@ -1,5 +1,5 @@
# client-server
-post "/outbox" do
+post "/" do
protected!
date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
outbox_path = File.join("public/outbox", date + ".json")
@@ -65,10 +65,16 @@ post "/outbox" do
File.open(outbox_path, "w+") { |f| f.puts create.to_json }
File.open(notes_path, "w+") { |f| f.puts create["object"].to_json }
+ tag.each do |t|
+ dir = File.join('public','tags',t['name'].sub('#',''))
+ FileUtils.mkdir_p dir
+ FileUtils.ln_s File.join('/srv/social/',notes_path), dir
+ end
- recipients.delete "https://www.w3.org/ns/activitystreams#Public"
- recipients.each { |r| send_signed create, r }
- redirect to(params['redirect'])
+ #recipients.delete "https://www.w3.org/ns/activitystreams#Public"
+ #recipients.each { |r| send_signed create, r }
+ send_signed create #, r }
+ redirect params['redirect']
end
post "/archive" do
@@ -77,53 +83,45 @@ post "/archive" do
redirect to(params['redirect'])
end
-post "/delete" do
+post "/delete" do # delete not supported by html forms
protected!
- FileUtils.rm params['file']
- redirect to(params['redirect'])
+ params['file'] ? FileUtils.rm_f(params['file']) : FileUtils.rm_f(Dir["inbox/*.json"])
+ params['redirect'] ? redirect(params['redirect']) : redirect('/')
end
-post "/delete_all" do
+post "/follow" do
protected!
- FileUtils.rm Dir["inbox/*.json"]
- redirect to("/")
-end
-
-post "/follow/*" do
- protected!
- mention = params['splat'][0]
- actor = actor(mention)
- return 502 unless actor
+ actor, mention = parse_follow params['follow']
follow = { "@context" => "https://www.w3.org/ns/activitystreams",
- "id" => File.join(SOCIAL_URL, "following", mention + ".json"),
+ "id" => File.join(SOCIAL_URL, "following", mention+ ".json"),
"type" => "Follow",
"actor" => ACTOR,
- "object" => actor }
- send_signed follow, actor
- redirect to("/")
+ "object" => actor,
+ 'to' => [ actor ] }
+ send_signed follow#, actor
+ redirect "/"
end
-post "/unfollow/*" do
+post "/unfollow" do
protected!
- mention = params['splat'][0]
- actor = actor(mention)
- return 502 unless actor
+ actor, mention = parse_follow params['follow']
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
+ "object" => JSON.parse(File.read(following_path)),
+ 'to' => [ actor ] }
+ send_signed undo#, actor
FileUtils.rm following_path
- redirect to("/")
+ redirect "/"
end
end
post "/login" do
session["client"] = true if OpenSSL::Digest::SHA256.base64digest(params["secret"]) == File.read(".digest").chomp
- redirect to("/")
+ redirect "/"
end
# private
@@ -152,7 +150,9 @@ helpers do
def items
nr = 0
- files = Dir[File.join(@dir, '*.json')] + Dir['public/notes/*.json']
+ p @dir
+ p Dir[File.join(@dir, '*', '*.json')]# + Dir['public/notes/*.json']
+ files = Dir[File.join(@dir, '*', '*.json')] + Dir['public/notes/*.json']
@items = files.sort.collect do |file|
item = JSON.parse(File.read(file))
mention = mention(item['attributedTo'])
@@ -195,4 +195,20 @@ helpers do
erb :item
end
+ def parse_follow follow
+ case follow
+ when /^#/
+ actor = "https://relay.fedi.buzz/tag/#{follow.sub(/^#/,'')}"
+ mention = follow
+ when /^http/
+ actor = follow
+ mention = mention actor
+ when /^@*\w+@\w+/
+ mention = follow
+ actor = actor(follow)
+ return 502 unless actor
+ end
+ [actor, mention]
+ end
+
end
diff --git a/helpers.rb b/helpers.rb
index 68aabe4..a51ea3d 100644
--- a/helpers.rb
+++ b/helpers.rb
@@ -1,8 +1,7 @@
helpers do
def curl ext, url
- #p "/run/current-system/sw/bin/curl -fsSL #{ext} #{url}"
- response = `/run/current-system/sw/bin/curl -fsSL #{ext} #{url}`
+ response = `/run/current-system/sw/bin/curl --fail-with-body -sSL #{ext} #{url}`
$?.success? ? response : nil
end
@@ -12,24 +11,33 @@ helpers do
end
# https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb
- def send_signed object, url
+ def send_signed object#, url
keypair = OpenSSL::PKey::RSA.new(File.read('private.pem'))
date = Time.now.utc.httpdate
body = object.to_json
sha256 = OpenSSL::Digest::SHA256.new
digest = "SHA-256=" + sha256.base64digest(body)
- host = URI.parse(url).host
- inbox = fetch(url)["inbox"]
- return false unless inbox
- request_uri = URI(inbox).request_uri
+ jj object
+ #(object['to'] + object['cc'] + object['bto'] + object['bcc']).uniq.each do |url|
+ object['to'].uniq.each do |url|
+ unless url == 'https://www.w3.org/ns/activitystreams#Public'
+ host = URI.parse(url).host
+ inbox = fetch(url)["inbox"]
+ if inbox
+ request_uri = URI(inbox).request_uri
- signed_string = "(request-target): post #{request_uri}\nhost: #{host}\ndate: #{date}\ndigest: #{digest}\ncontent-type: application/activity+json"
- 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 + '"'
+ signed_string = "(request-target): post #{request_uri}\nhost: #{host}\ndate: #{date}\ndigest: #{digest}\ncontent-type: application/activity+json"
+ 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 + '"'
- p curl("-X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}'", inbox)
- $?.success?
+ p curl("-X POST -H 'Content-Type: application/activity+json' -H 'Host: #{host}' -H 'Date: #{date}' -H 'Digest: #{digest}' -H 'Signature: #{signed_header}' -d '#{body}'", inbox)
+ #$?.success?
+ else
+ p "No inbox for #{url}"
+ end
+ end
+ end
end
diff --git a/public/follow.html b/public/follow.html
new file mode 100644
index 0000000..7cbb37c
--- /dev/null
+++ b/public/follow.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang='en'>
+
+<head>
+ <link rel='stylesheet' type='text/css' href='/style.css'>
+</head>
+
+<body>
+ <form action='/follow' method='post'>
+ <input name='follow' />
+ <input type='submit' name='button' value='Follow' />
+ </form>
+</body>
+
+</html> \ No newline at end of file
diff --git a/pdp8.json b/public/pdp8
index fafc4e0..fafc4e0 100644
--- a/pdp8.json
+++ b/public/pdp8
diff --git a/server.rb b/server.rb
index 2c24bb9..809538b 100644
--- a/server.rb
+++ b/server.rb
@@ -1,59 +1,22 @@
-# server-server
-post "/inbox" do
+before '/inbox' do
request.body.rewind # in case someone already read it
@body = request.body.read
- @action = JSON.parse @body
- verify!
-
- 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" => @action }
- send_signed accept, @action["actor"]
-
- when "Undo"
- o = @action["object"]
- case o["type"]
- when "Follow"
- Dir["public/followers/*.json"].each do |follower|
- FileUtils.rm follower if JSON.parse(File.read(follower))["actor"] == o["actor"]
- end
- end
-
- when "Accept"
- o = @action["object"]
- case o["type"]
- when "Follow"
- File.open(File.join("public","following",mention(o['object'])+".json"),"w+"){|f| f.puts o.to_json}
- end
+ @activity = JSON.parse @body
+ @object = @activity['object']
+ @object = fetch(@object) if @object.is_a? String and @object.match(/^http/)
+end
- when "Announce"
- download @action["object"]
- #when "Move"
- #when "Add"
- #when "Remove"
- #when "Like"
- #when "Block"
+# client-server
+post '/outbox' do
+ protected!
+ #send_signed @activity
+end
- else
- p "Unknown @action: #{@action['type']}"
- p @body
- end
+# server-server
+post "/inbox" do
+ verify!
+ type = @activity['type'].downcase.to_sym
+ respond_to?(type) ? send(type) : p("Unknown activity: #{type}")
end
# public
@@ -61,20 +24,16 @@ get "/.well-known/webfinger" do
request["resource"] == "acct:#{ACCOUNT}" ? send_file("./public/webfinger", :type => "application/jrd+json") : halt(404)
end
-get "/pdp8", :provides => 'html' do
- redirect 'https://pdp8.info'
-end
-
-get "/pdp8" do
- send_file "pdp8.json", :type => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
-end
-
["/outbox","/following","/followers"].each do |path|
get path do
- ordered_collection(path).to_json
+ ordered_collection(File.join(PUBLIC_DIR,path)).to_json
end
end
+get '/tags/:tag' do |tag|
+ ordered_collection(File.join(TAGS,tag)).to_json
+end
+
helpers do
# https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb
@@ -95,13 +54,11 @@ helpers do
headers = signature_params['headers']
signature = Base64.decode64(signature_params['signature'])
- if @action["type"] == "Delete" # deleted users do not return actors
- delete @action["object"]
+ actor = fetch key_id
+ if not actor and @activity["type"] == "Delete" # deleted users do not return actors
halt 200
end
- jj @action
- actor = fetch key_id
halt 403 unless actor
key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
@@ -118,33 +75,81 @@ helpers do
halt 403 unless key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
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
+ def create
+ unless object_exists?
+ File.open(object_file, "w+") { |f| f.puts @object.to_json }
+ if @object and @object['inReplyTo']
+ @object = fetch @object['inReplyTo']
+ create if @object
end
end
end
- def download object_url
- object = fetch(object_url)
- object and object["type"] ? create(object) : p(object_url, object)
+ def delete
+ Dir["inbox/*/*.json"].each do |file|
+ FileUtils.rm file if JSON.parse(File.read(file))['id'] == @object['id']
+ end
+ end
+
+ def update
+ delete
+ create
+ end
+
+ def announce
+ create
end
- def delete object
- Dir["inbox/*.json"].each do |doc|
- FileUtils.rm doc if JSON.parse(File.read(doc))["id"] == object["id"]
+ def accept
+ if @object['type'] == 'Follow'
+ File.open(File.join(FOLLOWING, mention(@object['object'])+'.json'),'w+'){|f| f.puts @object.to_json}
end
end
+ def undo
+ if @object['type'] == 'Follow'
+ Dir[File.join(FOLLOWERS, '*.json')].each do |follower|
+ FileUtils.rm follower if JSON.parse(File.read(follower))["actor"] == @object["actor"]
+ end
+ end
+ end
+
+ def follow
+ File.open(File.join(FOLLOWERS, mention(@activity['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' => @activity,
+ 'to' => [ @activity['actor'] ]
+ }
+ send_signed accept#, @activity['actor']
+ end
+
+ #when "Move"
+ #when "Add"
+ #when "Remove"
+ #when "Like"
+ #when "Block"
+
+ def inbox
+ Dir[File.join(INBOX,'*','*.json')].collect do |file|
+ JSON.parse(File.read(file))
+ end.sort_by { |o| o["published"] }
+ end
+
+ def object_exists?
+ not inbox.select{|o| o['id'] == @object['id']}.empty?
+ end
+
+ def object_file
+ dir = File.join 'inbox', @object['type'].downcase
+ FileUtils.mkdir_p dir
+ File.join dir, "#{Time.now.strftime('%Y-%m-%dT%H:%M:%S.%N')}.json"
+ end
+
def ordered_collection dir
- posts = Dir[File.join("public",dir, "*.json")].collect { |f| JSON.parse(File.read f) }.sort_by { |o| o["published"] }
+ posts = Dir[File.join(dir, "*.json")].collect { |f| JSON.parse(File.read f) }.sort_by { |o| o["published"] }
{
"@context" => "https://www.w3.org/ns/activitystreams",
"summary" => "#{USER} #{dir}",
diff --git a/views/index.erb b/views/index.erb
index 0021ec9..b512911 100644
--- a/views/index.erb
+++ b/views/index.erb
@@ -13,7 +13,7 @@
<%= html item %>
<% end %>
<% if @dir == 'inbox' %>
- <form action='delete_all' method='post'>
+ <form action='/delete' method='post'>
<button>Delete all</button>
</form>
<% end %>
diff --git a/views/item.erb b/views/item.erb
index ebc632e..8036b39 100644
--- a/views/item.erb
+++ b/views/item.erb
@@ -1,7 +1,9 @@
<div style='margin-left:<%= @item[:indent] %>em' id='<%= @item[:nr] %>'>
<b><a href='<%= @item[:actor_url] %>', target='_blank'><%= @item[:mention] %></a></b>&nbsp;
<% if @item[:mention] != ACCOUNT %>
- <form action='<%= File.join @item[:follow], @item[:mention] %>' method='post'>
+ <form action='/follow' method='post'>
+ <input type='hidden' name='follow' value='<%= @item[:mention] %>' />
+ <input type='hidden' name='redirect' value='/<%= @dir.sub('inbox','') %>#<%= @item[:nr] %>' />
<button><%= @item[:follow].capitalize %></button>
</form>
&nbsp;
@@ -37,7 +39,7 @@
<% end %>
<p>
<button class="reply" data-index='<%= @item[:nr] %>'>Reply</button>
- <form action='/outbox' method='post' id='form<%= @item[:nr] %>' style='display:none;' >
+ <form action='/' method='post' id='form<%= @item[:nr] %>' style='display:none;' >
<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] %>' />