Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: fixed a theoretical vuln where it was possible to guess the reco…
…very key PW recovery was also moved to the auth module
- Loading branch information
Showing
15 changed files
with
368 additions
and
436 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
146 changes: 146 additions & 0 deletions
146
application/modules/auth/controllers/Password_recovery.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
<?php | ||
|
||
class Password_recovery extends MX_Controller | ||
{ | ||
public function __construct() | ||
{ | ||
parent::__construct(); | ||
|
||
$this->load->model('password_recovery_model'); | ||
|
||
$this->load->helper('email_helper'); | ||
|
||
$this->load->library('security'); | ||
$this->load->library('form_validation'); | ||
|
||
$this->user->guestArea(); | ||
|
||
requirePermission("view"); | ||
|
||
if (!$this->config->item('has_smtp')) | ||
{ | ||
redirect('errors'); | ||
} | ||
} | ||
|
||
public function index() | ||
{ | ||
clientLang("email_sent", "recovery"); | ||
|
||
$this->template->setTitle(lang("password_recovery", "recovery")); | ||
|
||
$data = []; | ||
|
||
$content = $this->template->loadPage("password_recovery.tpl", $data); | ||
$box = $this->template->box(lang("password_recovery", "recovery"), $content); | ||
$this->template->view($box, "modules/auth/css/recovery.css", "modules/auth/js/recovery.js"); | ||
} | ||
|
||
public function create_request() | ||
{ | ||
$this->form_validation->set_rules('email', 'email', 'trim|required|valid_email'); | ||
|
||
$this->form_validation->set_error_delimiters('', ''); | ||
|
||
$data = [ | ||
"messages" => false, | ||
"success" => false | ||
]; | ||
|
||
if ($this->form_validation->run()) | ||
{ | ||
//Check csrf | ||
if ($this->input->post("token") != $this->security->get_csrf_hash()) | ||
{ | ||
$data['messages']["error"] = 'Something went wrong. Please reload the page.'; | ||
die(json_encode($data)); | ||
} | ||
|
||
$email = $this->input->post("email"); | ||
|
||
if ($this->external_account_model->emailExists($email)) | ||
{ | ||
$username = $this->password_recovery_model->get_username($email); | ||
$token = $this->generate_token($username, $email); | ||
|
||
$link = base_url() . 'password_recovery/reset_password?token=' . $token; | ||
sendMail($email, $this->config->item('server_name') . ': ' . lang("reset_password", "recovery"), $username, lang("email", "recovery") . ' <a href="' . $link . '">' . $link . '</a>', 1); | ||
|
||
$this->password_recovery_model->insert_token($token, $username, $email, $this->input->ip_address()); | ||
$this->logger->createLog("user", "recovery", "Password recovery requested", [], Logger::STATUS_SUCCEED, $this->user->getId($this->input->post("username"))); | ||
} | ||
|
||
$data['messages']["success"] = lang("email_sent", "recovery"); | ||
die(json_encode($data)); | ||
} | ||
else | ||
{ | ||
$data['messages']["error"] = validation_errors(); | ||
die(json_encode($data)); | ||
} | ||
} | ||
|
||
public function reset_password() | ||
{ | ||
clientLang("password_changed", "recovery"); | ||
|
||
$this->form_validation->set_rules('token', 'token', 'trim|required'); | ||
$this->form_validation->set_rules('new_password', 'new_password', 'trim|required|min_length[6]'); | ||
|
||
$this->form_validation->set_error_delimiters('', ''); | ||
|
||
if ($this->input->method() === 'post') | ||
{ | ||
if ($this->form_validation->run()) | ||
{ | ||
$new_password = $this->input->post('new_password'); | ||
$token = $this->input->post('token'); | ||
$token_data = $this->password_recovery_model->get_token($token); | ||
|
||
if ($this->input->post("csrf_token") != $this->security->get_csrf_hash()) | ||
{ | ||
$data['messages']["error"] = 'Something went wrong. Please reload the page.'; | ||
die(json_encode($data)); | ||
} | ||
|
||
if (!$token_data) | ||
{ | ||
$data['messages']["error"] = lang('invalid', 'recovery'); | ||
die(json_encode($data)); | ||
} | ||
|
||
$hash = $this->user->createHash($token_data['username'], $new_password); | ||
$this->external_account_model->setPassword($token_data['username'], $hash["verifier"]); | ||
|
||
$this->logger->createLog("user", "recovery", "Password changed via reset", [], Logger::STATUS_SUCCEED, $this->user->getId($token_data['username'])); | ||
|
||
$this->password_recovery_model->delete_token($token); | ||
|
||
$data['messages']["success"] = lang("password_reset_success", "recovery"); | ||
die(json_encode($data)); | ||
} | ||
else | ||
{ | ||
$data['messages']["error"] = validation_errors(); | ||
die(json_encode($data)); | ||
} | ||
} | ||
|
||
$this->template->setTitle(lang("password_reset", "recovery")); | ||
|
||
$data = []; | ||
$data['token'] = $this->input->get('token'); | ||
|
||
$content = $this->template->loadPage("password_reset.tpl", $data); | ||
$box = $this->template->box(lang("password_reset", "recovery"), $content); | ||
$this->template->view($box, "modules/auth/css/recovery.css", "modules/auth/js/recovery.js"); | ||
} | ||
|
||
private function generate_token($username, $email) | ||
{ | ||
$timestamp = time(); | ||
$random_string = bin2hex(random_bytes(32)); | ||
$token = hash('sha512', $username . $email . $timestamp . $random_string); | ||
return $token; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
.page-subbody { | ||
margin: 0; | ||
} | ||
|
||
.is-fullwidth .page_form { | ||
padding: 0; | ||
} | ||
|
||
.invalid-feedback { | ||
color: #dc3545!important; | ||
} | ||
|
||
.valid-feedback { | ||
color: #0f5132!important; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
var Recovery = { | ||
timeout: null, | ||
useCaptcha: false, | ||
|
||
request: function() { | ||
var postData = { | ||
"email": $(".email-input").val(), | ||
"csrf_token_name": Config.CSRF, | ||
"token": Config.CSRF | ||
}; | ||
|
||
clearTimeout (Recovery.timeout); | ||
Recovery.timeout = setTimeout (function() | ||
{ | ||
$.post(Config.URL + "password_recovery/create_request", postData, function(data) { | ||
try { | ||
data = JSON.parse(data); | ||
console.log(data); | ||
|
||
if(data["messages"]["error"]) { | ||
if($(".email-input").val() != "") { | ||
$(".error-feedback").addClass("invalid-feedback alert-danger d-block").removeClass("d-none").html(data["messages"]["error"]); | ||
} | ||
} | ||
else if(data["messages"]["success"]) { | ||
if($(".email-input").val() != "") { | ||
$(".error-feedback").addClass("valid-feedback alert-success d-block").removeClass("d-none").html(data["messages"]["success"]); | ||
$(".email-input").val(''); | ||
} | ||
} | ||
|
||
} catch(e) { | ||
console.error(e); | ||
console.log(data); | ||
} | ||
}); | ||
|
||
console.log(postData); | ||
|
||
}, 500); | ||
}, | ||
|
||
reset: function() { | ||
var postData = { | ||
"token": $(".token-input").val(), | ||
"new_password": $(".password-input").val(), | ||
"csrf_token_name": Config.CSRF, | ||
"csrf_token": Config.CSRF | ||
}; | ||
|
||
clearTimeout (Recovery.timeout); | ||
Recovery.timeout = setTimeout (function() | ||
{ | ||
$.post(Config.URL + "password_recovery/reset_password", postData, function(data) { | ||
try { | ||
data = JSON.parse(data); | ||
console.log(data); | ||
|
||
if(data["messages"]["error"]) { | ||
if($(".password-input").val() != "") { | ||
$(".error-feedback").addClass("invalid-feedback alert-danger d-block").removeClass("d-none").html(data["messages"]["error"]); | ||
} | ||
} | ||
else if(data["messages"]["success"]) { | ||
if($(".password-input").val() != "") { | ||
$(".error-feedback").addClass("valid-feedback alert-success d-block").removeClass("invalid-feedback alert-danger d-none").html(data["messages"]["success"]); | ||
$(".password-input, .token-input").val(''); | ||
} | ||
} | ||
|
||
} catch(e) { | ||
console.error(e); | ||
console.log(data); | ||
} | ||
}); | ||
|
||
console.log(postData); | ||
|
||
}, 500); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
$lang['email'] = "E-Mail"; | ||
$lang['new_password'] = "New password"; | ||
$lang['token'] = "Token"; | ||
$lang['recover'] = "Recover password"; | ||
$lang['password_recovery'] = "Password recovery"; | ||
$lang['password_reset'] = "Reset password"; | ||
$lang['password_reset_success'] = "The password has been successfully reset"; | ||
$lang['password_changed'] = "Password reseted"; | ||
$lang['email_sent'] = "If your E-Mail address exists in our system, an E-Mail has been sent to your registered E-Mail address. Please check your inbox to proceed."; | ||
$lang['reset_password'] = "Reset your password"; | ||
$lang['email_text'] = "You have requested to reset your password, to complete the request please navigate to "; | ||
$lang['go_back'] = "Go back"; | ||
$lang['invalid'] = "Invalid token."; | ||
$lang['error_while_inserting'] = "Error while inserting the token."; | ||
$lang['lost_password'] = "Lost password?"; | ||
$lang['enter_your_email'] = "Enter your E-Mail below and we'll send you a link to reset your password."; |
76 changes: 76 additions & 0 deletions
76
application/modules/auth/models/Password_recovery_model.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
|
||
class Password_recovery_model extends CI_Model | ||
{ | ||
private $connection; | ||
|
||
public function __construct() | ||
{ | ||
if (empty($this->connection)) | ||
{ | ||
$this->connection = $this->load->database("account", true); | ||
} | ||
} | ||
|
||
public function get_username($email) | ||
{ | ||
$this->connection->select(column("account", "username")); | ||
$this->connection->from(table("account")); | ||
$this->connection->where(column("account", "email"), $email); | ||
$query = $this->connection->get(); | ||
$result = $query->result_array(); | ||
return $result[0][column("account", "username")]; | ||
} | ||
|
||
public function get_token($token) | ||
{ | ||
if ($token) | ||
{ | ||
$this->db->select("*"); | ||
$this->db->from("password_recovery_key"); | ||
$this->db->where("recoverykey", $token); | ||
$query = $this->db->get(); | ||
|
||
$result = $query->result_array(); | ||
if (isset($result[0]["recoverykey"]) == $token) | ||
{ | ||
return $result[0]; | ||
} else { | ||
return false; | ||
} | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
public function insert_token($token, $username, $email, $ip) | ||
{ | ||
if (!$token || !$username || !$email || !$ip) | ||
{ | ||
return false; | ||
} | ||
|
||
$data = [ | ||
"recoverykey" => $token, | ||
"username" => $username, | ||
"email" => $email, | ||
"ip" => $ip, | ||
"time" => time(), | ||
]; | ||
|
||
$this->db->insert("password_recovery_key", $data); | ||
return true; | ||
} | ||
|
||
public function delete_token($token) | ||
{ | ||
if (!$token) | ||
{ | ||
return false; | ||
} | ||
|
||
$this->db->where("recoverykey", $token); | ||
$this->db->delete("password_recovery_key"); | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<div class="page-subbody"> | ||
<div class="page_form"> | ||
<h4 class="font-weight-semibold text-uppercase mb-0">{lang('lost_password', 'recovery')}</h4> | ||
<p class="text-2 opacity-7">{lang('enter_your_email', 'recovery')}</p> | ||
<form onSubmit="Recovery.request(); return false"> | ||
<div class="alert text-center error-feedback d-none" role="alert"></div> | ||
|
||
<label>{lang('email', 'recovery')}</label> | ||
<input type="email" class="email-input mb-3" required> | ||
|
||
<input type="submit" value="{lang('recover', 'recovery')}" class="nice_button text-center"> | ||
</form> | ||
</div> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<div class="page-subbody"> | ||
<div class="page_form"> | ||
<form onSubmit="Recovery.reset(); return false"> | ||
<div class="alert text-center error-feedback d-none" role="alert"></div> | ||
|
||
<label>{lang('token', 'recovery')}</label> | ||
<input type="text" value="{$token}" class="token-input mb-3" required> | ||
|
||
<label>{lang('new_password', 'recovery')}</label> | ||
<input type="password" class="password-input mb-3"required> | ||
|
||
<input type="submit" value="{lang('reset_password', 'recovery')}" class="nice_button text-center"> | ||
</form> | ||
</div> | ||
</div> |
Oops, something went wrong.