ver 1.0
This commit is contained in:
parent
f08ed3a2ba
commit
299faf5402
|
@ -0,0 +1 @@
|
||||||
|
config.inc.php
|
16
README.md
16
README.md
|
@ -1,2 +1,16 @@
|
||||||
#### Password Recovery Plugin for Roundcube
|
#### Password Recovery Plugin for Roundcube
|
||||||
Plugin that adds functionality so that a user can create a new password if the original is lost.
|
|
||||||
|
Plugin that adds functionality so that a user can
|
||||||
|
create a new password if the original is lost.
|
||||||
|
|
||||||
|
To restore the password, the user is asked a secret question,
|
||||||
|
and/or a confirmation code is sent to an additional email address
|
||||||
|
and SMS to the phone.
|
||||||
|
|
||||||
|
It is recommended that you use the "SMSTools" package to send SMS.
|
||||||
|
|
||||||
|
When checking and saving a new password,
|
||||||
|
the password is encrypted using the MD5-Crypt method.
|
||||||
|
The password is written directly to the Postfix database (mailbox table).
|
||||||
|
|
||||||
|
The Password plugin can also be used when configured accordingly.
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# only for Russia!!!
|
||||||
|
COUNTRY_CODE="7"
|
||||||
|
|
||||||
|
USER="smsd"
|
||||||
|
GROUP="smsd"
|
||||||
|
SPOOLDIR="/var/spool/sms/outgoing/"
|
||||||
|
|
||||||
|
if [ -z "$*" ]; then
|
||||||
|
echo "Usage: ./sendsms.sh \"phone number\" \"message\""
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DST=$1
|
||||||
|
MSG=$2
|
||||||
|
|
||||||
|
if [[ -z "${DST}" ]]; then
|
||||||
|
echo "No destination phone number"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${MSG}" ]]; then
|
||||||
|
echo "No message"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $DST == +* ]]; then
|
||||||
|
DST=${DST:1:11}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#DST} == 10 ]]; then
|
||||||
|
DST="$COUNTRY_CODE$DST"
|
||||||
|
elif [[ ${#DST} == 11 && $DST == 8* ]]; then
|
||||||
|
DST="$COUNTRY_CODE${DST:1:10}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#DST} != 11 ]]; then
|
||||||
|
echo "Error in destination phone number"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SMS=$(mktemp /tmp/sms_XXXXXXX)
|
||||||
|
chown :${GROUP} ${SMS}
|
||||||
|
chmod 0666 ${SMS}
|
||||||
|
|
||||||
|
echo "To: ${DST}" >> $SMS
|
||||||
|
echo "" >> $SMS
|
||||||
|
echo $MSG >> $SMS
|
||||||
|
|
||||||
|
mv ${SMS} ${SPOOLDIR}
|
||||||
|
|
||||||
|
echo 1
|
||||||
|
exit 1
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "AlfnRU/password_recovery",
|
||||||
|
"type": "roundcube-plugin",
|
||||||
|
"description": "Plugin that adds functionality so that a user can create a new password if the original is lost.",
|
||||||
|
"homepage": "https://github.com/AlfnRU/roundcube-password_recovery/",
|
||||||
|
"license": "GPL",
|
||||||
|
"version": "1.0",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Alexander Alferov",
|
||||||
|
"email": "a@alfn.ru",
|
||||||
|
"role": "Lead"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://plugins.roundcube.net"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"roundcube/plugin-installer": ">=0.1.6"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Database connection string and table name with user passwords
|
||||||
|
$config['pr_db_dsn'] = 'mysql://_USER_:_PASSWORD_@localhost/postfix';
|
||||||
|
$config['pr_users_table'] = 'mailbox';
|
||||||
|
|
||||||
|
// Array with names for ext_fields in 'pr_users_table'
|
||||||
|
// When using the postfix database mailbox table, you must add two fields to this table: 'question' and 'answer'
|
||||||
|
//
|
||||||
|
// Execute this query in the postfix database:
|
||||||
|
//
|
||||||
|
// ALTER TABLE mailbox ADD question VARCHAR(255) CHARACTER SET utf8 NOT NULL AFTER email_other;
|
||||||
|
// ALTER TABLE mailbox ADD answer VARCHAR(255) CHARACTER SET utf8 NOT NULL AFTER question;
|
||||||
|
//
|
||||||
|
$config['pr_fields'] = [
|
||||||
|
'altemail' => 'email_other',
|
||||||
|
'phone' => 'phone',
|
||||||
|
'question' => 'question',
|
||||||
|
'answer' => 'answer',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Admin email (this account will receive alerts when an user does not have an alternative email and phone)
|
||||||
|
$config['pr_admin_email'] = 'postmaster@your.domain.com';
|
||||||
|
|
||||||
|
// Use secret question/answer to confirmation password recovery
|
||||||
|
$config['pr_use_question'] = false;
|
||||||
|
|
||||||
|
// Use message with code to confirmation password recovery
|
||||||
|
$config['pr_use_confirm_code'] = true;
|
||||||
|
|
||||||
|
// Minimum length of new password
|
||||||
|
$config['pr_password_minimum_length'] = 8;
|
||||||
|
|
||||||
|
// Confirmation code length
|
||||||
|
$config['pr_confirm_code_length'] = 6;
|
||||||
|
|
||||||
|
// Maximum number of attempts to send confirmation code
|
||||||
|
$config['pr_confirm_code_count_max'] = 3;
|
||||||
|
|
||||||
|
// Confirmation code duration (in minutes)
|
||||||
|
$config['pr_confirm_code_validity_time'] = 30;
|
||||||
|
|
||||||
|
// SMTP settings
|
||||||
|
// $config['pr_default_smtp_server'] = 'tls://your.domain.com';
|
||||||
|
// $config['pr_default_smtp_user'] = 'no-reply@your.domain.com';
|
||||||
|
// $config['pr_default_smtp_pass'] = 'YOUR_SMTP_USER_PASSWORD';
|
||||||
|
$config['pr_default_smtp_server'] = 'localhost';
|
||||||
|
$config['pr_default_smtp_user'] = '';
|
||||||
|
$config['pr_default_smtp_pass'] = '';
|
||||||
|
|
||||||
|
// Full path to SMS send function
|
||||||
|
// This function must accept 2 parameters: phone number and message,
|
||||||
|
// and return true on success or false on failure
|
||||||
|
//
|
||||||
|
// Example of send SMS function using Clickatell HTTP API - see /lib/send.php
|
||||||
|
//
|
||||||
|
$config['pr_sms_send_function'] = dirname(__FILE__) . '/bin/sendsms.sh';
|
||||||
|
|
||||||
|
// Enables logging of password changes into /logs/password.log
|
||||||
|
$config['pr_password_log'] = true;
|
||||||
|
|
||||||
|
// Set to TRUE if you need write debug messages into /log/console.log
|
||||||
|
$config['pr_debug'] = false;
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,132 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*******************************************
|
||||||
|
*
|
||||||
|
* Function for save with Password plugin
|
||||||
|
*
|
||||||
|
*******************************************/
|
||||||
|
|
||||||
|
define('PASSWORD_SUCCESS', 0);
|
||||||
|
define('PASSWORD_CRYPT_ERROR', 1);
|
||||||
|
define('PASSWORD_ERROR', 2);
|
||||||
|
define('PASSWORD_CONNECT_ERROR', 3);
|
||||||
|
define('PASSWORD_IN_HISTORY', 4);
|
||||||
|
define('PASSWORD_CONSTRAINT_VIOLATION', 5);
|
||||||
|
define('PASSWORD_COMPARE_CURRENT', 6);
|
||||||
|
define('PASSWORD_COMPARE_NEW', 7);
|
||||||
|
|
||||||
|
|
||||||
|
class password_recovery_pwd {
|
||||||
|
|
||||||
|
private $rc;
|
||||||
|
private $pr;
|
||||||
|
private $drivers = [];
|
||||||
|
|
||||||
|
function __construct($pr_plugin) {
|
||||||
|
$this->pr = $pr_plugin;
|
||||||
|
$this->rc = $pr_plugin->rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _check_strength($passwd)
|
||||||
|
{
|
||||||
|
$min_score = ($this->pr->use_password ? $this->rc->config->get('password_minimum_score') : $this->rc->config->get('pr_password_minimum_score'));
|
||||||
|
|
||||||
|
if (!$min_score) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->pr->use_password && ($driver = $this->_load_driver('strength')) && method_exists($driver, 'check_strength')) {
|
||||||
|
list($score, $reason) = $driver->check_strength($passwd);
|
||||||
|
} else {
|
||||||
|
$score = (!preg_match("/[0-9]/", $passwd) || !preg_match("/[^A-Za-z0-9]/", $passwd)) ? 1 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($score < $min_score) {
|
||||||
|
return $this->pr->gettext('password_check_failed') . (!empty($reason) ? " $reason" : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _save($passwd, $username)
|
||||||
|
{
|
||||||
|
if ($res = $this->_check_strength($passwd)) {
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($driver = $this->_load_driver())) {
|
||||||
|
return $this->pr->gettext('write_failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $driver->save('', $passwd, $username);
|
||||||
|
$message = '';
|
||||||
|
|
||||||
|
if (is_array($result)) {
|
||||||
|
$message = $result['message'];
|
||||||
|
$result = $result['code'];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($result) {
|
||||||
|
case PASSWORD_SUCCESS:
|
||||||
|
return PASSWORD_SUCCESS;
|
||||||
|
case PASSWORD_CRYPT_ERROR:
|
||||||
|
$reason = $this->pr->gettext('crypt_error');
|
||||||
|
break;
|
||||||
|
case PASSWORD_CONNECT_ERROR:
|
||||||
|
$reason = $this->pr->gettext('connect_error');
|
||||||
|
break;
|
||||||
|
case PASSWORD_IN_HISTORY:
|
||||||
|
$reason = $this->pr->gettext('password_in_history');
|
||||||
|
break;
|
||||||
|
case PASSWORD_CONSTRAINT_VIOLATION:
|
||||||
|
$reason = $this->pr->gettext('password_const_viol');
|
||||||
|
break;
|
||||||
|
case PASSWORD_ERROR:
|
||||||
|
default:
|
||||||
|
$reason = $this->pr->gettext('write_failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($message) {
|
||||||
|
$reason .= ' ' . $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _load_driver($type = 'password')
|
||||||
|
{
|
||||||
|
if (!($type && $driver = $this->rc->config->get('password_' . $type . '_driver'))) {
|
||||||
|
$driver = $this->rc->config->get('password_driver', 'sql');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->drivers[$type])) {
|
||||||
|
$class = "rcube_{$driver}_password";
|
||||||
|
$file = __DIR__ . "/../../password/drivers/$driver.php";
|
||||||
|
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
rcube::raise_error([
|
||||||
|
'code' => 600, 'file' => __FILE__, 'line' => __LINE__,
|
||||||
|
'message' => "Password plugin: Driver file does not exist ($file)"
|
||||||
|
], true, false
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
include_once $file;
|
||||||
|
|
||||||
|
if (!class_exists($class, false) || (!method_exists($class, 'save') && !method_exists($class, 'check_strength'))) {
|
||||||
|
rcube::raise_error([
|
||||||
|
'code' => 600, 'file' => __FILE__, 'line' => __LINE__,
|
||||||
|
'message' => "Password plugin: Broken driver $driver"
|
||||||
|
], true, false
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->drivers[$type] = new $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->drivers[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,195 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class password_recovery_send {
|
||||||
|
|
||||||
|
private $rc;
|
||||||
|
private $pr;
|
||||||
|
private $user;
|
||||||
|
|
||||||
|
function __construct($pr_plugin) {
|
||||||
|
$this->pr = $pr_plugin;
|
||||||
|
$this->rc = $pr_plugin->rc;
|
||||||
|
$this->user = $pr_plugin->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send SMS over Clickatell API
|
||||||
|
function send_sms_clickatell($to, $message) {
|
||||||
|
$clickatell_api_id = 'CHANGEME';
|
||||||
|
$clickatell_user = 'CHANGEME';
|
||||||
|
$clickatell_password = 'CHANGEME';
|
||||||
|
$clickatell_sender = 'CHANGEME';
|
||||||
|
|
||||||
|
$url = 'https://api.clickatell.com/http/sendmsg?api_id=%s&user=%s&password=%s&to=%s&from=%s&text=%s';
|
||||||
|
$url = sprintf($url, $clickatell_api_id, $clickatell_user, $clickatell_password, $to, $clickatell_sender, urlencode($message));
|
||||||
|
$result = file_get_contents($url);
|
||||||
|
return $result !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Send SMS
|
||||||
|
function send_sms($to, $message) {
|
||||||
|
$ret = false;
|
||||||
|
$to = escapeshellarg($to);
|
||||||
|
$message = escapeshellarg($message);
|
||||||
|
$sms_send_function = $this->rc->config->get('pr_sms_send_function');
|
||||||
|
if ($sms_send_function) {
|
||||||
|
if (is_file($sms_send_function)) {
|
||||||
|
$ret = (int) exec("$sms_send_function $to $message");
|
||||||
|
} else if (is_callable($sms_send_function)) {
|
||||||
|
$ret = $sms_send_function($to, $message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $ret !== false || $ret > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send E-Mail
|
||||||
|
function send_email($to, $from, $subject, $body) {
|
||||||
|
$ctb = md5(rand() . microtime());
|
||||||
|
$subject = "=?UTF-8?B?".base64_encode($subject)."?=";
|
||||||
|
|
||||||
|
$headers = "Return-Path: $from\r\n";
|
||||||
|
$headers .= "MIME-Version: 1.0\r\n";
|
||||||
|
$headers .= "Content-Type: multipart/alternative; boundary=\"=_$ctb\"\r\n";
|
||||||
|
$headers .= "Date: " . date('r', time()) . "\r\n";
|
||||||
|
$headers .= "From: $from\r\n";
|
||||||
|
$headers .= "To: $to\r\n";
|
||||||
|
$headers .= "Subject: $subject\r\n";
|
||||||
|
$headers .= "Reply-To: $from\r\n";
|
||||||
|
|
||||||
|
$txt_body = "--=_$ctb";
|
||||||
|
$txt_body .= "\r\n";
|
||||||
|
$txt_body .= "Content-Transfer-Encoding: 7bit\r\n";
|
||||||
|
$txt_body .= "Content-Type: text/plain; charset=" . $this->rc->config->get('default_charset', RCUBE_CHARSET) . "\r\n";
|
||||||
|
|
||||||
|
$h2t = new rcube_html2text($body, false, true, 0);
|
||||||
|
$txt = rcube_mime::wordwrap($h2t->get_text(), $this->rc->config->get('line_length', 75), "\r\n");
|
||||||
|
$txt = wordwrap($txt, 998, "\r\n", true);
|
||||||
|
$txt_body .= "$txt\r\n";
|
||||||
|
$txt_body .= "--=_$ctb";
|
||||||
|
$txt_body .= "\r\n";
|
||||||
|
|
||||||
|
$msg_body = "Content-Type: multipart/alternative; boundary=\"=_$ctb\"\r\n\r\n";
|
||||||
|
$msg_body .= $txt_body;
|
||||||
|
$msg_body .= "Content-Transfer-Encoding: quoted-printable\r\n";
|
||||||
|
$msg_body .= "Content-Type: text/html; charset=" . $this->rc->config->get('default_charset', RCUBE_CHARSET) . "\r\n\r\n";
|
||||||
|
$msg_body .= str_replace("=","=3D",$body);
|
||||||
|
$msg_body .= "\r\n\r\n";
|
||||||
|
$msg_body .= "--=_$ctb--";
|
||||||
|
$msg_body .= "\r\n\r\n";
|
||||||
|
|
||||||
|
// send message
|
||||||
|
if (!is_object($this->rc->smtp)) {
|
||||||
|
$this->rc->smtp_init(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->rc->config->get('smtp_pass') == "%p") {
|
||||||
|
$this->rc->config->set('smtp_server', $this->rc->config->get('pr_default_smtp_server'));
|
||||||
|
$this->rc->config->set('smtp_user', $this->rc->config->get('pr_default_smtp_user'));
|
||||||
|
$this->rc->config->set('smtp_pass', $this->rc->config->get('pr_default_smtp_pass'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rc->smtp->connect();
|
||||||
|
if($this->rc->smtp->send_mail($from, $to, $headers, $msg_body)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
rcube::write_log('errors', 'response:' . print_r($this->rc->smtp->get_response(),true));
|
||||||
|
rcube::write_log('errors', 'errors:' . print_r($this->rc->smtp->get_error(),true));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send message to administrator
|
||||||
|
function send_alert_to_admin($user_requesting_new_password) {
|
||||||
|
$file = dirname(__FILE__) . "/../localization/" . $this->rc->user->language . "/alert_for_admin_to_reset_pw.html";
|
||||||
|
$body = strtr(file_get_contents($file), array('[USER]' => $user_requesting_new_password));
|
||||||
|
$subject = $this->pr->gettext('email_subject_admin');
|
||||||
|
return $this->send_email(
|
||||||
|
$this->rc->config->get('pr_admin_email'),
|
||||||
|
$this->get_email_from($user_requesting_new_password),
|
||||||
|
$subject,
|
||||||
|
$body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send code to user
|
||||||
|
function send_confirm_code_to_user() {
|
||||||
|
$send_email = false;
|
||||||
|
$send_sms = false;
|
||||||
|
$confirm_code = $this->generate_confirm_code();
|
||||||
|
|
||||||
|
if ($confirm_code && $this->pr->set_user_props(['token'=>$confirm_code])) {
|
||||||
|
// send EMail
|
||||||
|
if ($this->user['have_altemail']) {
|
||||||
|
$file = dirname(__FILE__) . "/../localization/" . $this->rc->user->language . "/reset_pw_body.html";
|
||||||
|
$link = "http://{$_SERVER['SERVER_NAME']}/?_task=login&_action=plugin.password_recovery&_username=". $this->user['username'];
|
||||||
|
$body = strtr(file_get_contents($file), ['[LINK]' => $link, '[CODE]' => $confirm_code]);
|
||||||
|
$subject = $this->pr->gettext('email_subject');
|
||||||
|
|
||||||
|
$send_email = $this->send_email(
|
||||||
|
$this->user['altemail'],
|
||||||
|
$this->get_email_from($this->rc->config->get('pr_admin_email')),
|
||||||
|
$subject,
|
||||||
|
$body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// send SMS
|
||||||
|
if ($this->user['have_phone']) {
|
||||||
|
$send_sms = $this->send_sms(
|
||||||
|
$this->user['phone'],
|
||||||
|
$this->pr->gettext('code') . ": " . $confirm_code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// log & message
|
||||||
|
if ($send_email || $send_sms) {
|
||||||
|
$log = "Send password recovery code [". $confirm_code . "] for '" . $this->user['username'] . "'";
|
||||||
|
$message = $this->pr->gettext('check_account');
|
||||||
|
if ($send_email) {
|
||||||
|
$log .= " to alt email: '" . $this->user['altemail'] . "'";
|
||||||
|
$message .= $this->pr->gettext('check_email');
|
||||||
|
}
|
||||||
|
if ($send_sms) {
|
||||||
|
if ($send_email) {
|
||||||
|
$log .= " and";
|
||||||
|
$message .= $this->pr->gettext('and');
|
||||||
|
}
|
||||||
|
$log .= " to phone: '" . $this->user['phone'] . "'";
|
||||||
|
$message .= $this->pr->gettext('check_sms');
|
||||||
|
}
|
||||||
|
$this->pr->logging($log);
|
||||||
|
} else {
|
||||||
|
$this->pr->set_user_props(['token'=>'', 'token_validity'=>'']);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message = $this->pr->gettext('write_failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'send' => ($send_email || $send_sms),
|
||||||
|
'message' => $message
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and return a random code
|
||||||
|
function generate_confirm_code() {
|
||||||
|
$code_length = (int) $this->rc->config->get('pr_confirm_code_length', 6);
|
||||||
|
$code = "";
|
||||||
|
$possible = "0123456789";
|
||||||
|
while (strlen($code) < $code_length) {
|
||||||
|
$random = random_int(0, strlen($possible)-1);
|
||||||
|
$char = substr($possible, $random, 1);
|
||||||
|
$code .= $char;
|
||||||
|
$possible = str_replace($char,"",$possible); //removing the used character from the possible
|
||||||
|
}
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_email_from($email) {
|
||||||
|
$parts = explode('@',$email);
|
||||||
|
return 'no-reply@'.$parts[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$labels['forgot_password'] = 'Forgot password?';
|
||||||
|
$labels['recovery_password'] = 'Recovery password';
|
||||||
|
$labels['phone'] = 'Phone';
|
||||||
|
$labels['altemail'] = 'Alternate E-mail';
|
||||||
|
$labels['newpassword'] = 'New password';
|
||||||
|
$labels['newpassword_confirm'] = 'Confirmation';
|
||||||
|
$labels['code'] = 'Confirmation code for recovery password';
|
||||||
|
$labels['question'] = 'Secret question';
|
||||||
|
$labels['answer'] = 'Answer to a secret question';
|
||||||
|
$labels['answer_confirm'] = 'Confirmation';
|
||||||
|
$labels['recovery'] = 'Recovery';
|
||||||
|
$labels['save'] = 'Save';
|
||||||
|
$labels['cancel'] = 'Cancel';
|
||||||
|
$labels['email_subject'] = 'Restore mail access';
|
||||||
|
$labels['email_subject_admin'] = 'Password recovery request';
|
||||||
|
$labels['click_here'] = 'Click here';
|
||||||
|
$labels['no_identities'] = 'Complete your password recovery information.';
|
||||||
|
$labels['no_username'] = 'Enter your user name (e-mail address)';
|
||||||
|
$labels['renew_code'] = 'Send a new confirmation code';
|
||||||
|
$labels['count_send_code_maximum'] = 'Maximum number of confirmation code submissions exceeded';
|
||||||
|
$labels['no_code'] = 'Enter confirmation code';
|
||||||
|
$labels['no_answer'] = 'Enter a secret answer';
|
||||||
|
$labels['no_password'] = 'Enter new password';
|
||||||
|
$labels['no_password_confirm'] = 'Enter password confirmation';
|
||||||
|
$labels['password_inconsistency'] = 'Password and confirmation do not match';
|
||||||
|
$labels['password_too_short'] = 'The password must be at least %d characters long.';
|
||||||
|
|
||||||
|
$messages['disabled'] = 'The system is currently being maintained and password recovery is not possible. Everything should return to normal soon. We apologize for the inconvenience.';
|
||||||
|
$messages['banned'] = 'Access is temporarily blocked (too many failed login attempts). Please try again later or contact your administrator.';
|
||||||
|
$messages['no_identities'] = 'The settings required for password recovery are not configured. %s and configure!';
|
||||||
|
$messages['user_not_found'] = 'User not found';
|
||||||
|
$messages['check_account_notice'] = 'The password recovery code has already been sent to you. Check your email and phone';
|
||||||
|
$messages['check_account'] = 'Password recovery code has been sent to you ';
|
||||||
|
$messages['check_email'] = 'at an additional email address ';
|
||||||
|
$messages['check_sms'] = 'on the phone';
|
||||||
|
$messages['and'] = 'and ';
|
||||||
|
$messages['sent_to_admin'] = 'No password recovery information was found, so your request was sent to the administrator.';
|
||||||
|
$messages['send_failed'] = 'Error sending code to restore password. Try again later.';
|
||||||
|
$messages['write_failed'] = 'Error writing data. Contact your administrator.';
|
||||||
|
$messages['code_expired'] = 'Confirmation code has expired';
|
||||||
|
$messages['code_failed'] = 'Invalid confirmation code';
|
||||||
|
$messages['answer_failed'] = 'Incorrect answer to secret question';
|
||||||
|
$messages['password_invalid'] = 'Invalid password';
|
||||||
|
$messages['password_check_failed'] = 'Error. The password is too simple!';
|
||||||
|
$messages['password_forbidden'] = 'The password contains invalid characters';
|
||||||
|
$messages['password_changed'] = 'Password changed successfully!';
|
||||||
|
$messages['password_not_changed'] = 'An error occurred while changing your password. Please try again later or contact your administrator.';
|
||||||
|
$messages['crypt_error'] = 'I cannot save my new password. Missing cryptographic function.';
|
||||||
|
$messages['connect_error'] = 'I cannot save my new password. Connection error.';
|
||||||
|
$messages['password_in_history'] = 'This password has already been used.';
|
||||||
|
$messages['password_const_viol'] = 'Password restriction violation. The password may be too weak.';
|
||||||
|
$messages['phone_invalid'] = 'Wrong phone number! Number must be %d digits';
|
||||||
|
$messages['altemail_invalid'] = 'The alternate email address is incorrect!';
|
||||||
|
$messages['altemail_match_primary'] = 'The secondary email address must not be the same as the primary email address!';
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,2 @@
|
||||||
|
<p>User [USER] requested a password change but they do not have an phone and alternate email address registered.</p>
|
||||||
|
<p>Please change the password or register an phone and/or alternative email for this user.</p>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<p>Hello!</p>
|
||||||
|
<p>You have requested password recovery.</p>
|
||||||
|
<p>Confirmation code: [CODE]</p>
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$labels['forgot_password'] = 'Забыли пароль?';
|
||||||
|
$labels['recovery_password'] = 'Восстановление пароля';
|
||||||
|
$labels['phone'] = 'Телефон';
|
||||||
|
$labels['altemail'] = 'Альтернативный E-mail';
|
||||||
|
$labels['newpassword'] = 'Новый пароль';
|
||||||
|
$labels['newpassword_confirm'] = 'Подтверждение';
|
||||||
|
$labels['code'] = 'Код для восстановления пароля';
|
||||||
|
$labels['question'] = 'Секретный вопрос';
|
||||||
|
$labels['answer'] = 'Ответ на секретный вопрос';
|
||||||
|
$labels['answer_confirm'] = 'Подтверждение';
|
||||||
|
$labels['recovery'] = 'Восстановить';
|
||||||
|
$labels['save'] = 'Записать';
|
||||||
|
$labels['cancel'] = 'Отмена';
|
||||||
|
$labels['email_subject'] = 'Восстановление доступа к почте';
|
||||||
|
$labels['email_subject_admin'] = 'Запрос на восстановление пароля';
|
||||||
|
$labels['click_here'] = 'Щелкните здесь';
|
||||||
|
$labels['no_identities'] = 'Заполните реквизиты для восстановления пароля.';
|
||||||
|
$labels['no_username'] = 'Введите имя пользователя (адрес электронной почты)';
|
||||||
|
$labels['renew_code'] = 'Отправить новый код подтверждения';
|
||||||
|
$labels['count_send_code_maximum'] = 'Превышено максимальное количество отправок кода подтверждения';
|
||||||
|
$labels['no_code'] = 'Введите код подтверждения';
|
||||||
|
$labels['no_answer'] = 'Введите ответ на секретный вопрос';
|
||||||
|
$labels['no_password'] = 'Введите новый пароль';
|
||||||
|
$labels['no_password_confirm'] = 'Введите подтверждение пароля';
|
||||||
|
$labels['password_inconsistency'] = 'Пароль и подтверждение не совпадают';
|
||||||
|
$labels['password_too_short'] = 'Длина пароля должна быть как минимум %d символов.';
|
||||||
|
|
||||||
|
$messages['disabled'] = 'В настоящее время проводится обслуживание системы и восстановление пароля невозможно. Все должно вернуться в нормальное состояние в ближайшее время. Приносим свои извинения за причиненные неудобства.';
|
||||||
|
$messages['banned'] = 'Доступ временно заблокирован (слишком много неудачных попыток войти). Пожалуйста, попробуйте еще раз позже или обратитесь к администратору.';
|
||||||
|
$messages['no_identities'] = 'Не настроены параметры, требуемые для восстановления пароля. %s и настройте!';
|
||||||
|
$messages['user_not_found'] = 'Пользователь не найден';
|
||||||
|
$messages['check_account_notice'] = 'Код для восстановления пароля уже был отправлен Вам ранее. Проверьте свою почту и телефон';
|
||||||
|
$messages['check_account'] = 'Код для восстановления пароля был отправлен Вам ';
|
||||||
|
$messages['check_email'] = 'на дополнительный адрес электронной почты ';
|
||||||
|
$messages['check_sms'] = 'на телефон';
|
||||||
|
$messages['and'] = 'и ';
|
||||||
|
$messages['sent_to_admin'] = 'Не найдены реквизиты для восстановления пароля, поэтому Ваш запрос был передан администратору.';
|
||||||
|
$messages['send_failed'] = 'Ошибка при отправке кода для восстановлению пароля. Попробуйте еще раз позже.';
|
||||||
|
$messages['write_failed'] = 'Ошибка при записи данных. Обратитесь к администратору.';
|
||||||
|
$messages['code_expired'] = 'Срок действия кода подтверждения истек';
|
||||||
|
$messages['code_failed'] = 'Неверный код подтверждения';
|
||||||
|
$messages['answer_failed'] = 'Неверный ответ на секретный вопрос';
|
||||||
|
$messages['password_invalid'] = 'Неверный пароль';
|
||||||
|
$messages['password_check_failed'] = 'Ошибка. Слишком простой пароль!';
|
||||||
|
$messages['password_forbidden'] = 'Пароль содержит недопустимые символы.';
|
||||||
|
$messages['password_changed'] = 'Пароль успешно изменен!';
|
||||||
|
$messages['password_not_changed'] = 'Произошла ошибка при изменении вашего пароля. Попробуйте еще раз позже или обратитесь к администратору.';
|
||||||
|
$messages['crypt_error'] = 'Не могу сохранить новый пароль. Отсутствует криптографическая функция.';
|
||||||
|
$messages['connect_error'] = 'Не могу сохранить новый пароль. Ошибка соединения.';
|
||||||
|
$messages['password_in_history'] = 'Этот пароль уже использовался ранее.';
|
||||||
|
$messages['password_const_viol'] = 'Нарушение ограничения пароля. Вероятно, слишком слабый пароль.';
|
||||||
|
$messages['phone_invalid'] = 'Неверный номер телефона! Номер должен состоять из %d цифр';
|
||||||
|
$messages['altemail_invalid'] = 'Неверный адрес альтернативной электронной почты!';
|
||||||
|
$messages['altemail_match_primary'] = 'Дополнительный адрес электронной почты не должен совпадать с основным!';
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,2 @@
|
||||||
|
<p>Пользователь <a href=mailto:[USER]>[USER]</a> запросил(а) сброс пароля, но у него/неё не найдены параметры для восстановления пароля.</p>
|
||||||
|
<p>Пожалуйста, поменяйте пароль пользователю или настройте дополнительный адрес электронной почты и/или телефон для него/неё.</p>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<p>Здравствуйте!</p>
|
||||||
|
<p>Вы запросили восстановление пароля.</p>
|
||||||
|
<p>Код подтверждения: [CODE]</p>
|
|
@ -0,0 +1,94 @@
|
||||||
|
|
||||||
|
if (window.rcmail) {
|
||||||
|
rcmail.addEventListener('init', function(evt) {
|
||||||
|
var loginform = $('#login-form');
|
||||||
|
if (loginform) {
|
||||||
|
loginform.append('<a class="home" id="password_forgot" href="javascript:forgot_password();">' + rcmail.gettext('forgot_password','password_recovery') + '</a>');
|
||||||
|
}
|
||||||
|
|
||||||
|
var newpasswordform = $('#new-password-form');
|
||||||
|
if (newpasswordform && rcmail.env.pr_use_confirm_code) {
|
||||||
|
newpasswordform.append('<a class="home" id="renew_confirm_code" href="javascript:renew_confirm_code();">' + rcmail.gettext('renew_code','password_recovery') + '</a>');
|
||||||
|
}
|
||||||
|
|
||||||
|
rcmail.register_command('plugin.password_recovery.cancel', function() {
|
||||||
|
rcmail.http_request('plugin.password_recovery', { '_a':'cancel' });
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
rcmail.register_command('plugin.password_recovery.reset', function() {
|
||||||
|
var input_username = rcube_find_object('_username');
|
||||||
|
if (input_username && input_username.value == '') {
|
||||||
|
rcmail.alert_dialog(rcmail.get_label('no_username', 'password_recovery'), function() {
|
||||||
|
input_username.focus();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rcmail.gui_objects.recoverypasswordform.submit();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
rcmail.register_command('plugin.password_recovery.save', function() {
|
||||||
|
var input_code = rcube_find_object('_code'),
|
||||||
|
input_answer = rcube_find_object('_answer'),
|
||||||
|
input_newpassword = rcube_find_object('_newpassword'),
|
||||||
|
input_newpassword_confirm = rcube_find_object('_newpassword_confirm');
|
||||||
|
|
||||||
|
if (rcmail.env.pr_use_confirm_code && input_code && input_code.value == '') {
|
||||||
|
rcmail.alert_dialog(rcmail.get_label('no_code', 'password_recovery'), function() {
|
||||||
|
input_code.focus();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (rcmail.env.pr_use_question && input_answer && input_answer.value == '') {
|
||||||
|
rcmail.alert_dialog(rcmail.get_label('no_answer', 'password_recovery'), function() {
|
||||||
|
input_answer.focus();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (input_newpassword && input_newpassword.value == '') {
|
||||||
|
rcmail.alert_dialog(rcmail.get_label('no_password', 'password_recovery'), function() {
|
||||||
|
input_newpassword.focus();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (input_newpassword_confirm && input_newpassword_confirm.value == '') {
|
||||||
|
rcmail.alert_dialog(rcmail.get_label('no_password_confirm', 'password_recovery'), function() {
|
||||||
|
input_newpassword_confirm.focus();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (input_newpassword && input_newpassword_confirm && input_newpassword.value != input_newpassword_confirm.value) {
|
||||||
|
rcmail.alert_dialog(rcmail.get_label('password_inconsistency', 'password_recovery'), function() {
|
||||||
|
input_newpassword.focus();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (input_newpassword && input_newpassword.value.length < rcmail.env.pr_password_minimum_length) {
|
||||||
|
rcmail.alert_dialog(rcmail.get_label('password_too_short', 'password_recovery').replace('%d', minimum_length), function() {
|
||||||
|
input_newpassword.focus();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rcmail.gui_objects.newpasswordform.submit();
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
$('input:not(:hidden)').first().focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function forgot_password() {
|
||||||
|
var url = "./?_task=login&_action=plugin.password_recovery";
|
||||||
|
/* var input_user = rcube_find_object('_user');
|
||||||
|
if (input_user && input_user.value != '') {
|
||||||
|
url = url + "&_u=" + input_user.value;
|
||||||
|
}*/
|
||||||
|
document.location.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renew_confirm_code() {
|
||||||
|
rcmail.http_request('plugin.password_recovery', { '_a':'renew', '_username':rcmail.env.pr_username });
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,510 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Password recovery
|
||||||
|
*
|
||||||
|
* Plugin to reset an account password
|
||||||
|
*
|
||||||
|
* @version 1.0
|
||||||
|
* @original_author Alexander Alferov
|
||||||
|
*
|
||||||
|
* @url https://github.com/AlfnRU/roundcube-password_recovery
|
||||||
|
*/
|
||||||
|
|
||||||
|
class password_recovery extends rcube_plugin {
|
||||||
|
|
||||||
|
public $task = 'login|logout|settings|mail';
|
||||||
|
public $rc;
|
||||||
|
public $db;
|
||||||
|
public $user;
|
||||||
|
public $use_password;
|
||||||
|
public $pwd_conf;
|
||||||
|
private $pwd;
|
||||||
|
private $fields;
|
||||||
|
private $send;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
$this->rc = rcmail::get_instance();
|
||||||
|
$this->load_config();
|
||||||
|
|
||||||
|
if (!$this->rc->config->get('pr_use_confirm_code') && !$this->rc->config->get('pr_use_question'))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$this->init_ui();
|
||||||
|
$this->add_texts('localization/');
|
||||||
|
|
||||||
|
if ($this->rc->task == 'login' || $this->rc->task == 'logout') {
|
||||||
|
$this->add_hook('render_page', [$this, 'add_labels_to_login_page']);
|
||||||
|
$this->add_hook('startup', [$this, 'startup']);
|
||||||
|
$this->register_action('plugin.get_confirm_code_count', [$this, 'get_confirm_code_count']);
|
||||||
|
} else if ($this->rc->task == 'mail') {
|
||||||
|
$this->add_hook('render_page', [$this, 'add_labels_to_mail_page']);
|
||||||
|
$this->add_hook('messages_list', [$this, 'check_identities']);
|
||||||
|
} else if ($this->rc->task == 'settings') {
|
||||||
|
$this->add_hook('identity_form', [$this, 'identity_form']);
|
||||||
|
$this->add_hook('identity_update', [$this, 'identity_update']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->include_script('password_recovery.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************
|
||||||
|
* STARTUP
|
||||||
|
*******************/
|
||||||
|
|
||||||
|
function init_ui() {
|
||||||
|
if (!$this->fields) $this->fields = $this->rc->config->get('pr_fields');
|
||||||
|
if (!$this->db) $this->get_dbh();
|
||||||
|
if (!$this->user) $this->get_user_props();
|
||||||
|
|
||||||
|
$this->use_password = ($this->rc->config->get('pr_use_password_plugin') && $this->rc->plugins->load_plugin('password', true));
|
||||||
|
|
||||||
|
foreach($this->fields as $field => $field_name){
|
||||||
|
$query = "SELECT " . $field_name . " FROM " . $this->rc->config->get('pr_users_table');
|
||||||
|
$result = $this->db->query($query);
|
||||||
|
if (!$result) {
|
||||||
|
$type = ($field == 'phone' ? 'VARCHAR(30)' : 'VARCHAR(255)');
|
||||||
|
$query = "ALTER TABLE " . $this->rc->config->get('pr_users_table') . " ADD " . $field_name . " " . $type . " CHARACTER SET utf8 NOT NULL";
|
||||||
|
$result = $this->db->query($query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once $this->home . '/lib/send.php';
|
||||||
|
$this->send = new password_recovery_send($this);
|
||||||
|
|
||||||
|
require_once $this->home . '/lib/password.php';
|
||||||
|
$this->pwd = new password_recovery_pwd($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startup($p) {
|
||||||
|
if ($this->rc->action != 'plugin.password_recovery' || !isset($_SESSION['temp']))
|
||||||
|
return $p;
|
||||||
|
|
||||||
|
switch ($this->get_action()) {
|
||||||
|
case 'init':
|
||||||
|
$this->recovery_password_form();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'renew':
|
||||||
|
$this->renew_confirm_code();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'new':
|
||||||
|
$this->new_password_form();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reset':
|
||||||
|
$this->reset_password();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'save':
|
||||||
|
$this->save_password();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'cancel':
|
||||||
|
$this->rc->kill_session();
|
||||||
|
$this->rc->output->command('redirect', './');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_labels_to_login_page($p) {
|
||||||
|
if ($p['template'] == 'login') {
|
||||||
|
$this->rc->output->add_label('password_recovery.forgot_password');
|
||||||
|
}
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_labels_to_mail_page($p) {
|
||||||
|
$this->rc->output->add_label('password_recovery.no_identities');
|
||||||
|
$this->rc->output->add_script('rcmail.message_time = 10000;');
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************
|
||||||
|
* PASSWORD
|
||||||
|
*******************/
|
||||||
|
|
||||||
|
// Creating form for reset password
|
||||||
|
private function recovery_password_form() {
|
||||||
|
$this->rc->output->add_label(
|
||||||
|
'password_recovery.recovery_password',
|
||||||
|
'password_recovery.no_username'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->rc->output->set_pagetitle($this->gettext('recovery_password'));
|
||||||
|
$this->rc->output->add_gui_object('recoverypasswordform', 'recovery-password-form');
|
||||||
|
$this->rc->output->send('password_recovery.recovery_password_form');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating form for new password
|
||||||
|
private function new_password_form() {
|
||||||
|
$this->rc->output->add_label(
|
||||||
|
'password_recovery.recovery_password',
|
||||||
|
'password_recovery.renew_code',
|
||||||
|
'password_recovery.count_send_code_maximum',
|
||||||
|
'password_recovery.no_code',
|
||||||
|
'password_recovery.no_answer',
|
||||||
|
'password_recovery.no_password',
|
||||||
|
'password_recovery.no_password_confirm',
|
||||||
|
'password_recovery.password_inconsistency',
|
||||||
|
'password_recovery.password_too_short'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->rc->output->set_pagetitle($this->gettext('recovery_password'));
|
||||||
|
$this->rc->output->add_gui_object('newpasswordform', 'new-password-form');
|
||||||
|
|
||||||
|
$password_minimum_length = ($this->use_password ? $this->rc->config->get('password_minimum_length',8) : $this->rc->config->get('pr_password_minimum_length',8));
|
||||||
|
|
||||||
|
$this->rc->output->set_env('pr_username', $this->user['username']);
|
||||||
|
$this->rc->output->set_env('pr_question', $this->user['question']);
|
||||||
|
$this->rc->output->set_env('pr_use_question', (bool) ($this->rc->config->get('pr_use_question') && $this->user['have_answer']));
|
||||||
|
$this->rc->output->set_env('pr_use_confirm_code', (bool) ($this->rc->config->get('pr_use_confirm_code') && $this->user['have_code']));
|
||||||
|
$this->rc->output->set_env('pr_password_minimum_length', (int) $password_minimum_length);
|
||||||
|
|
||||||
|
$this->rc->output->send('password_recovery.new_password_form');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew and send confirmation code to user (to alternative email and phone)
|
||||||
|
private function renew_confirm_code() {
|
||||||
|
if ($this->get_confirm_code_count() < $this->rc->config->get('pr_confirm_code_count_max')) {
|
||||||
|
$result = $this->send->send_confirm_code_to_user();
|
||||||
|
if ($result['send']) {
|
||||||
|
$this->update_confirm_code_count(1);
|
||||||
|
$message = $result['message'];
|
||||||
|
$type = 'confirmation';
|
||||||
|
} else {
|
||||||
|
$message = $this->gettext('send_failed') . "\n" . $result['message'];
|
||||||
|
$type = 'error';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$message = $this->gettext('count_send_code_maximum');
|
||||||
|
$type = 'error';
|
||||||
|
}
|
||||||
|
$this->rc->output->command('display_message', $message, $type);
|
||||||
|
$this->rc->output->send('plugin');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating and send confirmation code to user (to alternative email and phone) or send message to administrator
|
||||||
|
private function reset_password() {
|
||||||
|
// kill remember_me cookies
|
||||||
|
setcookie ('rememberme_user', '', time()-3600);
|
||||||
|
setcookie ('rememberme_pass', '', time()-3600);
|
||||||
|
|
||||||
|
$allow_answer = ($this->rc->config->get('pr_use_question') && $this->user['have_answer']);
|
||||||
|
$allow_code = ($this->rc->config->get('pr_use_confirm_code') && $this->user['have_code']);
|
||||||
|
|
||||||
|
if (!$this->user['username']) {
|
||||||
|
$message = $this->gettext('user_not_found');
|
||||||
|
$type = 'error';
|
||||||
|
} else if (!$allow_answer && !$allow_code) {
|
||||||
|
$this->send->send_alert_to_admin($this->user['username']);
|
||||||
|
$message = $this->gettext('sent_to_admin');
|
||||||
|
$type = 'error';
|
||||||
|
} else if ($allow_code) {
|
||||||
|
$result['send'] = false;
|
||||||
|
if ($this->user['token_validity'] && !$this->user['token_expired']) {
|
||||||
|
$this->update_confirm_code_count(1);
|
||||||
|
$result['send'] = true;
|
||||||
|
$message = $this->gettext('check_account_notice');
|
||||||
|
$type = 'notice';
|
||||||
|
} else {
|
||||||
|
$result = $this->send->send_confirm_code_to_user();
|
||||||
|
if ($result['send']) {
|
||||||
|
$this->update_confirm_code_count(1);
|
||||||
|
$message = $result['message'];
|
||||||
|
$type = 'confirmation';
|
||||||
|
} else {
|
||||||
|
$message = $this->gettext('send_failed') . "\n" . $result['message'];
|
||||||
|
$type = 'error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logging("Password recovery request for '" . $this->user['username'] . "' (IP: " . rcube_utils::remote_addr() . ")");
|
||||||
|
if ($message) {
|
||||||
|
$this->rc->output->command('display_message', $message, $type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type == 'error') {
|
||||||
|
$this->recovery_password_form();
|
||||||
|
} else {
|
||||||
|
$this->new_password_form();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save new password to DB
|
||||||
|
private function save_password() {
|
||||||
|
$params = rcube_utils::request2param(rcube_utils::INPUT_POST);
|
||||||
|
$this->debug("Save new password: " . print_r($params, true));
|
||||||
|
|
||||||
|
if ($this->rc->config->get('pr_use_question') && $this->user['have_answer'] && $this->user['answer'] != $params['answer']) {
|
||||||
|
$message = $this->gettext('answer_failed');
|
||||||
|
$type = 'error';
|
||||||
|
} else if ($this->rc->config->get('pr_use_confirm_code') && $this->user['token_expired']) {
|
||||||
|
$message = $this->gettext('code_expired');
|
||||||
|
$type = 'error';
|
||||||
|
} else if ($this->rc->config->get('pr_use_confirm_code') && $this->user['token'] != $params['code']) {
|
||||||
|
$message = $this->gettext('code_failed');
|
||||||
|
$type = 'error';
|
||||||
|
} else {
|
||||||
|
// props to save
|
||||||
|
$save = ['token'=>'', 'token_validity'=>''];
|
||||||
|
|
||||||
|
// check allowed characters according to the configured 'password_charset' option
|
||||||
|
// by converting the password entered by the user to this charset and back to UTF-8
|
||||||
|
$rc_charset = strtoupper($this->rc->output->get_charset());
|
||||||
|
$orig_pwd = $params['newpassword'];
|
||||||
|
$chk_pwd = rcube_charset::convert($orig_pwd, $rc_charset, 'UTF-8');
|
||||||
|
$chk_pwd = rcube_charset::convert($chk_pwd, 'UTF-8', $rc_charset);
|
||||||
|
|
||||||
|
// We're doing this for consistence with Roundcube core
|
||||||
|
$newpassword = rcube_charset::convert($params['newpassword'], $rc_charset, 'UTF-8');
|
||||||
|
|
||||||
|
if ($chk_pwd != $orig_pwd || preg_match('/[\x00-\x1F\x7F]/', $newpassword)) {
|
||||||
|
$message = $this->gettext('password_forbidden');
|
||||||
|
$type = 'error';
|
||||||
|
} else if (!$this->use_password && ($chk_strength = $this->pwd->_check_strength($newpassword))) {
|
||||||
|
$message = $chk_strength;
|
||||||
|
$type = 'error';
|
||||||
|
} else {
|
||||||
|
if ($this->use_password) {
|
||||||
|
$result = $this->pwd->_save($newpassword, $this->user['username']);
|
||||||
|
if ($result != 0) {
|
||||||
|
$message = $this->gettext('write_failed') . ": " . $result;
|
||||||
|
$type = 'error';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$save['password'] = crypt($newpassword, '$1$' . rcube_utils::random_bytes(9));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type != 'error' && $this->set_user_props($save)) {
|
||||||
|
$this->logging("Save new password for '" . $this->user['username'] . "' (IP: " . rcube_utils::remote_addr() . ")");
|
||||||
|
$message = $this->gettext('password_changed');
|
||||||
|
$type = 'confirmation';
|
||||||
|
} else if (!$this->use_password) {
|
||||||
|
$message = $this->gettext('password_not_changed');
|
||||||
|
$type = 'error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rc->output->command('display_message', $message, $type);
|
||||||
|
|
||||||
|
if ($type != 'error') {
|
||||||
|
$this->rc->kill_session();
|
||||||
|
$this->rc->output->command('redirect', './', 2);
|
||||||
|
// $this->rc->output->send('login');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************
|
||||||
|
* IDENTITIES
|
||||||
|
*******************/
|
||||||
|
|
||||||
|
// Verifying the user identities needed to recover the password
|
||||||
|
function check_identities() {
|
||||||
|
if (!isset($_SESSION['show_notice_identities']) && !$this->user['have_altemail'] && !$this->user['have_phone']) {
|
||||||
|
$link = "<a href='./?_task=settings&_action=identities'>". $this->gettext('click_here') ."</a>";
|
||||||
|
$this->rc->output->command('display_message', sprintf($this->gettext('no_identities'), $link), 'notice');
|
||||||
|
$_SESSION['show_notice_identities'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for 'identity_form' hook (executed on identities form create)
|
||||||
|
function identity_form($p) {
|
||||||
|
if (isset($p['form']['addressing']) && !empty($p['record']['identity_id'])) {
|
||||||
|
$new_fields = [];
|
||||||
|
foreach ($p['form']['addressing']['content'] as $col => $colprop) {
|
||||||
|
$new_fields[$col] = $colprop;
|
||||||
|
if ($col == 'email') {
|
||||||
|
// add ext fields after 'email'
|
||||||
|
foreach ($this->fields as $field => $field_name){
|
||||||
|
$new_fields[$field] = ['type' => 'text', 'size' => 40, 'label' => $this->gettext($field)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$p['form']['addressing']['content'] = $new_fields;
|
||||||
|
|
||||||
|
if($this->user['username']){
|
||||||
|
foreach ($this->fields as $field => $field_name){
|
||||||
|
$p['record'][$field] = $this->user[$field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for identity_update hook (executed on identities form submit)
|
||||||
|
function identity_update($p) {
|
||||||
|
$save = [];
|
||||||
|
foreach ($this->fields as $field => $field_name) {
|
||||||
|
$save[$field] = rcube_utils::get_input_value("_".$field,rcube_utils::INPUT_POST);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($save as $par => $val) {
|
||||||
|
if ((!$this->user['username'] && empty($val)) || ($this->user['username'] && $val == $this->user[$par])) {
|
||||||
|
unset($save[$par]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($save['altemail']) {
|
||||||
|
$save['altemail'] = rcube_utils::idn_to_ascii($save['altemail']);
|
||||||
|
if (empty($save['altemail'])) {
|
||||||
|
$this->rc->output->command('display_message', $this->gettext('altemail_cleared'), 'confirmation');
|
||||||
|
} else if($save['altemail'] == $p['record']['email']) {
|
||||||
|
unset($save['altemail']);
|
||||||
|
$p['abort'] = true;
|
||||||
|
$p['message'] = $this->gettext('altemail_match_primary');
|
||||||
|
return $p;
|
||||||
|
} else if(!rcube_utils::check_email($save['altemail'])) {
|
||||||
|
unset($save['altemail']);
|
||||||
|
$p['abort'] = true;
|
||||||
|
$p['message'] = $this->gettext('altemail_invalid');
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($save)) {
|
||||||
|
$this->debug("Save user identities: " . print_r($save, true));
|
||||||
|
$this->set_user_props($save);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return array - user props (username must be user@domain.ltd)
|
||||||
|
function get_user_props($username = null, $with_alias = true) {
|
||||||
|
if (!$username) {
|
||||||
|
$username = ($this->rc->get_user_name() ? $this->rc->get_user_name() : rcube_utils::get_input_value("_username", rcube_utils::INPUT_GPC));
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = [];
|
||||||
|
$user = trim(urldecode($username));
|
||||||
|
if ($user) {
|
||||||
|
// get user row
|
||||||
|
$query = "SELECT u.user_id, u.username, i.email" .
|
||||||
|
" FROM users u" .
|
||||||
|
" INNER JOIN identities i ON i.user_id = u.user_id" .
|
||||||
|
" WHERE username=?";
|
||||||
|
|
||||||
|
$result = $this->rc->db->query($query, $user);
|
||||||
|
|
||||||
|
if ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
|
||||||
|
$fields = [];
|
||||||
|
foreach ($this->fields as $field => $field_name) {
|
||||||
|
$fields[] = $field_name . " as " . $field;
|
||||||
|
}
|
||||||
|
$query = "SELECT " . implode(",",$fields) . ", token, token_validity, token='' OR token_validity < NOW() as token_expired" .
|
||||||
|
" FROM " . $this->rc->config->get('pr_users_table') .
|
||||||
|
" WHERE username=?";
|
||||||
|
|
||||||
|
$result = $this->db->query($query, $arr['email']);
|
||||||
|
$ret = array_merge($arr, $this->db->fetch_assoc($result));
|
||||||
|
} else {
|
||||||
|
// for alias (with users_alias plugin)
|
||||||
|
if ($with_alias && $this->rc->plugins->load_plugin('users_alias', true)) {
|
||||||
|
$users_alias = new users_alias($this->api);
|
||||||
|
$result = $users_alias->alias2user(['user' => $user]);
|
||||||
|
if ($result['user']) {
|
||||||
|
$ret = $this->get_user_props($result['user'], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['username'] = $ret['username']; //for password plugin
|
||||||
|
|
||||||
|
$ret['have_altemail'] = ($ret['altemail'] && !empty($ret['altemail']));
|
||||||
|
$ret['have_phone'] = ($ret['phone'] && !empty($ret['phone']));
|
||||||
|
$ret['have_code'] = ($ret['have_altemail'] || $ret['have_phone']);
|
||||||
|
$ret['have_answer'] = ($ret['question'] && !empty($ret['question']) && $ret['answer'] && !empty($ret['answer']));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->user = $ret;
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save user props to DB
|
||||||
|
function set_user_props($props) {
|
||||||
|
$fields = [];
|
||||||
|
foreach ($this->fields as $field => $field_name) {
|
||||||
|
if (isset($props[$field])) {
|
||||||
|
$fields[] = $field_name . " = '" . $props[$field] . "'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($props['token'])) {
|
||||||
|
if (empty($props['token'])) {
|
||||||
|
$code_validity_time = 0;
|
||||||
|
} else {
|
||||||
|
$code_validity_time = (int) $this->rc->config->get('pr_confirm_code_validity_time', 30);
|
||||||
|
}
|
||||||
|
$fields[] = "token = '" . $props['token'] . "', token_validity = NOW() + INTERVAL " . $code_validity_time . " MINUTE";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($props['password']) {
|
||||||
|
$fields[] = "password = '" . $props['password'] . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($fields)) {
|
||||||
|
$query = "UPDATE " . $this->rc->config->get('pr_users_table') . " SET " . implode(",",$fields) . " WHERE username=?";
|
||||||
|
$this->db->query($query, $this->user['username']);
|
||||||
|
$this->get_user_props(); //update user props
|
||||||
|
$this->debug("Update user '" . $this->user['username'] . "' props: " . print_r($fields, true));
|
||||||
|
return $this->db->affected_rows() == 1;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_confirm_code_count($plus = 0) {
|
||||||
|
$count = $this->get_confirm_code_count() + $plus;
|
||||||
|
$_SESSION['pr_confirm_code_count'] = $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_confirm_code_count() {
|
||||||
|
if (!isset($_SESSION['pr_confirm_code_count'])) {
|
||||||
|
$_SESSION['pr_confirm_code_count'] = 0;
|
||||||
|
}
|
||||||
|
return $_SESSION['pr_confirm_code_count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************
|
||||||
|
* service functions
|
||||||
|
*******************/
|
||||||
|
|
||||||
|
function get_dbh() {
|
||||||
|
if (!$this->db) {
|
||||||
|
if ($dsn = $this->rc->config->get('pr_db_dsn')) {
|
||||||
|
$this->db = rcube_db::factory($dsn);
|
||||||
|
$this->db->set_debug((bool)$this->rc->config->get('sql_debug'));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->db = $this->rc->get_dbh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->db;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_action() {
|
||||||
|
$action = rcube_utils::get_input_value('_a', rcube_utils::INPUT_GPC);
|
||||||
|
if (!$action || empty($action)) {
|
||||||
|
$action = 'init';
|
||||||
|
}
|
||||||
|
return $action;
|
||||||
|
}
|
||||||
|
|
||||||
|
function logging($text) {
|
||||||
|
if ($this->rc->config->get('pr_password_log') == true) {
|
||||||
|
rcube::write_log('password', $text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function debug($text) {
|
||||||
|
if ($this->rc->config->get('pr_debug') == true) {
|
||||||
|
$msg = (is_array($text) ? print_r($text, true) : $text);
|
||||||
|
rcube::write_log('console', $msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -0,0 +1,71 @@
|
||||||
|
<roundcube:include file="includes/layout.html" />
|
||||||
|
|
||||||
|
<div id="layout-content" class="selected no-navbar" role="main"><br /><br />
|
||||||
|
<roundcube:object name="logo" style="top:0px" src="/images/logo.svg" id="logo" alt="Logo" />
|
||||||
|
<p style="margin-top: 1.5rem; margin-bottom: 1.5rem; font-size: 20px; font-weight: bold;"><roundcube:label name="password_recovery.recovery_password" /></p>
|
||||||
|
<form id="new-password-form" name="new-password-form" method="post" class="propform">
|
||||||
|
<input type="hidden" name="_username" value="<roundcube:var name="env:pr_username" />">
|
||||||
|
<input type="hidden" name="_a" value="save">
|
||||||
|
<table style="margin-left: auto; margin-right: auto; text-align:right;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_newpassword"><roundcube:label name="password_recovery.newpassword" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="password" size="35" maxlength="250" id="_newpassword" name="_newpassword" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_newpassword_confirm"><roundcube:label name="password_recovery.newpassword_confirm" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="password" size="35" maxlength="250" id="_newpassword_confirm" name="_newpassword_confirm" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<roundcube:if condition="env:pr_use_question == true" />
|
||||||
|
<tr><td colspan="2"> </td></tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_question"><roundcube:label name="password_recovery.question" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_question" name="_question" readonly value="<roundcube:var name="env:pr_question" />">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_answer"><roundcube:label name="password_recovery.answer" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_answer" name="_answer" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<roundcube:endif />
|
||||||
|
<roundcube:if condition="env:pr_use_confirm_code == true" />
|
||||||
|
<tr><td colspan="2"> </td></tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_code"><roundcube:label name="password_recovery.code" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_code" name="_code" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<roundcube:endif />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="formbuttons" style="text-align:center;"><br />
|
||||||
|
<roundcube:button command="plugin.password_recovery.save" label="password_recovery.save" class="button mainaction" />
|
||||||
|
<span> </span>
|
||||||
|
<roundcube:button command="plugin.password_recovery.cancel" label="password_recovery.cancel" class="button" />
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p class="noscriptwarning"><roundcube:label name="noscriptwarning" /></p>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<roundcube:include file="includes/footer.html" />
|
|
@ -0,0 +1,32 @@
|
||||||
|
<roundcube:include file="includes/layout.html" />
|
||||||
|
|
||||||
|
<div id="layout-content" class="selected no-navbar" role="main"><br /><br />
|
||||||
|
<roundcube:object name="logo" style="top:0px" src="/images/logo.svg" id="logo" alt="Logo" />
|
||||||
|
<p style="margin-top: 1.5rem; margin-bottom: 1.5rem; font-size: 20px; font-weight: bold;"><roundcube:label name="password_recovery.recovery_password" /></p>
|
||||||
|
<form id="recovery-password-form" name="recovery-password-form" method="post" class="propform">
|
||||||
|
<input type="hidden" name="_a" value="reset">
|
||||||
|
<table style="margin-left: auto; margin-right: auto; text-align:right;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_username"><roundcube:label name="username" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_username" name="_username" autocomplete="off" value="<roundcube:var name="request:_username" />">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="formbuttons" style="text-align:center;"><br />
|
||||||
|
<roundcube:button command="plugin.password_recovery.reset" label="password_recovery.recovery" class="button mainaction" />
|
||||||
|
<span> </span>
|
||||||
|
<roundcube:button command="plugin.password_recovery.cancel" label="password_recovery.cancel" class="button" />
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p class="noscriptwarning"><roundcube:label name="noscriptwarning" /></p>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<roundcube:include file="includes/footer.html" />
|
|
@ -0,0 +1,71 @@
|
||||||
|
<roundcube:include file="includes/layout.html" />
|
||||||
|
|
||||||
|
<div id="layout-content" class="selected no-navbar" role="main"><br /><br />
|
||||||
|
<roundcube:object name="logo" style="top:0px" src="/images/logo.svg" id="logo" alt="Logo" />
|
||||||
|
<p style="margin-top: 1.5rem; margin-bottom: 1.5rem; font-size: 20px; font-weight: bold;"><roundcube:label name="password_recovery.recovery_password" /></p>
|
||||||
|
<form id="new-password-form" name="new-password-form" method="post" class="propform">
|
||||||
|
<input type="hidden" name="_username" value="<roundcube:var name="env:pr_username" />">
|
||||||
|
<input type="hidden" name="_a" value="save">
|
||||||
|
<table style="margin-left: auto; margin-right: auto; text-align:right;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_newpassword"><roundcube:label name="password_recovery.newpassword" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="password" size="35" maxlength="250" id="_newpassword" name="_newpassword" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_newpassword_confirm"><roundcube:label name="password_recovery.newpassword_confirm" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="password" size="35" maxlength="250" id="_newpassword_confirm" name="_newpassword_confirm" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<roundcube:if condition="env:pr_use_question == true" />
|
||||||
|
<tr><td colspan="2"> </td></tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_question"><roundcube:label name="password_recovery.question" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_question" name="_question" readonly value="<roundcube:var name="env:pr_question" />">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_answer"><roundcube:label name="password_recovery.answer" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_answer" name="_answer" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<roundcube:endif />
|
||||||
|
<roundcube:if condition="env:pr_use_confirm_code == true" />
|
||||||
|
<tr><td colspan="2"> </td></tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_code"><roundcube:label name="password_recovery.code" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_code" name="_code" autocomplete="off">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<roundcube:endif />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="formbuttons" style="text-align:center;"><br />
|
||||||
|
<roundcube:button command="plugin.password_recovery.save" label="password_recovery.save" class="button mainaction" />
|
||||||
|
<span> </span>
|
||||||
|
<roundcube:button command="plugin.password_recovery.cancel" label="password_recovery.cancel" class="button" />
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p class="noscriptwarning"><roundcube:label name="noscriptwarning" /></p>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<roundcube:include file="includes/footer.html" />
|
|
@ -0,0 +1,32 @@
|
||||||
|
<roundcube:include file="includes/layout.html" />
|
||||||
|
|
||||||
|
<div id="layout-content" class="selected no-navbar" role="main"><br /><br />
|
||||||
|
<roundcube:object name="logo" style="top:0px" src="/images/logo.svg" id="logo" alt="Logo" />
|
||||||
|
<p style="margin-top: 1.5rem; margin-bottom: 1.5rem; font-size: 20px; font-weight: bold;"><roundcube:label name="password_recovery.recovery_password" /></p>
|
||||||
|
<form id="recovery-password-form" name="recovery-password-form" method="post" class="propform">
|
||||||
|
<input type="hidden" name="_a" value="reset">
|
||||||
|
<table style="margin-left: auto; margin-right: auto; text-align:right;">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="title">
|
||||||
|
<label for="_username"><roundcube:label name="username" /></label>
|
||||||
|
</td>
|
||||||
|
<td class="input">
|
||||||
|
<input type="text" size="35" maxlength="250" id="_username" name="_username" autocomplete="off" value="<roundcube:var name="request:_u" />">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p class="formbuttons" style="text-align:center;"><br />
|
||||||
|
<roundcube:button command="plugin.password_recovery.reset" label="password_recovery.recovery" class="button mainaction" />
|
||||||
|
<span> </span>
|
||||||
|
<roundcube:button command="plugin.password_recovery.cancel" label="password_recovery.cancel" class="button" />
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<noscript>
|
||||||
|
<p class="noscriptwarning"><roundcube:label name="noscriptwarning" /></p>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<roundcube:include file="includes/footer.html" />
|
Loading…
Reference in New Issue