Error pages: custom descriptions

このコミットが含まれているのは:
n9k 2022-06-22 07:52:07 +00:00
コミット 0548065b1d
4個のファイルの変更83行の追加40行の削除

ファイルの表示

@ -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')

ファイルの表示

@ -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'<!doctype html>\n'
f'<p>{hint}</p>\n'
)
description = hint
else:
body = (
f'<!doctype html>\n'
f'<p>Wrong username or password. Refresh the page to try again.</p>\n'
f'<p>{hint}</p>\n'
description = Markup(
f'Wrong username or password. Refresh the page to try again. '
f'<br>'
f'{hint}'
)
return body, 401, {'WWW-Authenticate': 'Basic'}
error = Unauthorized(description)
response = await current_app.handle_http_exception(error)
response = await make_response(response)
response.headers['WWW-Authenticate'] = 'Basic'
return response
return wrapper
def generate_and_add_user(timestamp, token=None, broadcaster=False):
@ -103,7 +104,11 @@ def with_user_from(context, fallback_to_token=False):
# Reject invalid tokens
if isinstance(token, str) and not RE_TOKEN.fullmatch(token):
raise abort(400)
raise BadRequest(Markup(
f'Your token contains disallowed characters or is too '
f'long. Tokens must match this regular expression: <br>'
f'<code>{RE_TOKEN.pattern}</code>'
))
# 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"<a href=\"{url_for('login')}\">click here</a> "
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"<a href=\"{url_for('home', token=token)}\">"
f"Click here."
f"</a>"
))
else:
if user is None:
user = generate_and_add_user(timestamp, token, broadcaster)

ファイルの表示

@ -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;
}
</style>
</head>
<body>
<h1>{{ error.code }} {{ error.name }}</h1>
<main>
<h1>{{ error.code }} {{ error.name }}</h1>
{% if error.description != error.__class__.description %}
<p>{{ error.description }}</p>
{% endif %}
</main>
</body>
</html>

ファイルの表示

@ -259,7 +259,7 @@ def create_eyes(timestamp, user, headers):
# Treat eyes as a stack, do not create new eyes if it would
# cause the limit to be exceeded
if not CONFIG['FLOOD_VIDEO_OVERWRITE']:
raise TooManyEyes
raise TooManyEyes(len(user['eyes']['current']))
# Treat eyes as a queue, expire old eyes upon creating new eyes
# if the limit would have been exceeded otherwise
elif user['eyes']['current']: