Skip to content
This repository has been archived by the owner on Jul 3, 2023. It is now read-only.

Commit

Permalink
Implement naive and mostly hardcoded flow for following a remote user
Browse files Browse the repository at this point in the history
  • Loading branch information
berkes committed Mar 12, 2021
1 parent 791328b commit a354101
Show file tree
Hide file tree
Showing 17 changed files with 352 additions and 2 deletions.
22 changes: 22 additions & 0 deletions app/aggregates/peer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Aggregates
##
# A +Peer+ is a remote actor.
class Peer
include EventSourcery::AggregateRoot

AWAIT_SYNCING_VALUE = 'Synching...'

attr_reader :name, :bio

def initialize(id, events)
@name = AWAIT_SYNCING_VALUE
@bio = AWAIT_SYNCING_VALUE
super(id, events)
end

apply PeerSynched do
end
end
end
8 changes: 8 additions & 0 deletions app/events/events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@
# A Member was Tagged
MemberTagAdded = Class.new(EventSourcery::Event)

##
# Fetching of Details for peer from remote server requested.
PeerFetchRequested = Class.new(EventSourcery::Event)

##
# Fetching of Details for peer finished
PeerSynched = Class.new(EventSourcery::Event)

##
# A Registration is confirmed
RegistrationConfirmed = Class.new(EventSourcery::Event)
Expand Down
23 changes: 23 additions & 0 deletions app/reactors/contact_fetcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module Reactors
##
# Adds an actor to a members' followers on various Events
class ContactFetcher
include EventSourcery::Postgres::Reactor

processor_name :contact_fetcher

emits_events PeerSynched

process PeerFetchRequested do |event|
emit_event(
PeerSynched.new(
aggregate_id: event.aggregate_id,
body: event.body,
causation_id: event.uuid
)
)
end
end
end
4 changes: 4 additions & 0 deletions app/web/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def authorize(&block)
raise Unauthorized unless block.call
end

def authorized?
current_member.active?
end

def current_member
return OpenStruct.new(active?: false) unless member_id

Expand Down
3 changes: 2 additions & 1 deletion app/web/controllers/web/contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class ContactsController < WebController

# Add
post '/contacts' do
requires_authorization
redirect '/remote' unless authorized?

authorize { may_add_contact? }

Commands.handle(
Expand Down
45 changes: 45 additions & 0 deletions app/web/controllers/web/remote_confirmations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module Web
##
# Handles incoming redirects from remotes to confirm an action
class RemoteConfirmationsController < WebController
# TODO: authenticate and authorize
get '/remote_confirmation' do
erb(
:remote_confirmation,
layout: :layout_member,
locals: {
message: message,
form_action: form_action,
target: target,
taget_type: target_type
}
)
end

private

# TODO: Once we have more actions, fetch this from signed attributes and
# pull through an allowlist
def form_action
'contacts'
end

# TODO: Unhardcode this message
def message
"As @harry@example.com you want to follow #{target}"
end

# TODO: Fetch the target from attributes
def target
'@luna@ravenclaw.example.org'
end

# TODO: Once we have more target types, fetch this from signed attributes
# and pull through an allowlist
def target_type
'account'
end
end
end
58 changes: 58 additions & 0 deletions app/web/controllers/web/remote_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

module Web
##
# Handles Remote redirects
# TODO: sanitize and whitelist actions
# TODO: handle misparsed handles
# TODO: exchange server-server signed secrets with remote instance so that
# remote instance knows this request is coming and can validate it.
class RemoteController < WebController
get '/remote' do
erb(
:remote,
layout: :layout_anonymous,
locals: {
message: message,
action: action,
target: target,
taget_type: target_type
}
)
end

post '/remote' do
redirect remote_confirm_uri
end

private

def remote_confirm_uri
URI::HTTPS.build(
host: Handle.parse(params[:handle]).domain,
path: '/remote_confirmation',
query: URI.encode_www_form(
action: action,
target: target,
target_type: target_type
)
)
end

def action
'follow'
end

def message
'Provide your handle to follow @luna@ravenclaw.example.org'
end

def target
'@luna.ravenclaw.example.org'
end

def target_type
'account'
end
end
end
20 changes: 20 additions & 0 deletions app/web/views/remote.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<form action="/remote" method="post">
<input name="action" type="hidden" value="<%= action %>">

<input name="target" type="hidden" value="<%= target %>">
<input name="target_type" type="hidden" value="<%= target_type %>">

<div class="field">
<label class="label" for="handle"><%= message %></label>
<div class="control">
<input name="handle"
id="handle"
class="input"
type="text"
placeholder="@username@example.com">
</div>
</div>
<div class="control">
<button class="button is-primary"><%= action.capitalize %></button>
</div>
</form>
7 changes: 7 additions & 0 deletions app/web/views/remote_confirmation.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<p><%= message %></p>
<form action="/<%= form_action %>" method="post">
<input name="handle" type="hidden" value="<%= target %>">
<div class="control">
<button class="button is-primary">Confirm</button>
</div>
</form>
3 changes: 3 additions & 0 deletions config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use Web::HomeController
use Web::ProfilesController
use Web::TagsController

use Web::RemoteController
use Web::RemoteConfirmationsController

use Web::LoginController
# TODO: change from RPC alike "register" to "registration"
use Web::RegistrationsController
Expand Down
2 changes: 1 addition & 1 deletion lib/handle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Handle

def initialize(username,
handle_domain = Roost.config.domain,
local_domain = Roost.config.domain)
local_domain = handle_domain)
@domain = handle_domain
@local_domain = local_domain
@username = username
Expand Down
22 changes: 22 additions & 0 deletions test/aggregates/peer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

require 'test_helper'

module Aggregates
##
# Unit test for the more complex logic in Peer Aggregate
class PeerTest < Minitest::Spec
let(:applied_events) { [] }
let(:aggregate_id) { fake_uuid(Aggregates::Peer, 1) }

let(:subject) { Aggregates::Peer.new(aggregate_id, applied_events) }

it '.name defaults to "Synching..."' do
assert_equal('Synching...', subject.name)
end

it '.bio defaults to "Synching..."' do
assert_equal('Synching...', subject.bio)
end
end
end
30 changes: 30 additions & 0 deletions test/integration/web/federated_contacts_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require 'test_helper'

##
# As a member using the web-app
# When I visit another members' profile
# And I click the "add to contacts" button
# Then the member is added to my contacts
class FederatedContactsTest < Minitest::WebSpec
before do
skip 'implement remote flow first'
harry
as(harry)

# INK: remote_action.
adds_contact.upto(:contact_added)
end

it 'adds another member to contacts' do
# NOTE that the handle uses .com and rons email .org
assert_content(
flash(:success),
'ron@example.com was added to your contacts'
)

visit '/contacts'
assert_content('ron@example.com')
end
end
54 changes: 54 additions & 0 deletions test/integration/web/remote_action_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'test_helper'

##
# As a member of this.example.com instance
# When I visit other.example.com instance
# And I request an action there
# Then I am am presented with a form for my handle
# And when I fill that form and click "{action}" button
# Then I am redirected to my own instance with the proper payload
# So that I can finalize the action on my own instance
#
# NOTE: {action} is any of (but not limited to), add contact, tag, annotate,
# etc.
class RemoteActionTest < Minitest::WebSpec
let(:handle) { luna[:handle] }

it 'adds remote member as contact' do
landing_path = at(ravenclaw) do
visit "/m/#{handle}"
click_icon('account-plus')

fill_in(
"Provide your handle to follow #{handle}",
with: '@harry@example.com'
)
click_button('Follow')
page.current_path
end

# Revisit the page to open it with harry as session
as(harry)
visit landing_path

assert_content(page, "As @harry@example.com you want to follow #{handle}")
click_button('Confirm')
assert_content(flash(:success), "#{handle} was added to your contacts")

process_events(%w[contact_fetch_requested contact_added])

visit '/contacts'
assert_content(luna[:handle])
end

# TODO: handle non-logged in on local.
# TODO: handle authentication properly with oauth secrets exchange.

private

def ravenclaw
@ravenclaw ||= RemoteInstance.new('ravenclaw.example.org', self)
end
end
14 changes: 14 additions & 0 deletions test/support/data_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ module DataHelpers
'Aggregates::Contact' => 4
}.freeze

def luna
return @_luna if @_luna

@_luna = {
handle: '@luna@ravenclaw.example.org',
username: 'luna',
email: 'luna@ravenclaw.example.org',
password: 'secret'
}
member_registers(@_luna).upto(:confirmed).html

@_luna
end

protected

##
Expand Down
Loading

0 comments on commit a354101

Please sign in to comment.