1234 error components (#1465)

Start cleaning up a lot of the core theme. 
Extract pieces into components that can be included into overarching templates. 
Work on #1234
This commit is contained in:
Kevin Chung
2020-06-03 00:17:18 -04:00
committed by GitHub
parent 97f0beb9ca
commit 8313ccb443
34 changed files with 586 additions and 659 deletions

View File

@@ -66,7 +66,7 @@ def confirm(data=None):
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
# User is trying to start or restart the confirmation flow # User is trying to start or restart the confirmation flow
if not current_user.authed(): if current_user.authed() is False:
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
user = Users.query.filter_by(id=session["id"]).first_or_404() user = Users.query.filter_by(id=session["id"]).first_or_404()
@@ -82,13 +82,11 @@ def confirm(data=None):
format="[{date}] {ip} - {name} initiated a confirmation email resend", format="[{date}] {ip} - {name} initiated a confirmation email resend",
) )
return render_template( return render_template(
"confirm.html", "confirm.html", infos=[f"Confirmation email sent to {user.email}!"]
user=user,
infos=["Your confirmation email has been resent!"],
) )
elif request.method == "GET": elif request.method == "GET":
# User has been directed to the confirm page # User has been directed to the confirm page
return render_template("confirm.html", user=user) return render_template("confirm.html")
@auth.route("/reset_password", methods=["POST", "GET"]) @auth.route("/reset_password", methods=["POST", "GET"])
@@ -115,7 +113,7 @@ def reset_password(data=None):
if user.oauth_id: if user.oauth_id:
return render_template( return render_template(
"reset_password.html", "reset_password.html",
errors=[ infos=[
"Your account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider." "Your account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
], ],
) )
@@ -153,7 +151,7 @@ def reset_password(data=None):
if not user: if not user:
return render_template( return render_template(
"reset_password.html", "reset_password.html",
errors=[ infos=[
"If that account exists you will receive an email, please check your inbox" "If that account exists you will receive an email, please check your inbox"
], ],
) )
@@ -161,7 +159,7 @@ def reset_password(data=None):
if user.oauth_id: if user.oauth_id:
return render_template( return render_template(
"reset_password.html", "reset_password.html",
errors=[ infos=[
"The email address associated with this account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider." "The email address associated with this account was registered via an authentication provider and does not have an associated password. Please login via your authentication provider."
], ],
) )
@@ -170,7 +168,7 @@ def reset_password(data=None):
return render_template( return render_template(
"reset_password.html", "reset_password.html",
errors=[ infos=[
"If that account exists you will receive an email, please check your inbox" "If that account exists you will receive an email, please check your inbox"
], ],
) )

View File

@@ -1,7 +1,7 @@
from flask import Blueprint, render_template from flask import Blueprint, render_template
from CTFd.utils import config, get_config from CTFd.utils import config
from CTFd.utils.dates import ctf_ended, ctf_paused, view_after_ctf from CTFd.utils.dates import ctf_ended, ctf_paused, ctf_started
from CTFd.utils.decorators import ( from CTFd.utils.decorators import (
during_ctf_time_only, during_ctf_time_only,
require_team, require_team,
@@ -21,16 +21,14 @@ challenges = Blueprint("challenges", __name__)
def listing(): def listing():
infos = get_infos() infos = get_infos()
errors = get_errors() errors = get_errors()
start = get_config("start") or 0
end = get_config("end") or 0
if ctf_paused(): if ctf_started() is False:
infos.append("{} is paused".format(config.ctf_name())) errors.append(f"{config.ctf_name()} has not started yet")
# CTF has ended but we want to allow view_after_ctf. Show error but let JS load challenges. if ctf_paused() is True:
if ctf_ended() and view_after_ctf(): infos.append(f"{config.ctf_name()} is paused")
infos.append("{} has ended".format(config.ctf_name()))
return render_template( if ctf_ended() is True:
"challenges.html", infos=infos, errors=errors, start=int(start), end=int(end) infos.append(f"{config.ctf_name()} has ended")
)
return render_template("challenges.html", infos=infos, errors=errors)

19
CTFd/constants/config.py Normal file
View File

@@ -0,0 +1,19 @@
from markupsafe import Markup
from CTFd.utils import get_config
class _ConfigsWrapper:
def __getattr__(self, attr):
return get_config(attr)
@property
def theme_header(self):
return Markup(get_config("theme_header", default=""))
@property
def theme_footer(self):
return Markup(get_config("theme_footer", default=""))
Configs = _ConfigsWrapper()

54
CTFd/constants/plugins.py Normal file
View File

@@ -0,0 +1,54 @@
from flask import current_app
from markupsafe import Markup
from CTFd.plugins import get_admin_plugin_menu_bar, get_user_page_menu_bar
from CTFd.utils.plugins import get_registered_scripts, get_registered_stylesheets
class _PluginWrapper:
@property
def scripts(self):
application_root = current_app.config.get("APPLICATION_ROOT")
subdir = application_root != "/"
scripts = []
for script in get_registered_scripts():
if script.startswith("http"):
scripts.append(f'<script defer src="{script}"></script>')
elif subdir:
scripts.append(
f'<script defer src="{application_root}/{script}"></script>'
)
else:
scripts.append(f'<script defer src="{script}"></script>')
return Markup("\n".join(scripts))
@property
def styles(self):
application_root = current_app.config.get("APPLICATION_ROOT")
subdir = application_root != "/"
_styles = []
for stylesheet in get_registered_stylesheets():
if stylesheet.startswith("http"):
_styles.append(
f'<link rel="stylesheet" type="text/css" href="{stylesheet}">'
)
elif subdir:
_styles.append(
f'<link rel="stylesheet" type="text/css" href="{application_root}/{stylesheet}">'
)
else:
_styles.append(
f'<link rel="stylesheet" type="text/css" href="{stylesheet}">'
)
return Markup("\n".join(_styles))
@property
def user_menu_pages(self):
return get_user_page_menu_bar()
@property
def admin_menu_pages(self):
return get_admin_plugin_menu_bar()
Plugins = _PluginWrapper()

View File

@@ -266,6 +266,16 @@ class Users(db.Model):
elif user_mode == "users": elif user_mode == "users":
return self.id return self.id
@hybrid_property
def account(self):
from CTFd.utils import get_config
user_mode = get_config("user_mode")
if user_mode == "teams":
return self.team
elif user_mode == "users":
return self
@property @property
def solves(self): def solves(self):
return self.get_solves(admin=False) return self.get_solves(admin=False)

View File

@@ -4,7 +4,7 @@ import os
from collections import namedtuple from collections import namedtuple
from flask import current_app as app from flask import current_app as app
from flask import send_file, send_from_directory from flask import send_file, send_from_directory, url_for
from CTFd.utils.config.pages import get_pages from CTFd.utils.config.pages import get_pages
from CTFd.utils.decorators import admins_only as admins_only_wrapper from CTFd.utils.decorators import admins_only as admins_only_wrapper
@@ -114,6 +114,9 @@ def register_admin_plugin_menu_bar(title, route):
:param route: A string that is the href used by the link :param route: A string that is the href used by the link
:return: :return:
""" """
if (route.startswith("http://") or route.startswith("https://")) is False:
route = url_for("views.static_html", route=route)
am = Menu(title=title, route=route) am = Menu(title=title, route=route)
app.admin_plugin_menu_bar.append(am) app.admin_plugin_menu_bar.append(am)
@@ -135,6 +138,9 @@ def register_user_page_menu_bar(title, route):
:param route: A string that is the href used by the link :param route: A string that is the href used by the link
:return: :return:
""" """
if (route.startswith("http://") or route.startswith("https://")) is False:
route = url_for("views.static_html", route=route)
p = Menu(title=title, route=route) p = Menu(title=title, route=route)
app.plugin_menu_bar.append(p) app.plugin_menu_bar.append(p)

View File

@@ -3,6 +3,7 @@ from flask import Blueprint, render_template
from CTFd.cache import cache, make_cache_key from CTFd.cache import cache, make_cache_key
from CTFd.utils import config from CTFd.utils import config
from CTFd.utils.decorators.visibility import check_score_visibility from CTFd.utils.decorators.visibility import check_score_visibility
from CTFd.utils.helpers import get_infos
from CTFd.utils.scores import get_standings from CTFd.utils.scores import get_standings
scoreboard = Blueprint("scoreboard", __name__) scoreboard = Blueprint("scoreboard", __name__)
@@ -12,9 +13,10 @@ scoreboard = Blueprint("scoreboard", __name__)
@check_score_visibility @check_score_visibility
@cache.cached(timeout=60, key_prefix=make_cache_key) @cache.cached(timeout=60, key_prefix=make_cache_key)
def listing(): def listing():
infos = get_infos()
if config.is_scoreboard_frozen():
infos.append("Scoreboard has been frozen")
standings = get_standings() standings = get_standings()
return render_template( return render_template("scoreboard.html", standings=standings, infos=infos)
"scoreboard.html",
standings=standings,
score_frozen=config.is_scoreboard_frozen(),
)

View File

@@ -1,4 +1,4 @@
/* Move down content because we have a fixed navbar that is 3.5rem tall */ /* Move down content because we have a fixed navbar that is 3.5rem tall */
body { main {
padding-top: 3.5rem; padding-top: 3.5rem;
} }

View File

@@ -5,9 +5,9 @@ html {
min-height: 100%; min-height: 100%;
} }
body { main {
/* Margin bottom by footer height */ /* Margin bottom by footer height */
margin-bottom: 60px; margin-bottom: 100px;
} }
.footer { .footer {

File diff suppressed because one or more lines are too long

View File

@@ -8,16 +8,10 @@
<link rel="stylesheet" href="{{ url_for('views.themes', path='css/fonts.css') }}"> <link rel="stylesheet" href="{{ url_for('views.themes', path='css/fonts.css') }}">
<link rel="stylesheet" href="{{ url_for('views.themes', path='css/main.css') }}"> <link rel="stylesheet" href="{{ url_for('views.themes', path='css/main.css') }}">
<link rel="stylesheet" href="{{ url_for('views.themes', path='css/core.css') }}"> <link rel="stylesheet" href="{{ url_for('views.themes', path='css/core.css') }}">
{% block stylesheets %}{% endblock %} {% block stylesheets %}
{% for stylesheet in get_registered_stylesheets() %} {% endblock %}
{% if stylesheet.startswith('http') %}
<link rel="stylesheet" type="text/css" href="{{ stylesheet }}"> {{ Plugins.styles }}
{% elif request.script_root %}
<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/{{ stylesheet }}">
{% else %}
<link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
{% endif %}
{% endfor %}
<script type="text/javascript"> <script type="text/javascript">
var init = { var init = {
'urlRoot': "{{ request.script_root }}", 'urlRoot': "{{ request.script_root }}",
@@ -28,154 +22,10 @@
'end': {{ get_config("end") | tojson }}, 'end': {{ get_config("end") | tojson }},
} }
</script> </script>
{{ get_config('theme_header', '') | safe }} {{ Configs.theme_header }}
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top"> {% include "components/navbar.html" %}
<div class="container">
<a href="{{ url_for('views.static_html', route='/') }}" class="navbar-brand">
{% if get_ctf_logo() %}
<img class="img-responsive ctf_logo" src="{{ url_for('views.files', path=get_ctf_logo()) }}" height="25" alt="{{ get_ctf_name() }}">
{% else %}
{{ get_ctf_name() }}
{% endif %}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#base-navbars"
aria-controls="base-navbars" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="base-navbars">
<ul class="navbar-nav mr-auto">
{% for page in get_user_page_menu_bar() %}
{% if page.route.startswith('http://') or page.route.startswith('https://') %}
<li class="nav-item">
<a class="nav-link" href="{{ page.route }}">{{ page.title }}</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('views.static_html', route=page.route) }}">{{ page.title }}</a>
</li>
{% endif %}
{% endfor %}
{% if get_config('account_visibility') != 'admins' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('users.listing') }}">Users</a>
</li>
{% if get_config('user_mode') == 'teams' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('teams.listing') }}">Teams</a>
</li>
{% endif %}
{% endif %}
{% if get_config('score_visibility') != 'admins' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('scoreboard.listing') }}">Scoreboard</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('challenges.listing') }}">Challenges</a>
</li>
</ul>
<hr class="d-sm-flex d-md-flex d-lg-none">
<ul class="navbar-nav ml-md-auto d-block d-sm-flex d-md-flex">
{% if authed() %}
{% if is_admin() %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.view') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Admin Panel">
<i class="fas fa-wrench d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-wrench pr-1"></i>Admin Panel
</span>
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('views.notifications') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Notifications">
<i class="fas fa-bell d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-bell pr-1"></i>
<span class="badge badge-pill badge-danger badge-notification"></span>
Notifications
</span>
</a>
</li>
{% if config.user_mode() == "teams" %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('teams.private') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Team">
<i class="fas fa-users d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-users pr-1"></i>Team
</span>
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('users.private') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Profile">
<i class="fas fa-user-circle d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-user-circle pr-1"></i>Profile
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('views.settings') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Settings">
<i class="fas fa-cogs d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-cogs pr-1"></i>Settings
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Logout">
<i class="fas fa-sign-out-alt d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-sign-out-alt pr-1"></i><span class="d-lg-none">Logout</span>
</span>
</a>
</li>
{% else %}
{% if registration_visible() %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Register">
<i class="fas fa-user-plus d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-user-plus pr-1"></i>Register
</span>
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Login">
<i class="fas fa-sign-in-alt d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-sign-in-alt pr-1"></i>Login
</span>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<main role="main"> <main role="main">
{% block content %} {% block content %}
@@ -201,16 +51,8 @@
{% block scripts %} {% block scripts %}
{% endblock %} {% endblock %}
{% for script in get_registered_scripts() %} {{ Plugins.scripts }}
{% if script.startswith('http') %}
<script defer src="{{ script }}"></script>
{% elif request.script_root %}
<script defer src="{{ request.script_root }}/{{ script }}"></script>
{% else %}
<script defer src="{{ script }}"></script>
{% endif %}
{% endfor %}
{{ get_config('theme_footer', '') | safe }} {{ Configs.theme_footer }}
</body> </body>
</html> </html>

View File

@@ -5,58 +5,29 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="jumbotron"> <div class="jumbotron">
<div class="container"> <div class="container">
<h1>Challenges</h1> <h1>Challenges</h1>
</div> </div>
</div> </div>
{% if infos %} <div class="modal fade" id="challenge-window" tabindex="-1" role="dialog">
<div class="container">
<div id='errors' class="row">
<div class="col-md-12">
{% for info in infos %}
<h1 class="text-center">{{ info }}</h1>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if errors %}
<div class="container">
<div id='errors' class="row">
<div class="col-md-12">
{% for error in errors %}
<h1 class="text-center">{{ error }}</h1>
{% endfor %}
</div>
</div>
</div> </div>
{% endif %}
{% if admin or not errors %}
<div class="container"> <div class="container">
{% include "components/errors.html" %}
<div id='challenges-board'> <div id='challenges-board'>
<div class="text-center"> <div class="text-center">
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i> <i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
</div> </div>
</div> </div>
</div> </div>
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
<div class="modal fade" id="challenge-window" tabindex="-1" role="dialog">
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{% endblock %} {% endblock %}
{% block entrypoint %} {% block entrypoint %}
{% if admin or not errors %}
<script defer src="{{ url_for('views.themes', path='js/pages/challenges.js') }}"></script> <script defer src="{{ url_for('views.themes', path='js/pages/challenges.js') }}"></script>
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,18 @@
<div>
{% for info in infos %}
<div class="alert alert-info alert-dismissable text-center" role="alert">
<span>{{ info }}</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
{% for error in errors %}
<div class="alert alert-danger alert-dismissable text-center" role="alert">
<span>{{ error }}</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
{% endfor %}
</div>

View File

@@ -0,0 +1,141 @@
<nav class="navbar navbar-expand-md navbar-dark bg-dark fixed-top">
<div class="container">
<a href="{{ url_for('views.static_html', route='/') }}" class="navbar-brand">
{% if get_ctf_logo() %}
<img class="img-responsive ctf_logo" src="{{ url_for('views.files', path=get_ctf_logo()) }}" height="25" alt="{{ get_ctf_name() }}">
{% else %}
{{ get_ctf_name() }}
{% endif %}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#base-navbars"
aria-controls="base-navbars" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="base-navbars">
<ul class="navbar-nav mr-auto">
{% for page in Plugins.user_menu_pages %}
<li class="nav-item">
<a class="nav-link" href="{{ page.route }}">{{ page.title }}</a>
</li>
{% endfor %}
{% if Configs.account_visibility != 'admins' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('users.listing') }}">Users</a>
</li>
{% if Configs.user_mode == 'teams' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('teams.listing') }}">Teams</a>
</li>
{% endif %}
{% endif %}
{% if Configs.score_visibility != 'admins' %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('scoreboard.listing') }}">Scoreboard</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('challenges.listing') }}">Challenges</a>
</li>
</ul>
<hr class="d-sm-flex d-md-flex d-lg-none">
<ul class="navbar-nav ml-md-auto d-block d-sm-flex d-md-flex">
{% if authed() %}
{% if is_admin() %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.view') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Admin Panel">
<i class="fas fa-wrench d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-wrench pr-1"></i>Admin Panel
</span>
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('views.notifications') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Notifications">
<i class="fas fa-bell d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-bell pr-1"></i>
<span class="badge badge-pill badge-danger badge-notification"></span>
Notifications
</span>
</a>
</li>
{% if Configs.user_mode == "teams" %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('teams.private') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Team">
<i class="fas fa-users d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-users pr-1"></i>Team
</span>
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('users.private') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Profile">
<i class="fas fa-user-circle d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-user-circle pr-1"></i>Profile
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('views.settings') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Settings">
<i class="fas fa-cogs d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-cogs pr-1"></i>Settings
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.logout') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Logout">
<i class="fas fa-sign-out-alt d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-sign-out-alt pr-1"></i><span class="d-lg-none">Logout</span>
</span>
</a>
</li>
{% else %}
{% if registration_visible() %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.register') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Register">
<i class="fas fa-user-plus d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-user-plus pr-1"></i>Register
</span>
</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('auth.login') }}">
<span class="d-block" data-toggle="tooltip" data-placement="bottom" title="Login">
<i class="fas fa-sign-in-alt d-none d-md-block d-lg-none"></i>
</span>
<span class="d-sm-block d-md-none d-lg-block">
<i class="fas fa-sign-in-alt pr-1"></i>Login
</span>
</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>

View File

@@ -12,25 +12,10 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% for info in infos %} {% include "components/errors.html" %}
<div class="alert alert-info alert-dismissable" role="alert">
<span class="sr-only"></span>
{{ info }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% for error in errors %}
<div class="alert alert-danger alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
{{ error }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% if user %}
<h3 class="text-center"> <h3 class="text-center">
We've sent a confirmation email to {{ user.email }} We've sent a confirmation email to your email address.
</h3> </h3>
<br> <br>
@@ -38,11 +23,9 @@
<h4 class="text-center"> <h4 class="text-center">
Please click the link in that email to confirm your account. Please click the link in that email to confirm your account.
</h4> </h4>
{% endif %}
<hr> <hr>
{% if name %}
<form method="POST" action="{{ url_for('auth.confirm') }}"> <form method="POST" action="{{ url_for('auth.confirm') }}">
<h4 class="text-center"> <h4 class="text-center">
Need to resend the confirmation email? Need to resend the confirmation email?
@@ -52,7 +35,6 @@
</div> </div>
<input type="hidden" name="nonce" value="{{ nonce }}"> <input type="hidden" name="nonce" value="{{ nonce }}">
</form> </form>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,8 +5,11 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="text-center">Forbidden</h1> <div class="pt-5 mt-5 text-center">
<h2 class="text-center">{{ error }}</h2> <h1>Forbidden</h1>
<hr class="w-50">
<h2>{{ error }}</h2>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,9 +5,12 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="text-center">404</h1> <div class="pt-5 mt-5 text-center">
<h2 class="text-center">Whoops, looks like we can't find that.</h2> <h1>404</h1>
<h2 class="text-center">Sorry about that</h2> <hr class="w-50">
<h2>File not found</h2>
<h2>Sorry about that</h2>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,9 +5,12 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="text-center">429</h1> <div class="pt-5 mt-5 text-center">
<h2 class="text-center">Too many requests</h2> <h1>429</h1>
<h2 class="text-center">Please slow down!</h2> <hr class="w-50">
<h2>Too many requests</h2>
<h2>Please slow down!</h2>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,8 +5,11 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="text-center">500</h1> <div class="pt-5 mt-5 text-center">
<h2 class="text-center">An Internal Server Error has occurred</h2> <h1>500</h1>
<hr class="w-50">
<h2>An Internal Server Error has occurred</h2>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,8 +5,11 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h1 class="text-center">502</h1> <div class="pt-5 mt-5 text-center">
<h2 class="text-center">Bad Gateway</h2> <h1>502</h1>
<hr class="w-50">
<h2>Bad Gateway</h2>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -12,13 +12,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% for error in errors %} {% include "components/errors.html" %}
<div class="alert alert-danger alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
{{ error }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% if integrations.mlc() %} {% if integrations.mlc() %}
<a class="btn btn-secondary btn-lg btn-block" href="{{ url_for('auth.oauth_login') }}"> <a class="btn btn-secondary btn-lg btn-block" href="{{ url_for('auth.oauth_login') }}">

