]> git.gir.st - subscriptionfeed.git/blob - app/__init__.py
log api responses on error
[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. this makes debugging of api responses easier, as the request can be reconstructed and replayed.
21 from flask import g
22 from werkzeug.exceptions import InternalServerError
23 @app.errorhandler(InternalServerError)
24 def log_errors(e):
25 if 'api_requests' in g:
26 app.logger.error(g.api_requests)
27 return e
28
29 # TODO: build a proper flask extension
30 # Magic CSRF protection: This modifies outgoing HTML responses and injects a csrf token into all forms.
31 # All post requests are then checked if they contain the valid token.
32 # TODO:
33 # - don't use regex for injecting
34 # - inject a http header into all responses (that could be used by apis)
35 # - allow csrf token to be passed in http header, json, ...
36 # - a decorator on routes to opt out of verification or output munging
37 # https://stackoverflow.com/questions/19574694/flask-hit-decorator-before-before-request-signal-fires
38 # - allow specifying hmac message contents (currently request.remote_addr)
39 import re
40 import hmac
41 import hashlib
42 from flask import request
43 @app.after_request
44 def add_csrf_protection(response):
45 if response.mimetype == "text/html":
46 token = hmac.new(app.secret_key, request.remote_addr.encode('ascii'), hashlib.sha256).hexdigest() # TODO: will fail behind reverse proxy (remote_addr always localhost)
47 response.set_data( re.sub(
48 rb'''(<[Ff][Oo][Rr][Mm](\s+[a-zA-Z0-9-]+(=(\w*|'[^']*'|"[^"]*"))?)*>)''', # match form tags with any number of attributes and any type of quotes
49 rb'\1<input type="hidden" name="csrf" value="'+token.encode('ascii')+rb'">', # hackily append a hidden input with our csrf protection value
50 response.get_data()))
51 return response
52 @app.before_request
53 def verify_csrf_protection():
54 token = hmac.new(app.secret_key, request.remote_addr.encode('ascii'), hashlib.sha256).hexdigest() # TODO: will fail behind reverse proxy (remote_addr always localhost)
55 if request.method == "POST" and request.form.get('csrf') != token:
56 return "CSRF validation failed!", 400
57 request.form = request.form.copy() # make it mutable
58 request.form.poplist('csrf') # remove our csrf again
Imprint / Impressum