import base64 import secrets from flask import Flask from .common.common import * app = Flask(__name__) app.secret_key = base64.b64decode(cf['frontend'].get('secret_key','')) or \ secrets.token_bytes(16) # development fallback; CSRF/cookies won't persist. from .common.user import init_login init_login(app) from . import youtube app.register_blueprint(youtube.frontend) from . import reddit app.register_blueprint(reddit.frontend) # TODO: build a proper flask extension # Magic CSRF protection: This modifies outgoing HTML responses and injects a csrf token into all forms. # All post requests are then checked if they contain the valid token. # TODO: # - don't use regex for injecting # - inject a http header into all responses (that could be used by apis) # - allow csrf token to be passed in http header, json, ... # - a decorator on routes to opt out of verification or output munging # https://stackoverflow.com/questions/19574694/flask-hit-decorator-before-before-request-signal-fires # - allow specifying hmac message contents (currently request.remote_addr) import re import hmac import hashlib from flask import request @app.after_request def add_csrf_protection(response): if response.mimetype == "text/html": token = hmac.new(app.secret_key, request.remote_addr.encode('ascii'), hashlib.sha256).hexdigest() # TODO: will fail behind reverse proxy (remote_addr always localhost) response.set_data( re.sub( rb'''(<[Ff][Oo][Rr][Mm](\s+[a-zA-Z0-9-]+(=(\w*|'[^']*'|"[^"]*"))?)*>)''', # match form tags with any number of attributes and any type of quotes rb'\1', # hackily append a hidden input with our csrf protection value response.get_data())) return response @app.before_request def verify_csrf_protection(): token = hmac.new(app.secret_key, request.remote_addr.encode('ascii'), hashlib.sha256).hexdigest() # TODO: will fail behind reverse proxy (remote_addr always localhost) if request.method == "POST" and request.form.get('csrf') != token: return "CSRF validation failed!", 400 request.form = request.form.copy() # make it mutable request.form.poplist('csrf') # remove our csrf again