]> git.gir.st - subscriptionfeed.git/blob - app/__init__.py
make site R E S P O N S I V E (mobile friendly)
[subscriptionfeed.git] / app / __init__.py
1 import base64
2 import secrets
3 import importlib
4 from flask import Flask
5
6 from .common.common import *
7 from .common.user import init_login
8
9 app = Flask(__name__)
10 app.secret_key = base64.b64decode(cf['frontend'].get('secret_key','')) or \
11 secrets.token_bytes(16) # development fallback; CSRF/cookies won't persist.
12 init_login(app)
13
14 for name in cf['frontend']['modules'].split(','):
15 blueprint = importlib.import_module('.'+name, __name__)
16 app.register_blueprint(blueprint.frontend)
17
18
19 # TODO: should this go somewhere else?
20 # This error handler logs requests to external apis, and POST data. this makes debugging of api responses easier, as the request can be reconstructed and replayed.
21 from flask import g, request
22 from werkzeug.exceptions import InternalServerError
23 @app.errorhandler(InternalServerError)
24 def log_errors(e):
25 if request.method == "POST":
26 app.logger.error(request.data)
27 if 'api_requests' in g:
28 app.logger.error(g.api_requests)
29 return e
30
31 # TODO: build a proper flask extension
32 # Magic CSRF protection: This modifies outgoing HTML responses and injects a csrf token into all forms.
33 # All post requests are then checked if they contain the valid token.
34 # TODO:
35 # - don't use regex for injecting
36 # - inject a http header into all responses (that could be used by apis)
37 # - allow csrf token to be passed in http header, json, ...
38 # - a decorator on routes to opt out of verification or output munging
39 # https://stackoverflow.com/questions/19574694/flask-hit-decorator-before-before-request-signal-fires
40 # - allow specifying hmac message contents (currently request.remote_addr)
41 import re
42 import hmac
43 import hashlib
44 from flask import request
45 @app.after_request
46 def add_csrf_protection(response):
47 if response.mimetype == "text/html":
48 token = hmac.new(app.secret_key, request.remote_addr.encode('ascii'), hashlib.sha256).hexdigest() # TODO: will fail behind reverse proxy (remote_addr always localhost)
49 response.set_data( re.sub(
50 rb'''(<[Ff][Oo][Rr][Mm](\s+[a-zA-Z0-9-]+(=(\w*|'[^']*'|"[^"]*"))?)*>)''', # match form tags with any number of attributes and any type of quotes
51 rb'\1<input type="hidden" name="csrf" value="'+token.encode('ascii')+rb'">', # hackily append a hidden input with our csrf protection value
52 response.get_data()))
53 return response
54 @app.before_request
55 def verify_csrf_protection():
56 token = hmac.new(app.secret_key, request.remote_addr.encode('ascii'), hashlib.sha256).hexdigest() # TODO: will fail behind reverse proxy (remote_addr always localhost)
57 if request.method == "POST" and request.form.get('csrf') != token:
58 return "CSRF validation failed!", 400
59 request.form = request.form.copy() # make it mutable
60 request.form.poplist('csrf') # remove our csrf again
Imprint / Impressum