kopia lustrzana https://github.com/snarfed/bridgy-fed
rodzic
6e000d348d
commit
c50f0e0106
|
@ -8,8 +8,9 @@ from oauth_dropins.webutil import appengine_info, util
|
||||||
# otherwise.
|
# otherwise.
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
# Change to Lax if/when we add IndieAuth for anything.
|
# Not strict because we flash messages after cross-site redirects for OAuth,
|
||||||
SESSION_COOKIE_SAMESITE = 'Strict'
|
# which strict blocks.
|
||||||
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||||
|
|
||||||
if appengine_info.DEBUG:
|
if appengine_info.DEBUG:
|
||||||
ENV = 'development'
|
ENV = 'development'
|
||||||
|
|
164
follow.py
164
follow.py
|
@ -8,20 +8,71 @@ import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
from flask import redirect, request
|
from flask import redirect, request
|
||||||
|
from granary import as2
|
||||||
|
from oauth_dropins import indieauth
|
||||||
from oauth_dropins.webutil import flask_util, util
|
from oauth_dropins.webutil import flask_util, util
|
||||||
from oauth_dropins.webutil.flask_util import error, flash
|
from oauth_dropins.webutil.flask_util import error, flash
|
||||||
|
from oauth_dropins.webutil import util
|
||||||
from oauth_dropins.webutil.util import json_dumps, json_loads
|
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||||
|
|
||||||
from app import app
|
from app import app
|
||||||
import common
|
import common
|
||||||
from models import User
|
from models import Follower, User
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
SUBSCRIBE_LINK_REL = 'http://ostatus.org/schema/1.0/subscribe'
|
SUBSCRIBE_LINK_REL = 'http://ostatus.org/schema/1.0/subscribe'
|
||||||
|
|
||||||
|
|
||||||
@app.post('/follow')
|
def fetch_webfinger(addr):
|
||||||
|
"""Fetches and returns an address's Webfinger data.
|
||||||
|
|
||||||
|
On failure, flashes a message and returns None.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
addr: str, a Webfinger-compatible address, eg @x@y, acct:x@y, or
|
||||||
|
https://x/y
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict, fetched Webfinger data
|
||||||
|
"""
|
||||||
|
addr = addr.strip().strip('@')
|
||||||
|
split = addr.split('@')
|
||||||
|
if len(split) == 2:
|
||||||
|
addr_domain = split[1]
|
||||||
|
resource = f'acct:{addr}'
|
||||||
|
elif util.is_web(addr):
|
||||||
|
addr_domain = util.domain_from_link(addr, minimize=False)
|
||||||
|
resource = addr
|
||||||
|
else:
|
||||||
|
flash('Enter a fediverse address in @user@domain.social format')
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = util.requests_get(
|
||||||
|
f'https://{addr_domain}/.well-known/webfinger?resource={resource}')
|
||||||
|
except BaseException as e:
|
||||||
|
if util.is_connection_failure(e):
|
||||||
|
flash(f"Couldn't connect to {addr_domain}")
|
||||||
|
return None
|
||||||
|
raise
|
||||||
|
|
||||||
|
if not resp.ok:
|
||||||
|
flash(f'WebFinger on {addr_domain} returned HTTP {resp.status_code}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = resp.json()
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(f'Got {e}', exc_info=True)
|
||||||
|
flash(f'WebFinger on {addr_domain} returned non-JSON')
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.info(f'Got: {json_dumps(data, indent=2)}')
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/remote-follow')
|
||||||
def remote_follow():
|
def remote_follow():
|
||||||
"""Discovers and redirects to a remote follow page for a given user."""
|
"""Discovers and redirects to a remote follow page for a given user."""
|
||||||
logger.info(f'Got: {request.values}')
|
logger.info(f'Got: {request.values}')
|
||||||
|
@ -31,42 +82,11 @@ def remote_follow():
|
||||||
if not user:
|
if not user:
|
||||||
error(f'No Bridgy Fed user found for domain {domain}')
|
error(f'No Bridgy Fed user found for domain {domain}')
|
||||||
|
|
||||||
addr = request.values['address'].strip().strip('@')
|
webfinger = fetch_webfinger(request.values['address'])
|
||||||
split = addr.split('@')
|
if webfinger is None:
|
||||||
if len(split) == 2:
|
|
||||||
addr_domain = split[1]
|
|
||||||
resource = f'acct:{addr}'
|
|
||||||
elif util.is_web(addr):
|
|
||||||
addr_domain = util.domain_from_link(addr, minimize=False)
|
|
||||||
resource = addr
|
|
||||||
else:
|
|
||||||
flash('Enter your fediverse address in @user@domain.social format')
|
|
||||||
return redirect(f'/user/{domain}')
|
return redirect(f'/user/{domain}')
|
||||||
|
|
||||||
# look up remote user via webfinger
|
for link in webfinger.get('links', []):
|
||||||
try:
|
|
||||||
resp = util.requests_get(
|
|
||||||
f'https://{addr_domain}/.well-known/webfinger?resource={resource}')
|
|
||||||
except BaseException as e:
|
|
||||||
if util.is_connection_failure(e):
|
|
||||||
flash(f"Couldn't connect to {addr_domain}")
|
|
||||||
return redirect(f'/user/{domain}')
|
|
||||||
raise
|
|
||||||
|
|
||||||
if not resp.ok:
|
|
||||||
flash(f'WebFinger on {addr_domain} returned HTTP {resp.status_code}')
|
|
||||||
return redirect(f'/user/{domain}')
|
|
||||||
|
|
||||||
# find remote follow link and redirect
|
|
||||||
try:
|
|
||||||
data = resp.json()
|
|
||||||
except ValueError as e:
|
|
||||||
logger.warning(f'Got {e}', exc_info=True)
|
|
||||||
flash(f'WebFinger on {domain} returned non-JSON')
|
|
||||||
return redirect(f'/user/{domain}')
|
|
||||||
|
|
||||||
logger.info(f'Got: {json_dumps(data, indent=2)}')
|
|
||||||
for link in data.get('links', []):
|
|
||||||
if link.get('rel') == SUBSCRIBE_LINK_REL:
|
if link.get('rel') == SUBSCRIBE_LINK_REL:
|
||||||
template = link.get('template')
|
template = link.get('template')
|
||||||
if template and '{uri}' in template:
|
if template and '{uri}' in template:
|
||||||
|
@ -74,3 +94,75 @@ def remote_follow():
|
||||||
|
|
||||||
flash(f"Couldn't find remote follow link for {addr}")
|
flash(f"Couldn't find remote follow link for {addr}")
|
||||||
return redirect(f'/user/{domain}')
|
return redirect(f'/user/{domain}')
|
||||||
|
|
||||||
|
|
||||||
|
class FollowStart(indieauth.Start):
|
||||||
|
"""Starts the IndieAuth flow to add a follower to an existing user."""
|
||||||
|
def dispatch_request(self):
|
||||||
|
address = request.form['address']
|
||||||
|
|
||||||
|
try:
|
||||||
|
return redirect(self.redirect_url(state=address))
|
||||||
|
except Exception as e:
|
||||||
|
if util.is_connection_failure(e) or util.interpret_http_exception(e)[0]:
|
||||||
|
flash(f"Couldn't fetch your web site: {e}")
|
||||||
|
return redirect(f'/user/{domain}/following?address={address}')
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class FollowCallback(indieauth.Callback):
|
||||||
|
"""IndieAuth callback to add a follower to an existing user."""
|
||||||
|
def finish(self, auth_entity, state=None):
|
||||||
|
if not auth_entity:
|
||||||
|
return
|
||||||
|
|
||||||
|
domain = util.domain_from_link(auth_entity.key.id())
|
||||||
|
if not User.get_by_id(domain):
|
||||||
|
error(f'No user for domain {domain}')
|
||||||
|
|
||||||
|
# addr = state.get('address')
|
||||||
|
# if not addr:
|
||||||
|
# error(f'state missing address field')
|
||||||
|
addr = state
|
||||||
|
assert addr
|
||||||
|
webfinger = fetch_webfinger(addr)
|
||||||
|
if webfinger is None:
|
||||||
|
return redirect(f'/user/{domain}/following')
|
||||||
|
|
||||||
|
as2_url = None
|
||||||
|
for link in webfinger.get('links', []):
|
||||||
|
if link.get('rel') == 'self' and link.get('type') == as2.CONTENT_TYPE:
|
||||||
|
as2_url = link.get('href')
|
||||||
|
|
||||||
|
if not as2_url:
|
||||||
|
flash(f"Couldn't find ActivityPub profile link for {addr}")
|
||||||
|
return redirect(f'/user/{domain}/following')
|
||||||
|
|
||||||
|
resp = common.get_as2(as2_url)
|
||||||
|
obj = resp.json()
|
||||||
|
id = obj.get('id')
|
||||||
|
inbox = obj.get('inbox')
|
||||||
|
if not id or not inbox:
|
||||||
|
flash(f"AS2 profile {as2_url} missing id or inbox")
|
||||||
|
return redirect(f'/user/{domain}/following')
|
||||||
|
|
||||||
|
common.signed_post(inbox, data={
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'type': 'Follow',
|
||||||
|
'id': 'TODO',
|
||||||
|
'object': id,
|
||||||
|
'actor': common.host_url(domain),
|
||||||
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
|
})
|
||||||
|
|
||||||
|
Follower.get_or_create(dest=id, src=domain, status='active',
|
||||||
|
last_follow=json_dumps({}))
|
||||||
|
flash(f'Followed {addr}.')
|
||||||
|
return redirect(f'/user/{domain}/following')
|
||||||
|
|
||||||
|
|
||||||
|
app.add_url_rule('/follow/start',
|
||||||
|
view_func=FollowStart.as_view('follow_start', '/follow/callback'),
|
||||||
|
methods=['POST'])
|
||||||
|
app.add_url_rule('/follow/callback',
|
||||||
|
view_func=FollowCallback.as_view('follow_callback', 'unused'))
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
https://fed.brid.gy/
|
1
pages.py
1
pages.py
|
@ -97,6 +97,7 @@ def user(domain):
|
||||||
follow_url=request.values.get('url'),
|
follow_url=request.values.get('url'),
|
||||||
logs=logs,
|
logs=logs,
|
||||||
util=util,
|
util=util,
|
||||||
|
**request.args,
|
||||||
**locals(),
|
**locals(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,20 @@
|
||||||
|
|
||||||
{% include "user_addresses.html" %}
|
{% include "user_addresses.html" %}
|
||||||
|
|
||||||
<div class="row">Following</div>
|
<div class="row big">Following</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<form method="post" action="/follow">
|
||||||
|
<p>
|
||||||
|
<label for="follower-address">Add a follower (requires <a href="https://indieauth.net/">IndieAuth</a>):</label>
|
||||||
|
<input id="follower-address" name="address" type="text" required
|
||||||
|
placeholder="@user@domain.social" alt="fediverse address"
|
||||||
|
value="{{ address or '' }}"></input>
|
||||||
|
<input name="me" type="hidden" value="https://{{ domain }}"></input>
|
||||||
|
<button type="submit" class="btn btn-default">Follow</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% include "_followers.html" %}
|
{% include "_followers.html" %}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<form method="post" action="/follow">
|
<form method="post" action="/remote-follow">
|
||||||
<p>
|
<p>
|
||||||
<label for="follow-address">Enter your fediverse address to follow:</label>
|
<label for="follow-address">Enter your fediverse address to follow:</label>
|
||||||
<input id="follow-address" name="address" type="text" required
|
<input id="follow-address" name="address" type="text" required
|
||||||
|
@ -61,8 +61,6 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="row">Recent activity</div> -->
|
|
||||||
|
|
||||||
{% include "activities.html" %}
|
{% include "activities.html" %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,10 +2,15 @@
|
||||||
"""
|
"""
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from flask import get_flashed_messages
|
||||||
|
from granary import as2
|
||||||
|
from oauth_dropins import indieauth
|
||||||
|
from oauth_dropins.webutil import util
|
||||||
from oauth_dropins.webutil.testutil import requests_response
|
from oauth_dropins.webutil.testutil import requests_response
|
||||||
|
from oauth_dropins.webutil.util import json_dumps, json_loads
|
||||||
|
|
||||||
import common
|
import common
|
||||||
from models import User
|
from models import Follower, User
|
||||||
from . import testutil
|
from . import testutil
|
||||||
|
|
||||||
WEBFINGER = requests_response({
|
WEBFINGER = requests_response({
|
||||||
|
@ -16,31 +21,36 @@ WEBFINGER = requests_response({
|
||||||
'links': [{
|
'links': [{
|
||||||
'rel': 'http://ostatus.org/schema/1.0/subscribe',
|
'rel': 'http://ostatus.org/schema/1.0/subscribe',
|
||||||
'template': 'https://bar/follow?uri={uri}'
|
'template': 'https://bar/follow?uri={uri}'
|
||||||
|
}, {
|
||||||
|
'rel': 'self',
|
||||||
|
'type': as2.CONTENT_TYPE,
|
||||||
|
'href': 'https://bar/actor'
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@patch('requests.get')
|
@patch('requests.get')
|
||||||
class FollowTest(testutil.TestCase):
|
class RemoteFollowTest(testutil.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
User.get_or_create('me')
|
User.get_or_create('me')
|
||||||
|
|
||||||
def test_follow_no_domain(self, mock_get):
|
def test_follow_no_domain(self, mock_get):
|
||||||
got = self.client.post('/follow?address=@foo@bar')
|
got = self.client.post('/remote-follow?address=@foo@bar')
|
||||||
self.assertEqual(400, got.status_code)
|
self.assertEqual(400, got.status_code)
|
||||||
|
|
||||||
def test_follow_no_address(self, mock_get):
|
def test_follow_no_address(self, mock_get):
|
||||||
got = self.client.post('/follow?domain=baz.com')
|
got = self.client.post('/remote-follow?domain=baz.com')
|
||||||
self.assertEqual(400, got.status_code)
|
self.assertEqual(400, got.status_code)
|
||||||
|
|
||||||
def test_follow_no_user(self, mock_get):
|
def test_follow_no_user(self, mock_get):
|
||||||
got = self.client.post('/follow?address=@foo@bar&domain=baz.com')
|
got = self.client.post('/remote-follow?address=@foo@bar&domain=baz.com')
|
||||||
self.assertEqual(400, got.status_code)
|
self.assertEqual(400, got.status_code)
|
||||||
|
|
||||||
def test_follow(self, mock_get):
|
def test_follow(self, mock_get):
|
||||||
mock_get.return_value = WEBFINGER
|
mock_get.return_value = WEBFINGER
|
||||||
got = self.client.post('/follow?address=@foo@bar&domain=me')
|
got = self.client.post('/remote-follow?address=@foo@bar&domain=me')
|
||||||
self.assertEqual(302, got.status_code)
|
self.assertEqual(302, got.status_code)
|
||||||
self.assertEqual('https://bar/follow?uri=@me@me',
|
self.assertEqual('https://bar/follow?uri=@me@me',
|
||||||
got.headers['Location'])
|
got.headers['Location'])
|
||||||
|
@ -51,7 +61,7 @@ class FollowTest(testutil.TestCase):
|
||||||
|
|
||||||
def test_follow_url(self, mock_get):
|
def test_follow_url(self, mock_get):
|
||||||
mock_get.return_value = WEBFINGER
|
mock_get.return_value = WEBFINGER
|
||||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
got = self.client.post('/remote-follow?address=https://bar/foo&domain=me')
|
||||||
self.assertEqual(302, got.status_code)
|
self.assertEqual(302, got.status_code)
|
||||||
self.assertEqual('https://bar/follow?uri=@me@me', got.headers['Location'])
|
self.assertEqual('https://bar/follow?uri=@me@me', got.headers['Location'])
|
||||||
|
|
||||||
|
@ -65,20 +75,102 @@ class FollowTest(testutil.TestCase):
|
||||||
'links': [{'rel': 'other', 'template': 'meh'}],
|
'links': [{'rel': 'other', 'template': 'meh'}],
|
||||||
})
|
})
|
||||||
|
|
||||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
got = self.client.post('/remote-follow?address=https://bar/foo&domain=me')
|
||||||
self.assertEqual(302, got.status_code)
|
self.assertEqual(302, got.status_code)
|
||||||
self.assertEqual('/user/me', got.headers['Location'])
|
self.assertEqual('/user/me', got.headers['Location'])
|
||||||
|
|
||||||
def test_follow_no_webfinger_subscribe_link(self, mock_get):
|
def test_follow_no_webfinger_subscribe_link(self, mock_get):
|
||||||
mock_get.return_value = requests_response(status_code=500)
|
mock_get.return_value = requests_response(status_code=500)
|
||||||
|
|
||||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
got = self.client.post('/remote-follow?address=https://bar/foo&domain=me')
|
||||||
self.assertEqual(302, got.status_code)
|
self.assertEqual(302, got.status_code)
|
||||||
self.assertEqual('/user/me', got.headers['Location'])
|
self.assertEqual('/user/me', got.headers['Location'])
|
||||||
|
|
||||||
def test_follow_no_webfinger_subscribe_link(self, mock_get):
|
def test_follow_no_webfinger_subscribe_link(self, mock_get):
|
||||||
mock_get.return_value = requests_response('<html>not json</html>')
|
mock_get.return_value = requests_response('<html>not json</html>')
|
||||||
|
|
||||||
got = self.client.post('/follow?address=https://bar/foo&domain=me')
|
got = self.client.post('/remote-follow?address=https://bar/foo&domain=me')
|
||||||
self.assertEqual(302, got.status_code)
|
self.assertEqual(302, got.status_code)
|
||||||
self.assertEqual('/user/me', got.headers['Location'])
|
self.assertEqual('/user/me', got.headers['Location'])
|
||||||
|
|
||||||
|
|
||||||
|
@patch('requests.post')
|
||||||
|
@patch('requests.get')
|
||||||
|
class AddFollowerTest(testutil.TestCase):
|
||||||
|
|
||||||
|
def test_start(self, mock_get, _):
|
||||||
|
resp = self.client.post('/follow/start', data={
|
||||||
|
'me': 'https://snarfed.org',
|
||||||
|
'address': '@foo@bar',
|
||||||
|
})
|
||||||
|
self.assertEqual(302, resp.status_code)
|
||||||
|
self.assertTrue(resp.headers['Location'].startswith(indieauth.INDIEAUTH_URL),
|
||||||
|
resp.headers['Location'])
|
||||||
|
|
||||||
|
def test_callback(self, mock_get, mock_post):
|
||||||
|
mock_post.side_effect = (
|
||||||
|
requests_response('me=https://snarfed.org'),
|
||||||
|
requests_response('OK'), # AP Follow to inbox
|
||||||
|
)
|
||||||
|
mock_get.side_effect = (
|
||||||
|
# oauth-dropins indieauth https://snarfed.org fetch for user json
|
||||||
|
requests_response(''),
|
||||||
|
WEBFINGER,
|
||||||
|
self.as2_resp({
|
||||||
|
'type': 'Person',
|
||||||
|
'id': 'https://bar/id',
|
||||||
|
'inbox': 'http://bar/inbox',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
User.get_or_create('snarfed.org')
|
||||||
|
|
||||||
|
state = util.encode_oauth_state({
|
||||||
|
'endpoint': 'http://auth/endpoint',
|
||||||
|
'me': 'https://snarfed.org',
|
||||||
|
'state': '@foo@bar',
|
||||||
|
})
|
||||||
|
with self.client:
|
||||||
|
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
||||||
|
self.assertEqual(302, resp.status_code)
|
||||||
|
self.assertEqual('/user/snarfed.org/following',resp.headers['Location'])
|
||||||
|
self.assertEqual(['Followed @foo@bar.'], get_flashed_messages())
|
||||||
|
|
||||||
|
mock_get.assert_has_calls((
|
||||||
|
self.req('https://bar/.well-known/webfinger?resource=acct:foo@bar'),
|
||||||
|
self.as2_req('https://bar/actor'),
|
||||||
|
))
|
||||||
|
mock_post.assert_has_calls((
|
||||||
|
self.req('http://auth/endpoint', data={
|
||||||
|
'me': 'https://snarfed.org',
|
||||||
|
'state': '@foo@bar',
|
||||||
|
'code': 'my_code',
|
||||||
|
'client_id': indieauth.INDIEAUTH_CLIENT_ID,
|
||||||
|
'redirect_uri': 'http://localhost/follow/callback',
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
inbox_args, inbox_kwargs = mock_post.call_args_list[-1]
|
||||||
|
self.assertEqual(('http://bar/inbox',), inbox_args)
|
||||||
|
self.assert_equals({
|
||||||
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||||
|
'type': 'Follow',
|
||||||
|
'id': 'TODO',
|
||||||
|
'actor': 'http://localhost/snarfed.org',
|
||||||
|
'object': 'https://bar/id',
|
||||||
|
'to': [as2.PUBLIC_AUDIENCE],
|
||||||
|
}, json_loads(inbox_kwargs['data']))
|
||||||
|
|
||||||
|
followers = Follower.query().fetch()
|
||||||
|
self.assertEqual(1, len(followers))
|
||||||
|
self.assertEqual('https://bar/id snarfed.org', followers[0].key.id())
|
||||||
|
|
||||||
|
def test_callback_missing_user(self, mock_get, mock_post):
|
||||||
|
mock_post.return_value = requests_response('me=https://snarfed.org')
|
||||||
|
|
||||||
|
state = util.encode_oauth_state({
|
||||||
|
'endpoint': 'http://auth/endpoint',
|
||||||
|
'me': 'https://snarfed.org',
|
||||||
|
'state': '@foo@bar',
|
||||||
|
})
|
||||||
|
with self.client:
|
||||||
|
resp = self.client.get(f'/follow/callback?code=my_code&state={state}')
|
||||||
|
self.assertEqual(400, resp.status_code)
|
||||||
|
|
|
@ -120,12 +120,12 @@ class Actor(flask_util.XrdOrJrd):
|
||||||
# clue how or why. pay attention here if that happens again.
|
# clue how or why. pay attention here if that happens again.
|
||||||
'href': common.host_url(domain),
|
'href': common.host_url(domain),
|
||||||
}, {
|
}, {
|
||||||
|
# AP reads this and sharedInbox from the AS2 actor, not
|
||||||
|
# webfinger, so strictly speaking, it's probably not needed here.
|
||||||
'rel': 'inbox',
|
'rel': 'inbox',
|
||||||
'type': as2.CONTENT_TYPE,
|
'type': as2.CONTENT_TYPE,
|
||||||
'href': common.host_url(f'{domain}/inbox'),
|
'href': common.host_url(f'{domain}/inbox'),
|
||||||
}, {
|
}, {
|
||||||
# AP reads this from the AS2 actor, not webfinger, so strictly
|
|
||||||
# speaking, it's probably not needed here.
|
|
||||||
# https://www.w3.org/TR/activitypub/#sharedInbox
|
# https://www.w3.org/TR/activitypub/#sharedInbox
|
||||||
'rel': 'sharedInbox',
|
'rel': 'sharedInbox',
|
||||||
'type': as2.CONTENT_TYPE,
|
'type': as2.CONTENT_TYPE,
|
||||||
|
|
Ładowanie…
Reference in New Issue