2017-08-10 17:12:04 +02:00
|
|
|
from flask import Flask, request
|
2017-07-25 09:52:24 +02:00
|
|
|
from flask_sqlalchemy import SQLAlchemy
|
2017-08-14 20:19:51 +02:00
|
|
|
from sqlalchemy import MetaData, event
|
|
|
|
from sqlalchemy.engine import Engine
|
2017-07-25 09:52:24 +02:00
|
|
|
from flask_migrate import Migrate
|
2017-08-07 14:51:33 +02:00
|
|
|
import version
|
2017-08-29 21:27:38 +02:00
|
|
|
from lib.cachebust import cachebust
|
2017-08-10 17:07:39 +02:00
|
|
|
from flask_limiter import Limiter
|
2017-09-04 20:24:42 +02:00
|
|
|
from lib.auth import get_viewer
|
2017-08-14 20:19:51 +02:00
|
|
|
import os
|
2017-08-29 01:29:55 +02:00
|
|
|
import mimetypes
|
2017-09-07 01:10:02 +02:00
|
|
|
import lib.brotli
|
2017-09-16 13:58:02 +02:00
|
|
|
import lib.img_proxy
|
2017-07-25 09:52:24 +02:00
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
2017-07-26 11:11:54 +02:00
|
|
|
default_config = {
|
|
|
|
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
|
|
|
|
"SQLALCHEMY_DATABASE_URI": "postgresql+psycopg2:///forget",
|
2017-08-07 15:06:29 +02:00
|
|
|
"HTTPS": True,
|
2017-08-10 17:22:32 +02:00
|
|
|
"SENTRY_CONFIG": {},
|
2017-08-20 18:48:43 +02:00
|
|
|
"REPO_URL": "https://github.com/codl/forget",
|
2017-08-21 10:56:51 +02:00
|
|
|
"COMMIT_URL": "https://github.com/codl/forget/commits/{hash}",
|
2017-09-07 01:10:02 +02:00
|
|
|
"REDIS_URI": "redis://",
|
2017-07-26 11:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
app.config.update(default_config)
|
|
|
|
|
|
|
|
app.config.from_pyfile('config.py', True)
|
2017-07-25 23:05:46 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
metadata = MetaData(naming_convention={
|
2017-07-25 09:52:24 +02:00
|
|
|
"ix": 'ix_%(column_0_label)s',
|
|
|
|
"uq": "uq_%(table_name)s_%(column_0_name)s",
|
|
|
|
"ck": "ck_%(table_name)s_%(constraint_name)s",
|
|
|
|
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
|
|
|
|
"pk": "pk_%(table_name)s"
|
|
|
|
})
|
|
|
|
|
|
|
|
db = SQLAlchemy(app, metadata=metadata)
|
|
|
|
migrate = Migrate(app, db)
|
2017-08-07 13:29:31 +02:00
|
|
|
|
2017-09-07 01:10:02 +02:00
|
|
|
if not 'RATELIMIT_STORAGE_URL' in app.config:
|
|
|
|
uri = app.config['REDIS_URI']
|
|
|
|
assert not uri.startswith('unix://'), "flask-limiter does not support redis over a unix socket"
|
|
|
|
app.config['RATELIMIT_STORAGE_URL'] = uri
|
|
|
|
|
|
|
|
if not 'CELERY_BROKER' in app.config:
|
|
|
|
uri = app.config['REDIS_URI']
|
|
|
|
if uri.startswith('unix://'):
|
|
|
|
uri = url.replace('unix', 'redis+socket', 1)
|
|
|
|
app.config['CELERY_BROKER'] = uri
|
|
|
|
|
2017-08-07 13:29:31 +02:00
|
|
|
sentry = None
|
|
|
|
if 'SENTRY_DSN' in app.config:
|
|
|
|
from raven.contrib.flask import Sentry
|
2017-08-07 15:09:28 +02:00
|
|
|
app.config['SENTRY_CONFIG']['release'] = version.version
|
2017-08-07 14:59:38 +02:00
|
|
|
sentry = Sentry(app, dsn=app.config['SENTRY_DSN'])
|
2017-08-07 21:35:46 +02:00
|
|
|
|
2017-08-07 21:50:31 +02:00
|
|
|
url_for = cachebust(app)
|
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-07 21:35:46 +02:00
|
|
|
@app.context_processor
|
|
|
|
def inject_static():
|
|
|
|
def static(filename, **kwargs):
|
|
|
|
return url_for('static', filename=filename, **kwargs)
|
|
|
|
return {'st': static}
|
2017-08-10 17:07:39 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-10 17:07:39 +02:00
|
|
|
def rate_limit_key():
|
|
|
|
viewer = get_viewer()
|
|
|
|
if viewer:
|
|
|
|
return viewer.id
|
|
|
|
for address in request.access_route:
|
|
|
|
if address != '127.0.0.1':
|
|
|
|
print(address)
|
|
|
|
return address
|
|
|
|
return request.remote_addr
|
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-10 17:07:39 +02:00
|
|
|
limiter = Limiter(app, key_func=rate_limit_key)
|
2017-08-28 01:47:01 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-28 01:47:01 +02:00
|
|
|
@app.after_request
|
|
|
|
def install_security_headers(resp):
|
2017-08-29 14:46:32 +02:00
|
|
|
csp = ("default-src 'none';"
|
2017-09-16 13:58:02 +02:00
|
|
|
"img-src 'self';"
|
2017-08-29 14:46:32 +02:00
|
|
|
"style-src 'self' 'unsafe-inline';"
|
|
|
|
"frame-ancestors 'none';"
|
|
|
|
)
|
2017-08-31 19:58:48 +02:00
|
|
|
if 'SENTRY_DSN' in app.config:
|
|
|
|
csp += "script-src 'self' https://cdn.ravenjs.com/;"
|
2017-08-31 20:46:38 +02:00
|
|
|
csp += "connect-src 'self' https://sentry.io/;"
|
2017-08-31 19:58:48 +02:00
|
|
|
else:
|
|
|
|
csp += "script-src 'self';"
|
2017-08-31 20:46:38 +02:00
|
|
|
csp += "connect-src 'self';"
|
2017-08-31 19:58:48 +02:00
|
|
|
|
2017-08-28 01:47:01 +02:00
|
|
|
if 'CSP_REPORT_URI' in app.config:
|
2017-08-31 19:58:48 +02:00
|
|
|
csp += "report-uri " + app.config.get('CSP_REPORT_URI')
|
2017-08-28 01:47:01 +02:00
|
|
|
|
|
|
|
if app.config.get('HTTPS'):
|
2017-08-29 14:46:32 +02:00
|
|
|
resp.headers.set('strict-transport-security',
|
|
|
|
'max-age={}'.format(60*60*24*365))
|
2017-08-28 09:12:32 +02:00
|
|
|
csp += "; upgrade-insecure-requests"
|
2017-08-28 01:47:01 +02:00
|
|
|
|
2017-08-28 09:12:32 +02:00
|
|
|
resp.headers.set('Content-Security-Policy', csp)
|
2017-08-28 01:47:01 +02:00
|
|
|
resp.headers.set('referrer-policy', 'no-referrer')
|
|
|
|
resp.headers.set('x-content-type-options', 'nosniff')
|
|
|
|
resp.headers.set('x-frame-options', 'DENY')
|
2017-08-28 01:58:00 +02:00
|
|
|
resp.headers.set('x-xss-protection', '1')
|
2017-08-28 01:47:01 +02:00
|
|
|
|
|
|
|
return resp
|
2017-08-29 01:29:55 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-29 01:29:55 +02:00
|
|
|
mimetypes.add_type('image/webp', '.webp')
|
2017-09-07 01:10:02 +02:00
|
|
|
|
|
|
|
lib.brotli.brotli(app)
|
2017-09-16 13:58:02 +02:00
|
|
|
|
2017-09-18 10:29:52 +02:00
|
|
|
imgproxy = lib.img_proxy.ImgProxyCache(redis_uri=app.config.get('REDIS_URI'))
|