diff --git a/__init__.py b/__init__.py index 754a728..96b6b3c 100644 --- a/__init__.py +++ b/__init__.py @@ -54,12 +54,10 @@ def load(app): # Rate Limiting last_sub = Submissions.query.filter_by(user_id=user.id).order_by(Submissions.date.desc()).first() if last_sub and (time.time() - last_sub.date.timestamp() < cooldown): - return jsonify({'success': False, 'message': f'Wait {cooldown}s between tries'}) + return jsonify({'success': False, 'message': f'Wait {cooldown}s'}) - # Optimized Solve Check (Unified User/Team Mode) + # Find unsolved challenges solve_filter = (Solves.team_id == team.id) if team else (Solves.user_id == user.id) - - # Only query unsolved challenges in the specific category challenges = Challenges.query.filter( Challenges.category == category, Challenges.state == 'visible', @@ -69,22 +67,21 @@ def load(app): for chall in challenges: for flag in Flags.query.filter_by(challenge_id=chall.id).all(): try: - # Supports Static, Regex, and Case-Insensitive flags via CTFd internal classes if get_flag_class(flag.type).compare(flag, provided_flag): - solve = Solves( - user_id=user.id, team_id=team.id if team else None, - challenge_id=chall.id, ip=request.remote_addr, provided=provided_flag - ) - db.session.add(solve) - db.session.add(Submissions( - user_id=user.id, team_id=team.id if team else None, - challenge_id=chall.id, ip=request.remote_addr, provided=provided_flag, type='correct' - )) + # USE NATIVE CTFd SOLVE LOGIC + # This handles Solves, Submissions, and Scoreboard updates correctly. + chal_class = get_chal_class(chall.type) + chal_class.solve(user=user, team=team, challenge=chall, request=request) + db.session.commit() - return jsonify({'success': True, 'message': f'Correct: {chall.name}'}) + return jsonify({ + 'success': True, + 'message': f'Correct! You solved: {chall.name}', + 'challenge_id': chall.id # Pass this back to help the JS + }) except Exception: continue - # Record failed attempt for audit/brute-force detection + # Log incorrect submission natively db.session.add(Submissions( user_id=user.id, team_id=team.id if team else None, challenge_id=None, ip=request.remote_addr, provided=provided_flag, type='incorrect' diff --git a/assets/category_submit.js b/assets/category_submit.js index 9ecdb2b..c30c1bd 100644 --- a/assets/category_submit.js +++ b/assets/category_submit.js @@ -1,52 +1,44 @@ (function() { if (window.location.pathname !== '/challenges') return; - function injectSubmissionBoxes(config) { + function injectBoxes(config) { const enabledCategories = config.categories || []; - const headers = document.querySelectorAll('.category-header'); - - headers.forEach(header => { + document.querySelectorAll('.category-header').forEach(header => { const catName = header.textContent.trim(); - const alreadyInjected = header.nextElementSibling && - header.nextElementSibling.classList.contains('cat-sub-row'); - - if (enabledCategories.includes(catName) && !alreadyInjected) { - // Create a row container to match CTFd's grid layout + if (enabledCategories.includes(catName) && !header.nextElementSibling.classList.contains('cat-sub-row')) { const row = document.createElement('div'); row.className = "cat-sub-row row mb-4 justify-content-center"; - - // Use col-md-8 or 10 to keep the box from being too wide row.innerHTML = `
-
- -
- +
+
+ +
+
+
+
`; - header.after(row); row.querySelector('button').onclick = function() { const btn = this; - const input = document.getElementById(`in-${catName.replace(/\s+/g, '-')}`); + const container = row.querySelector('.col-md-10'); + const input = container.querySelector('input'); + const msg = container.querySelector('.status-msg'); const val = input.value.trim(); if (!val) return; btn.disabled = true; + msg.innerHTML = 'Checking...'; - const params = new URLSearchParams({ - submission: val, - category: catName, - nonce: init.csrfNonce - }); + const params = new URLSearchParams({ submission: val, category: catName, nonce: init.csrfNonce }); fetch('/category_submit', { method: 'POST', @@ -55,33 +47,28 @@ }) .then(r => r.json()) .then(data => { - alert(data.message); - if (data.success) location.reload(); - btn.disabled = false; - }) - .catch(() => { - alert("Error submitting flag."); - btn.disabled = false; + if (data.success) { + msg.innerHTML = `✔ ${data.message}`; + input.value = ''; + // Delay reload slightly so the user sees the success message + setTimeout(() => { window.location.reload(); }, 1200); + } else { + msg.innerHTML = `✘ ${data.message}`; + btn.disabled = false; + } }); }; } }); } - fetch('/category_submit/config') - .then(r => r.json()) - .then(config => { - injectSubmissionBoxes(config); - let timeout; - const observer = new MutationObserver(() => { - clearTimeout(timeout); - timeout = setTimeout(() => { - observer.disconnect(); - injectSubmissionBoxes(config); - observer.observe(document.body, { childList: true, subtree: true }); - }, 200); - }); + fetch('/category_submit/config').then(r => r.json()).then(config => { + injectBoxes(config); + const observer = new MutationObserver(() => { + observer.disconnect(); + injectBoxes(config); observer.observe(document.body, { childList: true, subtree: true }); - }) - .catch(err => console.error("Could not load category submission config", err)); + }); + observer.observe(document.body, { childList: true, subtree: true }); + }); })(); \ No newline at end of file