create Application class

This commit is contained in:
Izalia Mae 2022-11-07 07:54:32 -05:00
parent 70e4870ba9
commit 8d17749a50
6 changed files with 233 additions and 175 deletions

View file

@ -1,8 +1,3 @@
__version__ = '0.2.2' __version__ = '0.2.2'
from aiohttp.web import Application
from . import logger from . import logger
app = Application()

119
relay/application.py Normal file
View file

@ -0,0 +1,119 @@
import asyncio
import logging
import os
import signal
from aiohttp import web
from cachetools import LRUCache
from datetime import datetime, timedelta
from .config import RelayConfig
from .database import RelayDatabase
from .misc import DotDict, check_open_port, set_app
from .views import routes
class Application(web.Application):
def __init__(self, cfgpath):
web.Application.__init__(self)
self['starttime'] = None
self['running'] = False
self['is_docker'] = bool(os.environ.get('DOCKER_RUNNING'))
self['config'] = RelayConfig(cfgpath, self['is_docker'])
if not self['config'].load():
self['config'].save()
self['database'] = RelayDatabase(self['config'])
self['database'].load()
self['cache'] = DotDict({key: LRUCache(maxsize=self['config'][key]) for key in self['config'].cachekeys})
self['semaphore'] = asyncio.Semaphore(self['config'].push_limit)
self.set_signal_handler()
set_app(self)
@property
def cache(self):
return self['cache']
@property
def config(self):
return self['config']
@property
def database(self):
return self['database']
@property
def is_docker(self):
return self['is_docker']
@property
def semaphore(self):
return self['semaphore']
@property
def uptime(self):
if not self['starttime']:
return timedelta(seconds=0)
uptime = datetime.now() - self['starttime']
return timedelta(seconds=uptime.seconds)
def set_signal_handler(self):
signal.signal(signal.SIGHUP, self.stop)
signal.signal(signal.SIGINT, self.stop)
signal.signal(signal.SIGQUIT, self.stop)
signal.signal(signal.SIGTERM, self.stop)
def run(self):
if not check_open_port(self.config.listen, self.config.port):
return logging.error(f'A server is already running on port {self.config.port}')
for route in routes:
if route[1] == '/stats' and logging.DEBUG < logging.root.level:
continue
self.router.add_route(*route)
logging.info(f'Starting webserver at {self.config.host} ({self.config.listen}:{self.config.port})')
asyncio.run(self.handle_run())
def stop(self, *_):
self['running'] = False
async def handle_run(self):
self['running'] = True
runner = web.AppRunner(self, access_log_format='%{X-Forwarded-For}i "%r" %s %b "%{User-Agent}i"')
await runner.setup()
site = web.TCPSite(runner,
host = self.config.listen,
port = self.config.port,
reuse_address = True
)
await site.start()
self['starttime'] = datetime.now()
while self['running']:
await asyncio.sleep(0.25)
await site.stop()
self['starttime'] = None
self['running'] = False

View file

