From b1f5bbdecdda04ab5e300ee63f5c6b816f9d9737 Mon Sep 17 00:00:00 2001 From: n9k Date: Wed, 20 Jul 2022 07:39:33 +0000 Subject: [PATCH] Force absent users to do the access captcha again Before this, if a request was not coming from an existing user (no token in the request or no user with the given token), then and only then would we send the access captcha. This meant that if a user left a chat message and became absent, they wouldn't be prompted to do the access captcha again until their message was eventuallly rotated. (While messages exist we don't delete the users who posted them.) This commit makes it so if user['verified'] is None, the user is kicked and prompted with the access captcha. This is automatically done for absent users by a background task. --- anonstream/routes/wrappers.py | 1 + anonstream/tasks.py | 10 +++++--- anonstream/websocket.py | 45 ++++++++++++++++++++--------------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index 7217a46..fcc1a77 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -137,6 +137,7 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False): user['headers'] = tuple(context.headers) if not ignore_allowedness: assert_allowedness(timestamp, user) + if user is not None and user['verified'] is not None: response = await f(timestamp, user, *args, **kwargs) elif fallback_to_token: #assert not broadcaster diff --git a/anonstream/tasks.py b/anonstream/tasks.py index d01233c..63c1c55 100644 --- a/anonstream/tasks.py +++ b/anonstream/tasks.py @@ -64,9 +64,13 @@ async def t_sunset_users(timestamp, iteration): if iteration == 0: return - # Deverify absent users - for user in get_absent_users(timestamp): - user['verified'] = False + # De-access absent users + absent_users = tuple(get_absent_users(timestamp)) + for user in absent_users: + user['verified'] = None + # Absent users should have no connected websockets, + # so in normal operation this should always be a no-op + broadcast(users=absent_users, payload={'type': 'kick'}) # Remove as many absent users as possible diff --git a/anonstream/websocket.py b/anonstream/websocket.py index ba016f5..35be036 100644 --- a/anonstream/websocket.py +++ b/anonstream/websocket.py @@ -54,7 +54,11 @@ async def websocket_outbound(queue, user): await websocket.close(1001) break else: - await websocket.send_json(payload) + if user['verified'] is None: + await websocket.send_json({'type': 'kick'}) + await websocket.close(1001) + else: + await websocket.send_json(payload) async def websocket_inbound(queue, user): while True: @@ -73,25 +77,28 @@ async def websocket_inbound(queue, user): except AllowednessException: payload = {'type': 'kick'} else: - try: - receipt_type, parsed = parse_websocket_data(receipt) - except Malformed as e: - error , *_ = e.args - payload = { - 'type': 'error', - 'because': error, - } + if user['verified'] is None: + payload = {'type': 'kick'} else: - match receipt_type: - case WS.MESSAGE: - handle = handle_inbound_message - case WS.APPEARANCE: - handle = handle_inbound_appearance - case WS.CAPTCHA: - handle = handle_inbound_captcha - case WS.PONG: - handle = handle_inbound_pong - payload = handle(timestamp, queue, user, *parsed) + try: + receipt_type, parsed = parse_websocket_data(receipt) + except Malformed as e: + error , *_ = e.args + payload = { + 'type': 'error', + 'because': error, + } + else: + match receipt_type: + case WS.MESSAGE: + handle = handle_inbound_message + case WS.APPEARANCE: + handle = handle_inbound_appearance + case WS.CAPTCHA: + handle = handle_inbound_captcha + case WS.PONG: + handle = handle_inbound_pong + payload = handle(timestamp, queue, user, *parsed) # Write to websocket if payload is not None: