diff --git a/relay/database.py b/relay/database.py index 7bfdd93..1759f6b 100644 --- a/relay/database.py +++ b/relay/database.py @@ -177,7 +177,7 @@ class Connection(tinysql.ConnectionMixin): def get_hostnames(self): - return tuple(row.domain for row in self.select('instances')) + return tuple(row.domain for row in self.get_instances()) def get_instance(self, data): @@ -185,7 +185,8 @@ class Connection(tinysql.ConnectionMixin): data = data.split('#', 1)[0] query = 'SELECT * FROM instances WHERE domain = :data OR actor = :data OR inbox = :data' - return self.execute(query, dict(data=data), table='instances').one() + row = self.execute(query, dict(data=data), table='instances').one() + return row if row.joined else None def get_instances(self): @@ -194,11 +195,16 @@ class Connection(tinysql.ConnectionMixin): def get_request(self, domain): - return self.select('instances', domain=domain, joined=None).one() + for instance in self.get_requests(): + if instance.domain == domain: + return instance + + raise KeyError(domain) def get_requests(self): - self.select('instances', joined=None).all() + query = 'SELECT * FROM instances WHERE joined IS NULL' + return self.execute(query, table='instances').all() def get_whitelist(self): diff --git a/relay/manage.py b/relay/manage.py index 851b658..247e979 100644 --- a/relay/manage.py +++ b/relay/manage.py @@ -6,6 +6,7 @@ import logging import platform import yaml +from datetime import datetime from urllib.parse import urlparse from . import __version__ @@ -384,6 +385,77 @@ def cli_inbox_remove(domain): return click.echo(f'Error: Inbox does not exist: {domain}') +@cli.group('request') +def cli_request(): + 'Manage follow requests' + + +@cli_request.command('list') +def cli_request_list(): + 'List all the current follow requests' + + click.echo('Follow requests:') + + with app.database.session as s: + for row in s.get_requests(): + click.echo(f'- {row.domain}') + + +@cli_request.command('approve') +@click.argument('domain') +def cli_request_approve(domain): + 'Approve a follow request' + + with app.database.session as s: + try: + instance = s.get_request(domain) + + except KeyError: + return click.echo(f'No request for domain exists: {domain}') + + data = {'joined': datetime.now()} + s.update('instances', data, id=instance.id) + + asyncio.run(post( + instance.inbox, + Message.new_response( + host = app.config.host, + actor = instance.actor, + followid = instance.followid, + accept = True + ) + )) + + return click.echo(f'Accepted follow request for domain: {domain}') + + +@cli_request.command('deny') +@click.argument('domain') +def cli_request_deny(domain): + 'Deny a follow request' + + with app.database.session as s: + try: + instance = s.get_request(domain) + + except KeyError: + return click.echo(f'No request for domain exists: {domain}') + + s.delete_instance(domain) + + asyncio.run(post( + instance.inbox, + Message.new_response( + host = app.config.host, + actor = instance.actor, + followid = instance.followid, + accept = False + ) + )) + + return click.echo(f'Denied follow request for domain: {domain}') + + @cli.group('instance') def cli_instance(): 'Manage instance bans' diff --git a/relay/processors.py b/relay/processors.py index 9ef849f..e31d5a9 100644 --- a/relay/processors.py +++ b/relay/processors.py @@ -94,7 +94,7 @@ async def handle_follow(request, s): actor_data = request.actor, software = software, followid = request.message.id, - accept = s.get_config('require_approval') + accept = not s.get_config('require_approval') ) if s.get_config('require_approval'): @@ -180,24 +180,25 @@ async def run_processor(request): return with request.database.session as s: - new_data = {} + if request.instance: + new_data = {} - if request.instance and not request.instance.software: - logging.verbose(f'Fetching nodeinfo for instance: {request.instance.domain}') - nodeinfo = await request.app.client.fetch_nodeinfo(request.instance.domain) + if not request.instance.software: + logging.verbose(f'Fetching nodeinfo for instance: {request.instance.domain}') + nodeinfo = await request.app.client.fetch_nodeinfo(request.instance.domain) - if nodeinfo: - new_data['software'] = nodeinfo.sw_name + if nodeinfo: + new_data['software'] = nodeinfo.sw_name - if not request.instance.actor: - logging.verbose(f'Fetching actor for instance: {request.instance.domain}') - new_data['actor'] = request.signature.keyid.split('#', 1)[0] + if not request.instance.actor: + logging.verbose(f'Fetching actor for instance: {request.instance.domain}') + new_data['actor'] = request.signature.keyid.split('#', 1)[0] - if not request.instance.actor_data: - new_data['actor_data'] = request.actor + if not request.instance.actor_data: + new_data['actor_data'] = request.actor - if new_data: - s.put_instance(request.actor.domain, **new_data) + if new_data: + s.put_instance(request.actor.domain, **new_data) logging.verbose(f'New "{request.message.type}" from actor: {request.actor.id}') return await processors[request.message.type](request, s) diff --git a/relay/views.py b/relay/views.py index 90dfda8..f2060ea 100644 --- a/relay/views.py +++ b/relay/views.py @@ -138,7 +138,7 @@ async def inbox(request, s): return Response.new_error(401, str(e), 'json') ## reject if activity type isn't 'Follow' and the actor isn't following - if request.message.type != 'Follow' and not request.instance: + if request.message.type != 'Follow' and (not request.instance or not request.instance.joined): logging.verbose(f'Rejected actor for trying to post while not following: {request.actor.id}') return Response.new_error(401, 'access denied', 'json')