privatize user CSS (fixes #273)
implements issue comment: https://github.com/themotte/rDrama/issues/273#issuecomment-1240543608
This commit is contained in:
parent
d0ba568738
commit
fb65cf0416
14 changed files with 31 additions and 46 deletions
|
@ -57,8 +57,8 @@ class User(Base):
|
|||
verifiedcolor = Column(String)
|
||||
winnings = Column(Integer, default=0, nullable=False)
|
||||
email = deferred(Column(String))
|
||||
css = deferred(Column(String))
|
||||
profilecss = deferred(Column(String))
|
||||
css = deferred(Column(String(CSS_LENGTH_MAXIMUM)))
|
||||
profilecss = deferred(Column(String(CSS_LENGTH_MAXIMUM)))
|
||||
passhash = deferred(Column(String, nullable=False))
|
||||
post_count = Column(Integer, default=0, nullable=False)
|
||||
comment_count = Column(Integer, default=0, nullable=False)
|
||||
|
|
|
@ -62,6 +62,7 @@ COLORS = {'ff66ac','805ad5','62ca56','38a169','80ffff','2a96f3','eb4963','ff0000
|
|||
SUBMISSION_BODY_LENGTH_MAXIMUM: Final[int] = 20000
|
||||
COMMENT_BODY_LENGTH_MAXIMUM: Final[int] = 10000
|
||||
MESSAGE_BODY_LENGTH_MAXIMUM: Final[int] = 10000
|
||||
CSS_LENGTH_MAXIMUM: Final[int] = 4000
|
||||
|
||||
ERROR_MESSAGES = {
|
||||
400: "That request was bad and you should feel bad",
|
||||
|
|
|
@ -91,6 +91,7 @@ def inject_constants():
|
|||
"RENDER_DEPTH_LIMIT":RENDER_DEPTH_LIMIT,
|
||||
"SORTS_COMMENTS":SORTS_COMMENTS,
|
||||
"SORTS_POSTS":SORTS_POSTS,
|
||||
"CSS_LENGTH_MAXIMUM":CSS_LENGTH_MAXIMUM,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -369,3 +369,12 @@ def filter_emojis_only(title, edit=False, graceful=False):
|
|||
|
||||
if len(title) > 1500 and not graceful: abort(400)
|
||||
else: return title
|
||||
|
||||
def validate_css(css:str) -> tuple[bool, str]:
|
||||
'''
|
||||
Validates that the provided CSS is allowed. It looks somewhat ugly but
|
||||
this prevents users from XSSing themselves (not really too much of a
|
||||
practical concern) or causing styling issues with the rest of the page.
|
||||
'''
|
||||
if '</style' in css.lower(): return False, "Invalid CSS"
|
||||
return True, ""
|
||||
|
|
|
@ -497,20 +497,21 @@ def settings_images_banner(v):
|
|||
@app.get("/settings/blocks")
|
||||
@auth_required
|
||||
def settings_blockedpage(v):
|
||||
|
||||
return render_template("settings_blocks.html", v=v)
|
||||
|
||||
@app.get("/settings/css")
|
||||
@auth_required
|
||||
def settings_css_get(v):
|
||||
|
||||
return render_template("settings_css.html", v=v)
|
||||
|
||||
@app.post("/settings/css")
|
||||
@limiter.limit("1/second;30/minute;200/hour;1000/day")
|
||||
@auth_required
|
||||
def settings_css(v):
|
||||
css = request.values.get("css").strip().replace('\\', '').strip()[:4000]
|
||||
css = sanitize_raw(request.values.get("css", "").replace('\\', ''), allow_newlines=True, length_limit=CSS_LENGTH_MAXIMUM)
|
||||
ok, err = validate_css(css)
|
||||
if not ok:
|
||||
abort(400, err)
|
||||
v.css = css
|
||||
g.db.add(v)
|
||||
g.db.commit()
|
||||
|
@ -526,7 +527,10 @@ def settings_profilecss_get(v):
|
|||
@limiter.limit("1/second;30/minute;200/hour;1000/day")
|
||||
@auth_required
|
||||
def settings_profilecss(v):
|
||||
profilecss = request.values.get("profilecss").strip().replace('\\', '').strip()[:4000]
|
||||
profilecss = sanitize_raw(request.values.get("profilecss", "").replace('\\', ''), allow_newlines=True, length_limit=CSS_LENGTH_MAXIMUM)
|
||||
ok, err = validate_css(profilecss)
|
||||
if not ok:
|
||||
abort(400, err)
|
||||
v.profilecss = profilecss
|
||||
g.db.add(v)
|
||||
g.db.commit()
|
||||
|
|
|
@ -380,13 +380,6 @@ def leaderboard(v:User):
|
|||
|
||||
return render_template("leaderboard.html", v=v, leaderboards=leaderboards)
|
||||
|
||||
@app.get("/@<username>/css")
|
||||
def get_css(username):
|
||||
user = get_user(username)
|
||||
resp=make_response(user.css or "")
|
||||
resp.headers.add("Content-Type", "text/css")
|
||||
return resp
|
||||
|
||||
@app.get("/@<username>/profilecss")
|
||||
def get_profilecss(username):
|
||||
user = get_user(username)
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<link rel="stylesheet" href="{{ 'css/main.css' | asset }}">
|
||||
<link rel="stylesheet" href="{{ ('css/'~v.theme~'.css') | asset }}">
|
||||
{% if v.css %}
|
||||
<link rel="stylesheet" href="/@{{v.username}}/css">
|
||||
<style>{{v.css | safe}}</style>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<link rel="stylesheet" href="{{ 'css/main.css' | asset }}">
|
||||
<link rel="stylesheet" href="{{ ('css/'~v.theme~'.css') | asset }}">
|
||||
{% if v.css %}
|
||||
<link rel="stylesheet" href="/@{{v.username}}/css">
|
||||
<style>{{v.css | safe}}</style>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<link rel="stylesheet" href="{{ 'css/main.css' | asset }}">
|
||||
<link rel="stylesheet" href="{{ ('css/'~v.theme~'.css') | asset }}">
|
||||
{% if v.css %}
|
||||
<link rel="stylesheet" href="/@{{v.username}}/css">
|
||||
<style>{{v.css | safe}}</style>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<link rel="stylesheet" href="{{ 'css/main.css' | asset }}">
|
||||
<link rel="stylesheet" href="{{ ('css/'~v.theme~'.css') | asset }}">
|
||||
{% if v.css %}
|
||||
<link rel="stylesheet" href="/@{{v.username}}/css">
|
||||
<style>{{v.css | safe}}</style>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<link rel="stylesheet" href="{{ 'css/main.css' | asset }}">
|
||||
<link rel="stylesheet" href="{{ ('css/'~v.theme~'.css') | asset }}">
|
||||
{% if v.css and not request.path.startswith('/settings/css') %}
|
||||
<link rel="stylesheet" href="/@{{v.username}}/css">
|
||||
<style>{{v.css | safe}}</style>
|
||||
{% endif %}
|
||||
</head>
|
||||
|
||||
|
|
|
@ -3,39 +3,27 @@
|
|||
{% block pagetitle %}Custom CSS - {{SITE_TITLE}}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col col-md-8">
|
||||
|
||||
<div class="settings">
|
||||
|
||||
<div id="description">
|
||||
|
||||
<p class="text-small text-muted">Edit your custom CSS for the site.</p>
|
||||
|
||||
<div class="settings-section rounded mb-0">
|
||||
|
||||
<div class="body d-lg-flex border-bottom">
|
||||
<div class="w-lg-100">
|
||||
<form id="profile-settings" action="/settings/css" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<textarea autocomplete="off" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Custom CSS" rows="50" name="css" form="profile-settings" maxlength="4000">{% if v.css %}{{v.csslazy}}{% endif %}</textarea>
|
||||
<small>Limit of 4000 characters</small>
|
||||
<textarea autocomplete="off" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Custom CSS" rows="50" name="css" form="profile-settings" maxlength="{{CSS_LENGTH_MAXIMUM}}">{% if v.css %}{{v.csslazy}}{% endif %}</textarea>
|
||||
<small>Limit of {{CSS_LENGTH_MAXIMUM}} characters</small>
|
||||
<div class="d-flex mt-2">
|
||||
<input autocomplete="off" id="submit-btn" class="btn btn-primary ml-auto" type="submit" value="Save">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,37 +5,26 @@
|
|||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col col-md-8">
|
||||
|
||||
<div class="settings">
|
||||
|
||||
<div id="description">
|
||||
|
||||
<p class="text-small text-muted">Edit your profile css.</p>
|
||||
|
||||
<div class="settings-section rounded mb-0">
|
||||
|
||||
<div class="body d-lg-flex border-bottom">
|
||||
<div class="w-lg-100">
|
||||
<form id="profile-settings" action="/settings/profilecss" method="post">
|
||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||
<textarea autocomplete="off" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Custom profile css" rows="50" name="profilecss" form="profile-settings" maxlength="4000">{% if v.profilecss %}{{v.profilecss}}{% endif %}</textarea>
|
||||
<small>Limit of 4000 characters</small>
|
||||
<textarea autocomplete="off" class="form-control rounded" id="bio-text" aria-label="With textarea" placeholder="Custom profile css" rows="50" name="profilecss" form="profile-settings" maxlength="{{CSS_LENGTH_MAXIMUM}}">{% if v.profilecss %}{{v.profilecss}}{% endif %}</textarea>
|
||||
<small>Limit of {{CSS_LENGTH_MAXIMUM}} characters</small>
|
||||
<div class="d-flex mt-2">
|
||||
<input autocomplete="off" class="btn btn-primary ml-auto" type="submit" value="Save">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<link rel="stylesheet" href="{{ 'css/main.css' | asset }}">
|
||||
<link rel="stylesheet" href="{{ ('css/'~v.theme~'.css') | asset }}">
|
||||
{% if v.css %}
|
||||
<link rel="stylesheet" href="/@{{v.username}}/css">
|
||||
<style>{{v.css | safe}}</style>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue