mirror of
https://github.com/aljazceru/CTFd.git
synced 2025-12-17 14:04:20 +01:00
496 lines
15 KiB
JavaScript
496 lines
15 KiB
JavaScript
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(response) {
|
|
if (response.success) {
|
|
$(".challenge-state").text(response.data.state);
|
|
switch (response.data.state) {
|
|
case "visible":
|
|
$(".challenge-state")
|
|
.removeClass("badge-danger")
|
|
.addClass("badge-success");
|
|
break;
|
|
case "hidden":
|
|
$(".challenge-state")
|
|
.removeClass("badge-success")
|
|
.addClass("badge-danger");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
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);
|
|
|
|
$("#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);
|
|
});
|