From 6dbad04f085b6db042846562adb5cf6d151b707f Mon Sep 17 00:00:00 2001 From: justcool393 Date: Tue, 6 Dec 2022 22:07:12 +0000 Subject: [PATCH] band-aid fix for frozen session issue on signup (#50) through some reason or another, people are somehow getting cookies that aren't prepended with a dot. this is a problem because both sessions at, as best as I can tell, mix so it tries to read from a different cookie than we write to. this essentially "freezes" the session in place. users are unable to login, logout, signup, toggle poor mode, toggle NSFW, etc. ~~this attempts to delete bad session cookies (i.e. cookies with a domain that don't start with a dot).~~ ~~we don't do this on "dotless" domains (and by extension localhost) because browser support for setting cookies on FQDNs that only have one dot has tenuous support among browsers anyway).~~ ~~this *may* log some people out, but... their days of being able to do stuff on the site were numbered anyway.~~ **edit: as amazing as this thought was, browsers just wipe the entire cookies completely and there's no way to specifically target dotless cookies. for an issue that affects a few users, better to just tell them to clear their cookies. if *this* doesn't work, delete service-worker.js and be done with the whole service worker crap. forever. permanently. this PR also includes some QOL improvements.** Co-authored-by: justcool393 Reviewed-on: https://fsdfsd.net/rDrama/rDrama/pulls/50 Co-authored-by: justcool393 Co-committed-by: justcool393 --- files/__main__.py | 13 ++++++-- files/helpers/const.py | 2 ++ files/routes/allroutes.py | 49 ++++++++++++++++++++-------- files/routes/login.py | 23 +++++++------ files/templates/login/authforms.html | 5 +-- files/templates/login/sign_up.html | 3 -- 6 files changed, 62 insertions(+), 33 deletions(-) diff --git a/files/__main__.py b/files/__main__.py index 61cc3fe41..6cea565be 100644 --- a/files/__main__.py +++ b/files/__main__.py @@ -25,16 +25,25 @@ app.jinja_env.auto_reload = True app.jinja_env.add_extension('jinja2.ext.do') faulthandler.enable() +def _startup_check(): + ''' + Performs some sanity checks on startup to make sure we aren't attempting + to startup with obviously invalid values that won't work anyway + ''' + if not SITE: raise TypeError("SITE environment variable must exist and not be None") + if SITE.startswith('.'): raise ValueError("Domain must not start with a dot") + app.config['SERVER_NAME'] = SITE app.config['SECRET_KEY'] = environ.get('SECRET_KEY').strip() app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3153600 +_startup_check() if not IS_LOCALHOST: - app.config['SESSION_COOKIE_DOMAIN'] = f'.{SITE}' app.config["SESSION_COOKIE_SECURE"] = True + app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower() app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 app.config["SESSION_COOKIE_SAMESITE"] = "Lax" -app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 365 +app.config["PERMANENT_SESSION_LIFETIME"] = SESSION_LIFETIME app.config['SESSION_REFRESH_EACH_REQUEST'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False diff --git a/files/helpers/const.py b/files/helpers/const.py index 00eba309b..60d97140d 100644 --- a/files/helpers/const.py +++ b/files/helpers/const.py @@ -68,6 +68,8 @@ REDDIT_NOTIFS_CACHE_KEY = "reddit_notifications" MARSEYS_CACHE_KEY = "marseys" EMOJIS_CACHE_KEY = "emojis" +SESSION_LIFETIME = 60 * 60 * 24 * 365 + CASINO_RELEASE_DAY = 1662825600 if SITE_NAME == 'rDrama': patron = 'Paypig' diff --git a/files/routes/allroutes.py b/files/routes/allroutes.py index 9091c7ff1..bd8ef1a9a 100644 --- a/files/routes/allroutes.py +++ b/files/routes/allroutes.py @@ -14,6 +14,9 @@ def session_init(): @app.before_request def before_request(): g.desires_auth = False + if not IS_LOCALHOST: + app.config["COOKIE_DOMAIN"] = f".{request.host}" + app.config["SESSION_COOKIE_DOMAIN"] = app.config["COOKIE_DOMAIN"] if SITE == 'marsey.world' and request.path != '/kofi': abort(404) @@ -50,25 +53,43 @@ def before_request(): limiter.check() g.db = db_session() - @app.after_request -def after_request(response): +def after_request(response:Response): if response.status_code < 400: - if CLOUDFLARE_AVAILABLE and CLOUDFLARE_COOKIE_VALUE and g.desires_auth: - logged_in = bool(getattr(g, 'v', None)) - response.set_cookie("lo", CLOUDFLARE_COOKIE_VALUE if logged_in else '', - max_age=60*60*24*365 if logged_in else 1, samesite="Lax") - if getattr(g, 'db', None): - g.db.commit() - g.db.close() - del g.db + _set_cloudflare_cookie(response) + _commit_and_close_db() return response @app.teardown_appcontext def teardown_request(error): - if getattr(g, 'db', None): - g.db.rollback() - g.db.close() - del g.db + _rollback_and_close_db() stdout.flush() + +def _set_cloudflare_cookie(response:Response) -> None: + ''' + Sets a cookie that can be used by an upstream DDoS protection and caching provider + ''' + if not g.desires_auth: return + if not CLOUDFLARE_AVAILABLE or not CLOUDFLARE_COOKIE_VALUE: return + logged_in = bool(getattr(g, 'v', None)) + if not logged_in and request.cookies.get("lo"): + response.delete_cookie("lo", domain=app.config["COOKIE_DOMAIN"], samesite="Lax") + elif logged_in and not request.cookies.get("lo"): + response.set_cookie("lo", CLOUDFLARE_COOKIE_VALUE if logged_in else '', + max_age=SESSION_LIFETIME, samesite="Lax", + domain=app.config["COOKIE_DOMAIN"]) + +def _commit_and_close_db() -> bool: + if not getattr(g, 'db', None): return False + g.db.commit() + g.db.close() + del g.db + return True + +def _rollback_and_close_db() -> bool: + if not getattr(g, 'db', None): return False + g.db.rollback() + g.db.close() + del g.db + return True diff --git a/files/routes/login.py b/files/routes/login.py index e9a7d09c5..20e92c00f 100644 --- a/files/routes/login.py +++ b/files/routes/login.py @@ -205,8 +205,20 @@ def sign_up_post(v:Optional[User]): form_timestamp = request.values.get("now", '0') form_formkey = request.values.get("formkey", "none") + def signup_error(error, clear=False): + args = {"error": error} + if request.values.get("referred_by"): + user = get_account(request.values.get("referred_by"), include_shadowbanned=False) + if user: args["ref"] = user.username + resp = make_response(redirect(f"/signup?{urlencode(args)}")) + if clear: + session.clear() + resp.delete_cookie(app.config["SESSION_COOKIE_NAME"], httponly=True, secure=True, samesite="Lax") + return resp + submitted_token = session.get("signup_token", "") - if not submitted_token: abort(400) + if not submitted_token: + return signup_error(f"An error occurred while attempting to signup. If you get this repeatedly, please make sure cookies are enabled.", clear=True) correct_formkey_hashstr = form_timestamp + submitted_token + g.agent correct_formkey = hmac.new(key=bytes(SECRET_KEY, "utf-16"), @@ -219,15 +231,6 @@ def sign_up_post(v:Optional[User]): if not username: abort(400) username = username.strip() - def signup_error(error): - - args = {"error": error} - if request.values.get("referred_by"): - user = get_account(request.values.get("referred_by"), include_shadowbanned=False) - if user: args["ref"] = user.username - - return redirect(f"/signup?{urlencode(args)}") - if now - int(form_timestamp) < 5: return signup_error("There was a problem. Please try again.") diff --git a/files/templates/login/authforms.html b/files/templates/login/authforms.html index 2b0a120d0..48c4bebae 100644 --- a/files/templates/login/authforms.html +++ b/files/templates/login/authforms.html @@ -16,14 +16,11 @@

{% block authtitle %}{% endblock %}

{% block authtext %}{% endblock %}

{% if error %} -