View File

@@ -12,13 +12,7 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% for error in errors %} {% include "components/errors.html" %}
<div class="alert alert-danger alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
{{ error }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% if integrations.mlc() %} {% if integrations.mlc() %}
<a class="btn btn-secondary btn-lg btn-block" href="{{ url_for('auth.oauth_login') }}"> <a class="btn btn-secondary btn-lg btn-block" href="{{ url_for('auth.oauth_login') }}">

View File

@@ -12,13 +12,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% for error in errors %} {% include "components/errors.html" %}
<div class="alert alert-info alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
{{ error }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% if can_send_mail() %} {% if can_send_mail() %}
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal"> <form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal">
<input name='nonce' type='hidden' value="{{ nonce }}"> <input name='nonce' type='hidden' value="{{ nonce }}">

View File

@@ -7,22 +7,7 @@
</div> </div>
</div> </div>
<div class="container"> <div class="container">
{% if errors %} {% include "components/errors.html" %}
<div id='errors' class="row">
<div class="col-md-12">
{% for error in errors %}
<h1 class="text-center">{{ error }}</h1>
{% endfor %}
</div>
</div>
{% else %}
{% if score_frozen %}
<div class="row">
<div class="col-md-12">
<h1 class="text-center">Scoreboard has been frozen.</h1>
</div>
</div>
{% endif %}
<div id="score-graph" class="row"> <div id="score-graph" class="row">
<div class="col-md-12 text-center"> <div class="col-md-12 text-center">
@@ -30,7 +15,6 @@
</div> </div>
</div> </div>
{% if standings %}
<div id="scoreboard" class="row"> <div id="scoreboard" class="row">
<div class="col-md-12"> <div class="col-md-12">
<table class="table table-striped"> <table class="table table-striped">
@@ -69,8 +53,6 @@
</table> </table>
</div> </div>
</div> </div>
{% endif %}
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -20,18 +20,8 @@
<div class="col-md-8"> <div class="col-md-8">
<div class="tab-content" id="v-pills-tabContent"> <div class="tab-content" id="v-pills-tabContent">
<div class="tab-pane fade show active" id="profile" role="tabpanel"> <div class="tab-pane fade show active" id="profile" role="tabpanel">
{% if confirm_email %} {% include "components/errors.html" %}
<div class="alert alert-info alert-dismissable submit-row" role="alert">
Your email address isn't confirmed!
Please check your email to confirm your email address.
<br>
<br>
To have the confirmation email resent please <a href="{{ url_for('auth.confirm') }}">click
here.</a>
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
{% endif %}
<form id="user-profile-form" method="post" accept-charset="utf-8" autocomplete="off" role="form" <form id="user-profile-form" method="post" accept-charset="utf-8" autocomplete="off" role="form"
class="form-horizontal"> class="form-horizontal">
<div class="form-group"> <div class="form-group">

