2.2.0 / 2019-12-22
==================

## Notice
2.2.0 focuses on updating the front end of CTFd to use more modern programming practices and changes some aspects of core CTFd design. If your current installation is using a custom theme or custom plugin with ***any*** kind of JavaScript, it is likely that you will need to upgrade that theme/plugin to be useable with v2.2.0. 

**General**
* Team size limits can now be enforced from the configuration panel
* Access tokens functionality for API usage
* Admins can now choose how to deliver their notifications
    * Toast (new default)
    * Alert
    * Background
    * Sound On / Sound Off
* There is now a notification counter showing how many unread notifications were received
* Setup has been redesigned to have multiple steps
    * Added Description
    * Added Start time and End time,
    * Added MajorLeagueCyber integration
    * Added Theme and color selection
* Fixes issue where updating dynamic challenges could change the value to an incorrect value
* Properly use a less restrictive regex to validate email addresses
* Bump Python dependencies to latest working versions
* Admins can now give awards to team members from the team's admin panel page

**API**
* Team member removals (`DELETE /api/v1/teams/[team_id]/members`) from the admin panel will now delete the removed members's Submissions, Awards, Unlocks

**Admin Panel**
* Admins can now user a color input box to specify a theme color which is injected as part of the CSS configuration. Theme developers can use this CSS value to change colors and styles accordingly.
* Challenge updates will now alert you if the challenge doesn't have a flag
* Challenge entry now allows you to upload files and enter simple flags from the initial challenge creation page

**Themes**
* Significant JavaScript and CSS rewrite to use ES6, Webpack, yarn, and babel
* Theme asset specially generated URLs
    * Static theme assets are now loaded with either .dev.extension or .min.extension depending on production or development (i.e. debug server)
    * Static theme assets are also given a `d` GET parameter that changes per server start. Used to bust browser caches.
