{{ error.code }} {{ error.name }}
+ {% if error.description != error.__class__.description %} +{{ error.description }}
+ {% endif %} +diff --git a/anonstream/routes/core.py b/anonstream/routes/core.py index 73637c5..69f0b15 100644 --- a/anonstream/routes/core.py +++ b/anonstream/routes/core.py @@ -3,14 +3,14 @@ import math -from quart import current_app, request, render_template, abort, make_response, redirect, url_for, abort, send_from_directory -from werkzeug.exceptions import TooManyRequests +from quart import current_app, request, render_template, abort, make_response, redirect, url_for, send_from_directory +from werkzeug.exceptions import NotFound, TooManyRequests from anonstream.access import add_failure, pop_failure from anonstream.captcha import get_captcha_image, get_random_captcha_digest from anonstream.segments import segments, StopSendingSegments from anonstream.stream import is_online, get_stream_uptime -from anonstream.user import watching, create_eyes, renew_eyes, EyesException, RatelimitedEyes +from anonstream.user import watching, create_eyes, renew_eyes, EyesException, RatelimitedEyes, TooManyEyes from anonstream.routes.wrappers import with_user_from, auth_required, clean_cache_headers, generate_and_add_user from anonstream.helpers.captcha import check_captcha_digest, Answer from anonstream.utils.security import generate_csp @@ -43,28 +43,40 @@ async def home(timestamp, user_or_token): @with_user_from(request) async def stream(timestamp, user): if not is_online(): - return abort(404) - - try: - eyes_id = create_eyes(user, dict(request.headers)) - except RatelimitedEyes as e: - retry_after, *_ = e.args - return TooManyRequests(), {'Retry-After': math.ceil(retry_after)} - except EyesException: - return abort(429) - - def segment_read_hook(uri): + raise NotFound('The stream is offline.') + else: try: - renew_eyes(user, eyes_id, just_read_new_segment=True) - except EyesException as e: - raise StopSendingSegments(f'eyes {eyes_id} not allowed: {e!r}') from e - print(f'{uri}: {eyes_id}~{user["token"]}') - watching(user) - - generator = segments(segment_read_hook, token=user['token']) - response = await make_response(generator) - response.headers['Content-Type'] = 'video/mp4' - response.timeout = None + eyes_id = create_eyes(user, dict(request.headers)) + except RatelimitedEyes as e: + retry_after, *_ = e.args + error = TooManyRequests( + f'You have requested the stream recently. ' + f'Try again in {retry_after:.1f} seconds.' + ) + response = await current_app.handle_http_exception(error) + response = await make_response(response) + response.headers['Retry-After'] = math.ceil(retry_after) + raise abort(response) + except TooManyEyes as e: + n_eyes, *_ = e.args + raise TooManyRequests( + f'You have made {n_eyes} concurrent requests for the stream. ' + f'End one of those before making a new request.' + ) + else: + def segment_read_hook(uri): + try: + renew_eyes(user, eyes_id, just_read_new_segment=True) + except EyesException as e: + raise StopSendingSegments( + f'eyes {eyes_id} not allowed: {e!r}' + ) from e + print(f'{uri}: {eyes_id}~{user["token"]}') + watching(user) + generator = segments(segment_read_hook, token=user['token']) + response = await make_response(generator) + response.headers['Content-Type'] = 'video/mp4' + response.timeout = None return response @current_app.route('/login') diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index a18bd46..339489b 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -8,7 +8,8 @@ import string from functools import wraps from urllib.parse import quote, unquote -from quart import current_app, request, abort, make_response, render_template, request +from quart import current_app, request, make_response, render_template, request, url_for, Markup +from werkzeug.exceptions import BadRequest, Unauthorized, Forbidden from werkzeug.security import check_password_hash from anonstream.broadcast import broadcast @@ -57,18 +58,18 @@ def auth_required(f): 'their terminal.' ) if request.authorization is None: - body = ( - f'\n' - f'
{hint}
\n' - ) + description = hint else: - body = ( - f'\n' - f'Wrong username or password. Refresh the page to try again.
\n' - f'{hint}
\n' + description = Markup( + f'Wrong username or password. Refresh the page to try again. ' + f'{RE_TOKEN.pattern}
'
+ ))
# Only logged in broadcaster may have the broadcaster's token
if (
@@ -111,7 +116,13 @@ def with_user_from(context, fallback_to_token=False):
and isinstance(token, str)
and hmac.compare_digest(token, CONFIG['AUTH_TOKEN'])
):
- raise abort(401)
+ raise Unauthorized(Markup(
+ f"You are using the broadcaster's token but you are "
+ f"not logged in. The broadcaster should "
+ f"click here "
+ f"and log in with the credentials printed in their "
+ f"terminal when they started anonstream."
+ ))
# Create response
user = USERS_BY_TOKEN.get(token)
@@ -123,7 +134,12 @@ def with_user_from(context, fallback_to_token=False):
#assert not broadcaster
response = await f(timestamp, token, *args, **kwargs)
else:
- raise abort(403)
+ raise Forbidden(Markup(
+ f"You have not solved the access captcha. "
+ f""
+ f"Click here."
+ f""
+ ))
else:
if user is None:
user = generate_and_add_user(timestamp, token, broadcaster)
diff --git a/anonstream/templates/error.html b/anonstream/templates/error.html
index 42c71d3..38aa7ce 100644
--- a/anonstream/templates/error.html
+++ b/anonstream/templates/error.html
@@ -13,15 +13,30 @@
text-align: center;
text-shadow: 2px 0px 1px orangered;
}
+ main {
+ margin: auto;
+ max-width: 52rem;
+ }
h1 {
font-size: 32pt;
}
a {
color: #42a5d7;
}
+ code {
+ background-color: #333;
+ padding: 2px;
+ border-radius: 2px;
+ overflow-wrap: anywhere;
+ }
- {{ error.description }}
+ {% endif %} +