1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
# frozen_string_literal: true
# server-server
post '/inbox' do
request.body.rewind # in case someone already read it
@body = request.body.read
halt 400 if @body.empty?
begin
@activity = JSON.parse @body
rescue StandardError
p @body
halt 400
end
type = @activity['type'].downcase.to_sym
p type
halt 501 unless respond_to?(type)
@object = @activity['object']
@object = fetch(@object) if @object.is_a?(String) && @object.match(/^http/)
halt 400 unless @object
verify! unless type == :accept # pixelfed sends unsigned accept activities???
send(type)
halt 200
end
# public
get '/.well-known/webfinger' do
halt 404 unless request['resource'] == "acct:#{MENTION}"
send_file(WEBFINGER, type: 'application/jrd+json')
end
['/pdp8', '/following', '/followers', '/outbox', '/shared'].each do |path|
get path do
send_file(File.join(PUBLIC_DIR, path) + '.json', type: CONTENT_TYPE)
end
end
helpers do
def create
return unless @object
# return if File.readlines(VISITED).collect { |l| l.chomp }.include? @object['id']
# File.open(VISITED, 'a+') { |f| f.puts @object['id'] }
update_collection INBOX, @object
return unless @object['inReplyTo']
@object = fetch @object['inReplyTo']
create if @object
end
def announce
create
end
def follow
update_collection FOLLOWERS, @activity['actor']
outbox 'Accept', @activity, [@activity['actor']]
end
def accept
halt 501 unless @object['type'] == 'Follow'
update_collection FOLLOWING, @object['object']
end
def undo
halt 501 unless @object['type'] == 'Follow'
update_collection FOLLOWERS, @object['actor'], true
end
# https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb
def verify!
# digest
sha256 = OpenSSL::Digest.new('SHA256')
digest = "SHA-256=#{sha256.base64digest(@body)}"
halt 403 unless digest == request.env['HTTP_DIGEST']
# signature
signature_params = {}
request.env['HTTP_SIGNATURE'].split(',').each do |pair|
k, v = pair.split('=')
signature_params[k] = v.gsub('"', '')
end
key_id = signature_params['keyId']
headers = signature_params['headers']
signature = Base64.decode64(signature_params['signature'])
actor = fetch key_id
halt 403 unless actor
key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
comparison = headers.split(' ').map do |signed_params_name|
if signed_params_name == '(request-target)'
'(request-target): post /inbox'
elsif signed_params_name == 'content-type'
"#{signed_params_name}: #{request.env['CONTENT_TYPE']}"
else
"#{signed_params_name}: #{request.env["HTTP_#{signed_params_name.upcase}"]}"
end
end.join("\n")
halt 403 unless key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
end
end
|