Flood detection
このコミットが含まれているのは:
コミット
8c9b0d9da0
|
@ -42,6 +42,8 @@ def create_app(config_file):
|
||||||
'CHAT_NAME_MAX_LENGTH': config['chat']['max_name_length'],
|
'CHAT_NAME_MAX_LENGTH': config['chat']['max_name_length'],
|
||||||
'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'],
|
'CHAT_NAME_MIN_CONTRAST': config['chat']['min_name_contrast'],
|
||||||
'CHAT_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']),
|
'CHAT_BACKGROUND_COLOUR': color_to_colour(config['chat']['background_color']),
|
||||||
|
'FLOOD_DURATION': config['flood']['duration'],
|
||||||
|
'FLOOD_THRESHOLD': config['flood']['threshold'],
|
||||||
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
|
'CAPTCHA_LIFETIME': config['captcha']['lifetime'],
|
||||||
'CAPTCHA_FONTS': config['captcha']['fonts'],
|
'CAPTCHA_FONTS': config['captcha']['fonts'],
|
||||||
'CAPTCHA_ALPHABET': config['captcha']['alphabet'],
|
'CAPTCHA_ALPHABET': config['captcha']['alphabet'],
|
||||||
|
|
|
@ -35,6 +35,12 @@ def get_random_captcha_digest():
|
||||||
|
|
||||||
return digest
|
return digest
|
||||||
|
|
||||||
|
def get_random_captcha_digest_for(user):
|
||||||
|
if user['verified']:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return get_random_captcha_digest()
|
||||||
|
|
||||||
def get_captcha_image(digest):
|
def get_captcha_image(digest):
|
||||||
try:
|
try:
|
||||||
captcha = CAPTCHAS[digest]
|
captcha = CAPTCHAS[digest]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from quart import current_app, request, render_template, redirect, url_for, escape, Markup
|
from quart import current_app, request, render_template, redirect, url_for, escape, Markup
|
||||||
|
|
||||||
from anonstream.captcha import get_random_captcha_digest
|
from anonstream.captcha import get_random_captcha_digest_for
|
||||||
from anonstream.chat import add_chat_message, Rejected
|
from anonstream.chat import add_chat_message, Rejected
|
||||||
from anonstream.stream import get_stream_title
|
from anonstream.stream import get_stream_title
|
||||||
from anonstream.user import add_state, pop_state, try_change_appearance, verify, BadCaptcha
|
from anonstream.user import add_state, pop_state, try_change_appearance, verify, deverify, BadCaptcha
|
||||||
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
||||||
from anonstream.helpers.chat import get_scrollback
|
from anonstream.helpers.chat import get_scrollback
|
||||||
from anonstream.helpers.user import get_default_name
|
from anonstream.helpers.user import get_default_name
|
||||||
|
@ -45,14 +45,13 @@ async def nojs_form(user):
|
||||||
state_id = request.args.get('state', type=int)
|
state_id = request.args.get('state', type=int)
|
||||||
state = pop_state(user, state_id)
|
state = pop_state(user, state_id)
|
||||||
prefer_chat_form = request.args.get('landing') != 'appearance'
|
prefer_chat_form = request.args.get('landing') != 'appearance'
|
||||||
digest = None if user['verified'] else get_random_captcha_digest()
|
|
||||||
return await render_template(
|
return await render_template(
|
||||||
'nojs_form.html',
|
'nojs_form.html',
|
||||||
user=user,
|
user=user,
|
||||||
state=state,
|
state=state,
|
||||||
prefer_chat_form=prefer_chat_form,
|
prefer_chat_form=prefer_chat_form,
|
||||||
nonce=generate_nonce(),
|
nonce=generate_nonce(),
|
||||||
digest=digest,
|
digest=get_random_captcha_digest_for(user),
|
||||||
default_name=get_default_name(user),
|
default_name=get_default_name(user),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,7 +85,7 @@ async def nojs_submit_message(user):
|
||||||
else:
|
else:
|
||||||
nonce = form.get('nonce', '')
|
nonce = form.get('nonce', '')
|
||||||
try:
|
try:
|
||||||
# if the comment is empty but the captcha was just solved,
|
# If the comment is empty but the captcha was just solved,
|
||||||
# be lenient: don't raise an exception and don't create a notice
|
# be lenient: don't raise an exception and don't create a notice
|
||||||
add_chat_message(
|
add_chat_message(
|
||||||
user,
|
user,
|
||||||
|
@ -98,6 +97,7 @@ async def nojs_submit_message(user):
|
||||||
notice, *_ = e.args
|
notice, *_ = e.args
|
||||||
state_id = add_state(user, notice=notice)
|
state_id = add_state(user, notice=notice)
|
||||||
else:
|
else:
|
||||||
|
deverify(user)
|
||||||
state_id = None
|
state_id = None
|
||||||
|
|
||||||
return redirect(url_for(
|
return redirect(url_for(
|
||||||
|
|
|
@ -131,3 +131,19 @@ def verify(user, digest, answer):
|
||||||
verification_happened = True
|
verification_happened = True
|
||||||
|
|
||||||
return verification_happened
|
return verification_happened
|
||||||
|
|
||||||
|
@with_timestamp
|
||||||
|
def deverify(timestamp, user):
|
||||||
|
if not user['verified']:
|
||||||
|
return
|
||||||
|
|
||||||
|
n_user_messages = 0
|
||||||
|
for message in reversed(MESSAGES):
|
||||||
|
message_sent_ago = timestamp - message['timestamp']
|
||||||
|
if message_sent_ago >= CONFIG['FLOOD_DURATION']:
|
||||||
|
break
|
||||||
|
elif message['token'] == user['token']:
|
||||||
|
n_user_messages += 1
|
||||||
|
|
||||||
|
if n_user_messages >= CONFIG['FLOOD_THRESHOLD']:
|
||||||
|
user['verified'] = False
|
||||||
|
|
|
@ -3,9 +3,9 @@ import asyncio
|
||||||
from quart import current_app, websocket
|
from quart import current_app, websocket
|
||||||
|
|
||||||
from anonstream.stream import get_stream_title, get_stream_uptime
|
from anonstream.stream import get_stream_title, get_stream_uptime
|
||||||
from anonstream.captcha import get_random_captcha_digest
|
from anonstream.captcha import get_random_captcha_digest_for
|
||||||
from anonstream.chat import get_all_messages_for_websocket, add_chat_message, Rejected
|
from anonstream.chat import get_all_messages_for_websocket, add_chat_message, Rejected
|
||||||
from anonstream.user import get_all_users_for_websocket, see, verify, BadCaptcha
|
from anonstream.user import get_all_users_for_websocket, see, verify, deverify, BadCaptcha
|
||||||
from anonstream.utils.chat import generate_nonce
|
from anonstream.utils.chat import generate_nonce
|
||||||
from anonstream.utils.websocket import parse_websocket_data, Malformed
|
from anonstream.utils.websocket import parse_websocket_data, Malformed
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ async def websocket_outbound(queue, user):
|
||||||
False: CONFIG['DEFAULT_ANON_NAME'],
|
False: CONFIG['DEFAULT_ANON_NAME'],
|
||||||
},
|
},
|
||||||
'scrollback': CONFIG['MAX_CHAT_SCROLLBACK'],
|
'scrollback': CONFIG['MAX_CHAT_SCROLLBACK'],
|
||||||
'digest': None if user['verified'] else get_random_captcha_digest(),
|
'digest': get_random_captcha_digest_for(user),
|
||||||
}
|
}
|
||||||
await websocket.send_json(payload)
|
await websocket.send_json(payload)
|
||||||
while True:
|
while True:
|
||||||
|
@ -51,7 +51,7 @@ async def websocket_inbound(queue, user):
|
||||||
payload = {
|
payload = {
|
||||||
'type': 'captcha',
|
'type': 'captcha',
|
||||||
'notice': notice,
|
'notice': notice,
|
||||||
'digest': get_random_captcha_digest(),
|
'digest': get_random_captcha_digest_for(user),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
@ -63,9 +63,11 @@ async def websocket_inbound(queue, user):
|
||||||
'notice': notice,
|
'notice': notice,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
deverify(user)
|
||||||
payload = {
|
payload = {
|
||||||
'type': 'ack',
|
'type': 'ack',
|
||||||
'nonce': nonce,
|
'nonce': nonce,
|
||||||
'next': generate_nonce(),
|
'next': generate_nonce(),
|
||||||
|
'digest': get_random_captcha_digest_for(user),
|
||||||
}
|
}
|
||||||
queue.put_nowait(payload)
|
queue.put_nowait(payload)
|
||||||
|
|
|
@ -35,6 +35,10 @@ max_name_length = 24
|
||||||
min_name_contrast = 3.0
|
min_name_contrast = 3.0
|
||||||
background_color = "#232327"
|
background_color = "#232327"
|
||||||
|
|
||||||
|
[flood]
|
||||||
|
duration = 20
|
||||||
|
threshold = 4
|
||||||
|
|
||||||
[thresholds]
|
[thresholds]
|
||||||
user_notwatching = 8
|
user_notwatching = 8
|
||||||
user_tentative = 20
|
user_tentative = 20
|
||||||
|
|
読み込み中…
新しいイシューから参照