@ -1,17 +1,15 @@
import Crypto import Crypto
import asyncio import asyncio
import click import click
import json
import logging import logging
import os
import platform import platform
from aiohttp.web import AppRunner, TCPSite from . import misc, __version__
from cachetools import LRUCache from .application import Application
from .config import relay_software_names
from . import app, misc, views, __version__
from .config import DotDict, RelayConfig, relay_software_names app = None
from .database import RelayDatabase
@click.group('cli', context_settings={'show_default': True}, invoke_without_command=True) @click.group('cli', context_settings={'show_default': True}, invoke_without_command=True)
@ -19,23 +17,11 @@ from .database import RelayDatabase
@click.version_option(version=__version__, prog_name='ActivityRelay') @click.version_option(version=__version__, prog_name='ActivityRelay')
@click.pass_context @click.pass_context
def cli(ctx, config): def cli(ctx, config):
app['is_docker'] = bool(os.environ.get('DOCKER_RUNNING')) global app
app['config'] = RelayConfig(config, app['is_docker']) app = Application(config)
if not app['config'].load():
app['config'].save()
app['database'] = RelayDatabase(app['config'])
app['database'].load()
app['cache'] = DotDict()
app['semaphore'] = asyncio.Semaphore(app['config']['push_limit'])
for key in app['config'].cachekeys:
app['cache'][key] = LRUCache(app['config'][key])
if not ctx.invoked_subcommand: if not ctx.invoked_subcommand:
if app['config'].host.endswith('example.com'): if app.config.host.endswith('example.com'):
relay_setup.callback() relay_setup.callback()
else: else:
@ -55,7 +41,7 @@ def cli_inbox_list():
click.echo('Connected to the following instances or relays:') click.echo('Connected to the following instances or relays:')
for inbox in app['database'].inboxes: for inbox in app.database.inboxes:
click.echo(f'- {inbox}') click.echo(f'- {inbox}')
@ -64,16 +50,13 @@ def cli_inbox_list():
def cli_inbox_follow(actor): def cli_inbox_follow(actor):
'Follow an actor (Relay must be running)' 'Follow an actor (Relay must be running)'
config = app['config'] if app.config.is_banned(actor):
database = app['database']
if config.is_banned(actor):
return click.echo(f'Error: Refusing to follow banned actor: {actor}') return click.echo(f'Error: Refusing to follow banned actor: {actor}')
if not actor.startswith('http'): if not actor.startswith('http'):
actor = f'https://{actor}/actor' actor = f'https://{actor}/actor'
if database.get_inbox(actor): if app.database.get_inbox(actor):
return click.echo(f'Error: Already following actor: {actor}') return click.echo(f'Error: Already following actor: {actor}')
actor_data = asyncio.run(misc.request(actor, sign_headers=True)) actor_data = asyncio.run(misc.request(actor, sign_headers=True))
@ -81,8 +64,8 @@ def cli_inbox_follow(actor):
if not actor_data: if not actor_data:
return click.echo(f'Error: Failed to fetch actor: {actor}') return click.echo(f'Error: Failed to fetch actor: {actor}')
database.add_inbox(actor_data.shared_inbox) app.database.add_inbox(actor_data.shared_inbox)
database.save() app.database.save()
asyncio.run(misc.follow_remote_actor(actor)) asyncio.run(misc.follow_remote_actor(actor))
click.echo(f'Sent follow message to actor: {actor}') click.echo(f'Sent follow message to actor: {actor}')
@ -93,13 +76,11 @@ def cli_inbox_follow(actor):
def cli_inbox_unfollow(actor): def cli_inbox_unfollow(actor):
'Unfollow an actor (Relay must be running)' 'Unfollow an actor (Relay must be running)'
database = app['database']
if not actor.startswith('http'): if not actor.startswith('http'):
actor = f'https://{actor}/actor' actor = f'https://{actor}/actor'
if database.del_inbox(actor): if app.database.del_inbox(actor):
database.save() app.database.save()
asyncio.run(misc.unfollow_remote_actor(actor)) asyncio.run(misc.unfollow_remote_actor(actor))
return click.echo(f'Sent unfollow message to: {actor}') return click.echo(f'Sent unfollow message to: {actor}')
@ -111,17 +92,14 @@ def cli_inbox_unfollow(actor):
def cli_inbox_add(inbox): def cli_inbox_add(inbox):
'Add an inbox to the database' 'Add an inbox to the database'
database = app['database']
config = app['config']
if not inbox.startswith('http'): if not inbox.startswith('http'):
inbox = f'https://{inbox}/inbox' inbox = f'https://{inbox}/inbox'
if config.is_banned(inbox): if app.config.is_banned(inbox):
return click.echo(f'Error: Refusing to add banned inbox: {inbox}') return click.echo(f'Error: Refusing to add banned inbox: {inbox}')
if database.add_inbox(inbox): if app.database.add_inbox(inbox):
database.save() app.database.save()
return click.echo(f'Added inbox to the database: {inbox}') return click.echo(f'Added inbox to the database: {inbox}')
click.echo(f'Error: Inbox already in database: {inbox}') click.echo(f'Error: Inbox already in database: {inbox}')
@ -132,17 +110,15 @@ def cli_inbox_add(inbox):
def cli_inbox_remove(inbox): def cli_inbox_remove(inbox):
'Remove an inbox from the database' 'Remove an inbox from the database'
database = app['database']
try: try:
dbinbox = database.get_inbox(inbox, fail=True) dbinbox = app.database.get_inbox(inbox, fail=True)
except KeyError: except KeyError:
click.echo(f'Error: Inbox does not exist: {inbox}') click.echo(f'Error: Inbox does not exist: {inbox}')
return return
database.del_inbox(dbinbox['domain']) app.database.del_inbox(dbinbox['domain'])
database.save() app.database.save()
click.echo(f'Removed inbox from the database: {inbox}') click.echo(f'Removed inbox from the database: {inbox}')
@ -159,7 +135,7 @@ def cli_instance_list():
click.echo('Banned instances or relays:') click.echo('Banned instances or relays:')
for domain in app['config'].blocked_instances: for domain in app.config.blocked_instances:
click.echo(f'- {domain}') click.echo(f'- {domain}')
@ -168,17 +144,14 @@ def cli_instance_list():
def cli_instance_ban(target): def cli_instance_ban(target):
'Ban an instance and remove the associated inbox if it exists' 'Ban an instance and remove the associated inbox if it exists'
config = app['config']
database = app['database']
if target.startswith('http'): if target.startswith('http'):
target = urlparse(target).hostname target = urlparse(target).hostname
if config.ban_instance(target): if app.config.ban_instance(target):
config.save() app.config.save()
if database.del_inbox(target): if app.database.del_inbox(target):
database.save() app.database.save()
click.echo(f'Banned instance: {target}') click.echo(f'Banned instance: {target}')
return return
@ -191,10 +164,8 @@ def cli_instance_ban(target):
def cli_instance_unban(target): def cli_instance_unban(target):
'Unban an instance' 'Unban an instance'
config = app['config'] if app.config.unban_instance(target):
app.config.save()
if config.unban_instance(target):
config.save()
click.echo(f'Unbanned instance: {target}') click.echo(f'Unbanned instance: {target}')
return return
@ -214,7 +185,7 @@ def cli_software_list():
click.echo('Banned software:') click.echo('Banned software:')
for software in app['config'].blocked_software: for software in app.config.blocked_software:
click.echo(f'- {software}') click.echo(f'- {software}')
@ -226,13 +197,11 @@ def cli_software_list():
def cli_software_ban(name, fetch_nodeinfo): def cli_software_ban(name, fetch_nodeinfo):
'Ban software. Use RELAYS for NAME to ban relays' 'Ban software. Use RELAYS for NAME to ban relays'
config = app['config']
if name == 'RELAYS': if name == 'RELAYS':
for name in relay_software_names: for name in relay_software_names:
config.ban_software(name) app.config.ban_software(name)
config.save() app.config.save()
return click.echo('Banned all relay software') return click.echo('Banned all relay software')
if fetch_nodeinfo: if fetch_nodeinfo:
@ -244,7 +213,7 @@ def cli_software_ban(name, fetch_nodeinfo):
name = software name = software
if config.ban_software(name): if config.ban_software(name):
config.save() app.config.save()
return click.echo(f'Banned software: {name}') return click.echo(f'Banned software: {name}')
click.echo(f'Software already banned: {name}') click.echo(f'Software already banned: {name}')
@ -258,11 +227,9 @@ def cli_software_ban(name, fetch_nodeinfo):
def cli_software_unban(name, fetch_nodeinfo): def cli_software_unban(name, fetch_nodeinfo):
'Ban software. Use RELAYS for NAME to unban relays' 'Ban software. Use RELAYS for NAME to unban relays'
config = app['config']
if name == 'RELAYS': if name == 'RELAYS':
for name in relay_software_names: for name in relay_software_names:
config.unban_software(name) app.config.unban_software(name)
config.save() config.save()
return click.echo('Unbanned all relay software') return click.echo('Unbanned all relay software')
@ -275,8 +242,8 @@ def cli_software_unban(name, fetch_nodeinfo):
name = software name = software
if config.unban_software(name): if app.config.unban_software(name):
config.save() app.config.save()
return click.echo(f'Unbanned software: {name}') return click.echo(f'Unbanned software: {name}')
click.echo(f'Software wasn\'t banned: {name}') click.echo(f'Software wasn\'t banned: {name}')
@ -293,7 +260,7 @@ def cli_whitelist():
def cli_whitelist_list(): def cli_whitelist_list():
click.echo('Current whitelisted domains') click.echo('Current whitelisted domains')
for domain in app['config'].whitelist: for domain in app.config.whitelist:
click.echo(f'- {domain}') click.echo(f'- {domain}')
@ -302,12 +269,10 @@ def cli_whitelist_list():
def cli_whitelist_add(instance): def cli_whitelist_add(instance):
'Add an instance to the whitelist' 'Add an instance to the whitelist'
config = app['config'] if not app.config.add_whitelist(instance):
if not config.add_whitelist(instance):
return click.echo(f'Instance already in the whitelist: {instance}') return click.echo(f'Instance already in the whitelist: {instance}')
config.save() app.config.save()
click.echo(f'Instance added to the whitelist: {instance}') click.echo(f'Instance added to the whitelist: {instance}')
@ -316,17 +281,14 @@ def cli_whitelist_add(instance):
def cli_whitelist_remove(instance): def cli_whitelist_remove(instance):
'Remove an instance from the whitelist' 'Remove an instance from the whitelist'
config = app['config'] if not app.config.del_whitelist(instance):
database = app['database']
if not config.del_whitelist(instance):
return click.echo(f'Instance not in the whitelist: {instance}') return click.echo(f'Instance not in the whitelist: {instance}')
config.save() app.config.save()
if config.whitelist_enabled: if app.config.whitelist_enabled:
if database.del_inbox(inbox): if app.database.del_inbox(inbox):
database.save() app.database.save()
click.echo(f'Removed instance from the whitelist: {instance}') click.echo(f'Removed instance from the whitelist: {instance}')
@ -335,23 +297,21 @@ def cli_whitelist_remove(instance):
def relay_setup(): def relay_setup():
'Generate a new config' 'Generate a new config'
config = app['config']
while True: while True:
config.host = click.prompt('What domain will the relay be hosted on?', default=config.host) app.config.host = click.prompt('What domain will the relay be hosted on?', default=app.config.host)
if not config.host.endswith('example.com'): if not config.host.endswith('example.com'):
break break
click.echo('The domain must not be example.com') click.echo('The domain must not be example.com')
config.listen = click.prompt('Which address should the relay listen on?', default=config.listen) app.config.listen = click.prompt('Which address should the relay listen on?', default=app.config.listen)
while True: while True:
config.port = click.prompt('What TCP port should the relay listen on?', default=config.port, type=int) app.config.port = click.prompt('What TCP port should the relay listen on?', default=app.config.port, type=int)
break break
config.save() app.config.save()
if not app['is_docker'] and click.confirm('Relay all setup! Would you like to run it now?'): if not app['is_docker'] and click.confirm('Relay all setup! Would you like to run it now?'):
relay_run.callback() relay_run.callback()
@ -361,9 +321,7 @@ def relay_setup():
def relay_run(): def relay_run():
'Run the relay' 'Run the relay'
config = app['config'] if app.config.host.endswith('example.com'):
if config.host.endswith('example.com'):
return click.echo('Relay is not set up. Please edit your relay config or run "activityrelay setup".') return click.echo('Relay is not set up. Please edit your relay config or run "activityrelay setup".')
vers_split = platform.python_version().split('.') vers_split = platform.python_version().split('.')
@ -378,38 +336,10 @@ def relay_run():
click.echo('Warning: PyCrypto is old and should be replaced with pycryptodome') click.echo('Warning: PyCrypto is old and should be replaced with pycryptodome')
return click.echo(pip_command) return click.echo(pip_command)
if not misc.check_open_port(config.listen, config.port): if not misc.check_open_port(app.config.listen, app.config.port):
return click.echo(f'Error: A server is already running on port {config.port}') return click.echo(f'Error: A server is already running on port {app.config.port}')
# web pages app.run()
app.router.add_get('/', views.home)
# endpoints
app.router.add_post('/actor', views.inbox)
app.router.add_post('/inbox', views.inbox)
app.router.add_get('/actor', views.actor)
app.router.add_get('/nodeinfo/2.0.json', views.nodeinfo_2_0)
app.router.add_get('/.well-known/nodeinfo', views.nodeinfo_wellknown)
app.router.add_get('/.well-known/webfinger', views.webfinger)
if logging.DEBUG >= logging.root.level:
app.router.add_get('/stats', views.stats)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
asyncio.ensure_future(handle_start_webserver(), loop=loop)
loop.run_forever()
async def handle_start_webserver():
config = app['config']
runner = AppRunner(app, access_log_format='%{X-Forwarded-For}i "%r" %s %b "%{Referer}i" "%{User-Agent}i"')
logging.info(f'Starting webserver at {config.host} ({config.listen}:{config.port})')
await runner.setup()
site = TCPSite(runner, config.listen, config.port)
await site.start()
def main(): def main():

View file

@ -15,10 +15,10 @@ from json.decoder import JSONDecodeError
from urllib.parse import urlparse from urllib.parse import urlparse
from uuid import uuid4 from uuid import uuid4
from . import app
from .http_debug import http_debug from .http_debug import http_debug
app = None
HASHES = { HASHES = {
'sha1': SHA, 'sha1': SHA,
'sha256': SHA256, 'sha256': SHA256,
@ -26,6 +26,11 @@ HASHES = {
} }
def set_app(new_app):
global app
app = new_app
def build_signing_string(headers, used_headers): def build_signing_string(headers, used_headers):
return '\n'.join(map(lambda x: ': '.join([x.lower(), headers[x]]), used_headers)) return '\n'.join(map(lambda x: ': '.join([x.lower(), headers[x]]), used_headers))
@ -62,7 +67,7 @@ def distill_inboxes(actor, object_id):
database = app['database'] database = app['database']
for inbox in database.inboxes: for inbox in database.inboxes:
if inbox != actor.shared_inbox or urlparse(inbox).hostname != urlparse(object_id).hostname: if inbox != actor.shared_inbox and urlparse(inbox).hostname != urlparse(object_id).hostname:
yield inbox yield inbox

View file

@ -3,21 +3,18 @@ import logging
from uuid import uuid4 from uuid import uuid4
from . import app, misc from . import misc
async def handle_relay(request, actor, data, software): async def handle_relay(request, actor, data, software):
cache = app['cache'].objects if data.objectid in request.app.cache.objects:
config = app['config'] logging.verbose(f'already relayed {data.objectid}')
if data.objectid in cache:
logging.verbose(f'already relayed {data.objectid} as {cache[data.objectid]}')
return return
logging.verbose(f'Relaying post from {data.actorid}') logging.verbose(f'Relaying post from {data.actorid}')
message = misc.Message.new_announce( message = misc.Message.new_announce(
host = config.host, host = request.app.config.host,
object = data.objectid object = data.objectid
) )
@ -27,19 +24,16 @@ async def handle_relay(request, actor, data, software):
futures = [misc.request(inbox, data=message) for inbox in inboxes] futures = [misc.request(inbox, data=message) for inbox in inboxes]
asyncio.ensure_future(asyncio.gather(*futures)) asyncio.ensure_future(asyncio.gather(*futures))
cache[data.objectid] = message.id request.app.cache.objects[data.objectid] = message.id
async def handle_forward(request, actor, data, software): async def handle_forward(request, actor, data, software):
cache = app['cache'].objects if data.id in request.app.cache.objects:
config = app['config']
if data.id in cache:
logging.verbose(f'already forwarded {data.id}') logging.verbose(f'already forwarded {data.id}')
return return
message = misc.Message.new_announce( message = misc.Message.new_announce(
host = config.host, host = request.app.config.host,
object = data object = data
) )
@ -50,22 +44,19 @@ async def handle_forward(request, actor, data, software):
futures = [misc.request(inbox, data=message) for inbox in inboxes] futures = [misc.request(inbox, data=message) for inbox in inboxes]
asyncio.ensure_future(asyncio.gather(*futures)) asyncio.ensure_future(asyncio.gather(*futures))
cache[data.id] = message.id request.app.cache.objects[data.id] = message.id
async def handle_follow(request, actor, data, software): async def handle_follow(request, actor, data, software):
config = app['config'] if request.app.database.add_inbox(inbox, data.id):
database = app['database'] request.app.database.set_followid(actor.id, data.id)
if database.add_inbox(inbox, data.id): request.app.database.save()
database.set_followid(actor.id, data.id)
database.save()
await misc.request( await misc.request(
actor.shared_inbox, actor.shared_inbox,
misc.Message.new_response( misc.Message.new_response(
host = config.host, host = request.app.config.host,
actor = actor.id, actor = actor.id,
followid = data.id, followid = data.id,
accept = True accept = True
@ -78,7 +69,7 @@ async def handle_follow(request, actor, data, software):
misc.request( misc.request(
actor.shared_inbox, actor.shared_inbox,
misc.Message.new_follow( misc.Message.new_follow(
host = config.host, host = request.app.config.host,
actor = actor.id actor = actor.id
) )
) )
@ -89,15 +80,13 @@ async def handle_undo(request, actor, data, software):
if data['object']['type'] != 'Follow': if data['object']['type'] != 'Follow':
return await handle_forward(request, actor, data, software) return await handle_forward(request, actor, data, software)
database = app['database'] if not request.app.database.del_inbox(actor.domain, data.id):
if not database.del_inbox(actor.domain, data.id):
return return
database.save() request.app.database.save()
message = misc.Message.new_unfollow( message = misc.Message.new_unfollow(
host = config.host, host = request.app.config.host,
actor = actor.id, actor = actor.id,
follow = data follow = data
) )

View file

@ -2,14 +2,25 @@ import logging
import subprocess import subprocess
import traceback import traceback
from aiohttp.web import HTTPForbidden, HTTPUnauthorized, Response, json_response from aiohttp.web import HTTPForbidden, HTTPUnauthorized, Response, json_response, route
from . import __version__, app, misc from . import __version__, misc
from .http_debug import STATS from .http_debug import STATS
from .misc import Message from .misc import Message
from .processors import run_processor from .processors import run_processor
routes = []
def register_route(method, path):
def wrapper(func):
routes.append([method, path, func])
return func
return wrapper
try: try:
commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii') commit_label = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode('ascii')
version = f'{__version__} {commit_label}' version = f'{__version__} {commit_label}'
@ -18,9 +29,14 @@ except:
version = __version__ version = __version__
@register_route('GET', '/')
async def home(request): async def home(request):
targets = '<br>'.join(app['database'].hostnames) targets = '<br>'.join(request.app.database.hostnames)
text = """ note = request.app.config.note
count = len(request.app.database.hostnames)
host = request.app.config.host
text = f"""
<html><head> <html><head>
<title>ActivityPub Relay at {host}</title> <title>ActivityPub Relay at {host}</title>
<style> <style>
@ -37,7 +53,7 @@ a:hover {{ color: #8AF; }}
<p>You may subscribe to this relay with the address: <a href="https://{host}/actor">https://{host}/actor</a></p> <p>You may subscribe to this relay with the address: <a href="https://{host}/actor">https://{host}/actor</a></p>
<p>To host your own relay, you may download the code at this address: <a href="https://git.pleroma.social/pleroma/relay">https://git.pleroma.social/pleroma/relay</a></p> <p>To host your own relay, you may download the code at this address: <a href="https://git.pleroma.social/pleroma/relay">https://git.pleroma.social/pleroma/relay</a></p>
<br><p>List of {count} registered instances:<br>{targets}</p> <br><p>List of {count} registered instances:<br>{targets}</p>
</body></html>""".format(host=request.host, note=app['config'].note, targets=targets, count=len(app['database'].hostnames)) </body></html>"""
return Response( return Response(
status = 200, status = 200,
@ -47,21 +63,22 @@ a:hover {{ color: #8AF; }}
) )
@register_route('GET', '/inbox')
@register_route('GET', '/actor')
async def actor(request): async def actor(request):
config = app['config']
database = app['database']
data = Message.new_actor( data = Message.new_actor(
host = config.host, host = request.app.config.host,
pubkey = database.pubkey pubkey = request.app.database.pubkey
) )
return json_response(data, content_type='application/activity+json') return json_response(data, content_type='application/activity+json')
@register_route('POST', '/inbox')
@register_route('POST', '/actor')
async def inbox(request): async def inbox(request):
config = app['config'] config = request.app.config
database = app['database'] database = request.app.database
## reject if missing signature header ## reject if missing signature header
if 'signature' not in request.headers: if 'signature' not in request.headers:
@ -98,7 +115,7 @@ async def inbox(request):
raise HTTPForbidden(body='access denied') raise HTTPForbidden(body='access denied')
## reject if actor is banned ## reject if actor is banned
if app['config'].is_banned(data.domain): if request.app['config'].is_banned(data.domain):
logging.verbose(f'Ignored request from banned actor: {data.actorid}') logging.verbose(f'Ignored request from banned actor: {data.actorid}')
raise HTTPForbidden(body='access denied') raise HTTPForbidden(body='access denied')
@ -126,25 +143,26 @@ async def inbox(request):
return Response(body=b'{}', content_type='application/activity+json') return Response(body=b'{}', content_type='application/activity+json')
@register_route('GET', '/.well-known/webfinger')
async def webfinger(request): async def webfinger(request):
config = app['config']
subject = request.query['resource'] subject = request.query['resource']
if subject != f'acct:relay@{request.host}': if subject != f'acct:relay@{request.app.config.host}':
return json_response({'error': 'user not found'}, status=404) return json_response({'error': 'user not found'}, status=404)
data = { data = {
'subject': subject, 'subject': subject,
'aliases': [config.actor], 'aliases': [request.app.config.actor],
'links': [ 'links': [
{'href': config.actor, 'rel': 'self', 'type': 'application/activity+json'}, {'href': request.app.config.actor, 'rel': 'self', 'type': 'application/activity+json'},
{'href': config.actor, 'rel': 'self', 'type': 'application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'} {'href': request.app.config.actor, 'rel': 'self', 'type': 'application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"'}
] ]
} }
return json_response(data) return json_response(data)
@register_route('GET', '/nodeinfo/2.0.json')
async def nodeinfo_2_0(request): async def nodeinfo_2_0(request):
data = { data = {
# XXX - is this valid for a relay? # XXX - is this valid for a relay?
@ -165,7 +183,7 @@ async def nodeinfo_2_0(request):
} }
}, },
'metadata': { 'metadata': {
'peers': app['database'].hostnames 'peers': request.app.database.hostnames
}, },
'version': '2.0' 'version': '2.0'
} }
@ -173,17 +191,19 @@ async def nodeinfo_2_0(request):
return json_response(data) return json_response(data)
@register_route('GET', '/.well-known/nodeinfo')
async def nodeinfo_wellknown(request): async def nodeinfo_wellknown(request):
data = { data = {
'links': [ 'links': [
{ {
'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0', 'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
'href': f'https://{request.host}/nodeinfo/2.0.json' 'href': f'https://{request.app.config.host}/nodeinfo/2.0.json'
} }
] ]
} }
return json_response(data) return json_response(data)
@register_route('GET', '/stats')
async def stats(request): async def stats(request):
return json_response(STATS) return json_response(STATS)