* Use `defer` for script tags to not block page rendering
* Only show the MajorLeagueCyber button if configured in configuration
* The admin panel now links to https://help.ctfd.io/ in the top right
* Create an `ezToast()` function to use [Bootstrap's toasts](https://getbootstrap.com/docs/4.3/components/toasts/)
* The user-facing navbar now features icons
* Awards shown on a user's profile can now have award icons
* The default MarkdownIt render created by CTFd will now open links in new tabs
* Country flags can now be shown on the user pages

**Deployment**
* Switch `Dockerfile` from `python:2.7-alpine` to `python:3.7-alpine`
* Add `SERVER_SENT_EVENTS` config value to control whether Notifications are enabled
* Challenge ID is now recorded in the submission log

**Plugins**
* Add an endpoint parameter to `register_plugin_assets_directory()` and `register_plugin_asset()` to control what endpoint Flask uses for the added route

**Miscellaneous**
* `CTFd.utils.email.sendmail()` now allows the caller to specify subject as an argument
    * The subject allows for injecting custom variable via the new `CTFd.utils.formatters.safe_format()` function
* Admin user information is now error checked during setup
* Added yarn to the toolchain and the yarn dev, yarn build, yarn verify, and yarn clean scripts
* Prevent old CTFd imports from being imported
bulk-clear-sessions 2.2.0
Kevin Chung 2019-12-22 23:17:34 -05:00 committed by GitHub
parent 6d192a7c14
commit b8d0f80d01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
453 changed files with 41266 additions and 17454 deletions

2
.gitignore vendored
View File

@ -72,4 +72,4 @@ CTFd/uploads
*.zip
# JS
node_modules/
node_modules/

View File

@ -28,6 +28,7 @@ before_install:
- python3.6 -m pip install black==19.3b0
install:
- pip install -r development.txt
- yarn install --non-interactive
- yarn global add prettier@1.17.0
before_script:
- psql -c 'create database ctfd;' -U postgres

View File

@ -1,3 +1,66 @@
2.2.0 / 2019-12-22
==================
## Notice
2.2.0 focuses on updating the front end of CTFd to use more modern programming practices and changes some aspects of core CTFd design. If your current installation is using a custom theme or custom plugin with ***any*** kind of JavaScript, it is likely that you will need to upgrade that theme/plugin to be useable with v2.2.0.
**General**
* Team size limits can now be enforced from the configuration panel
* Access tokens functionality for API usage
* Admins can now choose how to deliver their notifications
* Toast (new default)
* Alert
* Background
* Sound On / Sound Off
* There is now a notification counter showing how many unread notifications were received
* Setup has been redesigned to have multiple steps
* Added Description
* Added Start time and End time,
* Added MajorLeagueCyber integration
* Added Theme and color selection
* Fixes issue where updating dynamic challenges could change the value to an incorrect value
* Properly use a less restrictive regex to validate email addresses
* Bump Python dependencies to latest working versions
* Admins can now give awards to team members from the team's admin panel page
**API**
* Team member removals (`DELETE /api/v1/teams/[team_id]/members`) from the admin panel will now delete the removed members's Submissions, Awards, Unlocks
**Admin Panel**
* Admins can now user a color input box to specify a theme color which is injected as part of the CSS configuration. Theme developers can use this CSS value to change colors and styles accordingly.
* Challenge updates will now alert you if the challenge doesn't have a flag
* Challenge entry now allows you to upload files and enter simple flags from the initial challenge creation page
**Themes**
* Significant JavaScript and CSS rewrite to use ES6, Webpack, yarn, and babel
* Theme asset specially generated URLs
* Static theme assets are now loaded with either .dev.extension or .min.extension depending on production or development (i.e. debug server)
* Static theme assets are also given a `d` GET parameter that changes per server start. Used to bust browser caches.
* Use `defer` for script tags to not block page rendering
* Only show the MajorLeagueCyber button if configured in configuration
* The admin panel now links to https://help.ctfd.io/ in the top right
* Create an `ezToast()` function to use [Bootstrap's toasts](https://getbootstrap.com/docs/4.3/components/toasts/)
* The user-facing navbar now features icons
* Awards shown on a user's profile can now have award icons
* The default MarkdownIt render created by CTFd will now open links in new tabs
* Country flags can now be shown on the user pages
**Deployment**
* Switch `Dockerfile` from `python:2.7-alpine` to `python:3.7-alpine`
* Add `SERVER_SENT_EVENTS` config value to control whether Notifications are enabled
* Challenge ID is now recorded in the submission log
**Plugins**
* Add an endpoint parameter to `register_plugin_assets_directory()` and `register_plugin_asset()` to control what endpoint Flask uses for the added route
**Miscellaneous**
* `CTFd.utils.email.sendmail()` now allows the caller to specify subject as an argument
* The subject allows for injecting custom variable via the new `CTFd.utils.formatters.safe_format()` function
* Admin user information is now error checked during setup
* Added yarn to the toolchain and the yarn dev, yarn build, yarn verify, and yarn clean scripts
* Prevent old CTFd imports from being imported
2.1.5 / 2019-10-2
=================

View File

@ -21,14 +21,16 @@ from CTFd.utils.initialization import (
init_logs,
init_events,
)
from CTFd.utils.crypto import sha256
from CTFd.plugins import init_plugins
import datetime
# Hack to support Unicode in Python 2 properly
if sys.version_info[0] < 3:
reload(sys) # noqa: F821
sys.setdefaultencoding("utf-8")
__version__ = "2.1.5"
__version__ = "2.2.0"
class CTFdRequest(Request):
@ -50,6 +52,12 @@ class CTFdFlask(Flask):
self.jinja_environment = SandboxedBaseEnvironment
self.session_interface = CachingSessionInterface(key_prefix="session")
self.request_class = CTFdRequest
# Store server start time
self.start_time = datetime.datetime.utcnow()
# Create generally unique run identifier
self.run_id = sha256(str(self.start_time))[0:8]
Flask.__init__(self, *args, **kwargs)
def create_jinja_environment(self):

View File

@ -2,7 +2,7 @@ from flask import render_template
from CTFd.utils.decorators import admins_only
from CTFd.utils.updates import update_check
from CTFd.utils.modes import get_model
from CTFd.models import db, Solves, Challenges, Fails, Tracking
from CTFd.models import db, Solves, Challenges, Fails, Tracking, Teams, Users
from CTFd.admin import admin
@ -13,7 +13,8 @@ def statistics():
Model = get_model()
teams_registered = Model.query.count()
teams_registered = Teams.query.count()
users_registered = Users.query.count()
wrong_count = (
Fails.query.join(Model, Fails.account_id == Model.id)
@ -65,6 +66,7 @@ def statistics():
return render_template(
"admin/statistics.html",
user_count=users_registered,
team_count=teams_registered,
ip_count=ip_count,
wrong_count=wrong_count,

View File

@ -16,6 +16,7 @@ from CTFd.api.v1.config import configs_namespace
from CTFd.api.v1.notifications import notifications_namespace
from CTFd.api.v1.pages import pages_namespace
from CTFd.api.v1.unlocks import unlocks_namespace
from CTFd.api.v1.tokens import tokens_namespace
api = Blueprint("api", __name__, url_prefix="/api/v1")
CTFd_API_v1 = Api(api, version="v1", doc=current_app.config.get("SWAGGER_UI"))
@ -35,3 +36,4 @@ CTFd_API_v1.add_namespace(notifications_namespace, "/notifications")
CTFd_API_v1.add_namespace(configs_namespace, "/configs")
CTFd_API_v1.add_namespace(pages_namespace, "/pages")
CTFd_API_v1.add_namespace(unlocks_namespace, "/unlocks")
CTFd_API_v1.add_namespace(tokens_namespace, "/tokens")

View File

@ -260,13 +260,10 @@ class Challenge(Resource):
Model = get_model()
if scores_visible() is True and accounts_visible() is True:
solves = (
Solves.query.join(Model, Solves.account_id == Model.id)
.filter(
Solves.challenge_id == chal.id,
Model.banned == False,
Model.hidden == False,
)
solves = Solves.query.join(Model, Solves.account_id == Model.id).filter(
Solves.challenge_id == chal.id,
Model.banned == False,
Model.hidden == False,
)
# Only show solves that happened before freeze time if configured
@ -391,8 +388,9 @@ class ChallengeAttempt(Resource):
)
log(
"submissions",
"[{date}] {name} submitted {submission} with kpm {kpm} [TOO FAST]",
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [TOO FAST]",
submission=request_data["submission"].encode("utf-8"),
challenge_id=challenge_id,
kpm=kpm,
)
# Submitting too fast
@ -437,8 +435,9 @@ class ChallengeAttempt(Resource):
log(
"submissions",
"[{date}] {name} submitted {submission} with kpm {kpm} [CORRECT]",
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [CORRECT]",
submission=request_data["submission"].encode("utf-8"),
challenge_id=challenge_id,
kpm=kpm,
)
return {
@ -454,8 +453,9 @@ class ChallengeAttempt(Resource):
log(
"submissions",
"[{date}] {name} submitted {submission} with kpm {kpm} [WRONG]",
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [WRONG]",
submission=request_data["submission"].encode("utf-8"),
challenge_id=challenge_id,
kpm=kpm,
)
@ -487,8 +487,9 @@ class ChallengeAttempt(Resource):
else:
log(
"submissions",
"[{date}] {name} submitted {submission} with kpm {kpm} [ALREADY SOLVED]",
"[{date}] {name} submitted {submission} on {challenge_id} with kpm {kpm} [ALREADY SOLVED]",
submission=request_data["submission"].encode("utf-8"),
challenge_id=challenge_id,
kpm=kpm,
)
return {

View File

@ -34,6 +34,13 @@ class NotificantionList(Resource):
db.session.commit()
response = schema.dump(result.data)
# Grab additional settings
notif_type = req.get("type", "alert")
notif_sound = req.get("sound", True)
response.data["type"] = notif_type
response.data["sound"] = notif_sound
current_app.events_manager.publish(data=response.data, type="notification")
return {"success": True, "data": response.data}

View File

@ -1,6 +1,6 @@
from flask import session, request, abort
from flask_restplus import Namespace, Resource
from CTFd.models import db, Users, Teams
from CTFd.models import db, Users, Teams, Submissions, Awards, Unlocks
from CTFd.schemas.teams import TeamSchema
from CTFd.schemas.submissions import SubmissionSchema
from CTFd.schemas.awards import AwardSchema
@ -68,6 +68,8 @@ class TeamPublic(Resource):
if response.errors:
return {"success": False, "errors": response.errors}, 400
response.data["place"] = team.place
response.data["score"] = team.score
return {"success": True, "data": response.data}
@admins_only
@ -118,6 +120,8 @@ class TeamPrivate(Resource):
if response.errors:
return {"success": False, "errors": response.errors}, 400
response.data["place"] = team.place
response.data["score"] = team.score
return {"success": True, "data": response.data}
@authed_only
@ -206,6 +210,12 @@ class TeamMembers(Resource):
if user.team_id == team.id:
team.members.remove(user)
# Remove information that links the user to the team
Submissions.query.filter_by(user_id=user.id).delete()
Awards.query.filter_by(user_id=user.id).delete()
Unlocks.query.filter_by(user_id=user.id).delete()
db.session.commit()
else:
return (

83
CTFd/api/v1/tokens.py Normal file
View File

@ -0,0 +1,83 @@
from flask import request, session
from flask_restplus import Namespace, Resource
from CTFd.models import db, Tokens
from CTFd.utils.user import get_current_user, is_admin
from CTFd.schemas.tokens import TokenSchema
from CTFd.utils.security.auth import generate_user_token
from CTFd.utils.decorators import require_verified_emails, authed_only
import datetime
tokens_namespace = Namespace("tokens", description="Endpoint to retrieve Tokens")
@tokens_namespace.route("")
class TokenList(Resource):
@require_verified_emails
@authed_only
def get(self):
user = get_current_user()
tokens = Tokens.query.filter_by(user_id=user.id)
response = TokenSchema(view=["id", "type", "expiration"], many=True).dump(
tokens
)
if response.errors:
return {"success": False, "errors": response.errors}, 400
return {"success": True, "data": response.data}
@require_verified_emails
@authed_only
def post(self):
req = request.get_json()
expiration = req.get("expiration")
if expiration:
expiration = datetime.datetime.strptime(expiration, "%Y-%m-%d")
user = get_current_user()
token = generate_user_token(user, expiration=expiration)
# Explicitly use admin view so that user's can see the value of their token
schema = TokenSchema(view="admin")
response = schema.dump(token)
if response.errors:
return {"success": False, "errors": response.errors}, 400
return {"success": True, "data": response.data}
@tokens_namespace.route("/<token_id>")
@tokens_namespace.param("token_id", "A Token ID")
class TokenDetail(Resource):
@require_verified_emails
@authed_only
def get(self, token_id):
if is_admin():
token = Tokens.query.filter_by(id=token_id).first_or_404()
else:
token = Tokens.query.filter_by(
id=token_id, user_id=session["id"]
).first_or_404()
schema = TokenSchema(view=session.get("type", "user"))
response = schema.dump(token)
if response.errors:
return {"success": False, "errors": response.errors}, 400
return {"success": True, "data": response.data}
@require_verified_emails
@authed_only
def delete(self, token_id):
if is_admin():
token = Tokens.query.filter_by(id=token_id).first_or_404()
else:
user = get_current_user()
token = Tokens.query.filter_by(id=token_id, user_id=user.id).first_or_404()
db.session.delete(token)
db.session.commit()
db.session.close()
return {"success": True}

View File

@ -400,6 +400,15 @@ def oauth_redirect():
db.session.add(team)
db.session.commit()
team_size_limit = get_config("team_size", default=0)
if team_size_limit and len(team.members) >= team_size_limit:
plural = "" if team_size_limit == 1 else "s"
size_error = "Teams are limited to {limit} member{plural}.".format(
limit=team_size_limit, plural=plural
)
error_for(endpoint="auth.login", message=size_error)
return redirect(url_for("auth.login"))
team.members.append(user)
db.session.commit()

View File

@ -228,6 +228,9 @@ class Config(object):
APPLICATION_ROOT:
Specifies what path CTFd is mounted under. It can be used to run CTFd in a subdirectory.
Example: /ctfd
SERVER_SENT_EVENTS:
Specifies whether or not to enable to server-sent events based Notifications system.
"""
REVERSE_PROXY = os.getenv("REVERSE_PROXY") or False
TEMPLATES_AUTO_RELOAD = not os.getenv("TEMPLATES_AUTO_RELOAD") # Defaults True
@ -237,6 +240,7 @@ class Config(object):
SWAGGER_UI = "/" if os.getenv("SWAGGER_UI") is not None else False # Defaults False
UPDATE_CHECK = not os.getenv("UPDATE_CHECK") # Defaults True
APPLICATION_ROOT = os.getenv("APPLICATION_ROOT") or "/"
SERVER_SENT_EVENTS = not os.getenv("SERVER_SENT_EVENTS") # Defaults True
"""
=== OAUTH ===

View File

@ -1,4 +1,5 @@
from flask import current_app, Blueprint, Response, stream_with_context
from CTFd.utils import get_app_config
from CTFd.utils.decorators import authed_only, ratelimit
events = Blueprint("events", __name__)
@ -13,4 +14,8 @@ def subscribe():
for event in current_app.events_manager.subscribe():
yield str(event)
enabled = get_app_config("SERVER_SENT_EVENTS")
if enabled is False:
return ("", 204)
return Response(gen(), mimetype="text/event-stream")

View File

@ -0,0 +1,6 @@
class UserNotFoundException(Exception):
pass
class UserTokenExpiredException(Exception):
pass

View File

@ -281,7 +281,12 @@ class Users(db.Model):
@property
def place(self):
return self.get_place(admin=False)
from CTFd.utils.config.visibility import scores_visible
if scores_visible():
return self.get_place(admin=False)
else:
return None
def get_solves(self, admin=False):
solves = Solves.query.filter_by(user_id=self.id)
@ -417,7 +422,12 @@ class Teams(db.Model):
@property
def place(self):
return self.get_place(admin=False)
from CTFd.utils.config.visibility import scores_visible
if scores_visible():
return self.get_place(admin=False)
else:
return None
def get_solves(self, admin=False):
member_ids = [member.id for member in self.members]
@ -631,6 +641,33 @@ class Configs(db.Model):
super(Configs, self).__init__(**kwargs)
class Tokens(db.Model):
__tablename__ = "tokens"
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(32))
user_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="CASCADE"))
created = db.Column(db.DateTime, default=datetime.datetime.utcnow)
expiration = db.Column(
db.DateTime,
default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30),
)
value = db.Column(db.String(128), unique=True)
user = db.relationship("Users", foreign_keys="Tokens.user_id", lazy="select")
__mapper_args__ = {"polymorphic_on": type}
def __init__(self, *args, **kwargs):
super(Tokens, self).__init__(**kwargs)
def __repr__(self):
return "<Token %r>" % self.id
class UserTokens(Tokens):
__mapper_args__ = {"polymorphic_identity": "user"}
@cache.memoize()
def get_config(key):
"""

View File

@ -18,7 +18,7 @@ from CTFd.utils.config.pages import get_pages
Menu = namedtuple("Menu", ["title", "route"])
def register_plugin_assets_directory(app, base_path, admins_only=False):
def register_plugin_assets_directory(app, base_path, admins_only=False, endpoint=None):
"""
Registers a directory to serve assets
@ -28,15 +28,17 @@ def register_plugin_assets_directory(app, base_path, admins_only=False):
:return:
"""
base_path = base_path.strip("/")
if endpoint is None:
endpoint = base_path.replace("/", ".")
def assets_handler(path):
return send_from_directory(base_path, path)
rule = "/" + base_path + "/<path:path>"
app.add_url_rule(rule=rule, endpoint=base_path, view_func=assets_handler)
app.add_url_rule(rule=rule, endpoint=endpoint, view_func=assets_handler)
def register_plugin_asset(app, asset_path, admins_only=False):
def register_plugin_asset(app, asset_path, admins_only=False, endpoint=None):
"""
Registers an file path to be served by CTFd
@ -46,6 +48,8 @@ def register_plugin_asset(app, asset_path, admins_only=False):
:return:
"""
asset_path = asset_path.strip("/")
if endpoint is None:
endpoint = asset_path.replace("/", ".")
def asset_handler():
return send_file(asset_path)
@ -53,7 +57,7 @@ def register_plugin_asset(app, asset_path, admins_only=False):
if admins_only:
asset_handler = admins_only_wrapper(asset_handler)
rule = "/" + asset_path
app.add_url_rule(rule=rule, endpoint=asset_path, view_func=asset_handler)
app.add_url_rule(rule=rule, endpoint=endpoint, view_func=asset_handler)
def override_template(*args, **kwargs):

View File

@ -1,7 +1,7 @@
<form method="POST" action="{{ script_root }}/admin/challenges/new" enctype="multipart/form-data">
<div class="form-group">
<label>
Name<br>
Name:<br>
<small class="form-text text-muted">
The name of your challenge
</small>
@ -11,7 +11,7 @@
<div class="form-group">
<label>
Category<br>
Category:<br>
<small class="form-text text-muted">
The category of your challenge
</small>
@ -22,10 +22,10 @@
<ul class="nav nav-tabs" role="tablist" id="new-desc-edit">
<li class="nav-item">
<a class="nav-link active" href="#new-desc-write" aria-controls="home" role="tab"
data-toggle="tab">Write</a>
data-toggle="tab" tabindex="-1">Write</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#new-desc-preview" aria-controls="home" role="tab" data-toggle="tab">Preview</a>
<a class="nav-link" href="#new-desc-preview" aria-controls="home" role="tab" data-toggle="tab" tabindex="-1">Preview</a>
</li>
</ul>
@ -47,7 +47,7 @@
<div class="form-group">
<label>
Value<br>
Value:<br>
<small class="form-text text-muted">
This is how many points are rewarded for solving this challenge.
</small>

View File

@ -1,29 +1,39 @@
// Markdown Preview
$('#desc-edit').on('shown.bs.tab', function (event) {
if (event.target.hash == '#desc-preview') {
var editor_value = $('#desc-editor').val();
$(event.target.hash).html(
window.challenge.render(editor_value)
);
}
});
$('#new-desc-edit').on('shown.bs.tab', function (event) {
if (event.target.hash == '#new-desc-preview') {
var editor_value = $('#new-desc-editor').val();
$(event.target.hash).html(
window.challenge.render(editor_value)
);
}
});
$("#solve-attempts-checkbox").change(function () {
if (this.checked) {
$('#solve-attempts-input').show();
} else {
$('#solve-attempts-input').hide();
$('#max_attempts').val('');
}
});
$(document).ready(function () {
$('[data-toggle="tooltip"]').tooltip();
});
CTFd.plugin.run((_CTFd) => {
const $ = _CTFd.lib.$
const md = _CTFd.lib.markdown()
$('a[href="#new-desc-preview"]').on('shown.bs.tab', function (event) {
if (event.target.hash == '#new-desc-preview') {
var editor_value = $('#new-desc-editor').val();
$(event.target.hash).html(
md.render(editor_value)
);
}
});
// $('#desc-edit').on('shown.bs.tab', function (event) {
// if (event.target.hash == '#desc-preview') {
// var editor_value = $('#desc-editor').val();
// $(event.target.hash).html(
// window.challenge.render(editor_value)
// );
// }
// });
// $('#new-desc-edit').on('shown.bs.tab', function (event) {
// if (event.target.hash == '#new-desc-preview') {
// var editor_value = $('#new-desc-editor').val();
// $(event.target.hash).html(
// window.challenge.render(editor_value)
// );
// }
// });
// $("#solve-attempts-checkbox").change(function () {
// if (this.checked) {
// $('#solve-attempts-input').show();
// } else {
// $('#solve-attempts-input').hide();
// $('#max_attempts').val('');
// }
// });
// $(document).ready(function () {
// $('[data-toggle="tooltip"]').tooltip();
// });
})

View File

@ -50,7 +50,7 @@
<small class="form-text text-muted">Changes the state of the challenge (e.g. visible, hidden)</small>
</label>
<select class="form-control" name="state">
<select class="form-control custom-select" name="state">
<option value="visible" {% if challenge.state == "visible" %}selected{% endif %}>Visible</option>
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
</select>

View File

@ -31,8 +31,7 @@
<div class="challenge-hints hint-row row">
{% for hint in hints %}
<div class='col-md-12 hint-button-wrapper text-center mb-3'>
<a class="btn btn-info btn-hint btn-block" href="javascript:;"
onclick="javascript:loadhint({{ hint.id }})">
<a class="btn btn-info btn-hint btn-block load-hint" href="javascript:;" data-hint-id="{{ hint.id }}">
{% if hint.content %}
<small>
View Hint

View File

@ -1,57 +1,40 @@
window.challenge.data = undefined;
CTFd._internal.challenge.data = undefined
window.challenge.renderer = new markdownit({
html: true,
linkify: true,
});
window.challenge.preRender = function () {
};
window.challenge.render = function (markdown) {
return window.challenge.renderer.render(markdown);
};
CTFd._internal.challenge.renderer = CTFd.lib.markdown();
window.challenge.postRender = function () {
CTFd._internal.challenge.preRender = function () { }
};
CTFd._internal.challenge.render = function (markdown) {
return CTFd._internal.challenge.renderer.render(markdown)
}
window.challenge.submit = function (cb, preview) {
var challenge_id = parseInt($('#challenge-id').val());
var submission = $('#submission-input').val();
var url = "/api/v1/challenges/attempt";
CTFd._internal.challenge.postRender = function () { }
CTFd._internal.challenge.submit = function (preview) {
var challenge_id = parseInt(CTFd.lib.$('#challenge-id').val())
var submission = CTFd.lib.$('#submission-input').val()
var body = {
'challenge_id': challenge_id,
'submission': submission,
}
var params = {}
if (preview) {
url += "?preview=true";
params['preview'] = true
}
var params = {
'challenge_id': challenge_id,
'submission': submission
};
CTFd.fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then(function (response) {
return CTFd.api.post_challenge_attempt(params, body).then(function (response) {
if (response.status === 429) {
// User was ratelimited but process response
return response.json();
return response
}
if (response.status === 403) {
// User is not logged in or CTF is paused.
return response.json();
return response
}
return response.json();
}).then(function (response) {
cb(response);
});
};
return response
})
};

View File

@ -42,6 +42,42 @@ class DynamicValueChallenge(BaseChallenge):
static_folder="assets",
)
@classmethod
def calculate_value(cls, challenge):
Model = get_model()
solve_count = (
Solves.query.join(Model, Solves.account_id == Model.id)
.filter(
Solves.challenge_id == challenge.id,
Model.hidden == False,
Model.banned == False,
)
.count()
)
# If the solve count is 0 we shouldn't manipulate the solve count to
# let the math update back to normal
if solve_count != 0:
# We subtract -1 to allow the first solver to get max point value
solve_count -= 1
# It is important that this calculation takes into account floats.
# Hence this file uses from __future__ import division
value = (
((challenge.minimum - challenge.initial) / (challenge.decay ** 2))
* (solve_count ** 2)
) + challenge.initial
value = math.ceil(value)
if value < challenge.minimum:
value = challenge.minimum
challenge.value = value
db.session.commit()
return challenge
@staticmethod
def create(request):
"""
@ -106,34 +142,7 @@ class DynamicValueChallenge(BaseChallenge):
value = float(value)
setattr(challenge, attr, value)
Model = get_model()
solve_count = (
Solves.query.join(Model, Solves.account_id == Model.id)
.filter(
Solves.challenge_id == challenge.id,
Model.hidden == False,
Model.banned == False,
)
.count()
)
# It is important that this calculation takes into account floats.
# Hence this file uses from __future__ import division
value = (
((challenge.minimum - challenge.initial) / (challenge.decay ** 2))
* (solve_count ** 2)
) + challenge.initial
value = math.ceil(value)
if value < challenge.minimum:
value = challenge.minimum
challenge.value = value
db.session.commit()
return challenge
return DynamicValueChallenge.calculate_value(challenge)
@staticmethod
def delete(challenge):
@ -185,12 +194,10 @@ class DynamicValueChallenge(BaseChallenge):
:param request: The request the user submitted
:return:
"""
chal = DynamicChallenge.query.filter_by(id=challenge.id).first()
challenge = DynamicChallenge.query.filter_by(id=challenge.id).first()
data = request.form or request.get_json()
submission = data["submission"].strip()
Model = get_model()
solve = Solves(
user_id=user.id,
team_id=team.id if team else None,
@ -199,35 +206,9 @@ class DynamicValueChallenge(BaseChallenge):
provided=submission,
)
db.session.add(solve)
solve_count = (
Solves.query.join(Model, Solves.account_id == Model.id)
.filter(
Solves.challenge_id == challenge.id,
Model.hidden == False,
Model.banned == False,
)
.count()
)
# We subtract -1 to allow the first solver to get max point value
solve_count -= 1
# It is important that this calculation takes into account floats.
# Hence this file uses from __future__ import division
value = (
((chal.minimum - chal.initial) / (chal.decay ** 2)) * (solve_count ** 2)
) + chal.initial
value = math.ceil(value)
if value < chal.minimum:
value = chal.minimum
chal.value = value
db.session.commit()
db.session.close()
DynamicValueChallenge.calculate_value(challenge)
@staticmethod
def fail(user, team, challenge, request):

View File

@ -16,34 +16,13 @@
<input type="text" class="form-control chal-category" name="category" value="{{ challenge.category }}">
</div>
<ul class="nav nav-tabs" role="tablist" id="desc-edit">
<li class="nav-item">
<a class="nav-link active" href="#desc-write" id="desc-write-link" aria-controls="home"
role="tab" data-toggle="tab">
Write
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#desc-preview" aria-controls="home" role="tab" data-toggle="tab">
Preview
</a>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="desc-write">
<div class="form-group">
<label for="message-text" class="control-label">Message<br>
<small class="form-text text-muted">
Use this to give a brief introduction to your challenge.
</small>
</label>
<textarea id="desc-editor" class="form-control chal-desc-editor" name="description"
rows="10">{{ challenge.description }}</textarea>
</div>
</div>
<div role="tabpanel" class="tab-pane content" id="desc-preview"
style="height:214px; overflow-y: scroll;">
</div>
<div class="form-group">
<label for="message-text" class="control-label">Message<br>
<small class="form-text text-muted">
Use this to give a brief introduction to your challenge.
</small>
</label>
<textarea id="desc-editor" class="form-control chal-desc-editor" name="description" rows="10">{{ challenge.description }}</textarea>
</div>
<div class="form-group">
@ -98,7 +77,7 @@
<small class="form-text text-muted">Changes the state of the challenge (e.g. visible, hidden)</small>
</label>
<select class="form-control" name="state">
<select class="form-control custom-select" name="state">
<option value="visible" {% if challenge.state == "visible" %}selected{% endif %}>Visible</option>
<option value="hidden" {% if challenge.state == "hidden" %}selected{% endif %}>Hidden</option>
</select>

View File

@ -1,51 +0,0 @@
$('#submit-key').click(function (e) {
submitkey($('#chalid').val(), $('#answer').val())
});
$('#submit-keys').click(function (e) {
e.preventDefault();
$('#update-keys').modal('hide');
});
$('#limit_max_attempts').change(function() {
if(this.checked) {
$('#chal-attempts-group').show();
} else {
$('#chal-attempts-group').hide();
$('#chal-attempts-input').val('');
}
});
// Markdown Preview
$('#desc-edit').on('shown.bs.tab', function (event) {
if (event.target.hash == '#desc-preview') {
var editor_value = $('#desc-editor').val();
$(event.target.hash).html(
window.challenge.render(editor_value)
);
}
});
$('#new-desc-edit').on('shown.bs.tab', function (event) {
if (event.target.hash == '#new-desc-preview') {
var editor_value = $('#new-desc-editor').val();
$(event.target.hash).html(
window.challenge.render(editor_value)
);
}
});
function loadchal(id, update) {
$.get(script_root + '/admin/chal/' + id, function(obj){
$('#desc-write-link').click(); // Switch to Write tab
if (typeof update === 'undefined')
$('#update-challenge').modal();
});
}
function openchal(id){
loadchal(id);
}
$(document).ready(function(){
$('[data-toggle="tooltip"]').tooltip();
});

View File

@ -1,57 +1,40 @@
window.challenge.data = undefined;
CTFd._internal.challenge.data = undefined
window.challenge.renderer = new markdownit({
html: true,
linkify: true,
});
window.challenge.preRender = function () {
};
window.challenge.render = function (markdown) {
return window.challenge.renderer.render(markdown);
};
CTFd._internal.challenge.renderer = CTFd.lib.markdown();
window.challenge.postRender = function () {
CTFd._internal.challenge.preRender = function () { }
};
CTFd._internal.challenge.render = function (markdown) {
return CTFd._internal.challenge.renderer.render(markdown)
}
window.challenge.submit = function (cb, preview) {
var challenge_id = parseInt($('#challenge-id').val());
var submission = $('#submission-input').val();
var url = "/api/v1/challenges/attempt";
CTFd._internal.challenge.postRender = function () { }
CTFd._internal.challenge.submit = function (preview) {
var challenge_id = parseInt(CTFd.lib.$('#challenge-id').val())
var submission = CTFd.lib.$('#submission-input').val()
var body = {
'challenge_id': challenge_id,
'submission': submission,
}
var params = {}
if (preview) {
url += "?preview=true";
params['preview'] = true
}
var params = {
'challenge_id': challenge_id,
'submission': submission
};
CTFd.fetch(url, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then(function (response) {
return CTFd.api.post_challenge_attempt(params, body).then(function (response) {
if (response.status === 429) {
// User was ratelimited but process response
return response.json();
return response
}
if (response.status === 403) {
// User is not logged in or CTF is paused.
return response.json();
return response
}
return response.json();
}).then(function (response) {
cb(response);
});
};
return response
})
};

View File

@ -6,7 +6,7 @@
<input type="text" class="form-control" name="content" value="{{ content }}">
</div>
<div class="form-group">
<select name="data">
<select class="form-control custom-select col-md-6" name="data">
<option value="">Case Sensitive</option>
<option value="case_insensitive">Case Insensitive</option>
</select>

View File

@ -6,7 +6,7 @@
<input type="text" class="form-control" name="content" value="{{ content }}">
</div>
<div class="form-group">
<select name="data">
<select class="form-control custom-select col-md-6" name="data">
<option value="">Case Sensitive</option>
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
</select>

View File

@ -6,7 +6,7 @@
<input type="text" class="form-control" name="content" value="{{ content }}">
</div>
<div class="form-group">
<select name="data">
<select class="form-control custom-select col-md-6" name="data">
<option value="">Case Sensitive</option>
<option value="case_insensitive">Case Insensitive</option>
</select>

View File

@ -6,7 +6,7 @@
<input type="text" class="form-control" name="content" value="{{ content }}">
</div>
<div class="form-group">
<select name="data">
<select class="form-control custom-select col-md-6" name="data">
<option value="">Case Sensitive</option>
<option value="case_insensitive" {% if data %}selected{% endif %}>Case Insensitive</option>
</select>

23
CTFd/schemas/tokens.py Normal file
View File

@ -0,0 +1,23 @@
from CTFd.models import ma, Tokens
from CTFd.utils import string_types
class TokenSchema(ma.ModelSchema):
class Meta:
model = Tokens
include_fk = True
dump_only = ("id", "expiration", "type")
views = {
"admin": ["id", "type", "user_id", "created", "expiration", "value"],
"user": ["id", "type", "created", "expiration"],
}
def __init__(self, view=None, *args, **kwargs):
if view:
if isinstance(view, string_types):
kwargs["only"] = self.views[view]
elif isinstance(view, list):
kwargs["only"] = view
super(TokenSchema, self).__init__(*args, **kwargs)

View File

@ -2,14 +2,14 @@ from flask import render_template, request, redirect, url_for, Blueprint
from CTFd.models import db, Teams
from CTFd.utils.decorators import authed_only, ratelimit
from CTFd.utils.decorators.modes import require_team_mode
from CTFd.utils import config
from CTFd.utils import config, get_config
from CTFd.utils.user import get_current_user
from CTFd.utils.crypto import verify_password
from CTFd.utils.decorators.visibility import (
check_account_visibility,
check_score_visibility,
)
from CTFd.utils.helpers import get_errors
from CTFd.utils.helpers import get_errors, get_infos
teams = Blueprint("teams", __name__)
@ -44,14 +44,35 @@ def listing():
@require_team_mode
@ratelimit(method="POST", limit=10, interval=5)
def join():
infos = get_infos()
errors = get_errors()
if request.method == "GET":
return render_template("teams/join_team.html")
team_size_limit = get_config("team_size", default=0)
if team_size_limit:
plural = "" if team_size_limit == 1 else "s"
infos.append(
"Teams are limited to {limit} member{plural}".format(
limit=team_size_limit, plural=plural
)
)
return render_template("teams/join_team.html", infos=infos, errors=errors)
if request.method == "POST":
teamname = request.form.get("name")
passphrase = request.form.get("password", "").strip()
team = Teams.query.filter_by(name=teamname).first()
user = get_current_user()
team_size_limit = get_config("team_size", default=0)
if team_size_limit and len(team.members) >= team_size_limit:
errors.append(
"{name} has already reached the team size limit of {limit}".format(
name=team.name, limit=team_size_limit
)
)
return render_template("teams/join_team.html", infos=infos, errors=errors)
if team and verify_password(passphrase, team.password):
user.team_id = team.id
db.session.commit()
@ -62,16 +83,27 @@ def join():
return redirect(url_for("challenges.listing"))
else:
errors = ["That information is incorrect"]
return render_template("teams/join_team.html", errors=errors)
errors.append("That information is incorrect")
return render_template("teams/join_team.html", infos=infos, errors=errors)
@teams.route("/teams/new", methods=["GET", "POST"])
@authed_only
@require_team_mode
def new():
infos = get_infos()
errors = get_errors()
if request.method == "GET":
return render_template("teams/new_team.html")
team_size_limit = get_config("team_size", default=0)
if team_size_limit:
plural = "" if team_size_limit == 1 else "s"
infos.append(
"Teams are limited to {limit} member{plural}".format(
limit=team_size_limit, plural=plural
)
)
return render_template("teams/new_team.html", infos=infos, errors=errors)
elif request.method == "POST":
teamname = request.form.get("name")
passphrase = request.form.get("password", "").strip()

View File

@ -0,0 +1,68 @@
@import "includes/sticky-footer.css";
#score-graph {
height: 450px;
display: block;
clear: both;
}
#solves-graph {
display: block;
height: 350px;
}
#keys-pie-graph {
height: 400px;
display: block;
}
#categories-pie-graph {
height: 400px;
display: block;
}
#solve-percentages-graph {
height: 400px;
display: block;
}
.no-decoration {
color: inherit !important;
text-decoration: none !important;
}
.no-decoration:hover {
color: inherit !important;
text-decoration: none !important;
}
.table td,
.table th {
vertical-align: inherit;
}
pre {
white-space: pre-wrap;
margin: 0;
padding: 0;
}
.form-control {
position: relative;
display: block;
/*padding: 0.8em;*/
border-radius: 0;
/*background: #f0f0f0;*/
/*color: #aaa;*/
font-weight: 400;
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-appearance: none;
}
tbody tr:hover {
background-color: rgba(0, 0, 0, 0.1) !important;
}
tr[data-href] {
cursor: pointer;
}

View File

@ -55,4 +55,12 @@
font-weight: 400;
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-appearance: none;
height: auto !important;
}
#challenge-window .form-control:focus {
background-color: transparent;
border-color: #a3d39c;
box-shadow: 0 0 0 0.2rem #a3d39c;
transition: background-color 0.3s, border-color 0.3s;
}

View File

@ -0,0 +1,30 @@
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px; /* Margin bottom by footer height */
}
.footer {
position: absolute;
/* prevent scrollbars from showing on pages that don't use the full page height */
bottom: 1px;
width: 100%;
/* Set the fixed height of the footer here */
height: 60px;
/* Override line-height from core because we have two lines in the admin panel */
line-height: normal !important;
/* Avoid covering things */
z-index: -20;
/*background-color: #f5f5f5;*/
}

View File

@ -0,0 +1,233 @@
import $ from "jquery";
import { ezToast } from "core/ezq";
import CTFd from "core/CTFd";
import nunjucks from "nunjucks";
function renderSubmissionResponse(response, cb) {
const result = response.data;
const result_message = $("#result-message");
const result_notification = $("#result-notification");
const answer_input = $("#submission-input");
result_notification.removeClass();
result_message.text(result.message);
if (result.status === "authentication_required") {
window.location =
CTFd.config.urlRoot +
"/login?next=" +
CTFd.config.urlRoot +
window.location.pathname +
window.location.hash;
return;
} else if (result.status === "incorrect") {
// Incorrect key
result_notification.addClass(
"alert alert-danger alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.removeClass("correct");
answer_input.addClass("wrong");
setTimeout(function() {
answer_input.removeClass("wrong");
}, 3000);
} else if (result.status === "correct") {
// Challenge Solved
result_notification.addClass(
"alert alert-success alert-dismissable text-center"
);
result_notification.slideDown();
$(".challenge-solves").text(
parseInt(
$(".challenge-solves")
.text()
.split(" ")[0]
) +
1 +
" Solves"
);
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
} else if (result.status === "already_solved") {
// Challenge already solved
result_notification.addClass(
"alert alert-info alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("correct");
} else if (result.status === "paused") {
// CTF is paused
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
} else if (result.status === "ratelimited") {
// Keys per minute too high
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("too-fast");
setTimeout(function() {
answer_input.removeClass("too-fast");
}, 3000);
}
setTimeout(function() {
$(".alert").slideUp();
$("#submit-key").removeClass("disabled-button");
$("#submit-key").prop("disabled", false);
}, 3000);
if (cb) {
cb(result);
}
}
$(() => {
$(".preview-challenge").click(function(event) {
window.challenge = new Object();
$.get(CTFd.config.urlRoot + "/api/v1/challenges/" + CHALLENGE_ID, function(
response
) {
const challenge_data = response.data;
challenge_data["solves"] = null;
$.getScript(
CTFd.config.urlRoot + challenge_data.type_data.scripts.view,
function() {
$.get(
CTFd.config.urlRoot + challenge_data.type_data.templates.view,
function(template_data) {
$("#challenge-window").empty();
const template = nunjucks.compile(template_data);
window.challenge.data = challenge_data;
window.challenge.preRender();
challenge_data["description"] = window.challenge.render(
challenge_data["description"]
);
challenge_data["script_root"] = CTFd.config.urlRoot;
$("#challenge-window").append(template.render(challenge_data));
$(".challenge-solves").click(function(event) {
getsolves($("#challenge-id").val());
});
$(".nav-tabs a").click(function(event) {
event.preventDefault();
$(this).tab("show");
});
// Handle modal toggling
$("#challenge-window").on("hide.bs.modal", function(event) {
$("#submission-input").removeClass("wrong");
$("#submission-input").removeClass("correct");
$("#incorrect-key").slideUp();
$("#correct-key").slideUp();
$("#already-solved").slideUp();
$("#too-fast").slideUp();
});
$("#submit-key").click(function(event) {
event.preventDefault();
$("#submit-key").addClass("disabled-button");
$("#submit-key").prop("disabled", true);
window.challenge.submit(function(data) {
renderSubmissionResponse(data);
}, true);
// Preview passed as true
});
$("#submission-input").keyup(function(event) {
if (event.keyCode == 13) {
$("#submit-key").click();
}
});
$(".input-field").bind({
focus: function() {
$(this)
.parent()
.addClass("input--filled");
$label = $(this).siblings(".input-label");
},
blur: function() {
if ($(this).val() === "") {
$(this)
.parent()
.removeClass("input--filled");
$label = $(this).siblings(".input-label");
$label.removeClass("input--hide");
}
}
});
window.challenge.postRender();
window.location.replace(
window.location.href.split("#")[0] + "#preview"
);
$("#challenge-window").modal();
}
);
}
);
});
});
$(".delete-challenge").click(function(event) {
ezQuery({
title: "Delete Challenge",
body: "Are you sure you want to delete {0}".format(
"<strong>" + htmlentities(CHALLENGE_NAME) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "DELETE"
}).then(function(response) {
if (response.success) {
window.location = CTFd.config.urlRoot + "/admin/challenges";
}
});
}
});
});
$("#challenge-update-container > form").submit(function(event) {
event.preventDefault();
const params = $(event.target).serializeJSON(true);
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
}).then(function(data) {
if (data.success) {
ezToast({
title: "Success",
body: "Your challenge has been updated!"
});
}
});
});
if (window.location.hash) {
let hash = window.location.hash.replace("<>[]'\"", "");
$('nav a[href="' + hash + '"]').tab("show");
}
$(".nav-tabs a").click(function(event) {
$(this).tab("show");
window.location.hash = this.hash;
});
});

View File

@ -0,0 +1,42 @@
import $ from "jquery";
import CTFd from "core/CTFd";
import { default as helpers } from "core/helpers";
import { ezQuery } from "core/ezq";
export function addFile(event) {
event.preventDefault();
let form = event.target;
let data = {
challenge: CHALLENGE_ID,
type: "challenge"
};
helpers.files.upload(form, data, function(response) {
setTimeout(function() {
window.location.reload();
}, 700);
});
}
export function deleteFile(event) {
const file_id = $(this).attr("file-id");
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Files",
body: "Are you sure you want to delete this file?",
success: function() {
CTFd.fetch("/api/v1/files/" + file_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
}

View File

@ -0,0 +1,137 @@
import $ from "jquery";
import CTFd from "core/CTFd";
import nunjucks from "nunjucks";
import { ezQuery } from "core/ezq";
export function deleteFlag(event) {
event.preventDefault();
const flag_id = $(this).attr("flag-id");
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Flag",
body: "Are you sure you want to delete this flag?",
success: function() {
CTFd.fetch("/api/v1/flags/" + flag_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
}
export function addFlagModal(event) {
$.get(CTFd.config.urlRoot + "/api/v1/flags/types", function(response) {
const data = response.data;
const flag_type_select = $("#flags-create-select");
flag_type_select.empty();
let option = $("<option> -- </option>");
flag_type_select.append(option);
for (const key in data) {
if (data.hasOwnProperty(key)) {
option = $(
"<option value='{0}'>{1}</option>".format(key, data[key].name)
);
flag_type_select.append(option);
}
}
$("#flag-edit-modal").modal();
});
$("#flag-edit-modal form").submit(function(event) {
event.preventDefault();
const params = $(this).serializeJSON(true);
params["challenge"] = CHALLENGE_ID;
CTFd.fetch("/api/v1/flags", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
window.location.reload();
});
});
$("#flag-edit-modal").modal();
}
export function editFlagModal(event) {
event.preventDefault();
const flag_id = $(this).attr("flag-id");
const row = $(this)
.parent()
.parent();
$.get(CTFd.config.urlRoot + "/api/v1/flags/" + flag_id, function(response) {
const data = response.data;
$.get(CTFd.config.urlRoot + data.templates.update, function(template_data) {
$("#edit-flags form").empty();
$("#edit-flags form").off();
const template = nunjucks.compile(template_data);
$("#edit-flags form").append(template.render(data));
$("#edit-flags form").submit(function(event) {
event.preventDefault();
const params = $("#edit-flags form").serializeJSON();
CTFd.fetch("/api/v1/flags/" + flag_id, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
$(row)
.find(".flag-content")
.text(response.data.content);
$("#edit-flags").modal("toggle");
}
});
});
$("#edit-flags").modal();
});
});
}
export function flagTypeSelect(event) {
event.preventDefault();
const flag_type_name = $(this)
.find("option:selected")
.text();
$.get(CTFd.config.urlRoot + "/api/v1/flags/types/" + flag_type_name, function(
response
) {
const data = response.data;
$.get(CTFd.config.urlRoot + data.templates.create, function(template_data) {
const template = nunjucks.compile(template_data);
$("#create-keys-entry-div").html(template.render());
$("#create-keys-button-div").show();
});
});
}

View File

@ -0,0 +1,145 @@
import $ from "jquery";
import CTFd from "core/CTFd";
import { ezQuery, ezAlert } from "core/ezq";
function hint(id) {
return CTFd.fetch("/api/v1/hints/" + id + "?preview=true", {
method: "GET",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
});
}
function loadhint(hintid) {
const md = CTFd.lib.markdown();
hint(hintid).then(function(response) {
if (response.data.content) {
ezAlert({
title: "Hint",
body: md.render(response.data.content),
button: "Got it!"
});
} else {
ezAlert({
title: "Error",
body: "Error loading hint!",
button: "OK"
});
}
});
}
export function showHintModal(event) {
event.preventDefault();
$("#hint-edit-modal form")
.find("input, textarea")
.val("");
// Markdown Preview
$("#new-hint-edit").on("shown.bs.tab", function(event) {
if (event.target.hash == "#hint-preview") {
const renderer = CTFd.lib.markdown();
const editor_value = $("#hint-write textarea").val();
$(event.target.hash).html(renderer.render(editor_value));
}
});
$("#hint-edit-modal").modal();
}
export function showEditHintModal(event) {
event.preventDefault();
const hint_id = $(this).attr("hint-id");
CTFd.fetch("/api/v1/hints/" + hint_id + "?preview=true", {
method: "GET",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
$("#hint-edit-form input[name=content],textarea[name=content]").val(
response.data.content
);
$("#hint-edit-form input[name=cost]").val(response.data.cost);
$("#hint-edit-form input[name=id]").val(response.data.id);
// Markdown Preview
$("#new-hint-edit").on("shown.bs.tab", function(event) {
if (event.target.hash == "#hint-preview") {
const renderer = CTFd.lib.markdown();
const editor_value = $("#hint-write textarea").val();
$(event.target.hash).html(renderer.render(editor_value));
}
});
$("#hint-edit-modal").modal();
}
});
}
export function deleteHint(event) {
event.preventDefault();
const hint_id = $(this).attr("hint-id");
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Hint",
body: "Are you sure you want to delete this hint?",
success: function() {
CTFd.fetch("/api/v1/hints/" + hint_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
row.remove();
}
});
}
});
}
export function editHint(event) {
event.preventDefault();
const params = $(this).serializeJSON(true);
params["challenge"] = CHALLENGE_ID;
const method = "POST";
const url = "/api/v1/hints";
if (params.id) {
method = "PATCH";
url = "/api/v1/hints/" + params.id;
}
CTFd.fetch(url, {
method: method,
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
// TODO: Refresh hints on submit.
window.location.reload();
}
});
}

View File

@ -0,0 +1,75 @@
import CTFd from "core/CTFd";
import $ from "jquery";
window.challenge = new Object();
function loadChalTemplate(challenge) {
$.getScript(CTFd.config.urlRoot + challenge.scripts.view, function() {
$.get(CTFd.config.urlRoot + challenge.templates.create, function(
template_data
) {
const template = nunjucks.compile(template_data);
$("#create-chal-entry-div").html(
template.render({
nonce: CTFd.config.csrfNonce,
script_root: CTFd.config.urlRoot
})
);
$.getScript(CTFd.config.urlRoot + challenge.scripts.create, function() {
$("#create-chal-entry-div form").submit(function(event) {
event.preventDefault();
const params = $("#create-chal-entry-div form").serializeJSON();
CTFd.fetch("/api/v1/challenges", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
}).then(function(response) {
if (response.success) {
window.location =
CTFd.config.urlRoot + "/admin/challenges/" + response.data.id;
}
});
});
});
});
});
}
$.get(CTFd.config.urlRoot + "/api/v1/challenges/types", function(response) {
$("#create-chals-select").empty();
const data = response.data;
const chal_type_amt = Object.keys(data).length;
if (chal_type_amt > 1) {
const option = "<option> -- </option>";
$("#create-chals-select").append(option);
for (const key in data) {
const challenge = data[key];
const option = $("<option/>");
option.attr("value", challenge.type);
option.text(challenge.name);
option.data("meta", challenge);
$("#create-chals-select").append(option);
}
$("#create-chals-select-div").show();
} else if (chal_type_amt == 1) {
const key = Object.keys(data)[0];
$("#create-chals-select").empty();
loadChalTemplate(data[key]);
}
});
function createChallenge(event) {
const challenge = $(this)
.find("option:selected")
.data("meta");
loadChalTemplate(challenge);
}
$(() => {
$("#create-chals-select").change(createChallenge);
});

View File

@ -0,0 +1,69 @@
import $ from "jquery";
import CTFd from "core/CTFd";
export function addRequirement(event) {
event.preventDefault();
const requirements = $("#prerequisite-add-form").serializeJSON();
// Shortcut if there's no prerequisite
if (!requirements["prerequisite"]) {
return;
}
CHALLENGE_REQUIREMENTS.prerequisites.push(
parseInt(requirements["prerequisite"])
);
const params = {
requirements: CHALLENGE_REQUIREMENTS
};
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
// TODO: Make this refresh requirements
window.location.reload();
}
});
}
export function deleteRequirement(event) {
const challenge_id = $(this).attr("challenge-id");
const row = $(this)
.parent()
.parent();
CHALLENGE_REQUIREMENTS.prerequisites.pop(challenge_id);
const params = {
requirements: CHALLENGE_REQUIREMENTS
};
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
row.remove();
}
});
}

View File

@ -0,0 +1,42 @@
import $ from "jquery";
import CTFd from "core/CTFd";
export function deleteTag(event) {
const $elem = $(this);
const tag_id = $elem.attr("tag-id");
CTFd.api.delete_tag({ tagId: tag_id }).then(response => {
if (response.success) {
$elem.parent().remove();
}
});
}
export function addTag(event) {
if (event.keyCode != 13) {
return;
}
const $elem = $(this);
const tag = $elem.val();
const params = {
value: tag,
challenge: CHALLENGE_ID
};
CTFd.api.post_tag_list({}, params).then(response => {
if (response.success) {
const tpl =
"<span class='badge badge-primary mx-1 challenge-tag'>" +
"<span>{0}</span>" +
"<a class='btn-fa delete-tag' tag-id='{1}'>&times;</a></span>";
const tag = $(tpl.format(response.data.value, response.data.id));
$("#challenge-tags").append(tag);
// TODO: tag deletion not working on freshly created tags
tag.click(deleteTag);
}
});
$elem.val("");
}

View File

@ -0,0 +1,490 @@
import "./main";
import "core/utils";
import $ from "jquery";
import "bootstrap/js/dist/tab";
import CTFd from "core/CTFd";
import { htmlEntities } from "core/utils";
import { ezQuery, ezAlert, ezToast } from "core/ezq";
import nunjucks from "nunjucks";
import { default as helpers } from "core/helpers";
import { addFile, deleteFile } from "../challenges/files";
import { addTag, deleteTag } from "../challenges/tags";
import { addRequirement, deleteRequirement } from "../challenges/requirements";
import {
showHintModal,
editHint,
deleteHint,
showEditHintModal
} from "../challenges/hints";
import {
addFlagModal,
editFlagModal,
deleteFlag,
flagTypeSelect
} from "../challenges/flags";
const md = CTFd.lib.markdown();
const displayHint = data => {
ezAlert({
title: "Hint",
body: md.render(data.content),
button: "Got it!"
});
};
const loadHint = id => {
CTFd.api.get_hint({ hintId: id, preview: true }).then(response => {
if (response.data.content) {
displayHint(response.data);
return;
}
displayUnlock(id);
});
};
function renderSubmissionResponse(response, cb) {
var result = response.data;
var result_message = $("#result-message");
var result_notification = $("#result-notification");
var answer_input = $("#submission-input");
result_notification.removeClass();
result_message.text(result.message);
if (result.status === "authentication_required") {
window.location =
CTFd.config.urlRoot +
"/login?next=" +
CTFd.config.urlRoot +
window.location.pathname +
window.location.hash;
return;
} else if (result.status === "incorrect") {
// Incorrect key
result_notification.addClass(
"alert alert-danger alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.removeClass("correct");
answer_input.addClass("wrong");
setTimeout(function() {
answer_input.removeClass("wrong");
}, 3000);
} else if (result.status === "correct") {
// Challenge Solved
result_notification.addClass(
"alert alert-success alert-dismissable text-center"
);
result_notification.slideDown();
$(".challenge-solves").text(
parseInt(
$(".challenge-solves")
.text()
.split(" ")[0]
) +
1 +
" Solves"
);
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
} else if (result.status === "already_solved") {
// Challenge already solved
result_notification.addClass(
"alert alert-info alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("correct");
} else if (result.status === "paused") {
// CTF is paused
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
} else if (result.status === "ratelimited") {
// Keys per minute too high
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("too-fast");
setTimeout(function() {
answer_input.removeClass("too-fast");
}, 3000);
}
setTimeout(function() {
$(".alert").slideUp();
$("#submit-key").removeClass("disabled-button");
$("#submit-key").prop("disabled", false);
}, 3000);
if (cb) {
cb(result);
}
}
function loadChalTemplate(challenge) {
CTFd._internal.challenge = {};
$.getScript(CTFd.config.urlRoot + challenge.scripts.view, function() {
$.get(CTFd.config.urlRoot + challenge.templates.create, function(
template_data
) {
const template = nunjucks.compile(template_data);
$("#create-chal-entry-div").html(
template.render({
nonce: CTFd.config.csrfNonce,
script_root: CTFd.config.urlRoot
})
);
$.getScript(CTFd.config.urlRoot + challenge.scripts.create, function() {
$("#create-chal-entry-div form").submit(function(event) {
event.preventDefault();
const params = $("#create-chal-entry-div form").serializeJSON();
CTFd.fetch("/api/v1/challenges", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
$("#challenge-create-options #challenge_id").val(
response.data.id
);
$("#challenge-create-options").modal();
}
});
});
});
});
});
}
function handleChallengeOptions(event) {
event.preventDefault();
var params = $(event.target).serializeJSON(true);
let flag_params = {
challenge_id: params.challenge_id,
content: params.flag || "",
type: params.flag_type,
data: params.flag_data ? params.flag_data : ""
};
// Define a save_challenge function
let save_challenge = function() {
CTFd.fetch("/api/v1/challenges/" + params.challenge_id, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
state: params.state
})
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
setTimeout(function() {
window.location =
CTFd.config.urlRoot + "/admin/challenges/" + params.challenge_id;
}, 700);
}
});
};
Promise.all([
// Save flag
new Promise(function(resolve, reject) {
if (flag_params.content.length == 0) {
resolve();
return;
}
CTFd.fetch("/api/v1/flags", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(flag_params)
}).then(function(response) {
resolve(response.json());
});
}),
// Upload files
new Promise(function(resolve, reject) {
let form = event.target;
let data = {
challenge: params.challenge_id,
type: "challenge"
};
let filepath = $(form.elements["file"]).val();
if (filepath) {
helpers.files.upload(form, data);
}
resolve();
})
]).then(responses => {
save_challenge();
});
}
function createChallenge(event) {
const challenge = $(this)
.find("option:selected")
.data("meta");
if (challenge === undefined) {
$("#create-chal-entry-div").empty();
return;
}
loadChalTemplate(challenge);
}
$(() => {
$(".preview-challenge").click(function(e) {
window.challenge = new Object();
CTFd._internal.challenge = {};
$.get(CTFd.config.urlRoot + "/api/v1/challenges/" + CHALLENGE_ID, function(
response
) {
const challenge = CTFd._internal.challenge;
var challenge_data = response.data;
challenge_data["solves"] = null;
$.getScript(
CTFd.config.urlRoot + challenge_data.type_data.scripts.view,
function() {
$.get(
CTFd.config.urlRoot + challenge_data.type_data.templates.view,
function(template_data) {
$("#challenge-window").empty();
var template = nunjucks.compile(template_data);
// window.challenge.data = challenge_data;
// window.challenge.preRender();
challenge.data = challenge_data;
challenge.preRender();
challenge_data["description"] = challenge.render(
challenge_data["description"]
);
challenge_data["script_root"] = CTFd.config.urlRoot;
$("#challenge-window").append(template.render(challenge_data));
$(".challenge-solves").click(function(e) {
getsolves($("#challenge-id").val());
});
$(".nav-tabs a").click(function(e) {
e.preventDefault();
$(this).tab("show");
});
// Handle modal toggling
$("#challenge-window").on("hide.bs.modal", function(event) {
$("#submission-input").removeClass("wrong");
$("#submission-input").removeClass("correct");
$("#incorrect-key").slideUp();
$("#correct-key").slideUp();
$("#already-solved").slideUp();
$("#too-fast").slideUp();
});
$(".load-hint").on("click", function(event) {
loadHint($(this).data("hint-id"));
});
$("#submit-key").click(function(e) {
e.preventDefault();
$("#submit-key").addClass("disabled-button");
$("#submit-key").prop("disabled", true);
CTFd._internal.challenge
.submit(true)
.then(renderSubmissionResponse);
// Preview passed as true
});
$("#submission-input").keyup(function(event) {
if (event.keyCode == 13) {
$("#submit-key").click();
}
});
$(".input-field").bind({
focus: function() {
$(this)
.parent()
.addClass("input--filled");
$label = $(this).siblings(".input-label");
},
blur: function() {
if ($(this).val() === "") {
$(this)
.parent()
.removeClass("input--filled");
$label = $(this).siblings(".input-label");
$label.removeClass("input--hide");
}
}
});
challenge.postRender();
window.location.replace(
window.location.href.split("#")[0] + "#preview"
);
$("#challenge-window").modal();
}
);
}
);
});
});
$(".delete-challenge").click(function(e) {
ezQuery({
title: "Delete Challenge",
body: "Are you sure you want to delete {0}".format(
"<strong>" + htmlEntities(CHALLENGE_NAME) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location = CTFd.config.urlRoot + "/admin/challenges";
}
});
}
});
});
$("#challenge-update-container > form").submit(function(e) {
e.preventDefault();
var params = $(e.target).serializeJSON(true);
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID + "/flags", {
method: "GET",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
let update_challenge = function() {
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
ezToast({
title: "Success",
body: "Your challenge has been updated!"
});
}
});
};
// Check if the challenge doesn't have any flags before marking visible
if (response.data.length === 0 && params.state === "visible") {
ezQuery({
title: "Missing Flags",
body:
"This challenge does not have any flags meaning it is unsolveable. Are you sure you'd like to update this challenge?",
success: update_challenge
});
} else {
update_challenge();
}
});
});
$("#challenge-create-options form").submit(handleChallengeOptions);
$(".nav-tabs a").click(function(e) {
$(this).tab("show");
window.location.hash = this.hash;
});
if (window.location.hash) {
let hash = window.location.hash.replace("<>[]'\"", "");
$('nav a[href="' + hash + '"]').tab("show");
}
$("#tags-add-input").keyup(addTag);
$(".delete-tag").click(deleteTag);
$("#prerequisite-add-form").submit(addRequirement);
$(".delete-requirement").click(deleteRequirement);
$("#file-add-form").submit(addFile);
$(".delete-file").click(deleteFile);
$("#hint-add-button").click(showHintModal);
$(".delete-hint").click(deleteHint);
$(".edit-hint").click(showEditHintModal);
$("#hint-edit-form").submit(editHint);
$("#flag-add-button").click(addFlagModal);
$(".delete-flag").click(deleteFlag);
$("#flags-create-select").change(flagTypeSelect);
$(".edit-flag").click(editFlagModal);
$.get(CTFd.config.urlRoot + "/api/v1/challenges/types", function(response) {
$("#create-chals-select").empty();
const data = response.data;
const chal_type_amt = Object.keys(data).length;
if (chal_type_amt > 1) {
const option = "<option> -- </option>";
$("#create-chals-select").append(option);
for (const key in data) {
const challenge = data[key];
const option = $("<option/>");
option.attr("value", challenge.type);
option.text(challenge.name);
option.data("meta", challenge);
$("#create-chals-select").append(option);
}
$("#create-chals-select-div").show();
$("#create-chals-select").val("standard");
loadChalTemplate(data["standard"]);
} else if (chal_type_amt == 1) {
const key = Object.keys(data)[0];
$("#create-chals-select").empty();
loadChalTemplate(data[key]);
}
});
$("#create-chals-select").change(createChallenge);
});

View File

@ -0,0 +1,298 @@
import "./main";
import "core/utils";
import "bootstrap/js/dist/tab";
import Moment from "moment-timezone";
import moment from "moment-timezone";
import CTFd from "core/CTFd";
import { default as helpers } from "core/helpers";
import $ from "jquery";
import { ezQuery, ezProgressBar } from "core/ezq";
function loadTimestamp(place, timestamp) {
if (typeof timestamp == "string") {
timestamp = parseInt(timestamp, 10);
}
const m = Moment(timestamp * 1000);
$("#" + place + "-month").val(m.month() + 1); // Months are zero indexed (http://momentjs.com/docs/#/get-set/month/)
$("#" + place + "-day").val(m.date());
$("#" + place + "-year").val(m.year());
$("#" + place + "-hour").val(m.hour());
$("#" + place + "-minute").val(m.minute());
loadDateValues(place);
}
function loadDateValues(place) {
const month = $("#" + place + "-month").val();
const day = $("#" + place + "-day").val();
const year = $("#" + place + "-year").val();
const hour = $("#" + place + "-hour").val();
const minute = $("#" + place + "-minute").val();
const timezone = $("#" + place + "-timezone").val();
const utc = convertDateToMoment(month, day, year, hour, minute);
if (isNaN(utc.unix())) {
$("#" + place).val("");
$("#" + place + "-local").val("");
$("#" + place + "-zonetime").val("");
} else {
$("#" + place).val(utc.unix());
$("#" + place + "-local").val(
utc.local().format("dddd, MMMM Do YYYY, h:mm:ss a zz")
);
$("#" + place + "-zonetime").val(
utc.tz(timezone).format("dddd, MMMM Do YYYY, h:mm:ss a zz")
);
}
}
function convertDateToMoment(month, day, year, hour, minute) {
let month_num = month.toString();
if (month_num.length == 1) {
month_num = "0" + month_num;
}
let day_str = day.toString();
if (day_str.length == 1) {
day_str = "0" + day_str;
}
let hour_str = hour.toString();
if (hour_str.length == 1) {
hour_str = "0" + hour_str;
}
let min_str = minute.toString();
if (min_str.length == 1) {
min_str = "0" + min_str;
}
// 2013-02-08 24:00
const date_string =
year.toString() +
"-" +
month_num +
"-" +
day_str +
" " +
hour_str +
":" +
min_str +
":00";
return Moment(date_string, Moment.ISO_8601);
}
function updateConfigs(event) {
event.preventDefault();
const obj = $(this).serializeJSON();
const params = {};
if (obj.mail_useauth === false) {
obj.mail_username = null;
obj.mail_password = null;
} else {
if (obj.mail_username === "") {
delete obj.mail_username;
}
if (obj.mail_password === "") {
delete obj.mail_password;
}
}
Object.keys(obj).forEach(function(x) {
if (obj[x] === "true") {
params[x] = true;
} else if (obj[x] === "false") {
params[x] = false;
} else {
params[x] = obj[x];
}
});
CTFd.api.patch_config_list({}, params).then(response => {
window.location.reload();
});
}
function uploadLogo(event) {
event.preventDefault();
let form = event.target;
helpers.files.upload(form, {}, function(response) {
const f = response.data[0];
const params = {
value: f.location
};
CTFd.fetch("/api/v1/configs/ctf_logo", {
method: "PATCH",
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location.reload();
} else {
ezAlert({
title: "Error!",
body: "Logo uploading failed!",
button: "Okay"
});
}
});
});
}
function removeLogo() {
ezQuery({
title: "Remove logo",
body: "Are you sure you'd like to remove the CTF logo?",
success: function() {
const params = {
value: null
};
CTFd.api
.patch_config({ configKey: "ctf_logo" }, params)
.then(response => {
window.location.reload();
});
}
});
}
function importConfig(event) {
event.preventDefault();
let import_file = document.getElementById("import-file").files[0];
let form_data = new FormData();
form_data.append("backup", import_file);
form_data.append("nonce", CTFd.config.csrfNonce);
let pg = ezProgressBar({
width: 0,
title: "Upload Progress"
});
$.ajax({
url: CTFd.config.urlRoot + "/admin/import",
type: "POST",
data: form_data,
processData: false,
contentType: false,
statusCode: {
500: function(resp) {
console.log(resp.responseText);
alert(resp.responseText);
}
},
xhr: function() {
let xhr = $.ajaxSettings.xhr();
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
let width = (e.loaded / e.total) * 100;
pg = ezProgressBar({
target: pg,
width: width
});
}
};
return xhr;
},
success: function(data) {
pg = ezProgressBar({
target: pg,
width: 100
});
setTimeout(function() {
pg.modal("hide");
}, 500);
setTimeout(function() {
window.location.reload();
}, 700);
}
});
}
function exportConfig(event) {
event.preventDefault();
const href = CTFd.config.urlRoot + "/admin/export";
window.location.href = $(this).attr("href");
}
function showTab(event) {
window.location.hash = this.hash;
}
function insertTimezones(target) {
let current = $("<option>").text(moment.tz.guess());
$(target).append(current);
let tz_names = moment.tz.names();
for (let i = 0; i < tz_names.length; i++) {
let tz = $("<option>").text(tz_names[i]);
$(target).append(tz);
}
}
$(() => {
$(".config-section > form:not(.form-upload)").submit(updateConfigs);
$("#logo-upload").submit(uploadLogo);
$("#remove-logo").click(removeLogo);
$("#export-button").click(exportConfig);
$("#import-button").click(importConfig);
$(".nav-pills a").click(showTab);
$("#config-color-update").click(function() {
const hex_code = $("#config-color-picker").val();
const user_css = $("#css-editor").val();
let new_css;
if (user_css.lenth) {
let css_vars = `theme-color: ${hex_code};`;
new_css = user_css.replace(/theme-color: (.*);/, css_vars);
} else {
new_css =
`:root {--theme-color: ${hex_code};}\n` +
`.navbar{background-color: var(--theme-color) !important;}\n` +
`.jumbotron{background-color: var(--theme-color) !important;}\n`;
}
$("#css-editor").val(new_css);
});
$(".start-date").change(function() {
loadDateValues("start");
});
$(".end-date").change(function() {
loadDateValues("end");
});
$(".freeze-date").change(function() {
loadDateValues("freeze");
});
let hash = window.location.hash;
if (hash) {
hash = hash.replace("<>[]'\"", "");
$('ul.nav a[href="' + hash + '"]').tab("show");
}
const start = $("#start").val();
const end = $("#end").val();
const freeze = $("#freeze").val();
if (start) {
loadTimestamp("start", start);
}
if (end) {
loadTimestamp("end", end);
}
if (freeze) {
loadTimestamp("freeze", freeze);
}
// Toggle username and password based on stored value
$("#mail_useauth")
.change(function() {
$("#mail_username_password").toggle(this.checked);
})
.change();
insertTimezones($("#start-timezone"));
insertTimezones($("#end-timezone"));
insertTimezones($("#freeze-timezone"));
});

View File

@ -1,12 +1,68 @@
var editor = CodeMirror.fromTextArea(
document.getElementById("admin-pages-editor"),
{
lineNumbers: true,
lineWrapping: true,
mode: "xml",
htmlMode: true
}
);
import "./main";
import "core/utils";
import $ from "jquery";
import CTFd from "core/CTFd";
import { default as helpers } from "core/helpers";
import CodeMirror from "codemirror";
import { ezQuery, ezToast } from "core/ezq";
function get_filetype_icon_class(filename) {
var mapping = {
// Image Files
png: "fa-file-image",
jpg: "fa-file-image",
jpeg: "fa-file-image",
gif: "fa-file-image",
bmp: "fa-file-image",
svg: "fa-file-image",
// Text Files
txt: "fa-file-alt",
// Video Files
mov: "fa-file-video",
mp4: "fa-file-video",
wmv: "fa-file-video",
flv: "fa-file-video",
mkv: "fa-file-video",
avi: "fa-file-video",
// PDF Files
pdf: "fa-file-pdf",
// Audio Files
mp3: "fa-file-sound",
wav: "fa-file-sound",
aac: "fa-file-sound",
// Archive Files
zip: "fa-file-archive",
gz: "fa-file-archive",
tar: "fa-file-archive",
"7z": "fa-file-archive",
rar: "fa-file-archive",
// Code Files
py: "fa-file-code",
c: "fa-file-code",
cpp: "fa-file-code",
html: "fa-file-code",
js: "fa-file-code",
rb: "fa-file-code",
go: "fa-file-code"
};
var ext = filename.split(".").pop();
return mapping[ext];
}
function get_page_files() {
return CTFd.fetch("/api/v1/files?type=page", {
credentials: "same-origin"
}).then(function(response) {
return response.json();
});
}
function show_files(data) {
var list = $("#media-library-list");
@ -71,7 +127,7 @@ function show_files(data) {
$("#media-item").show();
});
wrapper.append(link);
wrapper.attr("data-location", script_root + "/files/" + f.location);
wrapper.attr("data-location", CTFd.config.urlRoot + "/files/" + f.location);
wrapper.attr("data-id", f.id);
wrapper.attr("data-filename", fname);
list.append(wrapper);
@ -95,7 +151,8 @@ function insert_at_cursor(editor, text) {
}
function submit_form() {
editor.save(); // Save the CodeMirror data to the Textarea
// Save the CodeMirror data to the Textarea
window.editor.save();
var params = $("#page-edit").serializeJSON();
var target = "/api/v1/pages";
var method = "POST";
@ -119,31 +176,41 @@ function submit_form() {
})
.then(function(response) {
if (method === "PATCH" && response.success) {
ezal({
ezToast({
title: "Saved",
body: "Your changes have been saved",
button: "Okay"
body: "Your changes have been saved"
});
} else {
window.location = script_root + "/admin/pages/" + response.data.id;
window.location =
CTFd.config.urlRoot + "/admin/pages/" + response.data.id;
}
});
}
function preview_page() {
editor.save(); // Save the CodeMirror data to the Textarea
$("#page-edit").attr("action", script_root + "/admin/pages/preview");
$("#page-edit").attr("action", CTFd.config.urlRoot + "/admin/pages/preview");
$("#page-edit").attr("target", "_blank");
$("#page-edit").submit();
}
function upload_media() {
upload_files($("#media-library-upload"), function(data) {
helpers.files.upload($("#media-library-upload"), function(data) {
refresh_files();
});
}
$(document).ready(function() {
$(() => {
window.editor = CodeMirror.fromTextArea(
document.getElementById("admin-pages-editor"),
{
lineNumbers: true,
lineWrapping: true,
mode: "xml",
htmlMode: true
}
);
$("#media-insert").click(function(e) {
var tag = "";
try {
@ -171,7 +238,7 @@ $(document).ready(function() {
$("#media-delete").click(function(e) {
var file_id = $(this).attr("data-id");
ezq({
ezQuery({
title: "Delete File?",
body: "Are you sure you want to delete this file?",
success: function() {
@ -203,12 +270,15 @@ $(document).ready(function() {
$("#media-button").click(function() {
$("#media-library-list").empty();
refresh_files(function() {
$("#media-modal").modal("show");
$("#media-modal").modal();
});
// get_page_files().then(function (data) {
// var files = data;
// console.log(files);
// $('#media-modal').modal();
// });
});
$(".media-upload-button").click(function() {
upload_media();
});
$(".preview-page").click(function() {
preview_page();
});
});

View File

@ -0,0 +1,7 @@
import $ from "jquery";
import events from "core/events";
import CTFd from "core/CTFd";
$(() => {
events(CTFd.config.urlRoot);
});

View File

@ -0,0 +1,16 @@
import CTFd from "core/CTFd";
import $ from "jquery";
import events from "core/events";
import times from "core/times";
import styles from "../styles";
import { default as helpers } from "core/helpers";
CTFd.init(window.init);
window.CTFd = CTFd;
window.helpers = helpers;
$(() => {
styles();
times();
events(CTFd.config.urlRoot);
});

View File

@ -0,0 +1,51 @@
import "./main";
import "core/utils";
import $ from "jquery";
import CTFd from "core/CTFd";
import { ezQuery, ezAlert } from "core/ezq";
function submit(event) {
event.preventDefault();
const $form = $(this);
const params = $form.serializeJSON();
// Disable button after click
$form.find("button[type=submit]").attr("disabled", true);
CTFd.api.post_notification_list({}, params).then(response => {
// Admin should also see the notification sent out
setTimeout(function() {
$form.find("button[type=submit]").attr("disabled", false);
}, 1000);
if (!response.success) {
ezAlert({
title: "Error",
body: "Could not send notification. Please try again.",
button: "OK"
});
}
});
}
function deleteNotification(event) {
event.preventDefault();
const $elem = $(this);
const id = $elem.data("notif-id");
ezQuery({
title: "Delete Notification",
body: "Are you sure you want to delete this notification?",
success: function() {
CTFd.api.delete_notification({ notificationId: id }).then(response => {
if (response.success) {
$elem.parent().remove();
}
});
}
});
}
$(() => {
$("#notifications_form").submit(submit);
$(".delete-notification").click(deleteNotification);
});

View File

@ -0,0 +1,37 @@
import "./main";
import CTFd from "core/CTFd";
import $ from "jquery";
import { htmlEntities } from "core/utils";
import { ezQuery } from "core/ezq";
function deletePage(event) {
const elem = $(this);
const name = elem.attr("page-route");
const page_id = elem.attr("page-id");
ezQuery({
title: "Delete " + htmlEntities(name),
body: "Are you sure you want to delete {0}?".format(
"<strong>" + htmlEntities(name) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/pages/" + page_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
elem
.parent()
.parent()
.remove();
}
});
}
});
}
$(() => {
$(".delete-page").click(deletePage);
});

View File

@ -0,0 +1,20 @@
import "./main";
import $ from "jquery";
import { ezQuery } from "core/ezq";
function reset(event) {
event.preventDefault();
ezQuery({
title: "Reset CTF?",
body: "Are you sure you want to reset your CTFd instance?",
success: function() {
$("#reset-ctf-form")
.off("submit")
.submit();
}
});
}
$(() => {
$("#reset-ctf-form").submit(reset);
});

View File

@ -0,0 +1,42 @@
import "./main";
import CTFd from "core/CTFd";
import $ from "jquery";
const api_func = {
users: (x, y) => CTFd.api.patch_user_public({ userId: x }, y),
teams: (x, y) => CTFd.api.patch_team_public({ teamId: x }, y)
};
function toggleAccount() {
const $btn = $(this);
const id = $btn.data("account-id");
const state = $btn.data("state");
let hidden = undefined;
if (state === "visible") {
hidden = true;
} else if (state === "hidden") {
hidden = false;
}
const params = {
hidden: hidden
};
api_func[CTFd.config.userMode](id, params).then(response => {
if (response.success) {
if (hidden) {
$btn.data("state", "hidden");
$btn.addClass("btn-danger").removeClass("btn-success");
$btn.text("Hidden");
} else {
$btn.data("state", "visible");
$btn.addClass("btn-success").removeClass("btn-danger");
$btn.text("Visible");
}
}
});
}
$(() => {
$(".scoreboard-toggle").click(toggleAccount);
});

View File

@ -0,0 +1,222 @@
import "./main";
import "core/utils";
import CTFd from "core/CTFd";
import $ from "jquery";
import Plotly from "plotly.js-basic-dist";
import { createGraph, updateGraph } from "core/graphs";
const graph_configs = {
"#solves-graph": {
layout: annotations => ({
title: "Solve Counts",
annotations: annotations,
xaxis: {
title: "Challenge Name"
},
yaxis: {
title: "Amount of Solves"
}
}),
fn: () => "CTFd_solves_" + new Date().toISOString().slice(0, 19),
data: () => CTFd.api.get_challenge_solve_statistics(),
format: response => {
const data = response.data;
const chals = [];
const counts = [];
const annotations = [];
const solves = {};
for (let c = 0; c < data.length; c++) {
solves[data[c]["id"]] = {
name: data[c]["name"],
solves: data[c]["solves"]
};
}
const solves_order = Object.keys(solves).sort(function(a, b) {
return solves[b].solves - solves[a].solves;
});
$.each(solves_order, function(key, value) {
chals.push(solves[value].name);
counts.push(solves[value].solves);
const result = {
x: solves[value].name,
y: solves[value].solves,
text: solves[value].solves,
xanchor: "center",
yanchor: "bottom",
showarrow: false
};
annotations.push(result);
});
return [
{
type: "bar",
x: chals,
y: counts,
text: counts,
orientation: "v"
},
annotations
];
}
},
"#keys-pie-graph": {
layout: () => ({
title: "Submission Percentages"
}),
fn: () => "CTFd_submissions_" + new Date().toISOString().slice(0, 19),
data: () => CTFd.api.get_submission_property_counts({ column: "type" }),
format: response => {
const data = response.data;
const solves = data["correct"];
const fails = data["incorrect"];
return [
{
values: [solves, fails],
labels: ["Correct", "Incorrect"],
marker: {
colors: ["rgb(0, 209, 64)", "rgb(207, 38, 0)"]
},
text: ["Solves", "Fails"],
hole: 0.4,
type: "pie"
},
null
];
}
},
"#categories-pie-graph": {
layout: () => ({
title: "Category Breakdown"
}),
data: () => CTFd.api.get_challenge_property_counts({ column: "category" }),
fn: () => "CTFd_categories_" + new Date().toISOString().slice(0, 19),
format: response => {
const data = response.data;
const categories = [];
const count = [];
for (let category in data) {
if (data.hasOwnProperty(category)) {
categories.push(category);
count.push(data[category]);
}
}
for (let i = 0; i < data.length; i++) {
categories.push(data[i].category);
count.push(data[i].count);
}
return [
{
values: count,
labels: categories,
hole: 0.4,
type: "pie"
},
null
];
}
},
"#solve-percentages-graph": {
layout: annotations => ({
title: "Solve Percentages per Challenge",
xaxis: {
title: "Challenge Name"
},
yaxis: {
title: "Percentage of {0} (%)".format(
CTFd.config.userMode.charAt(0).toUpperCase() +
CTFd.config.userMode.slice(1)
),
range: [0, 100]
},
annotations: annotations
}),
data: () => CTFd.api.get_challenge_solve_percentages(),
fn: () =>
"CTFd_challenge_percentages_" + new Date().toISOString().slice(0, 19),
format: response => {
const data = response.data;
const names = [];
const percents = [];
const annotations = [];
for (let key in data) {
names.push(data[key].name);
percents.push(data[key].percentage * 100);
const result = {
x: data[key].name,
y: data[key].percentage * 100,
text: Math.round(data[key].percentage * 100) + "%",
xanchor: "center",
yanchor: "bottom",
showarrow: false
};
annotations.push(result);
}
return [
{
type: "bar",
x: names,
y: percents,
orientation: "v"
},
annotations
];
}
}
};
const config = {
displaylogo: false,
responsive: true
};
const createGraphs = () => {
for (let key in graph_configs) {
const cfg = graph_configs[key];
const $elem = $(key);
$elem.empty();
$elem[0].fn = cfg.fn();
cfg
.data()
.then(cfg.format)
.then(([data, annotations]) => {
Plotly.newPlot($elem[0], [data], cfg.layout(annotations), config);
});
}
};
function updateGraphs() {
for (let key in graph_configs) {
const cfg = graph_configs[key];
const $elem = $(key);
cfg
.data()
.then(cfg.format)
.then(([data, annotations]) => {
// FIXME: Pass annotations
Plotly.react($elem[0], [data], cfg.layout(annotations), config);
});
}
}
$(() => {
createGraphs();
setInterval(updateGraphs, 300000);
});

View File

@ -0,0 +1,8 @@
import $ from "jquery";
import styles from "../styles";
import times from "core/times";
$(() => {
styles();
times();
});

View File

@ -0,0 +1,45 @@
import "./main";
import CTFd from "core/CTFd";
import $ from "jquery";
import { htmlEntities } from "core/utils";
import { ezQuery } from "core/ezq";
function deleteCorrectSubmission(event) {
const key_id = $(this).data("submission-id");
const $elem = $(this)
.parent()
.parent();
const chal_name = $elem
.find(".chal")
.text()
.trim();
const team_name = $elem
.find(".team")
.text()
.trim();
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Submission",
body: "Are you sure you want to delete correct submission from {0} for challenge {1}".format(
"<strong>" + htmlEntities(team_name) + "</strong>",
"<strong>" + htmlEntities(chal_name) + "</strong>"
),
success: function() {
CTFd.api
.delete_submission({ submissionId: key_id })
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
}
$(() => {
$(".delete-correct-submission").click(deleteCorrectSubmission);
});

View File

@ -0,0 +1,422 @@
import "./main";
import $ from "jquery";
import CTFd from "core/CTFd";
import { htmlEntities } from "core/utils";
import { ezQuery, ezBadge } from "core/ezq";
import { createGraph, updateGraph } from "core/graphs";
function createTeam(event) {
event.preventDefault();
const params = $("#team-info-create-form").serializeJSON(true);
CTFd.fetch("/api/v1/teams", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
const team_id = response.data.id;
window.location = CTFd.config.urlRoot + "/admin/teams/" + team_id;
} else {
$("#team-info-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#team-info-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
const i = $("#team-info-form").find("input[name={0}]".format(key));
const input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
}
function updateTeam(event) {
event.preventDefault();
const params = $("#team-info-edit-form").serializeJSON(true);
CTFd.fetch("/api/v1/teams/" + TEAM_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location.reload();
} else {
$("#team-info-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#team-info-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
const i = $("#team-info-form").find("input[name={0}]".format(key));
const input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
}
const api_funcs = {
team: [
x => CTFd.api.get_team_solves({ teamId: x }),
x => CTFd.api.get_team_fails({ teamId: x }),
x => CTFd.api.get_team_awards({ teamId: x })
],
user: [
x => CTFd.api.get_user_solves({ userId: x }),
x => CTFd.api.get_user_fails({ userId: x }),
x => CTFd.api.get_user_awards({ userId: x })
]
};
const createGraphs = (type, id, name, account_id) => {
let [solves_func, fails_func, awards_func] = api_funcs[type];
Promise.all([
solves_func(account_id),
fails_func(account_id),
awards_func(account_id)
]).then(responses => {
createGraph(
"score_graph",
"#score-graph",
responses,
type,
id,
name,
account_id
);
createGraph(
"category_breakdown",
"#categories-pie-graph",
responses,
type,
id,
name,
account_id
);
createGraph(
"solve_percentages",
"#keys-pie-graph",
responses,
type,
id,
name,
account_id
);
});
};
const updateGraphs = (type, id, name, account_id) => {
let [solves_func, fails_func, awards_func] = api_funcs[type];
Promise.all([
solves_func(account_id),
fails_func(account_id),
awards_func(account_id)
]).then(responses => {
updateGraph(
"score_graph",
"#score-graph",
responses,
type,
id,
name,
account_id
);
updateGraph(
"category_breakdown",
"#categories-pie-graph",
responses,
type,
id,
name,
account_id
);
updateGraph(
"solve_percentages",
"#keys-pie-graph",
responses,
type,
id,
name,
account_id
);
});
};
$(() => {
$("#team-captain-form").submit(function(e) {
e.preventDefault();
const params = $("#team-captain-form").serializeJSON(true);
CTFd.fetch("/api/v1/teams/" + TEAM_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location.reload();
} else {
$("#team-captain-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#team-captain-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
const i = $("#team-captain-form").find(
"select[name={0}]".format(key)
);
const input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
});
$(".edit-team").click(function(e) {
$("#team-info-edit-modal").modal("toggle");
});
$(".edit-captain").click(function(e) {
$("#team-captain-modal").modal("toggle");
});
$(".award-team").click(function(e) {
$("#team-award-modal").modal("toggle");
});
$("#user-award-form").submit(function(e) {
e.preventDefault();
const params = $("#user-award-form").serializeJSON(true);
params["user_id"] = $("#award-member-input").val();
$("#user-award-form > #results").empty();
if (!params["user_id"]) {
$("#user-award-form > #results").append(
ezBadge({
type: "error",
body: "Please select a team member"
})
);
return;
}
params["user_id"] = parseInt(params["user_id"]);
CTFd.fetch("/api/v1/awards", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location.reload();
} else {
$("#user-award-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#user-award-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
const i = $("#user-award-form").find("input[name={0}]".format(key));
const input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
});
$(".delete-member").click(function(e) {
e.preventDefault();
const member_id = $(this).attr("member-id");
const member_name = $(this).attr("member-name");
const params = {
user_id: member_id
};
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Remove Member",
body: "Are you sure you want to remove {0} from {1}? <br><br><strong>All of their challenges solves, attempts, awards, and unlocked hints will also be deleted!</strong>".format(
"<strong>" + htmlEntities(member_name) + "</strong>",
"<strong>" + htmlEntities(TEAM_NAME) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/teams/" + TEAM_ID + "/members", {
method: "DELETE",
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
});
$(".delete-team").click(function(e) {
ezQuery({
title: "Delete Team",
body: "Are you sure you want to delete {0}".format(
"<strong>" + htmlEntities(TEAM_NAME) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/teams/" + TEAM_ID, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location = CTFd.config.urlRoot + "/admin/teams";
}
});
}
});
});
$(".delete-submission").click(function(e) {
e.preventDefault();
const submission_id = $(this).attr("submission-id");
const submission_type = $(this).attr("submission-type");
const submission_challenge = $(this).attr("submission-challenge");
const body = "<span>Are you sure you want to delete <strong>{0}</strong> submission from <strong>{1}</strong> for <strong>{2}</strong>?</span>".format(
htmlEntities(submission_type),
htmlEntities(TEAM_NAME),
htmlEntities(submission_challenge)
);
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Submission",
body: body,
success: function() {
CTFd.fetch("/api/v1/submissions/" + submission_id, {
method: "DELETE",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
});
$(".delete-award").click(function(e) {
e.preventDefault();
const award_id = $(this).attr("award-id");
const award_name = $(this).attr("award-name");
const body = "<span>Are you sure you want to delete the <strong>{0}</strong> award from <strong>{1}</strong>?".format(
htmlEntities(award_name),
htmlEntities(TEAM_NAME)
);
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Award",
body: body,
success: function() {
CTFd.fetch("/api/v1/awards/" + award_id, {
method: "DELETE",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
});
$("#team-info-create-form").submit(createTeam);
$("#team-info-edit-form").submit(updateTeam);
let type, id, name, account_id;
({ type, id, name, account_id } = window.stats_data);
createGraphs(type, id, name, account_id);
setInterval(() => {
updateGraphs(type, id, name, account_id);
}, 300000);
});

View File

@ -0,0 +1,440 @@
import "./main";
import $ from "jquery";
import CTFd from "core/CTFd";
import { htmlEntities } from "core/utils";
import { ezQuery, ezBadge } from "core/ezq";
import { createGraph, updateGraph } from "core/graphs";
function createUser(event) {
event.preventDefault();
const params = $("#user-info-create-form").serializeJSON(true);
CTFd.fetch("/api/v1/users", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
const user_id = response.data.id;
window.location = CTFd.config.urlRoot + "/admin/users/" + user_id;
} else {
$("#user-info-create-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#user-info-create-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
const i = $("#user-info-form").find("input[name={0}]".format(key));
const input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
}
function updateUser(event) {
event.preventDefault();
const params = $("#user-info-edit-form").serializeJSON(true);
CTFd.fetch("/api/v1/users/" + USER_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location.reload();
} else {
$("#user-info-edit-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#user-info-edit-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
const i = $("#user-info-edit-form").find(
"input[name={0}]".format(key)
);
const input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
}
function deleteUser(event) {
event.preventDefault();
ezQuery({
title: "Delete User",
body: "Are you sure you want to delete {0}".format(
"<strong>" + htmlEntities(USER_NAME) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/users/" + USER_ID, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location = CTFd.config.urlRoot + "/admin/users";
}
});
}
});
}
function awardUser(event) {
event.preventDefault();
const params = $("#user-award-form").serializeJSON(true);
params["user_id"] = USER_ID;
CTFd.fetch("/api/v1/awards", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location.reload();
} else {
$("#user-award-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#user-award-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
const i = $("#user-award-form").find("input[name={0}]".format(key));
const input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
}
function emailUser(event) {
event.preventDefault();
var params = $("#user-mail-form").serializeJSON(true);
CTFd.fetch("/api/v1/users/" + USER_ID + "/email", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
$("#user-mail-form > #results").append(
ezBadge({
type: "success",
body: "E-Mail sent successfully!"
})
);
$("#user-mail-form")
.find("input[type=text], textarea")
.val("");
} else {
$("#user-mail-form > #results").empty();
Object.keys(response.errors).forEach(function(key, index) {
$("#user-mail-form > #results").append(
ezBadge({
type: "error",
body: response.errors[key]
})
);
var i = $("#user-mail-form").find(
"input[name={0}], textarea[name={0}]".format(key)
);
var input = $(i);
input.addClass("input-filled-invalid");
input.removeClass("input-filled-valid");
});
}
});
}
function deleteUserSubmission(event) {
event.preventDefault();
const submission_id = $(this).attr("submission-id");
const submission_type = $(this).attr("submission-type");
const submission_challenge = $(this).attr("submission-challenge");
const body = "<span>Are you sure you want to delete <strong>{0}</strong> submission from <strong>{1}</strong> for <strong>{2}</strong>?</span>".format(
htmlEntities(submission_type),
htmlEntities(USER_NAME),
htmlEntities(submission_challenge)
);
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Submission",
body: body,
success: function() {
CTFd.fetch("/api/v1/submissions/" + submission_id, {
method: "DELETE",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
}
function deleteUserAward(event) {
event.preventDefault();
const award_id = $(this).attr("award-id");
const award_name = $(this).attr("award-name");
const body = "<span>Are you sure you want to delete the <strong>{0}</strong> award from <strong>{1}</strong>?".format(
htmlEntities(award_name),
htmlEntities(USER_NAME)
);
const row = $(this)
.parent()
.parent();
ezQuery({
title: "Delete Award",
body: body,
success: function() {
CTFd.fetch("/api/v1/awards/" + award_id, {
method: "DELETE",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
}
function correctUserSubmission(event) {
event.preventDefault();
const challenge_id = $(this).attr("challenge-id");
const challenge_name = $(this).attr("challenge-name");
const row = $(this)
.parent()
.parent();
const body = "<span>Are you sure you want to mark <strong>{0}</strong> solved for from <strong>{1}</strong>?".format(
htmlEntities(challenge_name),
htmlEntities(USER_NAME)
);
const params = {
provided: "MARKED AS SOLVED BY ADMIN",
user_id: USER_ID,
team_id: TEAM_ID,
challenge_id: challenge_id,
type: "correct"
};
ezQuery({
title: "Mark Correct",
body: body,
success: function() {
CTFd.fetch("/api/v1/submissions", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
// TODO: Refresh missing and solves instead of reloading
row.remove();
window.location.reload();
}
});
}
});
}
const api_funcs = {
team: [
x => CTFd.api.get_team_solves({ teamId: x }),
x => CTFd.api.get_team_fails({ teamId: x }),
x => CTFd.api.get_team_awards({ teamId: x })
],
user: [
x => CTFd.api.get_user_solves({ userId: x }),
x => CTFd.api.get_user_fails({ userId: x }),
x => CTFd.api.get_user_awards({ userId: x })
]
};
const createGraphs = (type, id, name, account_id) => {
let [solves_func, fails_func, awards_func] = api_funcs[type];
Promise.all([
solves_func(account_id),
fails_func(account_id),
awards_func(account_id)
]).then(responses => {
createGraph(
"score_graph",
"#score-graph",
responses,
type,
id,
name,
account_id
);
createGraph(
"category_breakdown",
"#categories-pie-graph",
responses,
type,
id,
name,
account_id
);
createGraph(
"solve_percentages",
"#keys-pie-graph",
responses,
type,
id,
name,
account_id
);
});
};
const updateGraphs = (type, id, name, account_id) => {
let [solves_func, fails_func, awards_func] = api_funcs[type];
Promise.all([
solves_func(account_id),
fails_func(account_id),
awards_func(account_id)
]).then(responses => {
updateGraph(
"score_graph",
"#score-graph",
responses,
type,
id,
name,
account_id
);
updateGraph(
"category_breakdown",
"#categories-pie-graph",
responses,
type,
id,
name,
account_id
);
updateGraph(
"solve_percentages",
"#keys-pie-graph",
responses,
type,
id,
name,
account_id
);
});
};
$(() => {
$(".delete-user").click(deleteUser);
$(".edit-user").click(function(event) {
$("#user-info-modal").modal("toggle");
});
$(".award-user").click(function(event) {
$("#user-award-modal").modal("toggle");
});
$(".email-user").click(function(event) {
$("#user-email-modal").modal("toggle");
});
$("#user-mail-form").submit(emailUser);
$(".delete-submission").click(deleteUserSubmission);
$(".delete-award").click(deleteUserAward);
$(".correct-submission").click(correctUserSubmission);
$("#user-info-create-form").submit(createUser);
$("#user-info-edit-form").submit(updateUser);
$("#user-award-form").submit(awardUser);
let type, id, name, account_id;
({ type, id, name, account_id } = window.stats_data);
createGraphs(type, id, name, account_id);
setInterval(() => {
updateGraphs(type, id, name, account_id);
}, 300000);
});

View File

@ -0,0 +1 @@
import "./main";

View File

@ -0,0 +1,51 @@
import "bootstrap/dist/js/bootstrap.bundle";
import $ from "jquery";
export default () => {
$(".form-control").bind({
focus: function() {
$(this).addClass("input-filled-valid");
},
blur: function() {
if ($(this).val() === "") {
$(this).removeClass("input-filled-valid");
}
}
});
$(".modal").on("show.bs.modal", function(e) {
$(".form-control").each(function() {
if ($(this).val()) {
$(this).addClass("input-filled-valid");
}
});
});
$(function() {
$(".form-control").each(function() {
if ($(this).val()) {
$(this).addClass("input-filled-valid");
}
});
$("tr[data-href]").click(function() {
var sel = getSelection().toString();
if (!sel) {
var href = $(this).attr("data-href");
if (href) {
window.location = href;
}
}
return false;
});
$("tr[data-href] a, tr[data-href] button").click(function(e) {
// TODO: This is a hack to allow modal close buttons to work
if (!$(this).attr("data-dismiss")) {
e.stopPropagation();
}
});
$('[data-toggle="tooltip"]').tooltip();
});
};

View File

@ -0,0 +1,4 @@
html{position:relative;min-height:100%}body{margin-bottom:60px}.footer{position:absolute;bottom:1px;width:100%;height:60px;line-height:normal !important;z-index:-20}
#score-graph{height:450px;display:block;clear:both}#solves-graph{display:block;height:350px}#keys-pie-graph{height:400px;display:block}#categories-pie-graph{height:400px;display:block}#solve-percentages-graph{height:400px;display:block}.no-decoration{color:inherit !important;text-decoration:none !important}.no-decoration:hover{color:inherit !important;text-decoration:none !important}.table td,.table th{vertical-align:inherit}pre{white-space:pre-wrap;margin:0;padding:0}.form-control{position:relative;display:block;border-radius:0;font-weight:400;font-family:"Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;-webkit-appearance:none}tbody tr:hover{background-color:rgba(0,0,0,0.1) !important}tr[data-href]{cursor:pointer}

View File

@ -0,0 +1 @@
html{position:relative;min-height:100%}body{margin-bottom:60px}.footer{position:absolute;bottom:1px;width:100%;height:60px;line-height:normal!important;z-index:-20}#score-graph{height:450px;display:block;clear:both}#solves-graph{display:block;height:350px}#categories-pie-graph,#keys-pie-graph,#solve-percentages-graph{height:400px;display:block}.no-decoration,.no-decoration:hover{color:inherit!important;text-decoration:none!important}.table td,.table th{vertical-align:inherit}pre{white-space:pre-wrap;margin:0;padding:0}.form-control{position:relative;display:block;border-radius:0;font-weight:400;font-family:Avenir Next,Helvetica Neue,Helvetica,Arial,sans-serif;-webkit-appearance:none}tbody tr:hover{background-color:rgba(0,0,0,.1)!important}tr[data-href]{cursor:pointer}

View File

@ -1,200 +0,0 @@
html,
body,
.container {
font-family: "Lato", "LatoOffline", sans-serif;
}
h1,
h2 {
font-family: "Raleway", "RalewayOffline", sans-serif;
font-weight: 500;
letter-spacing: 2px;
}
a {
color: #337ab7;
text-decoration: none;
}
table > thead > tr > td {
/* Remove border line from thead of all tables */
/* It can overlap with other element styles */
border-top: none !important;
}
.table td,
.table th {
vertical-align: inherit;
}
pre {
white-space: pre-wrap;
margin: 0;
padding: 0;
}
.fa-spin.spinner {
margin-top: 225px;
text-align: center;
opacity: 0.5;
}
.spinner-error {
padding-top: 20vh;
text-align: center;
opacity: 0.5;
}
.jumbotron {
/*background-color: #343a40;*/
/*color: #FFF;*/
border-radius: 0;
text-align: center;
}
.form-control {
position: relative;
display: block;
/*padding: 0.8em;*/
border-radius: 0;
/*background: #f0f0f0;*/
/*color: #aaa;*/
font-weight: 400;
font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-appearance: none;
}
.form-control:focus {
background-color: transparent;
border-color: #a3d39c;
box-shadow: 0 0 0 0.2rem #a3d39c;
transition: background-color 0.3s, border-color 0.3s;
}
.input-filled-valid {
background-color: transparent;
border-color: #a3d39c;
box-shadow: 0 0 0 0.2rem #a3d39c;
transition: background-color 0.3s, border-color 0.3s;
}
.input-filled-invalid {
background-color: transparent;
border-color: #d46767;
box-shadow: 0 0 0 0.2rem #d46767;
transition: background-color 0.3s, border-color 0.3s;
}
.btn-outlined.btn-theme {
background: none;
color: #545454;
border-color: #545454;
border: 3px solid;
}
.btn-outlined {
border-radius: 0;
-webkit-transition: all 0.3s;
-moz-transition: all 0.3s;
transition: all 0.3s;
}
.btn {
letter-spacing: 1px;
text-decoration: none;
-moz-user-select: none;
border-radius: 0;
cursor: pointer;
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
white-space: nowrap;
font-size: 14px;
line-height: 20px;
font-weight: 700;
padding: 8px 20px;
}
.btn-info {
background-color: #5b7290 !important;
border-color: #5b7290 !important;
}
.badge-info {
background-color: #5b7290 !important;
}
.alert {
border-radius: 0 !important;
padding: 0.8em;
}
#score-graph {
height: 450px;
display: block;
clear: both;
}
#solves-graph {
display: block;
height: 350px;
}
#keys-pie-graph {
height: 400px;
display: block;
}
#categories-pie-graph {
height: 400px;
display: block;
}
#solve-percentages-graph {
height: 400px;
display: block;
}
.no-decoration {
color: inherit !important;
text-decoration: none !important;
}
.no-decoration:hover {
color: inherit !important;
text-decoration: none !important;
}
.btn-fa {
cursor: pointer;
}
.close {
cursor: pointer;
}
.cursor-pointer {
cursor: pointer;
}
.cursor-help {
cursor: help;
}
.modal-content {
-webkit-border-radius: 0 !important;
-moz-border-radius: 0 !important;
border-radius: 0 !important;
}
.text-break {
/* TODO: This is .text-break cloned from Bootstrap 4.3 with a fix for browsers not supporting break-word. Remove later. */
word-break: break-all !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
}
.fa-disabled {
opacity: 0.5;
cursor: not-allowed;
}

View File

@ -0,0 +1,2 @@
.chal-desc{padding-left:30px;padding-right:30px;font-size:14px}.chal-desc img{max-width:100%;height:auto}.modal-content{border-radius:0px;max-width:1000px;padding:1em;margin:0 auto}.btn-info{background-color:#5b7290 !important}.badge-info{background-color:#5b7290 !important}.challenge-button{box-shadow:3px 3px 3px grey}.solved-challenge{background-color:#37d63e !important;opacity:0.4;border:none}.corner-button-check{margin-top:-10px;margin-right:25px;position:absolute;right:0}.key-submit .btn{height:51px}#challenge-window .form-control{position:relative;display:block;padding:0.8em;border-radius:0;background:#f0f0f0;color:#aaa;font-weight:400;font-family:"Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif;-webkit-appearance:none;height:auto !important}#challenge-window .form-control:focus{background-color:transparent;border-color:#a3d39c;box-shadow:0 0 0 0.2rem #a3d39c;transition:background-color 0.3s, border-color 0.3s}

View File

@ -0,0 +1 @@
.chal-desc{padding-left:30px;padding-right:30px;font-size:14px}.chal-desc img{max-width:100%;height:auto}.modal-content{border-radius:0;max-width:1000px;padding:1em;margin:0 auto}.badge-info,.btn-info{background-color:#5b7290!important}.challenge-button{box-shadow:3px 3px 3px grey}.solved-challenge{background-color:#37d63e!important;opacity:.4;border:none}.corner-button-check{margin-top:-10px;margin-right:25px;position:absolute;right:0}.key-submit .btn{height:51px}#challenge-window .form-control{position:relative;display:block;padding:.8em;border-radius:0;background:#f0f0f0;color:#aaa;font-weight:400;font-family:Avenir Next,Helvetica Neue,Helvetica,Arial,sans-serif;-webkit-appearance:none;height:auto!important}#challenge-window .form-control:focus{background-color:transparent;border-color:#a3d39c;box-shadow:0 0 0 .2rem #a3d39c;transition:background-color .3s,border-color .3s}

View File

@ -1,19 +0,0 @@
/* Sticky footer styles
-------------------------------------------------- */
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px; /* Margin bottom by footer height */
}
.footer {
position: absolute;
bottom: 1px; /* prevent scrollbars from showing on pages that don't use the full page height */
width: 100%;
height: 60px; /* Set the fixed height of the footer here */
/*line-height: 60px; !* Vertically center the text there *!*/
/*background-color: #f5f5f5;*/
}

View File

@ -1,7 +0,0 @@
tbody tr:hover {
background-color: rgba(0, 0, 0, 0.1) !important;
}
tr[data-href] {
cursor: pointer;
}

View File

@ -1,237 +0,0 @@
function renderSubmissionResponse(response, cb) {
var result = response.data;
var result_message = $("#result-message");
var result_notification = $("#result-notification");
var answer_input = $("#submission-input");
result_notification.removeClass();
result_message.text(result.message);
if (result.status === "authentication_required") {
window.location =
script_root +
"/login?next=" +
script_root +
window.location.pathname +
window.location.hash;
return;
} else if (result.status === "incorrect") {
// Incorrect key
result_notification.addClass(
"alert alert-danger alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.removeClass("correct");
answer_input.addClass("wrong");
setTimeout(function() {
answer_input.removeClass("wrong");
}, 3000);
} else if (result.status === "correct") {
// Challenge Solved
result_notification.addClass(
"alert alert-success alert-dismissable text-center"
);
result_notification.slideDown();
$(".challenge-solves").text(
parseInt(
$(".challenge-solves")
.text()
.split(" ")[0]
) +
1 +
" Solves"
);
answer_input.val("");
answer_input.removeClass("wrong");
answer_input.addClass("correct");
} else if (result.status === "already_solved") {
// Challenge already solved
result_notification.addClass(
"alert alert-info alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("correct");
} else if (result.status === "paused") {
// CTF is paused
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
} else if (result.status === "ratelimited") {
// Keys per minute too high
result_notification.addClass(
"alert alert-warning alert-dismissable text-center"
);
result_notification.slideDown();
answer_input.addClass("too-fast");
setTimeout(function() {
answer_input.removeClass("too-fast");
}, 3000);
}
setTimeout(function() {
$(".alert").slideUp();
$("#submit-key").removeClass("disabled-button");
$("#submit-key").prop("disabled", false);
}, 3000);
if (cb) {
cb(result);
}
}
$(document).ready(function() {
$(".preview-challenge").click(function(e) {
window.challenge = new Object();
$.get(script_root + "/api/v1/challenges/" + CHALLENGE_ID, function(
response
) {
var challenge_data = response.data;
challenge_data["solves"] = null;
$.getScript(
script_root + challenge_data.type_data.scripts.view,
function() {
$.get(script_root + challenge_data.type_data.templates.view, function(
template_data
) {
$("#challenge-window").empty();
var template = nunjucks.compile(template_data);
window.challenge.data = challenge_data;
window.challenge.preRender();
challenge_data["description"] = window.challenge.render(
challenge_data["description"]
);
challenge_data["script_root"] = script_root;
$("#challenge-window").append(template.render(challenge_data));
$(".challenge-solves").click(function(e) {
getsolves($("#challenge-id").val());
});
$(".nav-tabs a").click(function(e) {
e.preventDefault();
$(this).tab("show");
});
// Handle modal toggling
$("#challenge-window").on("hide.bs.modal", function(event) {
$("#submission-input").removeClass("wrong");
$("#submission-input").removeClass("correct");
$("#incorrect-key").slideUp();
$("#correct-key").slideUp();
$("#already-solved").slideUp();
$("#too-fast").slideUp();
});
$("#submit-key").click(function(e) {
e.preventDefault();
$("#submit-key").addClass("disabled-button");
$("#submit-key").prop("disabled", true);
window.challenge.submit(function(data) {
renderSubmissionResponse(data);
}, true);
// Preview passed as true
});
$("#submission-input").keyup(function(event) {
if (event.keyCode == 13) {
$("#submit-key").click();
}
});
$(".input-field").bind({
focus: function() {
$(this)
.parent()
.addClass("input--filled");
$label = $(this).siblings(".input-label");
},
blur: function() {
if ($(this).val() === "") {
$(this)
.parent()
.removeClass("input--filled");
$label = $(this).siblings(".input-label");
$label.removeClass("input--hide");
}
}
});
window.challenge.postRender();
window.location.replace(
window.location.href.split("#")[0] + "#preview"
);
$("#challenge-window").modal();
});
}
);
});
});
$(".delete-challenge").click(function(e) {
ezq({
title: "Delete Challenge",
body: "Are you sure you want to delete {0}".format(
"<strong>" + htmlentities(CHALLENGE_NAME) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location = script_root + "/admin/challenges";
}
});
}
});
});
$("#challenge-update-container > form").submit(function(e) {
e.preventDefault();
var params = $(e.target).serializeJSON(true);
console.log(params);
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
ezal({
title: "Success",
body: "Your challenge has been updated!",
button: "OK"
});
}
});
});
if (window.location.hash) {
let hash = window.location.hash.replace("<>[]'\"", "");
$('nav a[href="' + hash + '"]').tab("show");
}
$(".nav-tabs a").click(function(e) {
$(this).tab("show");
window.location.hash = this.hash;
});
});

View File

@ -1 +0,0 @@
$(document).ready(function() {});

View File

@ -1,75 +0,0 @@
$(document).ready(function() {
$("#file-add-form").submit(function(e) {
e.preventDefault();
var formData = new FormData(e.target);
formData.append("nonce", csrf_nonce);
formData.append("challenge", CHALLENGE_ID);
formData.append("type", "challenge");
var pg = ezpg({
width: 0,
title: "Upload Progress"
});
$.ajax({
url: script_root + "/api/v1/files",
data: formData,
type: "POST",
cache: false,
contentType: false,
processData: false,
xhr: function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
var width = (e.loaded / e.total) * 100;
pg = ezpg({
target: pg,
width: width
});
}
};
return xhr;
},
success: function(data) {
// TODO: Refresh files on submit
e.target.reset();
// Refresh modal
pg = ezpg({
target: pg,
width: 100
});
setTimeout(function() {
pg.modal("hide");
}, 500);
setTimeout(function() {
window.location.reload();
}, 700);
}
});
});
$(".delete-file").click(function(e) {
var file_id = $(this).attr("file-id");
var row = $(this)
.parent()
.parent();
ezq({
title: "Delete Files",
body: "Are you sure you want to delete this file?",
success: function() {
CTFd.fetch("/api/v1/files/" + file_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
});
});

View File

@ -1,134 +0,0 @@
$(document).ready(function() {
$("#flag-add-button").click(function(e) {
$.get(script_root + "/api/v1/flags/types", function(response) {
var data = response.data;
var flag_type_select = $("#flags-create-select");
flag_type_select.empty();
var option = "<option> -- </option>";
flag_type_select.append(option);
for (var key in data) {
if (data.hasOwnProperty(key)) {
option = "<option value='{0}'>{1}</option>".format(
key,
data[key].name
);
flag_type_select.append(option);
}
}
$("#flag-edit-modal").modal();
});
$("#flag-edit-modal form").submit(function(e) {
e.preventDefault();
var params = $(this).serializeJSON(true);
params["challenge"] = CHALLENGE_ID;
CTFd.fetch("/api/v1/flags", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
window.location.reload();
});
});
$("#flag-edit-modal").modal();
});
$("#flags-create-select").change(function(e) {
e.preventDefault();
var flag_type_name = $(this)
.find("option:selected")
.text();
$.get(script_root + "/api/v1/flags/types/" + flag_type_name, function(
response
) {
var data = response.data;
$.get(script_root + data.templates.create, function(template_data) {
var template = nunjucks.compile(template_data);
$("#create-keys-entry-div").html(template.render());
$("#create-keys-button-div").show();
});
});
});
$(".edit-flag").click(function(e) {
e.preventDefault();
var flag_id = $(this).attr("flag-id");
var row = $(this)
.parent()
.parent();
$.get(script_root + "/api/v1/flags/" + flag_id, function(response) {
var data = response.data;
$.get(script_root + data.templates.update, function(template_data) {
$("#edit-flags form").empty();
var template = nunjucks.compile(template_data);
$("#edit-flags form").append(template.render(data));
$("#edit-flags form").submit(function(e) {
e.preventDefault();
var params = $("#edit-flags form").serializeJSON();
CTFd.fetch("/api/v1/flags/" + flag_id, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
$(row)
.find(".flag-content")
.text(response.data.content);
$("#edit-flags").modal("toggle");
}
});
});
$("#edit-flags").modal();
});
});
});
$(".delete-flag").click(function(e) {
e.preventDefault();
var flag_id = $(this).attr("flag-id");
var row = $(this)
.parent()
.parent();
ezq({
title: "Delete Flag",
body: "Are you sure you want to delete this flag?",
success: function() {
CTFd.fetch("/api/v1/flags/" + flag_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
});
});

View File

@ -1,157 +0,0 @@
function hint(id) {
return CTFd.fetch("/api/v1/hints/" + id + "?preview=true", {
method: "GET",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
}).then(function(response) {
return response.json();
});
}
function loadhint(hintid) {
var md = window.markdownit({
html: true,
linkify: true
});
hint(hintid).then(function(response) {
if (response.data.content) {
ezal({
title: "Hint",
body: md.render(response.data.content),
button: "Got it!"
});
} else {
ezal({
title: "Error",
body: "Error loading hint!",
button: "OK"
});
}
});
}
$(document).ready(function() {
$("#hint-add-button").click(function(e) {
$("#hint-edit-modal form")
.find("input, textarea")
.val("");
// Markdown Preview
$("#new-hint-edit").on("shown.bs.tab", function(event) {
console.log(event.target.hash);
if (event.target.hash == "#hint-preview") {
console.log(event.target.hash);
var renderer = window.markdownit({
html: true,
linkify: true
});
var editor_value = $("#hint-write textarea").val();
$(event.target.hash).html(renderer.render(editor_value));
}
});
$("#hint-edit-modal").modal();
});
$(".delete-hint").click(function(e) {
e.preventDefault();
var hint_id = $(this).attr("hint-id");
var row = $(this)
.parent()
.parent();
ezq({
title: "Delete Hint",
body: "Are you sure you want to delete this hint?",
success: function() {
CTFd.fetch("/api/v1/hints/" + hint_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
row.remove();
}
});
}
});
});
$(".edit-hint").click(function(e) {
e.preventDefault();
var hint_id = $(this).attr("hint-id");
CTFd.fetch("/api/v1/hints/" + hint_id + "?preview=true", {
method: "GET",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
$("#hint-edit-form input[name=content],textarea[name=content]").val(
response.data.content
);
$("#hint-edit-form input[name=cost]").val(response.data.cost);
$("#hint-edit-form input[name=id]").val(response.data.id);
// Markdown Preview
$("#new-hint-edit").on("shown.bs.tab", function(event) {
console.log(event.target.hash);
if (event.target.hash == "#hint-preview") {
console.log(event.target.hash);
var renderer = new markdownit({
html: true,
linkify: true
});
var editor_value = $("#hint-write textarea").val();
$(event.target.hash).html(renderer.render(editor_value));
}
});
$("#hint-edit-modal").modal();
}
});
});
$("#hint-edit-form").submit(function(e) {
e.preventDefault();
var params = $(this).serializeJSON(true);
params["challenge"] = CHALLENGE_ID;
var method = "POST";
var url = "/api/v1/hints";
if (params.id) {
method = "PATCH";
url = "/api/v1/hints/" + params.id;
}
CTFd.fetch(url, {
method: method,
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
// TODO: Refresh hints on submit.
window.location.reload();
}
});
});
});

View File

@ -1,69 +0,0 @@
$.ajaxSetup({ cache: false });
window.challenge = new Object();
function load_chal_template(challenge) {
$.getScript(script_root + challenge.scripts.view, function() {
console.log("loaded renderer");
$.get(script_root + challenge.templates.create, function(template_data) {
var template = nunjucks.compile(template_data);
$("#create-chal-entry-div").html(
template.render({ nonce: nonce, script_root: script_root })
);
$.getScript(script_root + challenge.scripts.create, function() {
console.log("loaded");
$("#create-chal-entry-div form").submit(function(e) {
e.preventDefault();
var params = $("#create-chal-entry-div form").serializeJSON();
CTFd.fetch("/api/v1/challenges", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location =
script_root + "/admin/challenges/" + response.data.id;
}
});
});
});
});
});
}
$.get(script_root + "/api/v1/challenges/types", function(response) {
$("#create-chals-select").empty();
var data = response.data;
var chal_type_amt = Object.keys(data).length;
if (chal_type_amt > 1) {
var option = "<option> -- </option>";
$("#create-chals-select").append(option);
for (var key in data) {
var challenge = data[key];
var option = $("<option/>");
option.attr("value", challenge.type);
option.text(challenge.name);
option.data("meta", challenge);
$("#create-chals-select").append(option);
}
$("#create-chals-select-div").show();
} else if (chal_type_amt == 1) {
var key = Object.keys(data)[0];
$("#create-chals-select").empty();
load_chal_template(data[key]);
}
});
$("#create-chals-select").change(function() {
var challenge = $(this)
.find("option:selected")
.data("meta");
load_chal_template(challenge);
});

View File

@ -1,63 +0,0 @@
$(document).ready(function() {
$("#prerequisite-add-form").submit(function(e) {
e.preventDefault();
var requirements = $("#prerequisite-add-form").serializeJSON();
CHALLENGE_REQUIREMENTS.prerequisites.push(
parseInt(requirements["prerequisite"])
);
var params = {
requirements: CHALLENGE_REQUIREMENTS
};
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
// TODO: Make this refresh requirements
window.location.reload();
}
});
});
$(".delete-requirement").click(function(e) {
var challenge_id = $(this).attr("challenge-id");
var row = $(this)
.parent()
.parent();
CHALLENGE_REQUIREMENTS.prerequisites.pop(challenge_id);
var params = {
requirements: CHALLENGE_REQUIREMENTS
};
CTFd.fetch("/api/v1/challenges/" + CHALLENGE_ID, {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data.success) {
row.remove();
}
});
});
});

View File

@ -1,55 +0,0 @@
function delete_tag(elem) {
var elem = $(elem);
var tag_id = elem.attr("tag-id");
CTFd.fetch("/api/v1/tags/" + tag_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
$(elem)
.parent()
.remove();
}
});
}
$(document).ready(function() {
$("#tags-add-input").keyup(function(e) {
if (e.keyCode == 13) {
var tag = $("#tags-add-input").val();
var params = {
value: tag,
challenge: CHALLENGE_ID
};
CTFd.fetch("/api/v1/tags", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
var tpl =
"<span class='badge badge-primary mx-1 challenge-tag'>" +
"<span>{0}</span>" +
"<a class='btn-fa delete-tag' tag-id='{1}' onclick='delete_tag(this)'>&times;</a></span>";
tag = tpl.format(response.data.value, response.data.id);
$("#challenge-tags").append(tag);
}
});
$("#tags-add-input").val("");
}
});
});

View File

@ -1,317 +0,0 @@
var months = {
January: 1,
February: 2,
March: 3,
April: 4,
May: 5,
June: 6,
July: 7,
August: 8,
September: 9,
October: 10,
November: 11,
December: 12
};
function load_timestamp(place, timestamp) {
if (typeof timestamp == "string") {
var timestamp = parseInt(timestamp);
}
var m = moment(timestamp * 1000);
console.log("Loading " + place);
console.log(timestamp);
console.log(m.toISOString());
console.log(m.unix());
var month = $("#" + place + "-month").val(m.month() + 1); // Months are zero indexed (http://momentjs.com/docs/#/get-set/month/)
var day = $("#" + place + "-day").val(m.date());
var year = $("#" + place + "-year").val(m.year());
var hour = $("#" + place + "-hour").val(m.hour());
var minute = $("#" + place + "-minute").val(m.minute());
load_date_values(place);
}
function load_date_values(place) {
var month = $("#" + place + "-month").val();
var day = $("#" + place + "-day").val();
var year = $("#" + place + "-year").val();
var hour = $("#" + place + "-hour").val();
var minute = $("#" + place + "-minute").val();
var timezone = $("#" + place + "-timezone").val();
var utc = convert_date_to_moment(month, day, year, hour, minute, timezone);
if (isNaN(utc.unix())) {
$("#" + place).val("");
$("#" + place + "-local").val("");
$("#" + place + "-zonetime").val("");
} else {
$("#" + place).val(utc.unix());
$("#" + place + "-local").val(
utc.local().format("dddd, MMMM Do YYYY, h:mm:ss a zz")
);
$("#" + place + "-zonetime").val(
utc.tz(timezone).format("dddd, MMMM Do YYYY, h:mm:ss a zz")
);
}
}
function convert_date_to_moment(month, day, year, hour, minute, timezone) {
var month_num = month.toString();
if (month_num.length == 1) {
var month_num = "0" + month_num;
}
var day_str = day.toString();
if (day_str.length == 1) {
day_str = "0" + day_str;
}
var hour_str = hour.toString();
if (hour_str.length == 1) {
hour_str = "0" + hour_str;
}
var min_str = minute.toString();
if (min_str.length == 1) {
min_str = "0" + min_str;
}
// 2013-02-08 24:00
var date_string =
year.toString() +
"-" +
month_num +
"-" +
day_str +
" " +
hour_str +
":" +
min_str +
":00";
var m = moment(date_string, moment.ISO_8601);
return m;
}
function update_configs(obj) {
var target = "/api/v1/configs";
var method = "PATCH";
var params = {};
if (obj.mail_useauth === false) {
obj.mail_username = null;
obj.mail_password = null;
} else {
if (obj.mail_username === "") {
delete obj.mail_username;
}
if (obj.mail_password === "") {
delete obj.mail_password;
}
}
Object.keys(obj).forEach(function(x) {
if (obj[x] === "true") {
params[x] = true;
} else if (obj[x] === "false") {
params[x] = false;
} else {
params[x] = obj[x];
}
});
CTFd.fetch(target, {
method: method,
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
window.location.reload();
});
}
function upload_logo(form) {
upload_files(form, function(response) {
var upload = response.data[0];
if (upload.location) {
var params = {
value: upload.location
};
CTFd.fetch("/api/v1/configs/ctf_logo", {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
window.location.reload();
} else {
ezal({
title: "Error!",
body: "Logo uploading failed!",
button: "Okay"
});
}
});
}
});
}
function remove_logo() {
ezq({
title: "Remove logo",
body: "Are you sure you'd like to remove the CTF logo?",
success: function() {
var params = {
value: null
};
CTFd.fetch("/api/v1/configs/ctf_logo", {
method: "PATCH",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(data) {
window.location.reload();
});
}
});
}
$(function() {
$(".config-section > form:not(.form-upload)").submit(function(e) {
e.preventDefault();
var obj = $(this).serializeJSON();
update_configs(obj);
});
$("#logo-upload").submit(function(e) {
e.preventDefault();
var form = e.target;
upload_logo(form);
});
$(".start-date").change(function() {
load_date_values("start");
});
$(".end-date").change(function() {
load_date_values("end");
});
$(".freeze-date").change(function() {
load_date_values("freeze");
});
$("#export-button").click(function(e) {
e.preventDefault();
var href = script_root + "/admin/export";
window.location.href = $("#export-button").attr("href");
});
$("#import-button").click(function(e) {
e.preventDefault();
var import_file = document.getElementById("import-file").files[0];
var form_data = new FormData();
form_data.append("backup", import_file);
form_data.append("nonce", csrf_nonce);
var pg = ezpg({
width: 0,
title: "Upload Progress"
});
$.ajax({
url: script_root + "/admin/import",
type: "POST",
data: form_data,
processData: false,
contentType: false,
statusCode: {
500: function(resp) {
console.log(resp.responseText);
alert(resp.responseText);
}
},
xhr: function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
var width = (e.loaded / e.total) * 100;
pg = ezpg({
target: pg,
width: width
});
}
};
return xhr;
},
success: function(data) {
// Refresh modal
pg = ezpg({
target: pg,
width: 100
});
setTimeout(function() {
pg.modal("hide");
}, 500);
setTimeout(function() {
window.location.reload();
}, 700);
}
});
});
var hash = window.location.hash;
if (hash) {
hash = hash.replace("<>[]'\"", "");
$('ul.nav a[href="' + hash + '"]').tab("show");
}
$(".nav-pills a").click(function(e) {
$(this).tab("show");
window.location.hash = this.hash;
});
var start = $("#start").val();
var end = $("#end").val();
var freeze = $("#freeze").val();
if (start) {
load_timestamp("start", start);
}
if (end) {
load_timestamp("end", end);
}
if (freeze) {
load_timestamp("freeze", freeze);
}
// Toggle username and password based on stored value
$("#mail_useauth")
.change(function() {
$("#mail_username_password").toggle(this.checked);
})
.change();
});

File diff suppressed because one or more lines are too long

View File

View File

@ -1,46 +0,0 @@
function upload_files(form, cb) {
if (form instanceof jQuery) {
form = form[0];
}
var formData = new FormData(form);
formData.append("nonce", csrf_nonce);
var pg = ezpg({
width: 0,
title: "Upload Progress"
});
$.ajax({
url: script_root + "/api/v1/files",
data: formData,
type: "POST",
cache: false,
contentType: false,
processData: false,
xhr: function() {
var xhr = $.ajaxSettings.xhr();
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
var width = (e.loaded / e.total) * 100;
pg = ezpg({
target: pg,
width: width
});
}
};
return xhr;
},
success: function(data) {
// Refresh modal
pg = ezpg({
target: pg,
width: 100
});
setTimeout(function() {
pg.modal("hide");
}, 500);
if (cb) {
cb(data);
}
}
});
}

File diff suppressed because one or more lines are too long

View File

View File

@ -0,0 +1,63 @@
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["helpers"],{
/***/ "./CTFd/themes/core/assets/js/helpers.js":
/*!***********************************************!*\
!*** ./CTFd/themes/core/assets/js/helpers.js ***!
\***********************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _ezq = _interopRequireDefault(__webpack_require__(/*! ./ezq */ \"./CTFd/themes/core/assets/js/ezq.js\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\nvar files = {\n upload: function upload(form, extra_data, cb) {\n var CTFd = window.CTFd;\n\n if (form instanceof _jquery.default) {\n form = form[0];\n }\n\n var formData = new FormData(form);\n formData.append(\"nonce\", CTFd.config.csrfNonce);\n\n for (var _i = 0, _Object$entries = Object.entries(extra_data); _i < _Object$entries.length; _i++) {\n var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),\n key = _Object$entries$_i[0],\n value = _Object$entries$_i[1];\n\n formData.append(key, value);\n }\n\n var pg = _ezq.default.ezProgressBar({\n width: 0,\n title: \"Upload Progress\"\n });\n\n _jquery.default.ajax({\n url: CTFd.config.urlRoot + \"/api/v1/files\",\n data: formData,\n type: \"POST\",\n cache: false,\n contentType: false,\n processData: false,\n xhr: function xhr() {\n var xhr = _jquery.default.ajaxSettings.xhr();\n\n xhr.upload.onprogress = function (e) {\n if (e.lengthComputable) {\n var width = e.loaded / e.total * 100;\n pg = _ezq.default.ezProgressBar({\n target: pg,\n width: width\n });\n }\n };\n\n return xhr;\n },\n success: function success(data) {\n form.reset();\n pg = _ezq.default.ezProgressBar({\n target: pg,\n width: 100\n });\n setTimeout(function () {\n pg.modal(\"hide\");\n }, 500);\n\n if (cb) {\n cb(data);\n }\n }\n });\n }\n};\nvar helpers = {\n files: files,\n ezq: _ezq.default\n};\nvar _default = helpers;\nexports.default = _default;\n\n//# sourceURL=webpack:///./CTFd/themes/core/assets/js/helpers.js?");
/***/ }),
/***/ "./node_modules/markdown-it/lib/helpers/index.js":
/*!*******************************************************!*\
!*** ./node_modules/markdown-it/lib/helpers/index.js ***!
\*******************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("// Just a shortcut for bulk export\n\n\nexports.parseLinkLabel = __webpack_require__(/*! ./parse_link_label */ \"./node_modules/markdown-it/lib/helpers/parse_link_label.js\");\nexports.parseLinkDestination = __webpack_require__(/*! ./parse_link_destination */ \"./node_modules/markdown-it/lib/helpers/parse_link_destination.js\");\nexports.parseLinkTitle = __webpack_require__(/*! ./parse_link_title */ \"./node_modules/markdown-it/lib/helpers/parse_link_title.js\");\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/index.js?");
/***/ }),
/***/ "./node_modules/markdown-it/lib/helpers/parse_link_destination.js":
/*!************************************************************************!*\
!*** ./node_modules/markdown-it/lib/helpers/parse_link_destination.js ***!
\************************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("// Parse link destination\n//\n\n\nvar unescapeAll = __webpack_require__(/*! ../common/utils */ \"./node_modules/markdown-it/lib/common/utils.js\").unescapeAll;\n\nmodule.exports = function parseLinkDestination(str, pos, max) {\n var code,\n level,\n lines = 0,\n start = pos,\n result = {\n ok: false,\n pos: 0,\n lines: 0,\n str: ''\n };\n\n if (str.charCodeAt(pos) === 0x3C\n /* < */\n ) {\n pos++;\n\n while (pos < max) {\n code = str.charCodeAt(pos);\n\n if (code === 0x0A\n /* \\n */\n ) {\n return result;\n }\n\n if (code === 0x3E\n /* > */\n ) {\n result.pos = pos + 1;\n result.str = unescapeAll(str.slice(start + 1, pos));\n result.ok = true;\n return result;\n }\n\n if (code === 0x5C\n /* \\ */\n && pos + 1 < max) {\n pos += 2;\n continue;\n }\n\n pos++;\n } // no closing '>'\n\n\n return result;\n } // this should be ... } else { ... branch\n\n\n level = 0;\n\n while (pos < max) {\n code = str.charCodeAt(pos);\n\n if (code === 0x20) {\n break;\n } // ascii control characters\n\n\n if (code < 0x20 || code === 0x7F) {\n break;\n }\n\n if (code === 0x5C\n /* \\ */\n && pos + 1 < max) {\n pos += 2;\n continue;\n }\n\n if (code === 0x28\n /* ( */\n ) {\n level++;\n }\n\n if (code === 0x29\n /* ) */\n ) {\n if (level === 0) {\n break;\n }\n\n level--;\n }\n\n pos++;\n }\n\n if (start === pos) {\n return result;\n }\n\n if (level !== 0) {\n return result;\n }\n\n result.str = unescapeAll(str.slice(start, pos));\n result.lines = lines;\n result.pos = pos;\n result.ok = true;\n return result;\n};\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/parse_link_destination.js?");
/***/ }),
/***/ "./node_modules/markdown-it/lib/helpers/parse_link_label.js":
/*!******************************************************************!*\
!*** ./node_modules/markdown-it/lib/helpers/parse_link_label.js ***!
\******************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("// Parse link label\n//\n// this function assumes that first character (\"[\") already matches;\n// returns the end of the label\n//\n\n\nmodule.exports = function parseLinkLabel(state, start, disableNested) {\n var level,\n found,\n marker,\n prevPos,\n labelEnd = -1,\n max = state.posMax,\n oldPos = state.pos;\n state.pos = start + 1;\n level = 1;\n\n while (state.pos < max) {\n marker = state.src.charCodeAt(state.pos);\n\n if (marker === 0x5D\n /* ] */\n ) {\n level--;\n\n if (level === 0) {\n found = true;\n break;\n }\n }\n\n prevPos = state.pos;\n state.md.inline.skipToken(state);\n\n if (marker === 0x5B\n /* [ */\n ) {\n if (prevPos === state.pos - 1) {\n // increase level if we find text `[`, which is not a part of any token\n level++;\n } else if (disableNested) {\n state.pos = oldPos;\n return -1;\n }\n }\n }\n\n if (found) {\n labelEnd = state.pos;\n } // restore old state\n\n\n state.pos = oldPos;\n return labelEnd;\n};\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/parse_link_label.js?");
/***/ }),
/***/ "./node_modules/markdown-it/lib/helpers/parse_link_title.js":
/*!******************************************************************!*\
!*** ./node_modules/markdown-it/lib/helpers/parse_link_title.js ***!
\******************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("// Parse link title\n//\n\n\nvar unescapeAll = __webpack_require__(/*! ../common/utils */ \"./node_modules/markdown-it/lib/common/utils.js\").unescapeAll;\n\nmodule.exports = function parseLinkTitle(str, pos, max) {\n var code,\n marker,\n lines = 0,\n start = pos,\n result = {\n ok: false,\n pos: 0,\n lines: 0,\n str: ''\n };\n\n if (pos >= max) {\n return result;\n }\n\n marker = str.charCodeAt(pos);\n\n if (marker !== 0x22\n /* \" */\n && marker !== 0x27\n /* ' */\n && marker !== 0x28\n /* ( */\n ) {\n return result;\n }\n\n pos++; // if opening marker is \"(\", switch it to closing marker \")\"\n\n if (marker === 0x28) {\n marker = 0x29;\n }\n\n while (pos < max) {\n code = str.charCodeAt(pos);\n\n if (code === marker) {\n result.pos = pos + 1;\n result.lines = lines;\n result.str = unescapeAll(str.slice(start + 1, pos));\n result.ok = true;\n return result;\n } else if (code === 0x0A) {\n lines++;\n } else if (code === 0x5C\n /* \\ */\n && pos + 1 < max) {\n pos++;\n\n if (str.charCodeAt(pos) === 0x0A) {\n lines++;\n }\n }\n\n pos++;\n }\n\n return result;\n};\n\n//# sourceURL=webpack:///./node_modules/markdown-it/lib/helpers/parse_link_title.js?");
/***/ })
}]);

View File

@ -0,0 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{"./CTFd/themes/core/assets/js/helpers.js":function(e,r,o){Object.defineProperty(r,"__esModule",{value:!0}),r.default=void 0;var p=t(o("./node_modules/jquery/dist/jquery.js")),f=t(o("./CTFd/themes/core/assets/js/ezq.js"));function t(e){return e&&e.__esModule?e:{default:e}}function c(e,r){return function(e){if(Array.isArray(e))return e}(e)||function(e,r){var o=[],t=!0,n=!1,s=void 0;try{for(var i,a=e[Symbol.iterator]();!(t=(i=a.next()).done)&&(o.push(i.value),!r||o.length!==r);t=!0);}catch(e){n=!0,s=e}finally{try{t||null==a.return||a.return()}finally{if(n)throw s}}return o}(e,r)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}var n={files:{upload:function(r,e,o){var t=window.CTFd;r instanceof p.default&&(r=r[0]);var n=new FormData(r);n.append("nonce",t.config.csrfNonce);for(var s=0,i=Object.entries(e);s<i.length;s++){var a=c(i[s],2),l=a[0],d=a[1];n.append(l,d)}var u=f.default.ezProgressBar({width:0,title:"Upload Progress"});p.default.ajax({url:t.config.urlRoot+"/api/v1/files",data:n,type:"POST",cache:!1,contentType:!1,processData:!1,xhr:function(){var e=p.default.ajaxSettings.xhr();return e.upload.onprogress=function(e){if(e.lengthComputable){var r=e.loaded/e.total*100;u=f.default.ezProgressBar({target:u,width:r})}},e},success:function(e){r.reset(),u=f.default.ezProgressBar({target:u,width:100}),setTimeout(function(){u.modal("hide")},500),o&&o(e)}})}},ezq:f.default};r.default=n},"./node_modules/markdown-it/lib/helpers/index.js":function(e,r,o){r.parseLinkLabel=o("./node_modules/markdown-it/lib/helpers/parse_link_label.js"),r.parseLinkDestination=o("./node_modules/markdown-it/lib/helpers/parse_link_destination.js"),r.parseLinkTitle=o("./node_modules/markdown-it/lib/helpers/parse_link_title.js")},"./node_modules/markdown-it/lib/helpers/parse_link_destination.js":function(e,r,o){var a=o("./node_modules/markdown-it/lib/common/utils.js").unescapeAll;e.exports=function(e,r,o){var t,n,s=r,i={ok:!1,pos:0,lines:0,str:""};if(60===e.charCodeAt(r)){for(r++;r<o;){if(10===(t=e.charCodeAt(r)))return i;if(62===t)return i.pos=r+1,i.str=a(e.slice(s+1,r)),i.ok=!0,i;92===t&&r+1<o?r+=2:r++}return i}for(n=0;r<o&&32!==(t=e.charCodeAt(r))&&!(t<32||127===t);)if(92===t&&r+1<o)r+=2;else{if(40===t&&n++,41===t){if(0===n)break;n--}r++}return s===r||0!==n||(i.str=a(e.slice(s,r)),i.lines=0,i.pos=r,i.ok=!0),i}},"./node_modules/markdown-it/lib/helpers/parse_link_label.js":function(e,r,o){e.exports=function(e,r,o){var t,n,s,i,a=-1,l=e.posMax,d=e.pos;for(e.pos=r+1,t=1;e.pos<l;){if(93===(s=e.src.charCodeAt(e.pos))&&0===--t){n=!0;break}if(i=e.pos,e.md.inline.skipToken(e),91===s)if(i===e.pos-1)t++;else if(o)return e.pos=d,-1}return n&&(a=e.pos),e.pos=d,a}},"./node_modules/markdown-it/lib/helpers/parse_link_title.js":function(e,r,o){var l=o("./node_modules/markdown-it/lib/common/utils.js").unescapeAll;e.exports=function(e,r,o){var t,n,s=0,i=r,a={ok:!1,pos:0,lines:0,str:""};if(o<=r)return a;if(34!==(n=e.charCodeAt(r))&&39!==n&&40!==n)return a;for(r++,40===n&&(n=41);r<o;){if((t=e.charCodeAt(r))===n)return a.pos=r+1,a.lines=s,a.str=l(e.slice(i+1,r)),a.ok=!0,a;10===t?s++:92===t&&r+1<o&&(r++,10===e.charCodeAt(r)&&s++),r++}return a}}}]);

View File

@ -1,3 +0,0 @@
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
});

View File

@ -1,51 +0,0 @@
$(document).ready(function() {
$("#notifications_form").submit(function(e) {
e.preventDefault();
var form = $("#notifications_form");
var params = form.serializeJSON();
CTFd.fetch("/api/v1/notifications", {
method: "POST",
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify(params)
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
setTimeout(function() {
window.location.reload();
}, 3000);
}
});
});
$(".delete-notification").click(function(e) {
e.preventDefault();
var elem = $(this);
var notif_id = elem.attr("notif-id");
ezq({
title: "Delete Notification",
body: "Are you sure you want to delete this notification?",
success: function() {
CTFd.fetch("/api/v1/notifications/" + notif_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
elem.parent().remove();
}
});
}
});
});
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,155 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/ return result;
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "pages/main": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/themes/admin/static/js";
/******/
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // add entry module to deferred list
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/main.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
/******/ // run deferred modules when ready
/******/ return checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ([]);

File diff suppressed because one or more lines are too long

View File

@ -1,64 +0,0 @@
function get_filetype_icon_class(filename) {
var mapping = {
// Image Files
png: "fa-file-image",
jpg: "fa-file-image",
jpeg: "fa-file-image",
gif: "fa-file-image",
bmp: "fa-file-image",
svg: "fa-file-image",
// Text Files
txt: "fa-file-alt",
// Video Files
mov: "fa-file-video",
mp4: "fa-file-video",
wmv: "fa-file-video",
flv: "fa-file-video",
mkv: "fa-file-video",
avi: "fa-file-video",
// PDF Files
pdf: "fa-file-pdf",
// Audio Files
mp3: "fa-file-sound",
wav: "fa-file-sound",
aac: "fa-file-sound",
// Archive Files
zip: "fa-file-archive",
gz: "fa-file-archive",
tar: "fa-file-archive",
"7z": "fa-file-archive",
rar: "fa-file-archive",
// Code Files
py: "fa-file-code",
c: "fa-file-code",
cpp: "fa-file-code",
html: "fa-file-code",
js: "fa-file-code",
rb: "fa-file-code",
go: "fa-file-code"
};
var ext = filename.split(".").pop();
return mapping[ext];
}
function get_page_files() {
return CTFd.fetch("/api/v1/files?type=page", {
credentials: "same-origin"
}).then(function(response) {
return response.json();
});
}
// .
// then(function (data) {
// data.map(function (f) {
// console.log(f);
// });
// });

View File

@ -0,0 +1,169 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/ return result;
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "pages/notifications": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/themes/admin/static/js";
/******/
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // add entry module to deferred list
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/notifications.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
/******/ // run deferred modules when ready
/******/ return checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ({
/***/ "./CTFd/themes/admin/assets/js/pages/notifications.js":
/*!************************************************************!*\
!*** ./CTFd/themes/admin/assets/js/pages/notifications.js ***!
\************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\n__webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction submit(event) {\n event.preventDefault();\n var $form = (0, _jquery.default)(this);\n var params = $form.serializeJSON(); // Disable button after click\n\n $form.find(\"button[type=submit]\").attr(\"disabled\", true);\n\n _CTFd.default.api.post_notification_list({}, params).then(function (response) {\n // Admin should also see the notification sent out\n setTimeout(function () {\n $form.find(\"button[type=submit]\").attr(\"disabled\", false);\n }, 1000);\n\n if (!response.success) {\n (0, _ezq.ezAlert)({\n title: \"Error\",\n body: \"Could not send notification. Please try again.\",\n button: \"OK\"\n });\n }\n });\n}\n\nfunction deleteNotification(event) {\n event.preventDefault();\n var $elem = (0, _jquery.default)(this);\n var id = $elem.data(\"notif-id\");\n (0, _ezq.ezQuery)({\n title: \"Delete Notification\",\n body: \"Are you sure you want to delete this notification?\",\n success: function success() {\n _CTFd.default.api.delete_notification({\n notificationId: id\n }).then(function (response) {\n if (response.success) {\n $elem.parent().remove();\n }\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#notifications_form\").submit(submit);\n (0, _jquery.default)(\".delete-notification\").click(deleteNotification);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/notifications.js?");
/***/ })
/******/ });

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,169 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/ return result;
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "pages/pages": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/themes/admin/static/js";
/******/
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // add entry module to deferred list
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/pages.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
/******/ // run deferred modules when ready
/******/ return checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ({
/***/ "./CTFd/themes/admin/assets/js/pages/pages.js":
/*!****************************************************!*\
!*** ./CTFd/themes/admin/assets/js/pages/pages.js ***!
\****************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _CTFd = _interopRequireDefault(__webpack_require__(/*! core/CTFd */ \"./CTFd/themes/core/assets/js/CTFd.js\"));\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _utils = __webpack_require__(/*! core/utils */ \"./CTFd/themes/core/assets/js/utils.js\");\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction deletePage(event) {\n var elem = (0, _jquery.default)(this);\n var name = elem.attr(\"page-route\");\n var page_id = elem.attr(\"page-id\");\n (0, _ezq.ezQuery)({\n title: \"Delete \" + (0, _utils.htmlEntities)(name),\n body: \"Are you sure you want to delete {0}?\".format(\"<strong>\" + (0, _utils.htmlEntities)(name) + \"</strong>\"),\n success: function success() {\n _CTFd.default.fetch(\"/api/v1/pages/\" + page_id, {\n method: \"DELETE\"\n }).then(function (response) {\n return response.json();\n }).then(function (response) {\n if (response.success) {\n elem.parent().parent().remove();\n }\n });\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\".delete-page\").click(deletePage);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/pages.js?");
/***/ })
/******/ });

View File

@ -1,29 +0,0 @@
$(document).ready(function() {
$(".delete-page").click(function() {
var elem = $(this);
var name = elem.attr("page-route");
var page_id = elem.attr("page-id");
ezq({
title: "Delete " + name,
body: "Are you sure you want to delete {0}?".format(
"<strong>" + htmlentities(name) + "</strong>"
),
success: function() {
CTFd.fetch("/api/v1/pages/" + page_id, {
method: "DELETE"
})
.then(function(response) {
return response.json();
})
.then(function(response) {
if (response.success) {
elem
.parent()
.parent()
.remove();
}
});
}
});
});
});

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,169 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/ return result;
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "pages/reset": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/themes/admin/static/js";
/******/
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // add entry module to deferred list
/******/ deferredModules.push(["./CTFd/themes/admin/assets/js/pages/reset.js","helpers","vendor","default~pages/challenge~pages/configs~pages/editor~pages/main~pages/notifications~pages/pages~pages/~0fc9fcae"]);
/******/ // run deferred modules when ready
/******/ return checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ({
/***/ "./CTFd/themes/admin/assets/js/pages/reset.js":
/*!****************************************************!*\
!*** ./CTFd/themes/admin/assets/js/pages/reset.js ***!
\****************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
;
eval("\n\n__webpack_require__(/*! ./main */ \"./CTFd/themes/admin/assets/js/pages/main.js\");\n\nvar _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ \"./node_modules/jquery/dist/jquery.js\"));\n\nvar _ezq = __webpack_require__(/*! core/ezq */ \"./CTFd/themes/core/assets/js/ezq.js\");\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction reset(event) {\n event.preventDefault();\n (0, _ezq.ezQuery)({\n title: \"Reset CTF?\",\n body: \"Are you sure you want to reset your CTFd instance?\",\n success: function success() {\n (0, _jquery.default)(\"#reset-ctf-form\").off(\"submit\").submit();\n }\n });\n}\n\n(0, _jquery.default)(function () {\n (0, _jquery.default)(\"#reset-ctf-form\").submit(reset);\n});\n\n//# sourceURL=webpack:///./CTFd/themes/admin/assets/js/pages/reset.js?");
/***/ })
/******/ });

Some files were not shown because too many files have changed in this diff Show More