Building a Fediverse Presence with Jekyll

Tom Brown

@tom@herestomwiththeweather.com

Slides at https://otisburgsocial.github.io

"Community-owned independent social media sites"

— Darius Kazemi, on the fediverse

Goal 1 of 3

Given my blog at blog.com, I want to be known as bob@blog.com.

Goal 2 of 3

My Fediverse posts are available on my blog.

Goal 3 of 3

"Remote" Follow support: FEP-3b86 (Activity Intents)

WebFinger

in the user interface: @bob@blog.com

on the wire protocol: acct:bob@blog.com

3.3.10 :001 > require 'webfinger'
 => true
3.3.10 :002 > WebFinger.discover! 'acct:gargron@mastodon.social'
 =>
{"subject"=>"acct:Gargron@mastodon.social",
 "aliases"=>["https://mastodon.social/@Gargron", "https://mastodon.social/users/Gargron"],
 "links"=>
  [{"rel"=>"http://webfinger.net/rel/profile-page", "type"=>"text/html", "href"=>"https://mastodon.social/@Gargron"},
   {"rel"=>"self", "type"=>"application/activity+json", "href"=>"https://mastodon.social/users/Gargron"},
   {"rel"=>"http://ostatus.org/schema/1.0/subscribe", "template"=>"https://mastodon.social/authorize_interaction?uri={uri}"},
   {"rel"=>"https://w3id.org/fep/3b86/Create", "template"=>"https://mastodon.social/share?text={content}"},
   {"rel"=>"https://w3id.org/fep/3b86/Object", "template"=>"https://mastodon.social/authorize_interaction?uri={object}"},
   {"rel"=>"http://webfinger.net/rel/avatar",
    "type"=>"image/png",
    "href"=>"https://files.mastodon.social/accounts/avatars/000/000/001/original/a0a49d80c3de5f75.png"}]}

Canonical Identifier

canonical bob@blog.com
as seen by
dumb server
bob@remote.com

Problem 1 of 2
A server only doing forward discovery will not use the canonical identifier.

Problem 2 of 2
The server hosting the actor document needs to be aware of the canonical identifier.

Reverse Discovery

@republik_magazin@republik.ch

3.4.4 :010 > result = WebFinger.discover! 'acct:republik_magazin@republik.ch'
 =>
{"subject" => "acct:republik_magazin@republik.ch",
...
3.4.4 :011 > link = result['links'].find {|l| l['rel'] == 'self'}
 => {"rel" => "self", "type" => "application/activity+json", "href" => "https://republik.social/users/republik_magazin"}
3.4.4 :012 > actor_url = URI(link['href'])
 => #<URI::HTTPS https://republik.social/users/republik_magazin>
3.4.4 :013 > request = Net::HTTP::Get.new(actor_url)
 => #<Net::HTTP::Get GET>
3.4.4 :014 > request['Accept'] = 'application/json'
 => "application/json"
3.4.4 :015 > http = Net::HTTP.new(actor_url.host, actor_url.port)
 => #<Net::HTTP republik.social:443 open=false>
3.4.4 :016 > http.use_ssl=true
 => true
3.4.4 :017 > response = http.request(request)
 => #<Net::HTTPOK 200 OK readbody=true>
3.4.4 :018 > actor = JSON.parse(response.body)
 =>
{"@context" =>
...
3.4.4 :019 > ap_server_domain = URI.parse(actor['id']).hostname
 => "republik.social"
3.4.4 :020 > reverse_result = WebFinger.discover! "acct:#{actor['preferredUsername']}@#{ap_server_domain}"
 =>
{"subject" => "acct:republik_magazin@republik.ch",
...

Custom domain with Mastodon

To install Mastodon on mastodon.example.com in such a way it can serve @alice@example.com, set LOCAL_DOMAIN to example.com and WEB_DOMAIN to mastodon.example.com.

This also requires additional configuration on the server hosting example.com to redirect requests from
https://example.com/.well-known/webfinger to
https://mastodon.example.com/.well-known/webfinger

Can I own my own identifier without root access?

[web,fed].brid.gy

Proof of Concept: otisburg.social

webfinger_serializer.rb

class WebfingerSerializer < ActiveModel::Serializer
  attributes :subject, :links

  def subject
    object.to_webfinger_s
  end

  def links
    [
      {
        "rel": "self",
        "type": "application/activity+json",
        "href": "#{object.actor_url}"
      }
    ]
  end
end

Fediverse Posts available on Jekyll

jekyll-indieweb commit: fediverse posts page

_config.yml:

fediverse_rss: https://otisburg.social/actor/tom@herestomwiththeweather.com.xml

"Remote" Follow support: FEP-3b86 (Activity Intents)

  • I follow Alice (away from my home server)
  • Alice clicks "Follow" on my Jekyll blog

I follow Alice (away from my home server)

jekyll-indieweb commit: add intents to webfinger

{
  "subject": "acct:tom@herestomwiththeweather.com",
  "links": [
  {"rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://herestomwiththeweather.com"},
   {"rel": "self", "type": "application/activity+json", "href": "https://otisburg.social/actor/tom@herestomwiththeweather.com"},
   {"rel": "https://w3id.org/fep/3b86/Follow",
    "template": "https://otisburg.social/authorize_interaction?uri={object}"},
   {"rel": "https://w3id.org/fep/3b86/Like",
    "template": "https://otisburg.social/intents/like?objectId={object}"}
  ]
}
					

Alice Follows me on my Jekyll blog

jekyll-indieweb commit: follow on the fediverse button (fep-3b86)

    var resource = 'acct:' + user + '@' + host;
    var url = 'https://' + host + '/.well-known/webfinger?resource=' + encodeURIComponent(resource);
    try {
      var resp = await fetch(url, { headers: { 'Accept': 'application/jrd+json, application/json' } });
      if (!resp.ok) { showError('Server returned ' + resp.status); return; }
      var data = await resp.json();
      var rels = ['https://w3id.org/fep/3b86/Follow', 'https://w3id.org/fep/3b86/Object'];
      var link = (data.links || []).find(function(l) { return rels.includes(l.rel); });
      if (!link || !link.template) {
        showError('Your server does not support remote follows (FEP-3b86).');
        return;
      }
      window.location.href = link.template.replace('{object}', encodeURIComponent(actor));
					

Building a Fediverse Presence with Jekyll

Tom Brown

@tom@herestomwiththeweather.com

Slides at https://otisburgsocial.github.io