summary refs log tree commit diff
diff options
context:
space:
mode:
authorpdp8 <pdp8@pdp8.info>2023-04-23 02:37:14 +0200
committerpdp8 <pdp8@pdp8.info>2023-04-23 02:37:14 +0200
commitf8afb7dec94bf9248b4032b7e95549361f190f3c (patch)
tree5a13b87f674ad87e7b325c16da7edd5e612442cc
initial import
-rw-r--r--.gitignore1
-rw-r--r--application.rb155
-rw-r--r--config.ru6
-rw-r--r--post.rb12
4 files changed, 174 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cfaad76
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pem
diff --git a/application.rb b/application.rb
new file mode 100644
index 0000000..6abf490
--- /dev/null
+++ b/application.rb
@@ -0,0 +1,155 @@
+require 'json'
+require 'net/http'
+require 'uri'
+
+USER = "pdp8"
+WWW_DOMAIN = "pdp8.info"
+WWW_URL = "https://#{WWW_DOMAIN}"
+SOCIAL_DOMAIN = "social.#{WWW_DOMAIN}"
+
+ACCOUNT = "#{USER}@#{SOCIAL_DOMAIN}"
+SOCIAL_URL = "https://#{SOCIAL_DOMAIN}"
+ACTOR = URI.join(SOCIAL_URL, USER)
+
+MATRIX = "@#{USER}:matrix.#{WWW_DOMAIN}"
+
+class Application
+  def call(env)
+    code = 404
+    type = "application/activity+json"
+
+    case env['REQUEST_METHOD']
+
+    when 'POST'
+      case env["REQUEST_URI"]
+
+      when "/inbox"
+        type = "text/plain"
+        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 = Base64.decode64(signature_header['signature'])
+        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|
+          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)
+          input = JSON.parse(env["rack.input"].gets)
+          code = 200
+          response = "OK"
+        else
+          code = 401
+          response = 'Request signature could not be verified'
+        end
+
+      end
+
+    when 'GET'
+
+      case env["REQUEST_URI"]
+
+      when "/.well-known/webfinger?resource=acct:#{ACCOUNT}"
+        code = 200
+        type = "application/jrd+json"
+        response = {
+          "subject" => "acct:#{ACCOUNT}",
+          "links" => [
+            {
+              "rel" => "self",
+              "type" => "application/activity+json",
+              "href" => ACTOR
+            }
+          ]
+        }
+
+      when "/#{USER}"
+        code = 200
+        response = {
+          "@context" => ["https://www.w3.org/ns/activitystreams"],
+          "id" => ACTOR,
+          "type" => "Person",
+          "preferredUsername" => USER,
+          "name" => USER,
+          "inbox" => URI.join(SOCIAL_URL, "inbox"),
+          "outbox" => URI.join(SOCIAL_URL, "outbox"),
+          "following" => URI.join(SOCIAL_URL, "following"),
+          "followers" => URI.join(SOCIAL_URL, "followers"),
+          "liked" => URI.join(SOCIAL_URL, "liked"),
+          "icon" => {
+            "type" => "Image",
+            "url" => "https://pdp8.info/pdp8.png"
+          },
+          "attachment": [
+            {
+              "type": "PropertyValue",
+              "name": "Web",
+              "value": "<a href=\"#{WWW_URL}\">#{WWW_DOMAIN}</a>"
+            },
+            {
+              "type": "PropertyValue",
+              "name": "Fediverse",
+              "value": "<a rel=\"me\" href=\"#{ACTOR}\">@#{ACCOUNT}</a>"
+            },
+            {
+              "type": "PropertyValue",
+              "name": "Matrix",
+              "value": "<a rel=\"me\" href=\"https://matrix.to/#/#{MATRIX}\">MATRIX</a>"
+            }
+          ],
+          "publicKey" => {
+            "@context" => "https://w3id.org/security/v1",
+            "@type" => "Key",
+            "id" => "#{ACTOR}#main-key",
+            "owner" => ACTOR,
+            "publicKeyPem" => File.read("public.pem")
+          }
+        }
+
+      when "/outbox"
+        code = 200
+        type = "application/activity+json"
+        response = {
+          "@context" => "https://www.w3.org/ns/activitystreams",
+          "summary" => "",
+          "type" => "OrderedCollection",
+          "totalItems" => 2,
+          # TODO generate items from src/www
+          "orderedItems" => [
+            {
+              "type" => "Note",
+              "name" => "A Simple Note",
+              "tag" => [
+                {
+                  "type" => "Hashtag",
+                  "name" => "#activitypub",
+                  "href" => "https://s3lph.me/activitypub/tags/activitypub"
+                },
+              ]
+            },
+            {
+              "type" => "Note",
+              "name" => "Another Simple Note"
+            }
+          ]
+        }
+      when "/following"
+      when "/followers"
+      when "/liked"
+      end
+
+    end
+    [code, { "Content-Type" => type }, [response.to_json]]
+  end
+end
diff --git a/config.ru b/config.ru
new file mode 100644
index 0000000..e2aedfd
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,6 @@
+require_relative './application.rb'
+require 'rack/protection'
+use Rack::Protection, :except => :session_hijacking
+use Rack::Reloader
+# use Rack::ContentType, "application/ld+json"
+run Application.new
diff --git a/post.rb b/post.rb
new file mode 100644
index 0000000..dc881b1
--- /dev/null
+++ b/post.rb
@@ -0,0 +1,12 @@
+require 'http'
+require 'openssl'
+
+document      = File.read('create-hello-world.json')
+date          = Time.now.utc.httpdate
+keypair       = OpenSSL::PKey::RSA.new(File.read('private.pem'))
+signed_string = "(request-target): post /inbox\nhost: mastodon.social\ndate: #{date}"
+signature     = Base64.strict_encode64(keypair.sign(OpenSSL::Digest::SHA256.new, signed_string))
+header        = 'keyId="https://my-example.com/actor",headers="(request-target) host date",signature="' + signature + '"'
+
+HTTP.headers({ 'Host': 'mastodon.social', 'Date': date, 'Signature': header })
+    .post('https://mastodon.social/inbox', body: document)