mikuli.cz

:)
git clone https://git.sr.ht/~ashymad/mikuli.cz
Log | Files | Refs

commit f7e00cd7e759185eb10292c55073f4f139f468b5
parent 6299c44a8990c92e45d7c59364af40d46bb4529f
Author: markseu <mark2011@mayberg.se>
Date:   Mon, 21 May 2018 23:13:32 +0200

Updated editor, improved token handling and brute force protection

Diffstat:
Msystem/plugins/edit.css | 9++++++++-
Msystem/plugins/edit.js | 49++++++++++++++++++++++++++++---------------------
Msystem/plugins/edit.php | 271++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
3 files changed, 195 insertions(+), 134 deletions(-)

diff --git a/system/plugins/edit.css b/system/plugins/edit.css @@ -171,11 +171,18 @@ #yellow-pane-signup-fields { width:15em; text-align:left; margin:0 auto; } #yellow-pane-signup-buttons { margin-top:-0.5em; } +#yellow-pane-forgot { text-align:center; white-space:nowrap; } +#yellow-pane-forgot .yellow-form-control { width:15em; box-sizing:border-box; } +#yellow-pane-forgot .yellow-btn { width:15em; margin:1em 1em 0.5em 0; } +#yellow-pane-forgot-status { margin:0.5em 0; display:inline-block; } +#yellow-pane-forgot-fields { width:15em; text-align:left; margin:0 auto; } +#yellow-pane-forgot-buttons { margin-top:-0.5em; } + #yellow-pane-recover { text-align:center; white-space:nowrap; } #yellow-pane-recover .yellow-form-control { width:15em; box-sizing:border-box; } #yellow-pane-recover .yellow-btn { width:15em; margin:1em 1em 0.5em 0; } #yellow-pane-recover-status { margin:0.5em 0; display:inline-block; } -#yellow-pane-recover-fields-first, #yellow-pane-recover-fields-second { width:15em; text-align:left; margin:0 auto; } +#yellow-pane-recover-fields { width:15em; text-align:left; margin:0 auto; } #yellow-pane-recover-buttons { margin-top:-0.5em; } #yellow-pane-settings { text-align:center; white-space:nowrap; } diff --git a/system/plugins/edit.js b/system/plugins/edit.js @@ -49,10 +49,11 @@ yellow.edit = case "signup": this.showPane("yellow-pane-signup", action, status); break; case "confirm": this.showPane("yellow-pane-signup", action, status); break; case "approve": this.showPane("yellow-pane-signup", action, status); break; - case "reactivate": this.showPane("yellow-pane-settings", action, status); break; + case "forgot": this.showPane("yellow-pane-forgot", action, status); break; case "recover": this.showPane("yellow-pane-recover", action, status); break; + case "reactivate": this.showPane("yellow-pane-settings", action, status); break; case "settings": this.showPane("yellow-pane-settings", action, status); break; - case "reconfirm": this.showPane("yellow-pane-settings", action, status); break; + case "verify": this.showPane("yellow-pane-settings", action, status); break; case "change": this.showPane("yellow-pane-settings", action, status); break; case "version": this.showPane("yellow-pane-version", action, status); break; case "update": this.sendPane("yellow-pane-update", action, status, args); break; @@ -182,7 +183,7 @@ yellow.edit = "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("LoginButton")+"\" /></p>"+ "</div>"+ "<div id=\"yellow-pane-login-buttons\">"+ - "<p><a href=\"#\" id=\"yellow-pane-login-recover\" data-action=\"recover\">"+this.getText("LoginRecover")+"</a><p>"+ + "<p><a href=\"#\" id=\"yellow-pane-login-forgot\" data-action=\"forgot\">"+this.getText("LoginForgot")+"</a><p>"+ "<p><a href=\"#\" id=\"yellow-pane-login-signup\" data-action=\"signup\">"+this.getText("LoginSignup")+"</a><p>"+ "</div>"+ "</form>"; @@ -205,18 +206,29 @@ yellow.edit = "</div>"+ "</form>"; break; + case "yellow-pane-forgot": + elementDiv.innerHTML = + "<form method=\"post\">"+ + "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+ + "<h1>"+this.getText("ForgotTitle")+"</h1>"+ + "<div id=\"yellow-pane-forgot-status\" class=\""+paneStatus+"\">"+this.getText(paneAction+"Status", "", paneStatus)+"</div>"+ + "<div id=\"yellow-pane-forgot-fields\">"+ + "<input type=\"hidden\" name=\"action\" value=\"forgot\" />"+ + "<p><label for=\"yellow-pane-forgot-email\">"+this.getText("ForgotEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-forgot-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+ + "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("OkButton")+"\" /></p>"+ + "</div>"+ + "<div id=\"yellow-pane-forgot-buttons\">"+ + "<p><a href=\"#\" class=\"yellow-btn\" data-action=\"close\">"+this.getText("OkButton")+"</a></p>"+ + "</div>"+ + "</form>"; + break; case "yellow-pane-recover": elementDiv.innerHTML = "<form method=\"post\">"+ "<a href=\"#\" class=\"yellow-close\" data-action=\"close\"><i class=\"yellow-icon yellow-icon-close\"></i></a>"+ "<h1>"+this.getText("RecoverTitle")+"</h1>"+ "<div id=\"yellow-pane-recover-status\" class=\""+paneStatus+"\">"+this.getText(paneAction+"Status", "", paneStatus)+"</div>"+ - "<div id=\"yellow-pane-recover-fields-first\">"+ - "<input type=\"hidden\" name=\"action\" value=\"recover\" />"+ - "<p><label for=\"yellow-pane-recover-email\">"+this.getText("RecoverEmail")+"</label><br /><input class=\"yellow-form-control\" name=\"email\" id=\"yellow-pane-recover-email\" maxlength=\"64\" value=\""+yellow.toolbox.encodeHtml(this.getRequest("email"))+"\" /></p>"+ - "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("OkButton")+"\" /></p>"+ - "</div>"+ - "<div id=\"yellow-pane-recover-fields-second\">"+ + "<div id=\"yellow-pane-recover-fields\">"+ "<p><label for=\"yellow-pane-recover-password\">"+this.getText("RecoverPassword")+"</label><br /><input class=\"yellow-form-control\" type=\"password\" name=\"password\" id=\"yellow-pane-recover-password\" maxlength=\"64\" value=\"\" /></p>"+ "<p><input class=\"yellow-btn\" type=\"submit\" value=\""+this.getText("OkButton")+"\" /></p>"+ "</div>"+ @@ -318,7 +330,7 @@ yellow.edit = updatePane: function(paneId, paneAction, paneStatus, init) { if(yellow.config.debug) console.log("yellow.edit.updatePane id:"+paneId); - var showFields = paneStatus!="next" && paneStatus!="done" && paneStatus!="expired"; + var showFields = paneStatus!="next" && paneStatus!="done"; switch(paneId) { case "yellow-pane-login": @@ -331,19 +343,13 @@ yellow.edit = yellow.toolbox.setVisible(document.getElementById("yellow-pane-signup-fields"), showFields); yellow.toolbox.setVisible(document.getElementById("yellow-pane-signup-buttons"), !showFields); break; + case "yellow-pane-forgot": + yellow.toolbox.setVisible(document.getElementById("yellow-pane-forgot-fields"), showFields); + yellow.toolbox.setVisible(document.getElementById("yellow-pane-forgot-buttons"), !showFields); + break; case "yellow-pane-recover": - yellow.toolbox.setVisible(document.getElementById("yellow-pane-recover-fields-first"), showFields); - yellow.toolbox.setVisible(document.getElementById("yellow-pane-recover-fields-second"), showFields); + yellow.toolbox.setVisible(document.getElementById("yellow-pane-recover-fields"), showFields); yellow.toolbox.setVisible(document.getElementById("yellow-pane-recover-buttons"), !showFields); - if(showFields) - { - if(this.getRequest("id")) - { - yellow.toolbox.setVisible(document.getElementById("yellow-pane-recover-fields-first"), false); - } else { - yellow.toolbox.setVisible(document.getElementById("yellow-pane-recover-fields-second"), false); - } - } break; case "yellow-pane-settings": yellow.toolbox.setVisible(document.getElementById("yellow-pane-settings-fields"), showFields); @@ -423,6 +429,7 @@ yellow.edit = { case "yellow-pane-login": case "yellow-pane-signup": + case "yellow-pane-forgot": case "yellow-pane-recover": case "yellow-pane-settings": case "yellow-pane-version": diff --git a/system/plugins/edit.php b/system/plugins/edit.php @@ -5,7 +5,7 @@ class YellowEdit { - const VERSION = "0.7.15"; + const VERSION = "0.7.16"; var $yellow; //access to API var $response; //web response var $users; //user accounts @@ -187,7 +187,7 @@ class YellowEdit function processRequest($scheme, $address, $base, $location, $fileName) { $statusCode = 0; - if($this->checkUser($scheme, $address, $base, $location, $fileName)) + if($this->checkUserAuth($scheme, $address, $base, $location, $fileName)) { switch($_REQUEST["action"]) { @@ -203,21 +203,22 @@ class YellowEdit case "preview": $statusCode = $this->processRequestPreview($scheme, $address, $base, $location, $fileName); break; case "upload": $statusCode = $this->processRequestUpload($scheme, $address, $base, $location, $fileName); break; } - } else { + } else if($this->checkUserUnauth($scheme, $address, $base, $location, $fileName)) { $this->yellow->lookup->requestHandler = "core"; switch($_REQUEST["action"]) { case "": $statusCode = $this->processRequestShow($scheme, $address, $base, $location, $fileName); break; case "signup": $statusCode = $this->processRequestSignup($scheme, $address, $base, $location, $fileName); break; + case "forgot": $statusCode = $this->processRequestForgot($scheme, $address, $base, $location, $fileName); break; case "confirm": $statusCode = $this->processRequestConfirm($scheme, $address, $base, $location, $fileName); break; case "approve": $statusCode = $this->processRequestApprove($scheme, $address, $base, $location, $fileName); break; - case "reactivate": $statusCode = $this->processRequestReactivate($scheme, $address, $base, $location, $fileName); break; case "recover": $statusCode = $this->processRequestRecover($scheme, $address, $base, $location, $fileName); break; - case "reconfirm": $statusCode = $this->processRequestReconfirm($scheme, $address, $base, $location, $fileName); break; + case "reactivate": $statusCode = $this->processRequestReactivate($scheme, $address, $base, $location, $fileName); break; + case "verify": $statusCode = $this->processRequestVerify($scheme, $address, $base, $location, $fileName); break; case "change": $statusCode = $this->processRequestChange($scheme, $address, $base, $location, $fileName); break; } - $this->checkUserFailed($scheme, $address, $base, $location, $fileName); } + $this->checkUserFailed($scheme, $address, $base, $location, $fileName); return $statusCode; } @@ -310,7 +311,7 @@ class YellowEdit $this->response->action = "confirm"; $this->response->status = "ok"; $email = $_REQUEST["email"]; - $this->response->status = $this->users->getResponseStatus($email, $_REQUEST["action"], $_REQUEST["expire"], $_REQUEST["id"]); + $this->response->status = $this->getUserStatus($email, $_REQUEST["action"]); if($this->response->status=="ok") { $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); @@ -332,7 +333,7 @@ class YellowEdit $this->response->action = "approve"; $this->response->status = "ok"; $email = $_REQUEST["email"]; - $this->response->status = $this->users->getResponseStatus($email, $_REQUEST["action"], $_REQUEST["expire"], $_REQUEST["id"]); + $this->response->status = $this->getUserStatus($email, $_REQUEST["action"]); if($this->response->status=="ok") { $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); @@ -348,18 +349,18 @@ class YellowEdit return $statusCode; } - // Process request to reactivate account - function processRequestReactivate($scheme, $address, $base, $location, $fileName) + // Process request for forgotten password + function processRequestForgot($scheme, $address, $base, $location, $fileName) { - $this->response->action = "reactivate"; + $this->response->action = "forgot"; $this->response->status = "ok"; - $email = $_REQUEST["email"]; - $this->response->status = $this->users->getResponseStatus($email, $_REQUEST["action"], $_REQUEST["expire"], $_REQUEST["id"]); + $email = trim($_REQUEST["email"]); + if(!filter_var($email, FILTER_VALIDATE_EMAIL)) $this->response->status = "invalid"; + if($this->response->status=="ok" && !$this->users->isExisting($email)) $this->response->status = "next"; if($this->response->status=="ok") { - $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); - $this->response->status = $this->users->update($fileNameUser, $email, "", "", "", "active") ? "done" : "error"; - if($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); + $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "recover") ? "next" : "error"; + if($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!"); } $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false); return $statusCode; @@ -372,40 +373,46 @@ class YellowEdit $this->response->status = "ok"; $email = trim($_REQUEST["email"]); $password = trim($_REQUEST["password"]); - if(empty($_REQUEST["id"])) + $this->response->status = $this->getUserStatus($email, $_REQUEST["action"]); + if($this->response->status=="ok") { - if(!filter_var($email, FILTER_VALIDATE_EMAIL)) $this->response->status = "invalid"; - if($this->response->status=="ok" && !$this->users->isExisting($email)) $this->response->status = "next"; + if(empty($password)) $this->response->status = "password"; + if($this->response->status=="ok") $this->response->status = $this->getUserAccount($email, $password, $this->response->action); if($this->response->status=="ok") { - $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "recover") ? "next" : "error"; - if($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!"); + $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); + $this->response->status = $this->users->update($fileNameUser, $email, $password) ? "ok" : "error"; + if($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); } - } else { - $this->response->status = $this->users->getResponseStatus($email, $_REQUEST["action"], $_REQUEST["expire"], $_REQUEST["id"]); if($this->response->status=="ok") { - if(empty($password)) $this->response->status = "password"; - if($this->response->status=="ok") $this->response->status = $this->getUserAccount($email, $password, $this->response->action); - if($this->response->status=="ok") - { - $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); - $this->response->status = $this->users->update($fileNameUser, $email, $password) ? "ok" : "error"; - if($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); - } - if($this->response->status=="ok") - { - $this->response->userEmail = ""; - $this->response->destroyCookies($scheme, $address, $base); - $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "information") ? "done" : "error"; - if($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!"); - } + $this->response->userEmail = ""; + $this->response->destroyCookies($scheme, $address, $base); + $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, "information") ? "done" : "error"; + if($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!"); } } $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false); return $statusCode; } + // Process request to reactivate account + function processRequestReactivate($scheme, $address, $base, $location, $fileName) + { + $this->response->action = "reactivate"; + $this->response->status = "ok"; + $email = $_REQUEST["email"]; + $this->response->status = $this->getUserStatus($email, $_REQUEST["action"]); + if($this->response->status=="ok") + { + $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); + $this->response->status = $this->users->update($fileNameUser, $email, "", "", "", "active") ? "done" : "error"; + if($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); + } + $statusCode = $this->yellow->processRequest($scheme, $address, $base, $location, $fileName, false); + return $statusCode; + } + // Process request to change settings function processRequestSettings($scheme, $address, $base, $location, $fileName) { @@ -426,7 +433,7 @@ class YellowEdit $pending = $emailSource; $home = $this->users->getHome($emailSource); $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); - $this->response->status = $this->users->update($fileNameUser, $email, "no", $name, $language, "unconfirmed", "", "", "", $pending, $home) ? "ok" : "error"; + $this->response->status = $this->users->update($fileNameUser, $email, "no", $name, $language, "unverified", "", "", "", $pending, $home) ? "ok" : "error"; if($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); } if($this->response->status=="ok") @@ -438,7 +445,7 @@ class YellowEdit } if($this->response->status=="ok") { - $action = $email!=$emailSource ? "reconfirm" : "change"; + $action = $email!=$emailSource ? "verify" : "change"; $this->response->status = $this->response->sendMail($scheme, $address, $base, $email, $action) ? "next" : "error"; if($this->response->status=="error") $this->yellow->page->error(500, "Can't send email on this server!"); } @@ -460,13 +467,13 @@ class YellowEdit return $statusCode; } - // Process request to reconfirm email - function processRequestReconfirm($scheme, $address, $base, $location, $fileName) + // Process request to verify email + function processRequestVerify($scheme, $address, $base, $location, $fileName) { - $this->response->action = "reconfirm"; + $this->response->action = "verify"; $this->response->status = "ok"; $email = $emailSource = $_REQUEST["email"]; - $this->response->status = $this->users->getResponseStatus($email, $_REQUEST["action"], $_REQUEST["expire"], $_REQUEST["id"]); + $this->response->status = $this->getUserStatus($email, $_REQUEST["action"]); if($this->response->status=="ok") { $emailSource = $this->users->getPending($email); @@ -487,13 +494,13 @@ class YellowEdit return $statusCode; } - // Process request to change account + // Process request to change email or password function processRequestChange($scheme, $address, $base, $location, $fileName) { $this->response->action = "change"; $this->response->status = "ok"; $email = $emailSource = trim($_REQUEST["email"]); - $this->response->status = $this->users->getResponseStatus($email, $_REQUEST["action"], $_REQUEST["expire"], $_REQUEST["id"]); + $this->response->status = $this->getUserStatus($email, $_REQUEST["action"]); if($this->response->status=="ok") { list($email, $hash) = explode(':', $this->users->getPending($email), 2); @@ -503,7 +510,7 @@ class YellowEdit { $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); $this->users->users[$emailSource]["pending"] = "none"; - $this->response->status = $this->users->update($fileNameUser, $emailSource, "", "", "", "inactive") ? "ok" : "error"; + $this->response->status = $this->users->update($fileNameUser, $emailSource, "", "", "", "removed") ? "ok" : "error"; if($this->response->status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); } if($this->response->status=="ok") @@ -741,8 +748,8 @@ class YellowEdit return $this->response->isActive(); } - // Check user - function checkUser($scheme, $address, $base, $location, $fileName) + // Check user authentication + function checkUserAuth($scheme, $address, $base, $location, $fileName) { if($this->isRequestSameSite("POST", $scheme, $address) || $_REQUEST["action"]=="") { @@ -757,8 +764,9 @@ class YellowEdit $this->response->userRestrictions = $this->getUserRestrictions($email, $location, $fileName); $this->response->language = $this->getUserLanguage($email); } else { - $this->response->userFailed = true; + $this->response->userFailedError = "login"; $this->response->userFailedEmail = $email; + $this->response->userFailedExpire = PHP_INT_MAX; } } else if(isset($_COOKIE["authtoken"]) && isset($_COOKIE["csrftoken"])) { if($this->users->checkAuthToken($_COOKIE["authtoken"], $_COOKIE["csrftoken"], $_POST["csrftoken"], $_REQUEST["action"]=="")) @@ -767,22 +775,43 @@ class YellowEdit $this->response->userRestrictions = $this->getUserRestrictions($email, $location, $fileName); $this->response->language = $this->getUserLanguage($email); } else { - $this->response->userFailed = true; + $this->response->userFailedError = "auth"; $this->response->userFailedEmail = $this->users->getAuthEmail($_COOKIE["authtoken"]); + $this->response->userFailedExpire = $this->users->getAuthExpire($_COOKIE["authtoken"]); } } } return $this->response->isUser(); } - + + // Check user without authentication + function checkUserUnauth($scheme, $address, $base, $location, $fileName) + { + $ok = false; + if($_REQUEST["action"]=="" || $_REQUEST["action"]=="signup" || $_REQUEST["action"]=="forgot") + { + $ok = true; + } else if(isset($_REQUEST["actiontoken"])) { + if($this->users->checkActionToken($_REQUEST["actiontoken"], $_REQUEST["email"], $_REQUEST["action"], $_REQUEST["expire"])) + { + $ok = true; + } else { + $this->response->userFailedError = "action"; + $this->response->userFailedEmail = $_REQUEST["email"]; + $this->response->userFailedExpire = $_REQUEST["expire"]; + } + } + return $ok; + } + // Check user failed function checkUserFailed($scheme, $address, $base, $location, $fileName) { - if($this->response->userFailed) + if(!empty($this->response->userFailedError)) { - $email = $this->response->userFailedEmail; - if($this->users->isExisting($email)) + if($this->response->userFailedExpire>time() && $this->users->isExisting($this->response->userFailedEmail)) { + $email = $this->response->userFailedEmail; $modified = $this->users->getModified($email); $errors = $this->users->getErrors($email)+1; $fileNameUser = $this->yellow->config->get("configDir").$this->yellow->config->get("editUserFile"); @@ -790,24 +819,47 @@ class YellowEdit if($status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); if($errors==$this->yellow->config->get("editBruteForceProtection")) { + $statusBeforeProtection = $this->users->getStatus($email); + $statusAfterProtection = ($statusBeforeProtection=="active" || $statusBeforeProtection=="inactive") ? "inactive" : "removed"; if($status=="ok") { - $status = $this->users->update($fileNameUser, $email, "", "", "", "inactive", "", $modified, $errors) ? "ok" : "error"; + $status = $this->users->update($fileNameUser, $email, "", "", "", $statusAfterProtection, "", $modified, $errors) ? "ok" : "error"; if($status=="error") $this->yellow->page->error(500, "Can't write file '$fileNameUser'!"); } - if($status=="ok") + if($status=="ok" && $statusBeforeProtection=="active") { $status = $this->response->sendMail($scheme, $address, $base, $email, "reactivate") ? "done" : "error"; if($status=="error") $this->yellow->page->error(500, "Can't send email on this server!"); } } } - $this->response->destroyCookies($scheme, $address, $base); - $this->response->status = "error"; - $this->yellow->page->error(430); + if($this->response->userFailedError=="login" || $this->response->userFailedError=="auth") + { + $this->response->destroyCookies($scheme, $address, $base); + $this->response->status = "error"; + $this->yellow->page->error(430); + } else { + $this->response->status = "error"; + $this->yellow->page->error(500, "Link has expired!"); + } } } + // Return user status changes + function getUserStatus($email, $action) + { + switch($action) + { + case "confirm": $statusExpected = "unconfirmed"; break; + case "approve": $statusExpected = "unapproved"; break; + case "recover": $statusExpected = "active"; break; + case "reactivate": $statusExpected = "inactive"; break; + case "verify": $statusExpected = "unverified"; break; + case "change": $statusExpected = "active"; break; + } + return $this->users->getStatus($email)==$statusExpected ? "ok" : "done"; + } + // Return user account changes function getUserAccount($email, $password, $action) { @@ -873,8 +925,9 @@ class YellowResponse var $active; //location is active? (boolean) var $userEmail; //user email var $userRestrictions; //user can change page? (boolean) - var $userFailed; //user failed authentication? (boolean) + var $userFailedError; //error of failed authentication var $userFailedEmail; //email of failed authentication + var $userFailedExpire; //expiration time of failed authentication var $rawDataSource; //raw data of page for comparison var $rawDataEdit; //raw data of page for editing var $rawDataOutput; //raw data of dynamic output @@ -1090,7 +1143,7 @@ class YellowResponse $data = array(); foreach($_REQUEST as $key=>$value) { - if($key=="password" || $key=="authtoken" || $key=="csrftoken" || substru($key, 0, 7)=="rawdata") continue; + if($key=="password" || $key=="authtoken" || $key=="csrftoken" || $key=="actiontoken" || substru($key, 0, 7)=="rawdata") continue; $data["request".ucfirst($key)] = trim($value); } return $data; @@ -1267,11 +1320,11 @@ class YellowResponse // Create browser cookies function createCookies($scheme, $address, $base, $email) { - $authToken = $this->plugin->users->createAuthToken($email); + $expire = time() + $this->yellow->config->get("editLoginSessionTimeout"); + $authToken = $this->plugin->users->createAuthToken($email, $expire); $csrfToken = $this->plugin->users->createCsrfToken(); - $timeout = $this->yellow->config->get("editLoginSessionTimeout"); - setcookie("authtoken", $authToken, $timeout ? time()+$timeout : 0, "$base/", "", $scheme=="https", true); - setcookie("csrftoken", $csrfToken, $timeout ? time()+$timeout : 0, "$base/", "", $scheme=="https", false); + setcookie("authtoken", $authToken, $expire, "$base/", "", $scheme=="https", true); + setcookie("csrftoken", $csrfToken, $expire, "$base/", "", $scheme=="https", false); } // Destroy browser cookies @@ -1288,9 +1341,9 @@ class YellowResponse { $url = "$scheme://$address$base/"; } else { - $expire = time()+60*60*24; - $id = $this->plugin->users->createRequestId($email, $action, $expire); - $url = "$scheme://$address$base"."/action:$action/email:$email/expire:$expire/id:$id/"; + $expire = time() + 60*60*24; + $actionToken = $this->plugin->users->createActionToken($email, $action, $expire); + $url = "$scheme://$address$base"."/action:$action/email:$email/expire:$expire/actiontoken:$actionToken/"; } if($action=="approve") { @@ -1417,7 +1470,7 @@ class YellowUsers { list($hash, $name, $language, $status, $stamp, $modified, $errors, $pending, $home) = explode(',', $matches[2]); if($errors=="none") { $home=$pending; $pending=$errors; $errors=$modified; $modified=$stamp; $stamp=$this->createStamp(); } //TODO: remove later - if(strlenu($stamp)!=20) $stamp=$this->createStamp(); //TODO: remove later, converts old file format + if(strlenb($stamp)!=20) $stamp=$this->createStamp(); //TODO: remove later, converts old file format if($status=="active" || $status=="inactive") { $pending = "none"; @@ -1502,33 +1555,42 @@ class YellowUsers // Check user authentication from tokens function checkAuthToken($authToken, $csrfTokenExpected, $csrfTokenReceived, $ignoreCsrfToken) { - $session = "$5y$".substru($authToken, 0, 96); + $signature = "$5y$".substrb($authToken, 0, 96); $email = $this->getAuthEmail($authToken); - return $this->isExisting($email) && $this->users[$email]["status"]=="active" && - $this->yellow->toolbox->verifyHash($this->users[$email]["hash"], "sha256", $session) && + $expire = $this->getAuthExpire($authToken); + return $expire>time() && $this->isExisting($email) && $this->users[$email]["status"]=="active" && + $this->yellow->toolbox->verifyHash($this->users[$email]["hash"]."auth".$expire, "sha256", $signature) && ($this->verifyToken($csrfTokenExpected, $csrfTokenReceived) || $ignoreCsrfToken); } + // Check action token + function checkActionToken($actionToken, $email, $action, $expire) + { + $signature = "$5y$".$actionToken; + return $expire>time() && $this->isExisting($email) && + $this->yellow->toolbox->verifyHash($this->users[$email]["hash"].$action.$expire, "sha256", $signature); + } + // Create authentication token - function createAuthToken($email) + function createAuthToken($email, $expire) { - $session = $this->yellow->toolbox->createHash($this->users[$email]["hash"], "sha256"); - if(empty($session)) $session = "padd"."error-hash-algorithm-sha256"; - return substru($session, 4).$this->getStamp($email); + $signature = $this->yellow->toolbox->createHash($this->users[$email]["hash"]."auth".$expire, "sha256"); + if(empty($signature)) $signature = "padd"."error-hash-algorithm-sha256"; + return substrb($signature, 4).$this->getStamp($email).dechex($expire); } - // Create CSRF token - function createCsrfToken() + // Create action token + function createActionToken($email, $action, $expire) { - return $this->yellow->toolbox->createSalt(64); + $signature = $this->yellow->toolbox->createHash($this->users[$email]["hash"].$action.$expire, "sha256"); + if(empty($signature)) $signature = "padd"."error-hash-algorithm-sha256"; + return substrb($signature, 4); } - // Create user stamp - function createStamp() + // Create CSRF token + function createCsrfToken() { - $stamp = $this->yellow->toolbox->createSalt(20); - while($this->getAuthEmail("none", $stamp)) $stamp = $this->yellow->toolbox->createSalt(20); - return $stamp; + return $this->yellow->toolbox->createSalt(64); } // Create password hash @@ -1541,39 +1603,18 @@ class YellowUsers return $hash; } - // Create request ID for action - function createRequestId($email, $action, $expire) + // Create user stamp + function createStamp() { - $id = $this->yellow->toolbox->createHash($this->users[$email]["hash"].$action.$expire, "sha256"); - if(empty($id)) $hash = "error-hash-algorithm-sha256"; - return $id; + $stamp = $this->yellow->toolbox->createSalt(20); + while($this->getAuthEmail("none", $stamp)) $stamp = $this->yellow->toolbox->createSalt(20); + return $stamp; } - // Return response status for action - function getResponseStatus($email, $action, $expire, $id) - { - $status = "done"; - switch($action) - { - case "confirm": $statusExpected = "unconfirmed"; break; - case "reconfirm": $statusExpected = "unconfirmed"; break; - case "approve": $statusExpected = "unapproved"; break; - case "reactivate": $statusExpected = "inactive"; break; - default: $statusExpected = "active"; break; - } - if($this->isExisting($email) && $this->users[$email]["status"]==$statusExpected && - $this->yellow->toolbox->verifyHash($this->users[$email]["hash"].$action.$expire, "sha256", $id)) - { - $status = "ok"; - } - if($expire<=time()) $status = "expired"; - return $status; - } - // Return user email from authentication, timing attack safe email lookup function getAuthEmail($authToken, $stamp = "") { - if(empty($stamp)) $stamp = substru($authToken, 96); + if(empty($stamp)) $stamp = substrb($authToken, 96, 20); foreach($this->users as $key=>$value) { if($this->verifyToken($value["stamp"], $stamp)) $email = $key; @@ -1581,6 +1622,12 @@ class YellowUsers return $email; } + // Return expiration time from authentication + function getAuthExpire($authToken) + { + return hexdec(substrb($authToken, 96+20)); + } + // Return user hash function getHash($email) {