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 <justcool393@gmail.com>
Reviewed-on: rDrama/rDrama#50
Co-authored-by: justcool393 <justcool393@noreply.fsdfsd.net>
Co-committed-by: justcool393 <justcool393@noreply.fsdfsd.net>
master
justcool393 2022-12-06 22:07:12 +00:00 committed by Snakes
parent c12bf5105f
commit 6dbad04f08
6 changed files with 62 additions and 33 deletions

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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.")

View File

@ -16,14 +16,11 @@
<h2>{% block authtitle %}{% endblock %}</h2>
<p class="text-muted mb-md-5">{% block authtext %}{% endblock %}</p>
{% if error %}
<div class="alert alert-danger alert-dismissible fade show d-flex my-3" role="alert">
<div class="alert alert-danger fade show d-flex my-3" role="alert">
<i class="fas fa-exclamation-circle my-auto"></i>
<span>
{{error}}
</span>
<button type="button" class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% if msg %}

View File

@ -58,11 +58,8 @@
</p>
<hr>
{% endif %}
<form action="/signup" method="post" class="mt-md-3" id="signup">
{% if error %}<div class="text-danger mb-2">{{error}}</div>{% endif %}
<input type="hidden" name="formkey" value="{{formkey}}">
<input type="hidden" name="now" value="{{now}}">
{% if redirect %}<input type="hidden" name="redirect" value="{{redirect}}">{% endif %}