View File

@@ -11,16 +11,8 @@
</div> </div>
<div class="container"> <div class="container">
<div class="col-md-8 offset-md-2"> <div class="col-md-8 offset-md-2">
{% for error in errors %} {% include "components/errors.html" %}
<div class="submit-row">
<div class="alert alert-danger alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
{{ error }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
</div>
{% endfor %}
<form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal" id="setup-form"> <form method="post" accept-charset="utf-8" autocomplete="off" role="form" class="form-horizontal" id="setup-form">
<ul class="nav nav-pills nav-fill mb-4"> <ul class="nav nav-pills nav-fill mb-4">
<li class="nav-item"> <li class="nav-item">

View File

@@ -12,22 +12,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% for info in infos %} {% include "components/errors.html" %}
<div class="alert alert-info alert-dismissable" role="alert">
<span class="sr-only"></span>
{{ info }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% for error in errors %}
<div class="alert alert-danger alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
{{ error }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
{% endfor %}
<form method="POST"> <form method="POST">
<div class="form-group"> <div class="form-group">
<label>Team Name:</label> <label>Team Name:</label>

View File

@@ -12,22 +12,8 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-6 offset-md-3"> <div class="col-md-6 offset-md-3">
{% for info in infos %} {% include "components/errors.html" %}
<div class="alert alert-info alert-dismissable" role="alert">
<span class="sr-only"></span>
{{ info }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
{% endfor %}
{% for error in errors %}
<div class="alert alert-danger alert-dismissable" role="alert">
<span class="sr-only">Error:</span>
{{ error }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
aria-hidden="true">×</span></button>
</div>
{% endfor %}
<form method="POST"> <form method="POST">
<div class="form-group"> <div class="form-group">
<label>Team Name:</label> <label>Team Name:</label>

View File

@@ -6,10 +6,10 @@
{% block content %} {% block content %}
<div class="jumbotron"> <div class="jumbotron">
<div class="container"> <div class="container">
<h1 id="user-id" user-id="{{ user.id }}">{{ user.name }}</h1> <h1>{{ user.name }}</h1>
{% if user.team_id %} {% if user.team_id %}
<h2 id="user-team-id" user-team-id="{{ user.team_id }}"> <h2>
<a href="{{ url_for('teams.private') }}"> <a href="{{ url_for('teams.private') }}">
<span class="badge badge-secondary"> <span class="badge badge-secondary">
{{ user.team.name }} {{ user.team.name }}
@@ -39,39 +39,21 @@
</h3> </h3>
{% endif %} {% endif %}
{% if user.team_id %} <div>
<div class="user-team-info"> <h2 class="text-center">
<h2 id="user-team-place" class="text-center"> {% if account.place %}
{# This intentionally hides the user's place because this can be their internal profile. #} {{ account.place }} <small>place</small>
{# Public page hiding is done at the route level #}
{% if scores_visible() and user.team.place %}
{{ user.team.place }} <small>place</small>
{% endif %} {% endif %}
</h2> </h2>
<h2 id="user-team-score" class="text-center"> <h2 class="text-center">
{{ user.team.score }} <small>points</small> {% if account.place %}
</h2> {{ account.score }} <small>points</small>
</div>
{% else %}
<div class="user-info">
<h2 id="user-place" class="text-center">
{# This intentionally hides the user's place because this can be their internal profile. #}
{# Public page hiding is done at the route level #}
{% if scores_visible() and user.place %}
{{ user.place }} <small>place</small>
{% endif %}
</h2>
<h2 id="user-score" class="text-center">
{% if score %}
{{ user.score }}
<small>points</small>
{% endif %} {% endif %}
</h2> </h2>
</div> </div>
{% endif %}
<div class="pt-3"> <div class="pt-3">
{% if user.website and (user.website.startswith('http://') or user.website.startswith('https://')) %} {% if user.website %}
<a href="{{ user.website }}" target="_blank" style="color: inherit;"> <a href="{{ user.website }}" target="_blank" style="color: inherit;">
<i class="fas fa-external-link-alt fa-2x px-2" data-toggle="tooltip" data-placement="top" <i class="fas fa-external-link-alt fa-2x px-2" data-toggle="tooltip" data-placement="top"
title="{{ user.website }}"></i> title="{{ user.website }}"></i>
@@ -81,26 +63,9 @@
</div> </div>
</div> </div>
<div class="container"> <div class="container">
{% if errors %} {% include "components/errors.html" %}
<div id='errors' class="row">
{% for error in errors %}
<h1>{{ error }}</h1>
{% endfor %}
</div>
{% else %}
{% if score_frozen %} {% if user.solves %}
<div class="row">
<h1 class="text-center">Scoreboard has been frozen.</h1>
</div>
{% endif %}
<br>
{% set solves = user.solves %}
{% set awards = user.awards %}
{% if solves %}
<div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block"> <div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
<div class="text-center"> <div class="text-center">
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i> <i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
@@ -120,27 +85,25 @@
<div class="clearfix"></div> <div class="clearfix"></div>
{% if awards %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h3>Awards</h3> <h3>Awards</h3>
</div> </div>
{% for award in awards %} {% for award in user.awards %}
<div class="col-md-3 col-sm-6"> <div class="col-md-3 col-sm-6">
<p class="text-center"> <p class="text-center">
<i class="award-icon award-{{ award.icon }} fa-2x"></i> <i class="award-icon award-{{ award.icon }} fa-2x"></i>
<br> <br>
<strong>{{ award.name }}</strong> <strong>{{ award.name }}</strong>
</p> </p>
{% if award.category %}<p class="text-center">{{ award.category }}</p>{% endif %} <p class="text-center">{{ award.category or ""}}</p>
{% if award.description %}<p class="text-center">{{ award.description }}</p>{% endif %} <p class="text-center">{{ award.description or ""}}</p>
<p class="text-center">{{ award.value }}</p> <p class="text-center">{{ award.value }}</p>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<br> <br>
{% endif %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -155,7 +118,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for solve in solves %} {% for solve in user.solves %}
<tr> <tr>
<td> <td>
<a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a> <a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a>
@@ -178,7 +141,6 @@
</h3> </h3>
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -6,10 +6,10 @@
{% block content %} {% block content %}
<div class="jumbotron"> <div class="jumbotron">
<div class="container"> <div class="container">
<h1 id="user-id" user-id="{{ user.id }}">{{ user.name }}</h1> <h1>{{ user.name }}</h1>
{% if user.team_id %} {% if user.team_id %}
<h2 id="user-team-id" user-team-id="{{ user.team_id }}"> <h2>
<a href="{{ url_for('teams.public', team_id=user.team_id) }}"> <a href="{{ url_for('teams.public', team_id=user.team_id) }}">
<span class="badge badge-secondary"> <span class="badge badge-secondary">
{{ user.team.name }} {{ user.team.name }}
@@ -39,42 +39,21 @@
</h3> </h3>
{% endif %} {% endif %}
{% if user.team_id %} <div>
<div class="user-team-info"> <h2 class="text-center">
<h2 id="user-team-place" class="text-center"> {% if account.place %}
{# This intentionally hides the user's place because this can be their internal profile. #} {{ account.place }} <small>place</small>
{# Public page hiding is done at the route level #}
{% if scores_visible() and user.team.place %}
{{ user.team.place }}
<small>place</small>
{% endif %} {% endif %}
</h2> </h2>
<h2 id="user-team-score" class="text-center"> <h2 class="text-center">
{{ user.team.score }} {% if account.place %}
<small>points</small> {{ account.score }} <small>points</small>
</h2>
</div>
{% else %}
<div class="user-info">
<h2 id="user-place" class="text-center">
{# This intentionally hides the user's place because this can be their internal profile. #}
{# Public page hiding is done at the route level #}
{% if scores_visible() and user.place %}
{{ user.place }}
<small>place</small>
{% endif %}
</h2>
<h2 id="user-score" class="text-center">
{% if user.score %}
{{ user.score }}
<small>points</small>
{% endif %} {% endif %}
</h2> </h2>
</div> </div>
{% endif %}
<div class="pt-3"> <div class="pt-3">
{% if user.website and (user.website.startswith('http://') or user.website.startswith('https://')) %} {% if user.website %}
<a href="{{ user.website }}" target="_blank" style="color: inherit;"> <a href="{{ user.website }}" target="_blank" style="color: inherit;">
<i class="fas fa-external-link-alt fa-2x px-2" data-toggle="tooltip" data-placement="top" <i class="fas fa-external-link-alt fa-2x px-2" data-toggle="tooltip" data-placement="top"
title="{{ user.website }}"></i> title="{{ user.website }}"></i>
@@ -84,26 +63,9 @@
</div> </div>
</div> </div>
<div class="container"> <div class="container">
{% if errors %} {% include "components/errors.html" %}
<div id='errors' class="row">
{% for error in errors %}
<h1>{{ error }}</h1>
{% endfor %}
</div>
{% else %}
{% if score_frozen %} {% if user.solves %}
<div class="row">
<h1 class="text-center">Scoreboard has been frozen.</h1>
</div>
{% endif %}
<br>
{% set solves = user.solves %}
{% set awards = user.awards %}
{% if solves %}
<div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block"> <div id="keys-pie-graph" class="w-50 mr-0 pr-0 float-left d-none d-md-block d-lg-block">
<div class="text-center"> <div class="text-center">
<i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i> <i class="fas fa-circle-notch fa-spin fa-3x fa-fw spinner"></i>
@@ -123,27 +85,25 @@
<div class="clearfix"></div> <div class="clearfix"></div>
{% if awards %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<h3>Awards</h3> <h3>Awards</h3>
</div> </div>
{% for award in awards %} {% for award in user.awards %}
<div class="col-md-3 col-sm-6"> <div class="col-md-3 col-sm-6">
<p class="text-center"> <p class="text-center">
<i class="award-icon award-{{ award.icon }} fa-2x"></i> <i class="award-icon award-{{ award.icon }} fa-2x"></i>
<br> <br>
<strong>{{ award.name }}</strong> <strong>{{ award.name }}</strong>
</p> </p>
{% if award.category %}<p class="text-center">{{ award.category }}</p>{% endif %} <p class="text-center">{{ award.category or "" }}</p>
{% if award.description %}<p class="text-center">{{ award.description }}</p>{% endif %} <p class="text-center">{{ award.description or "" }}</p>
<p class="text-center">{{ award.value }}</p> <p class="text-center">{{ award.value }}</p>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<br> <br>
{% endif %}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -158,7 +118,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for solve in solves %} {% for solve in user.solves %}
<tr> <tr>
<td> <td>
<a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a> <a href="{{ url_for('challenges.listing') }}#{{ solve.challenge.name }}">{{ solve.challenge.name }}</a>
@@ -181,7 +141,6 @@
</h3> </h3>
</div> </div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -7,6 +7,7 @@ from CTFd.utils.decorators.visibility import (
check_account_visibility, check_account_visibility,
check_score_visibility, check_score_visibility,
) )
from CTFd.utils.helpers import get_errors, get_infos
from CTFd.utils.user import get_current_user from CTFd.utils.user import get_current_user
users = Blueprint("users", __name__) users = Blueprint("users", __name__)
@@ -20,7 +21,7 @@ def listing():
users = ( users = (
Users.query.filter_by(banned=False, hidden=False) Users.query.filter_by(banned=False, hidden=False)
.order_by(Users.id.asc()) .order_by(Users.id.asc())
.paginate(page=page, per_page=10) .paginate(page=page, per_page=50)
) )
return render_template("users/users.html", users=users) return render_template("users/users.html", users=users)
@@ -30,22 +31,20 @@ def listing():
@users.route("/user") @users.route("/user")
@authed_only @authed_only
def private(): def private():
infos = get_infos()
errors = get_errors()
user = get_current_user() user = get_current_user()
solves = user.get_solves() if config.is_scoreboard_frozen():
awards = user.get_awards() infos.append("Scoreboard has been frozen")
place = user.place
score = user.score
return render_template( return render_template(
"users/private.html", "users/private.html",
solves=solves,
awards=awards,
user=user, user=user,
score=score, account=user.account,
place=place, infos=infos,
score_frozen=config.is_scoreboard_frozen(), errors=errors,
) )
@@ -53,5 +52,13 @@ def private():
@check_account_visibility @check_account_visibility
@check_score_visibility @check_score_visibility
def public(user_id): def public(user_id):
infos = get_infos()
errors = get_errors()
user = Users.query.filter_by(id=user_id, banned=False, hidden=False).first_or_404() user = Users.query.filter_by(id=user_id, banned=False, hidden=False).first_or_404()
return render_template("users/public.html", user=user)
if config.is_scoreboard_frozen():
infos.append("Scoreboard has been frozen")
return render_template(
"users/public.html", user=user, account=user.account, infos=infos, errors=errors
)

View File

@@ -1,6 +1,14 @@
import os import os
from flask import current_app, flash, get_flashed_messages, request from flask import current_app, flash, get_flashed_messages, request
from markupsafe import Markup
def markup(text):
"""
Mark text as safe to inject as HTML into templates
"""
return Markup(text)
def info_for(endpoint, message): def info_for(endpoint, message):

View File

@@ -8,6 +8,8 @@ from sqlalchemy.exc import IntegrityError, InvalidRequestError
from werkzeug.middleware.dispatcher import DispatcherMiddleware from werkzeug.middleware.dispatcher import DispatcherMiddleware
from CTFd.cache import clear_user_recent_ips from CTFd.cache import clear_user_recent_ips
from CTFd.constants.config import Configs
from CTFd.constants.plugins import Plugins
from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException from CTFd.exceptions import UserNotFoundException, UserTokenExpiredException
from CTFd.models import Tracking, db from CTFd.models import Tracking, db
from CTFd.utils import config, get_config, markdown from CTFd.utils import config, get_config, markdown
@@ -87,6 +89,8 @@ def init_template_globals(app):
app.jinja_env.globals.update(get_current_user_attrs=get_current_user_attrs) app.jinja_env.globals.update(get_current_user_attrs=get_current_user_attrs)
app.jinja_env.globals.update(get_current_team_attrs=get_current_team_attrs) app.jinja_env.globals.update(get_current_team_attrs=get_current_team_attrs)
app.jinja_env.globals.update(get_ip=get_ip) app.jinja_env.globals.update(get_ip=get_ip)
app.jinja_env.globals.update(Configs=Configs)
app.jinja_env.globals.update(Plugins=Plugins)
def init_logs(app): def init_logs(app):

View File

@@ -35,7 +35,7 @@ from CTFd.utils.email import (
DEFAULT_VERIFICATION_EMAIL_BODY, DEFAULT_VERIFICATION_EMAIL_BODY,
DEFAULT_VERIFICATION_EMAIL_SUBJECT, DEFAULT_VERIFICATION_EMAIL_SUBJECT,
) )
from CTFd.utils.helpers import get_errors from CTFd.utils.helpers import get_errors, get_infos, markup
from CTFd.utils.modes import USERS_MODE from CTFd.utils.modes import USERS_MODE
from CTFd.utils.security.auth import login_user from CTFd.utils.security.auth import login_user
from CTFd.utils.security.csrf import generate_nonce from CTFd.utils.security.csrf import generate_nonce
@@ -272,6 +272,8 @@ def notifications():
@views.route("/settings", methods=["GET"]) @views.route("/settings", methods=["GET"])
@authed_only @authed_only
def settings(): def settings():
infos = get_infos()
user = get_current_user() user = get_current_user()
name = user.name name = user.name
email = user.email email = user.email
@@ -282,7 +284,17 @@ def settings():
tokens = UserTokens.query.filter_by(user_id=user.id).all() tokens = UserTokens.query.filter_by(user_id=user.id).all()
prevent_name_change = get_config("prevent_name_change") prevent_name_change = get_config("prevent_name_change")
confirm_email = get_config("verify_emails") and not user.verified
if get_config("verify_emails") and not user.verified:
confirm_url = markup(url_for("auth.confirm"))
infos.append(
markup(
"Your email address isn't confirmed!<br>"
"Please check your email to confirm your email address.<br><br>"
f'To have the confirmation email resent please <a href="{confirm_url}">click here</a>.'
)
)
return render_template( return render_template(
"settings.html", "settings.html",
name=name, name=name,
@@ -292,7 +304,7 @@ def settings():
country=country, country=country,
tokens=tokens, tokens=tokens,
prevent_name_change=prevent_name_change, prevent_name_change=prevent_name_change,
confirm_email=confirm_email, infos=infos,
) )