federails 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +11 -0
- data/app/controllers/federails/application_controller.rb +4 -0
- data/app/controllers/federails/client/activities_controller.rb +1 -1
- data/app/controllers/federails/client/actors_controller.rb +1 -1
- data/app/controllers/federails/client/followings_controller.rb +9 -1
- data/app/controllers/federails/server/activities_controller.rb +5 -5
- data/app/controllers/federails/server/actors_controller.rb +1 -1
- data/app/controllers/federails/server/followings_controller.rb +2 -1
- data/app/controllers/federails/server/nodeinfo_controller.rb +2 -2
- data/app/controllers/federails/server/web_finger_controller.rb +2 -2
- data/app/helpers/federails/application_helper.rb +8 -0
- data/app/models/concerns/federails/entity.rb +12 -1
- data/app/models/concerns/federails/has_uuid.rb +35 -0
- data/app/models/federails/activity.rb +8 -13
- data/app/models/federails/actor.rb +38 -1
- data/app/models/federails/following.rb +9 -0
- data/app/views/federails/client/actors/show.html.erb +1 -1
- data/app/views/federails/server/activities/{_activity.json.jbuilder → _activity.activitypub.jbuilder} +6 -1
- data/app/views/federails/server/actors/{_actor.json.jbuilder → _actor.activitypub.jbuilder} +11 -1
- data/app/views/federails/server/web_finger/{find.json.jbuilder → find.jrd.jbuilder} +5 -1
- data/config/initializers/mime_types.rb +21 -0
- data/config/routes.rb +1 -1
- data/db/migrate/20241002094500_add_uuids.rb +13 -0
- data/db/migrate/20241002094501_add_keypair_to_actors.rb +8 -0
- data/lib/federails/configuration.rb +4 -0
- data/lib/federails/version.rb +1 -1
- data/lib/federails.rb +2 -1
- data/lib/fediverse/notifier.rb +44 -5
- data/lib/fediverse/signature.rb +49 -0
- data/lib/fediverse/webfinger.rb +31 -7
- metadata +20 -15
- /data/app/views/federails/server/activities/{outbox.json.jbuilder → outbox.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/activities/{show.json.jbuilder → show.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/actors/{followers.json.jbuilder → followers.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/actors/{following.json.jbuilder → following.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/actors/{show.json.jbuilder → show.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/followings/{_following.json.jbuilder → _following.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/followings/{show.json.jbuilder → show.activitypub.jbuilder} +0 -0
- /data/app/views/federails/server/nodeinfo/{index.json.jbuilder → index.nodeinfo.jbuilder} +0 -0
- /data/app/views/federails/server/nodeinfo/{show.json.jbuilder → show.nodeinfo.jbuilder} +0 -0
- /data/app/views/federails/server/web_finger/{host_meta.xml.erb → host_meta.xrd.erb} +0 -0
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 070da5d2cc3d016475a69f797c6280595618d5f60de95cf7506bb2a1e9cbce4c
         | 
| 4 | 
            +
              data.tar.gz: a954535a092fa71fdb911f973363d8372b6d56e42b32190394e92026567c6913
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 69fba1438beef26a150079c2a39eb185903025dcad3c24f5dfee941d844710d12938cf5546f0dc488f725096a2c4e6f32dfba4d1d3c825b9ba9418f063bb4ec9
         | 
| 7 | 
            +
              data.tar.gz: 01ef6821f7b1aa9422f013e23ed20b1225f0e630d4925ca81c7490f43d790a1d77a3b93e8d72783d93aee59f1760b48bbdd71a59feca353bca0a1277a6bd03c0
         | 
    
        data/README.md
    CHANGED
    
    | @@ -102,6 +102,17 @@ Federails.configure do |config| | |
| 102 102 | 
             
            end
         | 
| 103 103 | 
             
            ```
         | 
| 104 104 |  | 
| 105 | 
            +
            #### Remote following
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            By default, remote follow requests (where you press a follow button on another server and get redirected home to complete the follow)
         | 
| 108 | 
            +
            will use the built-in client paths. If you're not using the client, or want to provide your own user interface, you can set the path like this, assuming that `new_follow_url` is a valid route in your app. A `uri` query parameter template will be automatically appended, you don't need to specify that.
         | 
| 109 | 
            +
             | 
| 110 | 
            +
            ```
         | 
| 111 | 
            +
            Federails.configure do |config|
         | 
| 112 | 
            +
              config.remote_follow_url_method = :new_follow_url
         | 
| 113 | 
            +
            end
         | 
| 114 | 
            +
            ```
         | 
| 115 | 
            +
             | 
| 105 116 | 
             
            ### Migrations
         | 
| 106 117 |  | 
| 107 118 | 
             
            Copy the migrations:
         | 
| @@ -11,6 +11,10 @@ module Federails | |
| 11 11 | 
             
                def error_fallback(exception, fallback_message, status)
         | 
| 12 12 | 
             
                  message = exception&.message || fallback_message
         | 
| 13 13 | 
             
                  respond_to do |format|
         | 
| 14 | 
            +
                    format.jrd { head status }
         | 
| 15 | 
            +
                    format.xrd { head status }
         | 
| 16 | 
            +
                    format.activitypub { head status }
         | 
| 17 | 
            +
                    format.nodeinfo { head status }
         | 
| 14 18 | 
             
                    format.json { render json: { error: message }, status: status }
         | 
| 15 19 | 
             
                    format.html { raise exception }
         | 
| 16 20 | 
             
                  end
         | 
| @@ -8,7 +8,7 @@ module Federails | |
| 8 8 | 
             
                  # GET /app/activities.json
         | 
| 9 9 | 
             
                  def index
         | 
| 10 10 | 
             
                    @activities = policy_scope(Federails::Activity, policy_scope_class: Federails::Client::ActivityPolicy::Scope).all
         | 
| 11 | 
            -
                    @activities = @activities.where  | 
| 11 | 
            +
                    @activities = @activities.where actor: Actor.find_param(params[:actor_id]) if params[:actor_id]
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 |  | 
| 14 14 | 
             
                  # GET /app/feed
         | 
| @@ -25,7 +25,7 @@ module Federails | |
| 25 25 |  | 
| 26 26 | 
             
                  # Use callbacks to share common setup or constraints between actions.
         | 
| 27 27 | 
             
                  def set_actor
         | 
| 28 | 
            -
                    @actor = Federails::Actor. | 
| 28 | 
            +
                    @actor = Federails::Actor.find_param(params[:id])
         | 
| 29 29 | 
             
                    authorize @actor, policy_class: Federails::Client::ActorPolicy
         | 
| 30 30 | 
             
                  end
         | 
| 31 31 |  | 
| @@ -4,6 +4,14 @@ module Federails | |
| 4 4 | 
             
                  before_action :authenticate_user!
         | 
| 5 5 | 
             
                  before_action :set_following, only: [:accept, :destroy]
         | 
| 6 6 |  | 
| 7 | 
            +
                  # GET /app/followings/new?uri={uri}
         | 
| 8 | 
            +
                  def new
         | 
| 9 | 
            +
                    # Find actor (and fetch if necessary)
         | 
| 10 | 
            +
                    actor = Actor.find_or_create_by_federation_url(params[:uri])
         | 
| 11 | 
            +
                    # Redirect to local profile page which will have a follow button on it
         | 
| 12 | 
            +
                    redirect_to federails.client_actor_url(actor)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 7 15 | 
             
                  # PUT /app/followings/:id/accept
         | 
| 8 16 | 
             
                  # PUT /app/followings/:id/accept.json
         | 
| 9 17 | 
             
                  def accept
         | 
| @@ -62,7 +70,7 @@ module Federails | |
| 62 70 |  | 
| 63 71 | 
             
                  # Use callbacks to share common setup or constraints between actions.
         | 
| 64 72 | 
             
                  def set_following
         | 
| 65 | 
            -
                    @following = Following. | 
| 73 | 
            +
                    @following = Following.find_param(params[:id])
         | 
| 66 74 | 
             
                    authorize @following, policy_class: Federails::Client::FollowingPolicy
         | 
| 67 75 | 
             
                  end
         | 
| 68 76 |  | 
| @@ -8,7 +8,7 @@ module Federails | |
| 8 8 | 
             
                  # GET /federation/activities
         | 
| 9 9 | 
             
                  # GET /federation/actors/1/outbox.json
         | 
| 10 10 | 
             
                  def outbox
         | 
| 11 | 
            -
                    @actor            = Actor. | 
| 11 | 
            +
                    @actor            = Actor.find_param(params[:actor_id])
         | 
| 12 12 | 
             
                    @activities       = policy_scope(Federails::Activity, policy_scope_class: Federails::Server::ActivityPolicy::Scope).where(actor: @actor).order(created_at: :desc)
         | 
| 13 13 | 
             
                    @total_activities = @activities.count
         | 
| 14 14 | 
             
                    @activities       = @activities.page(params[:page])
         | 
| @@ -20,12 +20,12 @@ module Federails | |
| 20 20 | 
             
                  # POST /federation/actors/1/inbox
         | 
| 21 21 | 
             
                  def create
         | 
| 22 22 | 
             
                    payload = payload_from_params
         | 
| 23 | 
            -
                    return  | 
| 23 | 
            +
                    return head :unprocessable_entity unless payload
         | 
| 24 24 |  | 
| 25 25 | 
             
                    if Fediverse::Inbox.dispatch_request(payload)
         | 
| 26 | 
            -
                       | 
| 26 | 
            +
                      head :created
         | 
| 27 27 | 
             
                    else
         | 
| 28 | 
            -
                       | 
| 28 | 
            +
                      head :unprocessable_entity
         | 
| 29 29 | 
             
                    end
         | 
| 30 30 | 
             
                  end
         | 
| 31 31 |  | 
| @@ -33,7 +33,7 @@ module Federails | |
| 33 33 |  | 
| 34 34 | 
             
                  # Use callbacks to share common setup or constraints between actions.
         | 
| 35 35 | 
             
                  def set_activity
         | 
| 36 | 
            -
                    @activity =  | 
| 36 | 
            +
                    @activity = Actor.find_param(params[:actor_id]).activities.find_param(params[:id])
         | 
| 37 37 | 
             
                  end
         | 
| 38 38 |  | 
| 39 39 | 
             
                  # Only allow a list of trusted parameters through.
         | 
| @@ -21,7 +21,7 @@ module Federails | |
| 21 21 |  | 
| 22 22 | 
             
                  # Use callbacks to share common setup or constraints between actions.
         | 
| 23 23 | 
             
                  def set_actor
         | 
| 24 | 
            -
                    @actor = Actor. | 
| 24 | 
            +
                    @actor = Actor.find_param(params[:id])
         | 
| 25 25 | 
             
                    authorize @actor, policy_class: Federails::Server::ActorPolicy
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| @@ -10,7 +10,8 @@ module Federails | |
| 10 10 |  | 
| 11 11 | 
             
                  # Use callbacks to share common setup or constraints between actions.
         | 
| 12 12 | 
             
                  def set_following
         | 
| 13 | 
            -
                     | 
| 13 | 
            +
                    actor = Actor.find_param(params[:actor_id])
         | 
| 14 | 
            +
                    @following = Following.find_by!(actor: actor, uuid: params[:id])
         | 
| 14 15 | 
             
                    authorize @following, policy_class: Federails::Server::FollowingPolicy
         | 
| 15 16 | 
             
                  end
         | 
| 16 17 | 
             
                end
         | 
| @@ -2,7 +2,7 @@ module Federails | |
| 2 2 | 
             
              module Server
         | 
| 3 3 | 
             
                class NodeinfoController < ServerController
         | 
| 4 4 | 
             
                  def index
         | 
| 5 | 
            -
                    render formats: [: | 
| 5 | 
            +
                    render formats: [:nodeinfo]
         | 
| 6 6 | 
             
                  end
         | 
| 7 7 |  | 
| 8 8 | 
             
                  def show # rubocop:todo Metrics/AbcSize
         | 
| @@ -15,7 +15,7 @@ module Federails | |
| 15 15 | 
             
                      @active_month += model.where(created_at: ((30.days.ago)...Time.current)).count
         | 
| 16 16 | 
             
                      @active_halfyear += model.where(created_at: ((180.days.ago)...Time.current)).count
         | 
| 17 17 | 
             
                    end
         | 
| 18 | 
            -
                    render formats: [: | 
| 18 | 
            +
                    render formats: [:nodeinfo]
         | 
| 19 19 | 
             
                  end
         | 
| 20 20 | 
             
                end
         | 
| 21 21 | 
             
              end
         | 
| @@ -15,11 +15,11 @@ module Federails | |
| 15 15 | 
             
                    end
         | 
| 16 16 | 
             
                    raise ActiveRecord::RecordNotFound if @user.nil?
         | 
| 17 17 |  | 
| 18 | 
            -
                    render formats: [: | 
| 18 | 
            +
                    render formats: [:jrd]
         | 
| 19 19 | 
             
                  end
         | 
| 20 20 |  | 
| 21 21 | 
             
                  def host_meta
         | 
| 22 | 
            -
                    render  | 
| 22 | 
            +
                    render formats: [:xrd]
         | 
| 23 23 | 
             
                  end
         | 
| 24 24 |  | 
| 25 25 | 
             
                  # TODO: complete missing endpoints
         | 
| @@ -1,4 +1,12 @@ | |
| 1 1 | 
             
            module Federails
         | 
| 2 2 | 
             
              module ApplicationHelper
         | 
| 3 | 
            +
                def remote_follow_url
         | 
| 4 | 
            +
                  method_name = Federails.configuration.remote_follow_url_method.to_s
         | 
| 5 | 
            +
                  if method_name.starts_with? 'federails.'
         | 
| 6 | 
            +
                    send(method_name.gsub('federails.', ''))
         | 
| 7 | 
            +
                  else
         | 
| 8 | 
            +
                    Rails.application.routes.url_helpers.send(method_name)
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
                end
         | 
| 3 11 | 
             
              end
         | 
| 4 12 | 
             
            end
         | 
| @@ -2,7 +2,18 @@ module Federails | |
| 2 2 | 
             
              module Entity
         | 
| 3 3 | 
             
                extend ActiveSupport::Concern
         | 
| 4 4 |  | 
| 5 | 
            -
                included do
         | 
| 5 | 
            +
                included do # rubocop:todo Metrics/BlockLength
         | 
| 6 | 
            +
                  include ActiveSupport::Callbacks
         | 
| 7 | 
            +
                  define_callbacks :followed
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # Define a method that will be called after the entity receives a follow request
         | 
| 10 | 
            +
                  # @param method [Symbol] The name of the method to call, or a block that will be called directly
         | 
| 11 | 
            +
                  # @example
         | 
| 12 | 
            +
                  #   after_followed :accept_follow
         | 
| 13 | 
            +
                  def self.after_followed(method)
         | 
| 14 | 
            +
                    set_callback :followed, :after, method
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 6 17 | 
             
                  has_one :actor, class_name: 'Federails::Actor', as: :entity, dependent: :destroy
         | 
| 7 18 |  | 
| 8 19 | 
             
                  after_create :create_actor
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module Federails
         | 
| 2 | 
            +
              module HasUuid
         | 
| 3 | 
            +
                extend ActiveSupport::Concern
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                included do
         | 
| 6 | 
            +
                  before_validation :generate_uuid
         | 
| 7 | 
            +
                  validates :uuid, presence: true, uniqueness: true
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def self.find_param(param)
         | 
| 10 | 
            +
                    find_by!(uuid: param)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def to_param
         | 
| 15 | 
            +
                  uuid
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                # Override UUID accessor to provide lazy initialization of UUIDs for old data
         | 
| 19 | 
            +
                def uuid
         | 
| 20 | 
            +
                  if self[:uuid].blank?
         | 
| 21 | 
            +
                    generate_uuid
         | 
| 22 | 
            +
                    save!
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                  self[:uuid]
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def generate_uuid
         | 
| 30 | 
            +
                  return if self[:uuid].present?
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  (self.uuid = SecureRandom.uuid) while self[:uuid].blank? || self.class.exists?(uuid: self[:uuid])
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            module Federails
         | 
| 2 2 | 
             
              class Activity < ApplicationRecord
         | 
| 3 | 
            +
                include Federails::HasUuid
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
                belongs_to :entity, polymorphic: true
         | 
| 4 6 | 
             
                belongs_to :actor
         | 
| 5 7 |  | 
| @@ -13,22 +15,15 @@ module Federails | |
| 13 15 |  | 
| 14 16 | 
             
                after_create_commit :post_to_inboxes
         | 
| 15 17 |  | 
| 16 | 
            -
                def recipients | 
| 18 | 
            +
                def recipients
         | 
| 17 19 | 
             
                  return [] unless actor.local?
         | 
| 18 20 |  | 
| 19 | 
            -
                   | 
| 20 | 
            -
                   | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
                     | 
| 24 | 
            -
                    actors.push(*actor.followers) if entity_type == 'Note'
         | 
| 25 | 
            -
                  when 'Accept'
         | 
| 26 | 
            -
                    actors.push(entity.actor) if entity_type == 'Federails::Following'
         | 
| 27 | 
            -
                  when 'Undo'
         | 
| 28 | 
            -
                    actors.push(entity.target_actor) if entity_type == 'Federails::Following'
         | 
| 21 | 
            +
                  case entity_type
         | 
| 22 | 
            +
                  when 'Federails::Following'
         | 
| 23 | 
            +
                    [(action == 'Accept' ? entity.actor : entity.target_actor)]
         | 
| 24 | 
            +
                  else
         | 
| 25 | 
            +
                    actor.followers
         | 
| 29 26 | 
             
                  end
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                  actors
         | 
| 32 27 | 
             
                end
         | 
| 33 28 |  | 
| 34 29 | 
             
                private
         | 
| @@ -3,6 +3,8 @@ require 'fediverse/webfinger' | |
| 3 3 |  | 
| 4 4 | 
             
            module Federails
         | 
| 5 5 | 
             
              class Actor < ApplicationRecord # rubocop:disable Metrics/ClassLength
         | 
| 6 | 
            +
                include Federails::HasUuid
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                validates :federated_url, presence: { unless: :entity }, uniqueness: { unless: :entity }
         | 
| 7 9 | 
             
                validates :username, presence: { unless: :entity }
         | 
| 8 10 | 
             
                validates :server, presence: { unless: :entity }
         | 
| @@ -112,7 +114,7 @@ module Federails | |
| 112 114 |  | 
| 113 115 | 
             
                  def find_by_federation_url(federated_url)
         | 
| 114 116 | 
             
                    local_route = Utils::Host.local_route federated_url
         | 
| 115 | 
            -
                    return  | 
| 117 | 
            +
                    return find_param(local_route[:id]) if local_route && local_route[:controller] == 'federails/server/actors' && local_route[:action] == 'show'
         | 
| 116 118 |  | 
| 117 119 | 
             
                    actor = find_by federated_url: federated_url
         | 
| 118 120 | 
             
                    return actor if actor
         | 
| @@ -148,5 +150,40 @@ module Federails | |
| 148 150 | 
             
                    end
         | 
| 149 151 | 
             
                  end
         | 
| 150 152 | 
             
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                def public_key
         | 
| 155 | 
            +
                  ensure_key_pair_exists!
         | 
| 156 | 
            +
                  self[:public_key]
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                def private_key
         | 
| 160 | 
            +
                  ensure_key_pair_exists!
         | 
| 161 | 
            +
                  self[:private_key]
         | 
| 162 | 
            +
                end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                def key_id
         | 
| 165 | 
            +
                  "#{federated_url}#main-key"
         | 
| 166 | 
            +
                end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                private
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                def ensure_key_pair_exists!
         | 
| 171 | 
            +
                  return if self[:private_key].present? || !local?
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  update!(generate_key_pair)
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                def generate_key_pair
         | 
| 177 | 
            +
                  rsa_key = OpenSSL::PKey::RSA.new 2048
         | 
| 178 | 
            +
                  cipher  = OpenSSL::Cipher.new('AES-128-CBC')
         | 
| 179 | 
            +
                  {
         | 
| 180 | 
            +
                    private_key: if Rails.application.credentials.secret_key_base
         | 
| 181 | 
            +
                                   rsa_key.to_pem(cipher, Rails.application.credentials.secret_key_base)
         | 
| 182 | 
            +
                                 else
         | 
| 183 | 
            +
                                   rsa_key.to_pem
         | 
| 184 | 
            +
                                 end,
         | 
| 185 | 
            +
                    public_key:  rsa_key.public_key.to_pem,
         | 
| 186 | 
            +
                  }
         | 
| 187 | 
            +
                end
         | 
| 151 188 | 
             
              end
         | 
| 152 189 | 
             
            end
         | 
| @@ -1,5 +1,7 @@ | |
| 1 1 | 
             
            module Federails
         | 
| 2 2 | 
             
              class Following < ApplicationRecord
         | 
| 3 | 
            +
                include Federails::HasUuid
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
                enum status: { pending: 0, accepted: 1 }
         | 
| 4 6 |  | 
| 5 7 | 
             
                validates :target_actor_id, uniqueness: { scope: [:actor_id, :target_actor_id] }
         | 
| @@ -9,6 +11,7 @@ module Federails | |
| 9 11 | 
             
                # FIXME: Handle this with something like undelete
         | 
| 10 12 | 
             
                has_many :activities, as: :entity, dependent: :destroy
         | 
| 11 13 |  | 
| 14 | 
            +
                after_create :after_follow
         | 
| 12 15 | 
             
                after_create :create_activity
         | 
| 13 16 | 
             
                after_destroy :destroy_activity
         | 
| 14 17 |  | 
| @@ -32,6 +35,12 @@ module Federails | |
| 32 35 |  | 
| 33 36 | 
             
                private
         | 
| 34 37 |  | 
| 38 | 
            +
                def after_follow
         | 
| 39 | 
            +
                  target_actor&.entity&.run_callbacks :followed, :after do
         | 
| 40 | 
            +
                    self
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 35 44 | 
             
                def create_activity
         | 
| 36 45 | 
             
                  Activity.create! actor: actor, action: 'Create', entity: self
         | 
| 37 46 | 
             
                end
         | 
| @@ -61,7 +61,7 @@ | |
| 61 61 | 
             
            <p>
         | 
| 62 62 | 
             
              <b>Home page:</b>
         | 
| 63 63 | 
             
              <% if @actor.local? && Federails::Configuration.user_profile_url_method %>
         | 
| 64 | 
            -
                <%= link_to @actor.send(Federails::Configuration.user_username_field), send(Federails::Configuration.user_profile_url_method, @actor.user) %>
         | 
| 64 | 
            +
                <%= link_to @actor.send(Federails::Configuration.user_username_field), Rails.application.routes.url_helpers.send(Federails::Configuration.user_profile_url_method, @actor.user) %>
         | 
| 65 65 | 
             
              <% elsif @actor.profile_url %>
         | 
| 66 66 | 
             
                <%= link_to @actor.name, @actor.profile_url %>
         | 
| 67 67 | 
             
              <% else %>
         | 
| @@ -6,4 +6,9 @@ json.type activity.action | |
| 6 6 | 
             
            json.actor activity.actor.federated_url
         | 
| 7 7 | 
             
            json.to ['https://www.w3.org/ns/activitystreams#Public']
         | 
| 8 8 | 
             
            json.cc [activity.actor.followers_url]
         | 
| 9 | 
            -
             | 
| 9 | 
            +
             | 
| 10 | 
            +
            if activity.entity.respond_to? :to_activitypub_object
         | 
| 11 | 
            +
              json.object activity.entity.to_activitypub_object
         | 
| 12 | 
            +
            elsif activity.entity.respond_to? :federated_url
         | 
| 13 | 
            +
              json.object activity.entity.federated_url
         | 
| 14 | 
            +
            end
         | 
| @@ -1,4 +1,7 @@ | |
| 1 | 
            -
            json.set! '@context',  | 
| 1 | 
            +
            json.set! '@context', [
         | 
| 2 | 
            +
              'https://www.w3.org/ns/activitystreams',
         | 
| 3 | 
            +
              'https://w3id.org/security/v1',
         | 
| 4 | 
            +
            ]
         | 
| 2 5 |  | 
| 3 6 | 
             
            json.id actor.federated_url
         | 
| 4 7 | 
             
            json.name actor.name
         | 
| @@ -9,3 +12,10 @@ json.outbox actor.outbox_url | |
| 9 12 | 
             
            json.followers actor.followers_url
         | 
| 10 13 | 
             
            json.following actor.followings_url
         | 
| 11 14 | 
             
            json.url actor.profile_url
         | 
| 15 | 
            +
            if actor.public_key
         | 
| 16 | 
            +
              json.publicKey do
         | 
| 17 | 
            +
                json.id actor.key_id
         | 
| 18 | 
            +
                json.owner actor.federated_url
         | 
| 19 | 
            +
                json.publicKeyPem actor.public_key
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
| @@ -4,7 +4,7 @@ links = [ | |
| 4 4 | 
             
              # Federation actor URL
         | 
| 5 5 | 
             
              {
         | 
| 6 6 | 
             
                rel:  'self',
         | 
| 7 | 
            -
                type:  | 
| 7 | 
            +
                type: Mime[:activitypub].to_s,
         | 
| 8 8 | 
             
                href: @user.actor.federated_url,
         | 
| 9 9 | 
             
              },
         | 
| 10 10 | 
             
            ]
         | 
| @@ -17,4 +17,8 @@ if @user.actor.profile_url | |
| 17 17 | 
             
                         href: @user.actor.profile_url
         | 
| 18 18 | 
             
            end
         | 
| 19 19 |  | 
| 20 | 
            +
            # Remote following
         | 
| 21 | 
            +
            links.push rel:      'http://ostatus.org/schema/1.0/subscribe',
         | 
| 22 | 
            +
                       template: "#{remote_follow_url}?uri={uri}"
         | 
| 23 | 
            +
             | 
| 20 24 | 
             
            json.links links
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            # Webfinger: https://datatracker.ietf.org/doc/html/rfc7033
         | 
| 2 | 
            +
            Mime::Type.register 'application/jrd+json', :jrd
         | 
| 3 | 
            +
            Mime::Type.register 'application/xrd+xml', :xrd
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # ActivityPub: https://www.w3.org/TR/activitypub/#retrieving-objects
         | 
| 6 | 
            +
            Mime::Type.register 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', :activitypub, ['application/activity+json', 'application/json']
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # Nodeinfo: https://github.com/jhass/nodeinfo/blob/main/PROTOCOL.md#retrieval
         | 
| 9 | 
            +
            Mime::Type.register 'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"', :nodeinfo
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            # Get current request parsers. Apparently we need to do it this way and can't add in-place, see
         | 
| 12 | 
            +
            # https://api.rubyonrails.org/classes/ActionDispatch/Http/Parameters/ClassMethods.html#method-i-parameter_parsers-3D
         | 
| 13 | 
            +
            parsers = ActionDispatch::Request.parameter_parsers
         | 
| 14 | 
            +
            # Copy the default JSON parsing for JSON types
         | 
| 15 | 
            +
            [:jrd, :activitypub, :nodeinfo].each do |mime_type|
         | 
| 16 | 
            +
              parsers[Mime[mime_type].symbol] = parsers[:json]
         | 
| 17 | 
            +
            end
         | 
| 18 | 
            +
            # XRD just needs a simple XML parser
         | 
| 19 | 
            +
            parsers[Mime[:xrd].symbol] = ->(raw_post) { Hash.from_xml(raw_post) || {} }
         | 
| 20 | 
            +
            # Store updated parsers
         | 
| 21 | 
            +
            ActionDispatch::Request.parameter_parsers = parsers
         | 
    
        data/config/routes.rb
    CHANGED
    
    | @@ -18,7 +18,7 @@ Federails::Engine.routes.draw do | |
| 18 18 | 
             
                    resources :activities, only: [:index]
         | 
| 19 19 | 
             
                  end
         | 
| 20 20 | 
             
                  get :feed, to: 'activities#feed'
         | 
| 21 | 
            -
                  resources :followings, only: [:create, :destroy] do
         | 
| 21 | 
            +
                  resources :followings, only: [:new, :create, :destroy] do
         | 
| 22 22 | 
             
                    collection do
         | 
| 23 23 | 
             
                      post :follow, to: 'followings#follow'
         | 
| 24 24 | 
             
                    end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            class AddUuids < ActiveRecord::Migration[7.0]
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                [
         | 
| 4 | 
            +
                  :federails_actors,
         | 
| 5 | 
            +
                  :federails_activities,
         | 
| 6 | 
            +
                  :federails_followings,
         | 
| 7 | 
            +
                ].each do |table|
         | 
| 8 | 
            +
                  change_table table do |t|
         | 
| 9 | 
            +
                    t.string :uuid, default: nil, index: { unique: true }
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| @@ -42,6 +42,10 @@ module Federails | |
| 42 42 | 
             
                mattr_accessor :client_routes_path
         | 
| 43 43 | 
             
                @@client_routes_path = :app
         | 
| 44 44 |  | 
| 45 | 
            +
                # Route method for remote-following requests
         | 
| 46 | 
            +
                mattr_accessor :remote_follow_url_method
         | 
| 47 | 
            +
                @@remote_follow_url_method = 'federails.new_client_following_url'
         | 
| 48 | 
            +
             | 
| 45 49 | 
             
                # Method to use for links to user profiles
         | 
| 46 50 | 
             
                # @deprecated Set profile_url_method option on acts_as_federails_actor instead
         | 
| 47 51 | 
             
                mattr_accessor :user_profile_url_method
         | 
    
        data/lib/federails/version.rb
    CHANGED
    
    
    
        data/lib/federails.rb
    CHANGED
    
    | @@ -27,10 +27,11 @@ module Federails | |
| 27 27 | 
             
                  :user_class, # @deprecated
         | 
| 28 28 | 
             
                  :server_routes_path,
         | 
| 29 29 | 
             
                  :client_routes_path,
         | 
| 30 | 
            +
                  :remote_follow_url_method,
         | 
| 30 31 | 
             
                  :user_profile_url_method, # @deprecated
         | 
| 31 32 | 
             
                  :user_name_field, # @deprecated
         | 
| 32 33 | 
             
                  :user_username_field, # @deprecated
         | 
| 33 | 
            -
                ].each { |key| Configuration.send :"#{key}=", config[key] }
         | 
| 34 | 
            +
                ].each { |key| Configuration.send :"#{key}=", config[key] if config.key?(key) }
         | 
| 34 35 | 
             
              end
         | 
| 35 36 | 
             
            end
         | 
| 36 37 | 
             
            # rubocop:enable Style/ClassVars
         | 
    
        data/lib/fediverse/notifier.rb
    CHANGED
    
    | @@ -1,21 +1,60 @@ | |
| 1 | 
            +
            require 'fediverse/signature'
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module Fediverse
         | 
| 2 4 | 
             
              class Notifier
         | 
| 3 5 | 
             
                class << self
         | 
| 4 6 | 
             
                  def post_to_inboxes(activity)
         | 
| 5 7 | 
             
                    actors = activity.recipients
         | 
| 6 | 
            -
             | 
| 7 8 | 
             
                    Rails.logger.debug('Nobody to notice') && return if actors.count.zero?
         | 
| 8 9 |  | 
| 9 | 
            -
                    message =  | 
| 10 | 
            +
                    message = payload(activity)
         | 
| 11 | 
            +
                    actors.each do |recipient|
         | 
| 12 | 
            +
                      Rails.logger.debug { "Sending activity ##{activity.id} to #{recipient.inbox_url}" }
         | 
| 13 | 
            +
                      post_to_inbox(to: recipient, message: message, from: activity.actor)
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  private
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def payload(activity)
         | 
| 20 | 
            +
                    Federails::ApplicationController.renderer.new.render(
         | 
| 10 21 | 
             
                      template: 'federails/server/activities/show',
         | 
| 11 22 | 
             
                      assigns:  { activity: activity },
         | 
| 12 23 | 
             
                      format:   :json
         | 
| 13 24 | 
             
                    )
         | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def post_to_inbox(to:, message:, from: nil)
         | 
| 28 | 
            +
                    conn = Faraday.default_connection
         | 
| 29 | 
            +
                    conn.builder.build_response(
         | 
| 30 | 
            +
                      conn,
         | 
| 31 | 
            +
                      signed_request(to: to, message: message, from: from)
         | 
| 32 | 
            +
                    )
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  def signed_request(to:, message:, from:)
         | 
| 36 | 
            +
                    req = request(to: to, message: message)
         | 
| 37 | 
            +
                    req.headers['Signature'] = Fediverse::Signature.sign(sender: from, request: req) if from
         | 
| 38 | 
            +
                    req
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def request(to:, message:) # rubocop:todo Metrics/AbcSize
         | 
| 42 | 
            +
                    Faraday.default_connection.build_request(:post) do |req|
         | 
| 43 | 
            +
                      req.url to.inbox_url
         | 
| 44 | 
            +
                      req.body = message
         | 
| 45 | 
            +
                      req.headers['Content-Type'] = Mime[:activitypub].to_s
         | 
| 46 | 
            +
                      req.headers['Accept'] = Mime[:activitypub].to_s
         | 
| 47 | 
            +
                      req.headers['Host'] = URI.parse(to.inbox_url).host
         | 
| 48 | 
            +
                      req.headers['Date'] = Time.now.utc.httpdate
         | 
| 49 | 
            +
                      req.headers['Digest'] = digest(message)
         | 
| 17 50 | 
             
                    end
         | 
| 18 51 | 
             
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def digest(message)
         | 
| 54 | 
            +
                    "SHA-256=#{Base64.strict_encode64(
         | 
| 55 | 
            +
                      OpenSSL::Digest.new('SHA256').digest(message)
         | 
| 56 | 
            +
                    )}"
         | 
| 57 | 
            +
                  end
         | 
| 19 58 | 
             
                end
         | 
| 20 59 | 
             
              end
         | 
| 21 60 | 
             
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            module Fediverse
         | 
| 2 | 
            +
              class Signature
         | 
| 3 | 
            +
                class << self
         | 
| 4 | 
            +
                  def sign(sender:, request:)
         | 
| 5 | 
            +
                    private_key = OpenSSL::PKey::RSA.new sender.private_key, Rails.application.credentials.secret_key_base
         | 
| 6 | 
            +
                    headers = '(request-target) host date digest'
         | 
| 7 | 
            +
                    sig = Base64.strict_encode64(
         | 
| 8 | 
            +
                      private_key.sign(
         | 
| 9 | 
            +
                        OpenSSL::Digest.new('SHA256'), signature_payload(request: request, headers: headers)
         | 
| 10 | 
            +
                      )
         | 
| 11 | 
            +
                    )
         | 
| 12 | 
            +
                    {
         | 
| 13 | 
            +
                      keyId:     sender.key_id,
         | 
| 14 | 
            +
                      headers:   headers,
         | 
| 15 | 
            +
                      signature: sig,
         | 
| 16 | 
            +
                    }.map { |k, v| "#{k}=\"#{v}\"" }.join(',')
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def verify(sender:, request:)
         | 
| 20 | 
            +
                    raise 'Unsigned headers' unless request.headers['Signature']
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    signature_header = request.headers['Signature'].split(',').to_h do |pair|
         | 
| 23 | 
            +
                      /\A(?<key>[\w]+)="(?<value>.*)"\z/ =~ pair
         | 
| 24 | 
            +
                      [key, value]
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    headers   = signature_header['headers']
         | 
| 28 | 
            +
                    signature = Base64.decode64(signature_header['signature'])
         | 
| 29 | 
            +
                    key       = OpenSSL::PKey::RSA.new(sender.public_key)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    comparison_string = signature_payload(request: request, headers: headers)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison_string)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  private
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def signature_payload(request:, headers:)
         | 
| 39 | 
            +
                    headers.split.map do |signed_header_name|
         | 
| 40 | 
            +
                      if signed_header_name == '(request-target)'
         | 
| 41 | 
            +
                        "(request-target): #{request.http_method} #{URI.parse(request.path).path}"
         | 
| 42 | 
            +
                      else
         | 
| 43 | 
            +
                        "#{signed_header_name}: #{request.headers[signed_header_name.capitalize]}"
         | 
| 44 | 
            +
                      end
         | 
| 45 | 
            +
                    end.join("\n")
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
    
        data/lib/fediverse/webfinger.rb
    CHANGED
    
    | @@ -30,28 +30,52 @@ module Fediverse | |
| 30 30 |  | 
| 31 31 | 
             
                  # Returns actor id
         | 
| 32 32 | 
             
                  def webfinger(username, domain)
         | 
| 33 | 
            -
                     | 
| 34 | 
            -
                    json = get_json "#{scheme}://#{domain}/.well-known/webfinger", resource: "acct:#{username}@#{domain}"
         | 
| 33 | 
            +
                    json = webfinger_response(username, domain)
         | 
| 35 34 | 
             
                    link = json['links'].find { |l| l['type'] == 'application/activity+json' }
         | 
| 36 35 |  | 
| 37 36 | 
             
                    link['href'] if link
         | 
| 38 37 | 
             
                  end
         | 
| 39 38 |  | 
| 39 | 
            +
                  # Returns remote follow link template, or complete link if actor_url is provided
         | 
| 40 | 
            +
                  def remote_follow_url(username, domain, actor_url: nil)
         | 
| 41 | 
            +
                    json = webfinger_response(username, domain)
         | 
| 42 | 
            +
                    link = json['links'].find { |l| l['rel'] == 'http://ostatus.org/schema/1.0/subscribe' }
         | 
| 43 | 
            +
                    return nil if link&.dig('template').nil?
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    if actor_url
         | 
| 46 | 
            +
                      link['template'].gsub('{uri}', CGI.escape(actor_url))
         | 
| 47 | 
            +
                    else
         | 
| 48 | 
            +
                      link['template']
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 40 52 | 
             
                  private
         | 
| 41 53 |  | 
| 54 | 
            +
                  def webfinger_response(username, domain)
         | 
| 55 | 
            +
                    scheme = Federails.configuration.force_ssl ? 'https' : 'http'
         | 
| 56 | 
            +
                    get_json "#{scheme}://#{domain}/.well-known/webfinger", resource: "acct:#{username}@#{domain}"
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  def server_and_port(id)
         | 
| 60 | 
            +
                    uri = URI.parse id
         | 
| 61 | 
            +
                    if uri.port && [80, 443].exclude?(uri.port)
         | 
| 62 | 
            +
                      "#{uri.host}:#{uri.port}"
         | 
| 63 | 
            +
                    else
         | 
| 64 | 
            +
                      uri.host
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 42 68 | 
             
                  def webfinger_to_actor(data)
         | 
| 43 | 
            -
                    uri    = URI.parse data['id']
         | 
| 44 | 
            -
                    server = uri.host
         | 
| 45 | 
            -
                    server += ":#{uri.port}" if uri.port && [80, 443].exclude?(uri.port)
         | 
| 46 69 | 
             
                    Federails::Actor.new federated_url:  data['id'],
         | 
| 47 70 | 
             
                                         username:       data['preferredUsername'],
         | 
| 48 71 | 
             
                                         name:           data['name'],
         | 
| 49 | 
            -
                                         server:          | 
| 72 | 
            +
                                         server:         server_and_port(data['id']),
         | 
| 50 73 | 
             
                                         inbox_url:      data['inbox'],
         | 
| 51 74 | 
             
                                         outbox_url:     data['outbox'],
         | 
| 52 75 | 
             
                                         followers_url:  data['followers'],
         | 
| 53 76 | 
             
                                         followings_url: data['following'],
         | 
| 54 | 
            -
                                         profile_url:    data['url']
         | 
| 77 | 
            +
                                         profile_url:    data['url'],
         | 
| 78 | 
            +
                                         public_key:     data.dig('publicKey', 'publicKeyPem')
         | 
| 55 79 | 
             
                  end
         | 
| 56 80 |  | 
| 57 81 | 
             
                  def get_json(url, payload = {})
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: federails
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Manuel Tancoigne
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-10-13 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: faraday
         | 
| @@ -134,6 +134,7 @@ files: | |
| 134 134 | 
             
            - app/jobs/federails/notify_inbox_job.rb
         | 
| 135 135 | 
             
            - app/mailers/federails/application_mailer.rb
         | 
| 136 136 | 
             
            - app/models/concerns/federails/entity.rb
         | 
| 137 | 
            +
            - app/models/concerns/federails/has_uuid.rb
         | 
| 137 138 | 
             
            - app/models/federails/activity.rb
         | 
| 138 139 | 
             
            - app/models/federails/actor.rb
         | 
| 139 140 | 
             
            - app/models/federails/application_record.rb
         | 
| @@ -166,24 +167,27 @@ files: | |
| 166 167 | 
             
            - app/views/federails/client/followings/index.json.jbuilder
         | 
| 167 168 | 
             
            - app/views/federails/client/followings/show.html.erb
         | 
| 168 169 | 
             
            - app/views/federails/client/followings/show.json.jbuilder
         | 
| 169 | 
            -
            - app/views/federails/server/activities/_activity. | 
| 170 | 
            -
            - app/views/federails/server/activities/outbox. | 
| 171 | 
            -
            - app/views/federails/server/activities/show. | 
| 172 | 
            -
            - app/views/federails/server/actors/_actor. | 
| 173 | 
            -
            - app/views/federails/server/actors/followers. | 
| 174 | 
            -
            - app/views/federails/server/actors/following. | 
| 175 | 
            -
            - app/views/federails/server/actors/show. | 
| 176 | 
            -
            - app/views/federails/server/followings/_following. | 
| 177 | 
            -
            - app/views/federails/server/followings/show. | 
| 178 | 
            -
            - app/views/federails/server/nodeinfo/index. | 
| 179 | 
            -
            - app/views/federails/server/nodeinfo/show. | 
| 180 | 
            -
            - app/views/federails/server/web_finger/find. | 
| 181 | 
            -
            - app/views/federails/server/web_finger/host_meta. | 
| 170 | 
            +
            - app/views/federails/server/activities/_activity.activitypub.jbuilder
         | 
| 171 | 
            +
            - app/views/federails/server/activities/outbox.activitypub.jbuilder
         | 
| 172 | 
            +
            - app/views/federails/server/activities/show.activitypub.jbuilder
         | 
| 173 | 
            +
            - app/views/federails/server/actors/_actor.activitypub.jbuilder
         | 
| 174 | 
            +
            - app/views/federails/server/actors/followers.activitypub.jbuilder
         | 
| 175 | 
            +
            - app/views/federails/server/actors/following.activitypub.jbuilder
         | 
| 176 | 
            +
            - app/views/federails/server/actors/show.activitypub.jbuilder
         | 
| 177 | 
            +
            - app/views/federails/server/followings/_following.activitypub.jbuilder
         | 
| 178 | 
            +
            - app/views/federails/server/followings/show.activitypub.jbuilder
         | 
| 179 | 
            +
            - app/views/federails/server/nodeinfo/index.nodeinfo.jbuilder
         | 
| 180 | 
            +
            - app/views/federails/server/nodeinfo/show.nodeinfo.jbuilder
         | 
| 181 | 
            +
            - app/views/federails/server/web_finger/find.jrd.jbuilder
         | 
| 182 | 
            +
            - app/views/federails/server/web_finger/host_meta.xrd.erb
         | 
| 183 | 
            +
            - config/initializers/mime_types.rb
         | 
| 182 184 | 
             
            - config/routes.rb
         | 
| 183 185 | 
             
            - db/migrate/20200712133150_create_federails_actors.rb
         | 
| 184 186 | 
             
            - db/migrate/20200712143127_create_federails_followings.rb
         | 
| 185 187 | 
             
            - db/migrate/20200712174938_create_federails_activities.rb
         | 
| 186 188 | 
             
            - db/migrate/20240731145400_change_actor_entity_rel_to_polymorphic.rb
         | 
| 189 | 
            +
            - db/migrate/20241002094500_add_uuids.rb
         | 
| 190 | 
            +
            - db/migrate/20241002094501_add_keypair_to_actors.rb
         | 
| 187 191 | 
             
            - lib/federails.rb
         | 
| 188 192 | 
             
            - lib/federails/configuration.rb
         | 
| 189 193 | 
             
            - lib/federails/engine.rb
         | 
| @@ -192,6 +196,7 @@ files: | |
| 192 196 | 
             
            - lib/fediverse/inbox.rb
         | 
| 193 197 | 
             
            - lib/fediverse/notifier.rb
         | 
| 194 198 | 
             
            - lib/fediverse/request.rb
         | 
| 199 | 
            +
            - lib/fediverse/signature.rb
         | 
| 195 200 | 
             
            - lib/fediverse/webfinger.rb
         | 
| 196 201 | 
             
            - lib/generators/federails/install/USAGE
         | 
| 197 202 | 
             
            - lib/generators/federails/install/install_generator.rb
         | 
    
        /data/app/views/federails/server/activities/{outbox.json.jbuilder → outbox.activitypub.jbuilder}
    RENAMED
    
    | 
            File without changes
         | 
    
        /data/app/views/federails/server/activities/{show.json.jbuilder → show.activitypub.jbuilder}
    RENAMED
    
    | 
            File without changes
         | 
    
        /data/app/views/federails/server/actors/{followers.json.jbuilder → followers.activitypub.jbuilder}
    RENAMED
    
    | 
            File without changes
         | 
    
        /data/app/views/federails/server/actors/{following.json.jbuilder → following.activitypub.jbuilder}
    RENAMED
    
    | 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
    
        /data/app/views/federails/server/followings/{show.json.jbuilder → show.activitypub.jbuilder}
    RENAMED
    
    | 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         |