Read-only maintenance mode setting for config.py (#356)

Disables all POSTs, optionally allowing users to log in (without updating last login date)
Blocked POSTs will redirect to the GET endpoint if possible, otherwise to referrer or in last case, home page.
API requests will get a plaintext message with 405 status code.
This commit is contained in:
Anna-Maria Meriniemi 2017-09-05 01:16:52 +03:00 committed by Arylide
parent 5e93d7ec6d
commit c5d705210d
6 changed files with 61 additions and 8 deletions

View File

@ -1,7 +1,13 @@
import os import os
DEBUG = True DEBUG = True
# A read-only maintenance mode, in which the database is not modified
MAINTENANCE_MODE = True
# A maintenance message (used in layout.html template)
MAINTENANCE_MODE_MESSAGE = 'Site is currently in read-only maintenance mode.'
# Allow logging in during maintenance (without updating last login date)
MAINTENANCE_MODE_LOGINS = True
USE_RECAPTCHA = False USE_RECAPTCHA = False
USE_EMAIL_VERIFICATION = False USE_EMAIL_VERIFICATION = False
USE_MYSQL = True USE_MYSQL = True

View File

@ -312,6 +312,12 @@
<div class="container"> <div class="container">
{% include "flashes.html" %} {% include "flashes.html" %}
{% if config.MAINTENANCE_MODE and config.MAINTENANCE_MODE_MESSAGE %}
<div class="alert alert-dismissable alert-warning" role="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
{{ config.MAINTENANCE_MODE_MESSAGE | safe }}
</div>
{% endif %}
{% block body %}{% endblock %} {% block body %}{% endblock %}
</div> <!-- /container --> </div> <!-- /container -->

View File

@ -1,3 +1,5 @@
import flask
from nyaa.views import ( # isort:skip from nyaa.views import ( # isort:skip
account, account,
admin, admin,
@ -8,8 +10,37 @@ from nyaa.views import ( # isort:skip
) )
def _maintenance_mode_hook():
''' Blocks POSTs, unless MAINTENANCE_MODE_LOGINS is True and the POST is for a login. '''
if flask.request.method == 'POST':
allow_logins = flask.current_app.config['MAINTENANCE_MODE_LOGINS']
endpoint = flask.request.endpoint
if not (allow_logins and endpoint == 'account.login'):
message = 'Site is currently in maintenance mode.'
# In case of an API request, return a plaintext error message
if endpoint.startswith('api.'):
resp = flask.make_response(message, 405)
resp.headers['Content-Type'] = 'text/plain'
return resp
else:
# Otherwise redirect to the target page and flash a message
flask.flash(flask.Markup(message), 'danger')
try:
target_url = flask.url_for(endpoint)
except:
# Non-GET-able endpoint, try referrer or default to home page
target_url = flask.request.referrer or flask.url_for('main.home')
return flask.redirect(target_url)
def register_views(flask_app): def register_views(flask_app):
""" Register the blueprints using the flask_app object """ """ Register the blueprints using the flask_app object """
# Add our POST blocker first
if flask_app.config['MAINTENANCE_MODE']:
flask_app.before_request(_maintenance_mode_hook)
flask_app.register_blueprint(account.bp) flask_app.register_blueprint(account.bp)
flask_app.register_blueprint(admin.bp) flask_app.register_blueprint(admin.bp)
flask_app.register_blueprint(main.bp) flask_app.register_blueprint(main.bp)

View File

@ -21,6 +21,10 @@ def login():
form = forms.LoginForm(flask.request.form) form = forms.LoginForm(flask.request.form)
if flask.request.method == 'POST' and form.validate(): if flask.request.method == 'POST' and form.validate():
if app.config['MAINTENANCE_MODE'] and not app.config['MAINTENANCE_MODE_LOGINS']:
flask.flash(flask.Markup('<strong>Logins are currently disabled.</strong>'), 'danger')
return flask.redirect(flask.url_for('account.login'))
username = form.username.data.strip() username = form.username.data.strip()
password = form.password.data password = form.password.data
user = models.User.by_username(username) user = models.User.by_username(username)
@ -40,8 +44,9 @@ def login():
user.last_login_date = datetime.utcnow() user.last_login_date = datetime.utcnow()
user.last_login_ip = ip_address(flask.request.remote_addr).packed user.last_login_ip = ip_address(flask.request.remote_addr).packed
db.session.add(user) if not app.config['MAINTENANCE_MODE']:
db.session.commit() db.session.add(user)
db.session.commit()
flask.g.user = user flask.g.user = user
flask.session['user_id'] = user.id flask.session['user_id'] = user.id

View File

@ -41,11 +41,12 @@ def before_request():
flask.session.permanent = True flask.session.permanent = True
flask.session.modified = True flask.session.modified = True
ip = ip_address(flask.request.remote_addr) if not app.config['MAINTENANCE_MODE']:
if user.last_login_ip != ip: ip = ip_address(flask.request.remote_addr)
user.last_login_ip = ip.packed if user.last_login_ip != ip:
db.session.add(user) user.last_login_ip = ip.packed
db.session.commit() db.session.add(user)
db.session.commit()
# Check if user is banned on POST # Check if user is banned on POST
if flask.request.method == 'POST': if flask.request.method == 'POST':

View File

@ -190,6 +190,10 @@ def view_user(user_name):
@bp.route('/user/activate/<payload>') @bp.route('/user/activate/<payload>')
def activate_user(payload): def activate_user(payload):
if app.config['MAINTENANCE_MODE']:
flask.flash(flask.Markup('<strong>Activations are currently disabled.</strong>'), 'danger')
return flask.redirect(flask.url_for('main.home'))
s = get_serializer() s = get_serializer()
try: try:
user_id = s.loads(payload) user_id = s.loads(payload)