From a62d1b85e022f9adbf49ec2fedbb2dacdb89d511 Mon Sep 17 00:00:00 2001 From: Skylar Ittner Date: Sun, 11 Jun 2017 23:22:13 -0600 Subject: [PATCH] Add accessible format, add support for multiple CAPTCHAS on a page (use class instead of id for container) --- api.php | 31 +++++++++--- captcheck.css | 24 +++++++++ captcheck.dist.js | 2 +- captcheck.js | 121 ++++++++++++++++++++++++++++------------------ readme.md | 10 ++-- test.html | 4 +- 6 files changed, 131 insertions(+), 61 deletions(-) diff --git a/api.php b/api.php index 9b5b8b8..0ca6505 100644 --- a/api.php +++ b/api.php @@ -35,9 +35,15 @@ switch ($VARS['action']) { $scrambled_insert[] = ["sid" => $sid, "aid" => $scrambled['real'][$i], "acode" => $scrambled['fake'][$i]]; } $database->insert("scrambled_answers", $scrambled_insert); + $questions = ["Please click on the [].", "Click the [].", "Find the []."]; + $accessible_questions = ["Please type [] here.", "Enter [] into the box.", "Type []."]; + shuffle($questions); + shuffle($accessible_questions); $resp = [ "session" => $skey, - "question" => $correct_answer['aname'], + "id_prefix" => substr(hash("md5", mt_rand()), 3, 5), + "question_i" => str_replace("[]", $correct_answer['aname'], $questions[0]), + "question_a" => str_replace("[]", $correct_answer['aname'], $accessible_questions[0]), "answers" => $scrambled["fake"] ]; exit(json_encode($resp)); @@ -88,15 +94,28 @@ switch ($VARS['action']) { echo json_encode(["session" => $VARS['session_id'], "result" => false, "msg" => "Session key already used."]); exit(); } - if (!$database->has("scrambled_answers", ["AND" => ["sid" => $sid, "acode" => $VARS['answer_id']]])) { + + $image = false; + if ($database->has("scrambled_answers", ["AND" => ["sid" => $sid, "acode" => $VARS['answer_id']]])) { + // Image maybe correct + $image = true; + } else if ($database->has("sessions", ["[>]answers" => ["aid" => "aid"]], ["AND" => ["sid" => $sid, "aname" => $VARS['answer_id']]])) { + // Accessible text correct + $image = false; + } else { + // Invalid answer echo json_encode(["session" => $VARS['session_id'], "result" => false, "msg" => "Answer invalid."]); exit(); } - $aid = $database->get('scrambled_answers', 'aid', ["AND" => ["sid" => $sid, "acode" => $VARS['answer_id']]]); - if ($database->has('sessions', ["AND" => ["sid" => $sid, "aid" => $aid]])) { - echo json_encode(["session" => $VARS['session_id'], "result" => true]); + if ($image) { + $aid = $database->get('scrambled_answers', 'aid', ["AND" => ["sid" => $sid, "acode" => $VARS['answer_id']]]); + if ($database->has('sessions', ["AND" => ["sid" => $sid, "aid" => $aid]])) { + echo json_encode(["session" => $VARS['session_id'], "result" => true]); + } else { + echo json_encode(["session" => $VARS['session_id'], "result" => false, "msg" => "Answer incorrect."]); + } } else { - echo json_encode(["session" => $VARS['session_id'], "result" => false, "msg" => "Answer incorrect."]); + echo json_encode(["session" => $VARS['session_id'], "result" => true]); } $database->update("sessions", ['expired' => 1], ["sid" => $sid]); exit(); diff --git a/captcheck.css b/captcheck.css index 83783c8..22b9c45 100644 --- a/captcheck.css +++ b/captcheck.css @@ -42,4 +42,28 @@ Don't use this file in your site; captcheck.js contains it. .captcheck_error_message { color: red; +} + +.captcheck_question_image { + display: initial; +} + +.captcheck_question_access { + display: none; +} + +.captcheck_alt_question_button { + float: right; + font-size: 80%; + cursor: pointer; + color: inherit; + text-decoration: inherit; +} + +.captcheck_answer_images { + display: initial; +} + +.captcheck_answer_access { + display: none; } \ No newline at end of file diff --git a/captcheck.dist.js b/captcheck.dist.js index 36d1f33..34d21f8 100644 --- a/captcheck.dist.js +++ b/captcheck.dist.js @@ -1 +1 @@ -function chooseAnswer(e){var a=document.getElementById("captcheck_answer_"+e);a.checked=!0}window.onload=function(){var e="https://captcheck.netsyms.com/api.php",a=function(e,a){var n=new XMLHttpRequest;n.open("GET",e,!0),n.onreadystatechange=function(){4==this.readyState&&a(this.status,this.responseText)},n.send()};a(e+"?action=new",function(a,n){var c=document.createElement("style");c.innerHTML=".captcheck_box,.captcheck_label_message,.captcheck_label_message b{color:#000;font-family:Ubuntu,Arial,sans-serif}.captcheck_box{border:1px solid #e0e0e0;border-radius:3px;display:inline-block;padding:3px;margin:5px 2px 5px 1px;background-color:#f5f5f5}.captcheck_answer_label>input{visibility:hidden;position:absolute}.captcheck_answer_label>input+img{cursor:pointer;border:2px solid transparent;border-radius:3px;min-width:32px;width:18%;max-width:64px}.captcheck_answer_label>input:checked+img{cursor:pointer;border:2px solid #424242;border-radius:3px}.captcheck_error_message{color:red}",document.body.appendChild(c);var t=document.getElementById("captcheck_container"),s=document.createElement("div");if(s.setAttribute("class","captcheck_box"),t.appendChild(s),200==a){for(var r=JSON.parse(n),i="",o=0,p=r.answers.length;p>o;o++){var d=e+"?action=img&s="+r.session+"&c="+r.answers[o];i+=""}var l=document.createElement("div");l.innerHTML=i;var h=document.createElement("div");h.setAttribute("class","captcheck_label_message"),h.innerHTML="Click on the "+r.question+":",s.appendChild(h),s.appendChild(l);var u=document.createElement("span");u.innerHTML="",s.appendChild(u)}else s.innerHTML="There was a problem loading the CAPTCHA."})}; \ No newline at end of file +function chooseAnswer(e,t){var a=document.getElementById("captcheck_"+e+"_answer_"+t);return a.checked=!0,!1}function switchMode(e){var t=document.getElementById("captcheck_"+e+"_alt_question_button"),a=document.getElementById("captcheck_"+e+"_question_image"),c=document.getElementById("captcheck_"+e+"_question_access"),n=document.getElementById("captcheck_"+e+"_answer_images"),s=document.getElementById("captcheck_"+e+"_answer_access");"> Text mode"==t.innerHTML?(t.innerHTML="> Image mode",a.style.display="none",c.style.display="initial",n.style.display="none",s.style.display="initial",s.innerHTML=""):(t.innerHTML="> Text mode",a.style.display="initial",c.style.display="none",n.style.display="initial",s.style.display="none",s.innerHTML="")}window.onload=function(){var e="https://captcheck.netsyms.com/api.php",t=document.createElement("style");t.innerHTML=".captcheck_box,.captcheck_label_message,.captcheck_label_message b{color:#000;font-family:Ubuntu,Arial,sans-serif}.captcheck_box{border:1px solid #e0e0e0;border-radius:3px;display:inline-block;padding:3px;margin:5px 2px 5px 1px;background-color:#f5f5f5}.captcheck_answer_label>input{visibility:hidden;position:absolute}.captcheck_answer_label>input+img{cursor:pointer;border:2px solid transparent;border-radius:3px;min-width:32px;width:18%;max-width:64px}.captcheck_answer_label>input:checked+img{cursor:pointer;border:2px solid #424242;border-radius:3px}.captcheck_error_message{color:red}.captcheck_question_image{display:initial}.captcheck_question_access{display:none}.captcheck_alt_question_button{float:right;font-size:80%;cursor:pointer;color:inherit;text-decoration:inherit}.captcheck_answer_images{display:initial}.captcheck_answer_access{display:none}",document.body.appendChild(t),Array.prototype.forEach.call(document.getElementsByClassName("captcheck_container"),function(t){var a=new XMLHttpRequest;a.open("GET",e+"?action=new",!0),a.onreadystatechange=function(){if(4==this.readyState){var a=this.status,c=this.responseText,n=document.createElement("div");if(n.setAttribute("class","captcheck_box"),t.appendChild(n),200==a){for(var s=JSON.parse(c),i=s.id_prefix,r="
",o=0,l=s.answers.length;l>o;o++){var p=e+"?action=img&s="+s.session+"&c="+s.answers[o];r+=""}r+="
";var d=document.createElement("div");d.innerHTML=r+"
";var _=document.createElement("div");_.setAttribute("class","captcheck_label_message"),_.setAttribute("id","captcheck_"+i+"_label_message"),_.innerHTML=""+s.question_i+""+s.question_a+"> Text mode",n.appendChild(_),n.appendChild(d);var h=document.createElement("span");h.innerHTML="",n.appendChild(h)}else n.innerHTML="There was a problem loading the CAPTCHA."}},a.send()})}; \ No newline at end of file diff --git a/captcheck.js b/captcheck.js index 44bb0e6..d3b63a3 100644 --- a/captcheck.js +++ b/captcheck.js @@ -1,60 +1,87 @@ window.onload = function () { var api_url = "https://captcheck.netsyms.com/api.php"; - var getJSON = function (url, callback) { + + /* Add custom styles */ + var styles = document.createElement('style'); + /* Remove newlines/comments from captcheck.css and put it here */ + styles.innerHTML = ".captcheck_box,.captcheck_label_message,.captcheck_label_message b{color:#000;font-family:Ubuntu,Arial,sans-serif}.captcheck_box{border:1px solid #e0e0e0;border-radius:3px;display:inline-block;padding:3px;margin:5px 2px 5px 1px;background-color:#f5f5f5}.captcheck_answer_label>input{visibility:hidden;position:absolute}.captcheck_answer_label>input+img{cursor:pointer;border:2px solid transparent;border-radius:3px;min-width:32px;width:18%;max-width:64px}.captcheck_answer_label>input:checked+img{cursor:pointer;border:2px solid #424242;border-radius:3px}.captcheck_error_message{color:red}.captcheck_question_image{display:initial}.captcheck_question_access{display:none}.captcheck_alt_question_button{float:right;font-size:80%;cursor:pointer;color:inherit;text-decoration:inherit}.captcheck_answer_images{display:initial}.captcheck_answer_access{display:none}"; + document.body.appendChild(styles); + + /* Loop over all the CAPTCHA containers on the page, setting up a different CAPTCHA in each */ + Array.prototype.forEach.call(document.getElementsByClassName("captcheck_container"), function (container) { var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); + xhr.open('GET', api_url + "?action=new", true); xhr.onreadystatechange = function () { if (this.readyState == 4) { - callback(this.status, this.responseText); - } - }; - xhr.send(); - }; - getJSON(api_url + "?action=new", function (status, json) { - /* Add custom styles */ - var styles = document.createElement('style'); - /* Remove newlines/comments from captcheck.css and put it here */ - styles.innerHTML = ".captcheck_box {font-family: Ubuntu, Arial, sans-serif; color: black; border: 1px solid #e0e0e0; border-radius: 3px; display: inline-block; padding: 3px; margin: 5px 2px 5px 1px; background-color: #f5f5f5;} .captcheck_label_message, .captcheck_label_message b {color: black; font-family: Ubuntu, Arial, sans-serif;} .captcheck_answer_label > input {visibility: hidden; position: absolute;} .captcheck_answer_label > input + img {cursor: pointer; border: 2px solid transparent; border-radius: 3px; min-width: 32px; width: 18%; max-width: 64px;} .captcheck_answer_label > input:checked + img {cursor: pointer; border: 2px solid #424242; border-radius: 3px;} .captcheck_error_message {color: red;}"; - document.body.appendChild(styles); + var status = this.status; + var json = this.responseText; + /* Create captcha div */ + var captcha = document.createElement("div"); + captcha.setAttribute("class", "captcheck_box"); + container.appendChild(captcha); - /* Get captcha container div */ - var container = document.getElementById("captcheck_container"); - /* Create captcha div */ - var captcha = document.createElement("div"); - captcha.setAttribute("class", "captcheck_box"); - container.appendChild(captcha); - - if (status == 200) { - var data = JSON.parse(json); - /* Create answer buttons */ - var answers = ""; - for (var i = 0, len = data.answers.length; i < len; i++) { - var src = api_url + "?action=img&s=" + data.session + "&c=" + data.answers[i]; - answers += ""; - } - var answer_div = document.createElement("div"); - answer_div.innerHTML = answers; - /* Create question */ - var question_div = document.createElement("div"); - question_div.setAttribute("class", "captcheck_label_message"); - question_div.innerHTML = "Click on the " + data.question + ":"; + if (status == 200) { + var data = JSON.parse(json); + // ID prefix to use for this instance + var idp = data.id_prefix; + /* Create answer buttons */ + var answers = "
"; + for (var i = 0, len = data.answers.length; i < len; i++) { + var src = api_url + "?action=img&s=" + data.session + "&c=" + data.answers[i]; + answers += ""; + } + answers += "
"; + var answer_div = document.createElement("div"); + answer_div.innerHTML = answers + "
"; + /* Create question */ + var question_div = document.createElement("div"); + question_div.setAttribute("class", "captcheck_label_message"); + question_div.setAttribute("id", "captcheck_" + idp + "_label_message") + question_div.innerHTML = "" + data.question_i + "" + data.question_a + "> Text mode"; - /* Add question and answers */ - captcha.appendChild(question_div); - captcha.appendChild(answer_div); + /* Add question and answers */ + captcha.appendChild(question_div); + captcha.appendChild(answer_div); - /* Add hidden session ID element */ - var skey_input = document.createElement("span"); - skey_input.innerHTML = ""; - captcha.appendChild(skey_input); - } else { - /* Add error message */ - captcha.innerHTML = "There was a problem loading the CAPTCHA."; - } + /* Add hidden session ID element */ + var skey_input = document.createElement("span"); + skey_input.innerHTML = ""; + captcha.appendChild(skey_input); + } else { + /* Add error message */ + captcha.innerHTML = "There was a problem loading the CAPTCHA."; + } + } + }; + xhr.send(); }); } -function chooseAnswer(ans) { - var box = document.getElementById("captcheck_answer_" + ans); +function chooseAnswer(idp, ans) { + var box = document.getElementById("captcheck_" + idp + "_answer_" + ans); box.checked = true; + return false; +} + +function switchMode(idp) { + var switch_label = document.getElementById("captcheck_" + idp + "_alt_question_button"); + var img_q = document.getElementById("captcheck_" + idp + "_question_image"); + var acc_q = document.getElementById("captcheck_" + idp + "_question_access"); + var img_a = document.getElementById("captcheck_" + idp + "_answer_images"); + var acc_a = document.getElementById("captcheck_" + idp + "_answer_access"); + if (switch_label.innerHTML == "> Text mode") { + switch_label.innerHTML = "> Image mode"; + img_q.style.display = "none"; + acc_q.style.display = "initial"; + img_a.style.display = "none"; + acc_a.style.display = "initial"; + acc_a.innerHTML = ""; + } else { + switch_label.innerHTML = "> Text mode"; + img_q.style.display = "initial"; + acc_q.style.display = "none"; + img_a.style.display = "initial"; + acc_a.style.display = "none"; + acc_a.innerHTML = ""; + } } \ No newline at end of file diff --git a/readme.md b/readme.md index 05ca9ef..0ff0356 100644 --- a/readme.md +++ b/readme.md @@ -8,8 +8,8 @@ IE9+. Uses icons from Font-Awesome. How to use ---------- -In your form, put an empty div with the ID "captcheck_container". -Add `captcheck.js` into your page. +In your form, put an empty div with the class "captcheck_container". +Add `captcheck.js` (or `captcheck.dist.js`) into your page. @@ -22,7 +22,7 @@ Add `captcheck.js` into your page.
-
+
@@ -65,7 +65,7 @@ Execution Flow JS -> API: Request session ID, question, and answers (with scrambled random codes) API -> JS: Sends info, saves session ID, correct answer, and scrambled answer codes in DB JS -> API: Requests answer images by sending scrambled value and session ID - JS -> FORM: Adds hidden field with value=session ID, displays question and images + JS -> FORM: Adds hidden field with value=session ID, displays question and images (or text box) [USER SUBMITS FORM] - SITE -> API: Sends session ID and scrambled answer + SITE -> API: Sends session ID and answer API -> SITE: Responds with true/false to indicate if the answer is valid, marks session as expired to prevent CAPTCHA reuse diff --git a/test.html b/test.html index 75e6bb6..6d21587 100644 --- a/test.html +++ b/test.html @@ -7,9 +7,9 @@ -
+ -
+