Allows a user or admin to reset his/her forgotten password with a code sent by email/SMS #18

pull/18/head
Sylvain Tissot 8 years ago
parent 25f50f262d
commit 9c9ba64a7f

@ -119,6 +119,14 @@ $CONF['database_tables'] = array (
// Leave blank to send email from the logged-in Admin's Email address.
$CONF['admin_email'] = '';
// Site admin name
// This will be used as signature in notification messages
$CONF['admin_name'] = 'Postmaster';
// Site admin phone number
// This will be used if a user cannot access his/her email and needs support
$CONF['admin_phone'] = '';
// Mail Server
// Hostname (FQDN) of your mail server.
// This is used to send email to Postfix in order to create mailboxes.
@ -572,6 +580,18 @@ $CONF['new_quota_table'] = 'YES';
// http://www.php.net/manual/en/function.imap-open.php
$CONF['create_mailbox_subdirs_hostoptions'] = array();
// Optional:
// Allows a user to reset his forgotten password with a code sent by email/SMS
$CONF['forgotten_user_password_reset'] = true;
// Allows an admin to reset his forgotten password with a code sent by email/SMS
$CONF['forgotten_admin_password_reset'] = true;
// Clickatell gateway to send SMS code for password reset
// API type: HTTP
$CONF['clickatell_api_id'] = '';
$CONF['clickatell_user'] = '';
$CONF['clickatell_password'] = '';
$CONF['clickatell_sender'] = '';
// Theme Config
// Specify your own logo and CSS file

@ -1939,4 +1939,21 @@ function getRemoteAddr() {
return $REMOTE_ADDR;
}
/**
* Returns a hash for a username valid for one day
*
* @param String $username user name
* @return String password recovery code
*/
function getPasswordRecoveryCode($username)
{
$username = trim(strtolower($username));
$date = date('Y-m-d');
$code = substr(strtoupper(md5('SECRET SALTING PHRASE' . $username . $date)), 0, 6);
return $code;
}
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */

@ -146,6 +146,10 @@ $PALANG['pCreate_mailbox_username_text_error1'] = 'The EMAIL is not valid!';
$PALANG['pCreate_mailbox_username_text_error3'] = 'You have reached your limit to create mailboxes!';
$PALANG['pCreate_mailbox_password_text'] = 'Password for POP3/IMAP';
$PALANG['pCreate_mailbox_name_text'] = 'Full name';
$PALANG['pCreate_mailbox_phone'] = 'Mobile phone';
$PALANG['pCreate_mailbox_phone_desc'] = "Used to send a SMS if the password is forgotten";
$PALANG['pCreate_mailbox_email'] = 'Other e-mail';
$PALANG['pCreate_mailbox_email_desc'] = "Used if the password is forgotten";
$PALANG['pCreate_mailbox_mail'] = 'Send Welcome mail';
$PALANG['pCreate_mailbox_result_error'] = 'Creating the mailbox %s failed!';
$PALANG['pCreate_mailbox_result_success'] = 'The mailbox %s has been added to the mailbox table.';
@ -174,6 +178,16 @@ $PALANG['change_password'] = 'Change Password';
$PALANG['pPassword_result_error'] = 'Changing the password for %s failed!';
$PALANG['pPassword_result_success'] = 'The password for %s has been changed.';
$PALANG['pPassword_recovery_title'] = 'Follow the instructions to reset your password.';
$PALANG['pPassword_recovery_button'] = 'Send me the code';
$PALANG['pPassword_recovery_email_body'] = "Hello,\n\nUse the following link to change your email password:\n%s\n\nRegards,\n\n" . $CONF['admin_name'];
$PALANG['pPassword_recovery_email_sent'] = 'An email was sent to:';
$PALANG['pPassword_recovery_sms_body'] = "Hello,\nThe code to change your password is: %s\n" . $CONF['admin_name'];
$PALANG['pPassword_recovery_sms_sent'] = 'An SMS was sent to:';
$PALANG['pPassword_recovery_no_alternative'] = 'No alternative contact info were found. Please contact the support at ' . $CONF['admin_email'] . 'or by phone to ' . $CONF['admin_phone'];
$PALANG['pPassword_password_code'] = 'Code sent by email/SMS';
$PALANG['pPassword_code_text_error'] = 'Invalid code';
$PALANG['pEdit_vacation_set'] = 'Change / Set away message';
$PALANG['pEdit_vacation_remove'] = 'Remove away message';
@ -298,6 +312,7 @@ $PALANG['pAdminEdit_admin_result_success'] = 'The admin %s has been modified.';
$PALANG['pUsersLogin_welcome'] = 'Mailbox users login to change your password and aliases.';
$PALANG['pUsersLogin_username_incorrect'] = 'Your login is not correct. Make sure that you login with your email address!';
$PALANG['pUsersLogin_password_incorrect'] = 'Your password is not correct!';
$PALANG['pUsersLogin_password_recover'] = 'I forgot my password';
$PALANG['pUsersMenu_vacation'] = 'Auto Response';
$PALANG['pUsersMenu_edit_alias'] = 'Change your forward';

@ -145,6 +145,10 @@ $PALANG['pCreate_mailbox_username_text_error1'] = 'L\'adresse courriel est inval
$PALANG['pCreate_mailbox_username_text_error3'] = 'Vous avez atteint le nombre maximum de comptes courriel !';
$PALANG['pCreate_mailbox_password_text'] = 'Mot de passe pour le compte POP3/IMAP';
$PALANG['pCreate_mailbox_name_text'] = 'Nom complet';
$PALANG['pCreate_mailbox_phone'] = 'Téléphone mobile';
$PALANG['pCreate_mailbox_phone_desc'] = "Utilisé pour l'envoi de SMS en cas d'oubli du mot de passe";
$PALANG['pCreate_mailbox_email'] = 'E-mail secondaire';
$PALANG['pCreate_mailbox_email_desc'] = "Utilisé en cas d'oubli du mot de passe";
$PALANG['pCreate_mailbox_mail'] = 'Envoyer le message de bienvenue';
$PALANG['pCreate_mailbox_result_error'] = 'Échec de la création du compte courriel %s !';
$PALANG['pCreate_mailbox_result_success'] = 'Le compte courriel %s a été ajouté à la table des comptes !';
@ -172,6 +176,17 @@ $PALANG['pPassword_password_text_error'] = 'Le mot de passe fourni ne correspond
$PALANG['change_password'] = 'Changer le mot de passe';
$PALANG['pPassword_result_error'] = 'Impossible de changer le mot de passe de %s !';
$PALANG['pPassword_result_success'] = 'Le mot de passe de %s a été changé !';
$PALANG['pPassword_recovery_title'] = 'Suivez les instructions pour réinitialiser votre mot de passe.';
$PALANG['pPassword_recovery_button'] = 'Envoyez-moi le code';
$PALANG['pPassword_recovery_email_body'] = "Bonjour,\n\nUtilisez le lien suivant pour modifier votre mot de passe :\n%s\n\nSalutations,\n\n" . $CONF['admin_name'];
$PALANG['pPassword_recovery_email_sent'] = 'Un code a été envoyé à :';
$PALANG['pPassword_recovery_sms_body'] = "Bonjour,\nLe code pour modifier votre mot de passe: %s\n" . $CONF['admin_name'];
$PALANG['pPassword_recovery_sms_sent'] = 'Un code a été envoyé par SMS à :';
$PALANG['pPassword_recovery_no_alternative'] = "Aucun moyen de contact alternatif n'a été trouvé. Contactez le support à " . $CONF['admin_email'] . ' ou par téléphone ' . $CONF['admin_phone'];
$PALANG['pPassword_password_code'] = 'Code reçu par email/SMS';
$PALANG['pPassword_code_text_error'] = 'Code invalide';
>>>>>>> Allows a user or admin to reset his/her forgotten password with a code sent by email/SMS #18
$PALANG['pEdit_vacation_set'] = 'Activer le répondeur';
$PALANG['pEdit_vacation_remove'] = 'Désactiver le répondeur';
$PALANG['pVacation_result_error'] = 'Impossible de mettre à jour les réglages du répondeur de %s !';
@ -294,6 +309,7 @@ $PALANG['pAdminEdit_admin_result_success'] = 'L\'administrateur %s a été ajout
$PALANG['pUsersLogin_welcome'] = 'Entrer votre adresse courriel pour modifier votre mot de passe et vos transferts.';
$PALANG['pUsersLogin_username_incorrect'] = 'L\'adresse courriel est invalide. Assurez-vous d\'avoir correctement saisi votre adresse courriel !';
$PALANG['pUsersLogin_password_incorrect'] = 'Votre mot de passe est invalide !';
$PALANG['pUsersLogin_password_recover'] = 'J\'ai oublié mon mot de passe';
$PALANG['pUsersMenu_vacation'] = 'Réponse Automatique';
$PALANG['pUsersMenu_edit_alias'] = 'Modifier votre transfert';

@ -85,6 +85,7 @@ if ($_SERVER['REQUEST_METHOD'] == "POST")
$smarty->assign ('language_selector', language_selector(), false);
$smarty->assign ('smarty_template', 'login');
$smarty->assign ('logintype', 'admin');
$smarty->assign ('forgotten_password_reset', Config::read('forgotten_admin_password_reset'));
$smarty->display ('index.tpl');
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */

@ -47,6 +47,10 @@ class AdminHandler extends PFAHandler {
/*select*/ 'password as password2'
),
'phone' => pacol( 1, 1, 0, 'text', 'pCreate_mailbox_phone', 'pCreate_mailbox_phone_desc', ''),
'email_other' => pacol( 1, 1, 0, 'mail', 'pCreate_mailbox_email', 'pCreate_mailbox_email_desc', ''),
'superadmin' => pacol( 1, 1, 0, 'bool', 'super_admin' , 'super_admin_desc' , 0
# TODO: (finally) replace the ALL domain with a column in the admin table
# TODO: current status: 'superadmin' column exists and is written when storing an admin with AdminHandler,

@ -35,6 +35,8 @@ class MailboxHandler extends PFAHandler {
# read_from_db_postprocess() also sets 'quotabytes' for use in init()
# TODO: read used quota from quota/quota2 table
'active' => pacol( 1, 1, 1, 'bool', 'active' , '' , 1 ),
'phone' => pacol( 1, 1, 0, 'text', 'pCreate_mailbox_phone' , 'pCreate_mailbox_phone_desc' , ''),
'email_other' => pacol( 1, 1, 0, 'mail', 'pCreate_mailbox_email' , 'pCreate_mailbox_email_desc' , ''),
'welcome_mail' => pacol( $this->new, $this->new, 0, 'bool', 'pCreate_mailbox_mail' , '' , 1,
/*options*/ '',
/*not_in_db*/ 1 ),

@ -0,0 +1 @@
users/password-change.php

@ -0,0 +1 @@
users/password-recover.php

@ -1,7 +1,7 @@
<!-- {$smarty.template} -->
{strip}
{include file="header.tpl"}
{if $smarty_template != 'login'}
{if $smarty_template !== 'login' && $smarty_template !== 'password-recover' && $smarty_template !== 'password-change'}
{config_load file="menu.conf" section=$smarty_template}
{if $authentication_has_role.user}
{include file='users_menu.tpl'}

@ -14,7 +14,8 @@
</tr>
<tr>
<td class="label"><label>{$PALANG.password}:</label></td>
<td><input class="flat" type="password" name="fPassword" /></td>
<td><input class="flat" type="password" name="fPassword" />{if $forgotten_password_reset}<br/>
<a href="password-recover.php">{$PALANG.pUsersLogin_password_recover}</a>{/if}</td>
</tr>
<tr>
<td class="label"><label>{$PALANG.pLogin_language}:</label></td>

@ -0,0 +1,28 @@
<div id="edit_form">
<form name="mailbox" method="post">
<table>
<tr>
<td colspan="3"><h3>{$PALANG.pPassword_welcome}</h3></td>
</tr>
<tr>
<td>{$PALANG.pLogin_username} :</td>
<td><input class="flat" type="text" name="fUsername" value="{$tUsername}" /></td>
</tr>
<tr>
<td>{$PALANG.pPassword_password_code} :</td>
<td><input class="flat" type="text" name="fCode" value="{$tCode}" /></td>
</tr>
<tr>
<td>{$PALANG.pPassword_password} :</td>
<td><input class="flat" type="password" name="fPassword" /></td>
</tr>
<tr>
<td>{$PALANG.pPassword_password2} :</td>
<td><input class="flat" type="password" name="fPassword2" /></td>
</tr>
<tr>
<td colspan="2" class="hlp_center"><input class="button" type="submit" name="submit" value="{$PALANG.change_password}" /></td>
</tr>
</table>
</form>
</div>

@ -0,0 +1,26 @@
<div id="edit_form">
<form name="frmPassword" method="post" action="">
<table>
<tr>
<th colspan="3">{$PALANG.pPassword_recovery_title}</th>
</tr>
<tr>
<td class="label"><label>{$PALANG.pLogin_username}:</label></td>
<td><input class="flat" type="text" name="fUsername" /></td>
</tr>
<tr>
<td class="label">&nbsp;</td>
<td colspan="2">
<input class="button" type="submit" name="submit" value="{$PALANG.pPassword_recovery_button}" />
</td>
</tr>
</table>
</form>
{literal}
<script type="text/javascript">
<!--
document.frmPassword.fUsername.focus();
// -->
</script>
{/literal}
</div>

@ -1672,6 +1672,19 @@ function upgrade_1836_mysql() {
}
}
function upgrade_1837_mysql() {
# alternative contact means to reset a forgotten password
foreach(array('admin', 'mailbox') as $table_to_change) {
$table = table_by_key($table_to_change);
if(!_mysql_field_exists($table, 'phone')) {
db_query_parsed("ALTER TABLE `$table` ADD COLUMN `phone` varchar(30) NOT NULL DEFAULT ''");
}
if(!_mysql_field_exists($table, 'email_other')) {
db_query_parsed("ALTER TABLE `$table` ADD COLUMN `email_other` varchar(255) NOT NULL DEFAULT ''");
}
}
}
# TODO MySQL:
# - various varchar fields do not have a default value
# https://sourceforge.net/projects/postfixadmin/forums/forum/676076/topic/3419725

@ -62,6 +62,7 @@ if ($_SERVER['REQUEST_METHOD'] == "POST")
$smarty->assign ('language_selector', language_selector(), false);
$smarty->assign ('smarty_template', 'login');
$smarty->assign ('logintype', 'user');
$smarty->assign ('forgotten_password_reset', Config::read('forgotten_user_password_reset'));
$smarty->display ('index.tpl');
/* vim: set expandtab softtabstop=3 tabstop=3 shiftwidth=3: */

@ -0,0 +1,104 @@
<?php
/**
* Postfix Admin
*
* LICENSE
* This source file is subject to the GPL license that is bundled with
* this package in the file LICENSE.TXT.
*
* Further details on the project are available at http://postfixadmin.sf.net
*
* @version $Id$
* @license GNU GPL v2 or later.
*
* File: password-change.php
* Used by users and admins to change their forgotten login password.
* Template File: password-change.tpl
*
* Template Variables:
*
* tUsername
* tCode
*
* Form POST \ GET Variables:
*
* fUsername
*/
if (preg_match('/\/users\//', $_SERVER['REQUEST_URI'])) {
$rel_path = '../';
$context = 'users';
} else {
$rel_path = './';
$context = 'admin';
}
require_once($rel_path . 'common.php');
if ($context == 'admin' && !Config::read('forgotten_admin_password_reset') || $context == 'users' && !Config::read('forgotten_user_password_reset'))
{
header('HTTP/1.0 403 Forbidden');
exit(0);
}
if ($_SERVER['REQUEST_METHOD'] == 'GET')
{
$tUsername = safeget('username');
$tCode = safeget('code');
}
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
if(safepost('fCancel')) {
header('Location: main.php');
exit(0);
}
$fPassword = safepost('fPassword');
$fPassword2 = safepost('fPassword2');
$tUsername = safepost('fUsername');
$tCode = trim(strtoupper(safepost('fCode')));
if (empty($fPassword) or ($fPassword != $fPassword2)) {
$error = true;
flash_error(Config::lang('pPassword_password_text_error'));
} elseif (trim(strtoupper($tCode) != getPasswordRecoveryCode($tUsername))) {
flash_error(Config::lang('pPassword_code_text_error'));
} else {
session_regenerate_id();
$_SESSION['sessid']['username'] = $tUsername;
if ($context == 'users') {
$_SESSION['sessid']['roles'][] = 'user';
$handler = new MailboxHandler;
} else {
$_SESSION['sessid']['roles'][] = 'admin';
$handler = new AdminHandler;
}
if (!$handler->init($tUsername)) {
flash_error($handler->errormsg);
} else {
$values = $handler->result;
$values[$handler->getId_field()] = $tUsername;
$values['password'] = $fPassword;
$values['password2'] = $fPassword2;
if ($handler->set($values) && $handler->store()) {
flash_info(Config::lang_f('pPassword_result_success', $tUsername));
header('Location: ' . dirname($_SERVER['REQUEST_URI']) . '/main.php');
exit(0);
} else {
foreach($handler->errormsg as $msg) {
flash_error($msg);
}
}
}
}
}
$smarty->assign ('language_selector', language_selector(), false);
$smarty->assign('tUsername', @$tUsername);
$smarty->assign('tCode', @$tCode);
$smarty->assign ('smarty_template', 'password-change');
$smarty->display ('index.tpl');
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
?>

@ -0,0 +1,124 @@
<?php
/**
* Postfix Admin
*
* LICENSE
* This source file is subject to the GPL license that is bundled with
* this package in the file LICENSE.TXT.
*
* Further details on the project are available at http://postfixadmin.sf.net
*
* @version $Id$
* @license GNU GPL v2 or later.
*
* File: password-recover.php
* Used by users and admins to recover their forgotten login password.
* Template File: password-recover.tpl
*
* Template Variables:
*
* none
*
* Form POST \ GET Variables:
*
* fUsername
*/
if (preg_match('/\/users\//', $_SERVER['REQUEST_URI'])) {
$rel_path = '../';
$context = 'users';
} else {
$rel_path = './';
$context = 'admin';
}
require_once($rel_path . 'common.php');
if ($context == 'admin' && !Config::read('forgotten_admin_password_reset') || $context == 'users' && !Config::read('forgotten_user_password_reset'))
{
header('HTTP/1.0 403 Forbidden');
exit(0);
}
function sendCodebyEmail($to, $username, $code)
{
$fHeaders = "To: " . $to . PHP_EOL;
$fHeaders .= "From: " . Config::read('admin_email') . PHP_EOL;
$fHeaders .= "Subject: " . encode_header(Config::Lang('pPassword_welcome')) . PHP_EOL;
$fHeaders .= "MIME-Version: 1.0" . PHP_EOL;
$fHeaders .= "Content-Type: text/plain; charset=utf-8" . PHP_EOL;
$fHeaders .= "Content-Transfer-Encoding: 8bit" . PHP_EOL . PHP_EOL;
$url = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['REQUEST_URI']) . '/password-change.php?username=' . urlencode($username) . '&code=' . $code;
$fHeaders .= Config::lang_f('pPassword_recovery_email_body', $url);
return smtp_mail($to, Config::read('admin_email') , $fHeaders);
}
function sendCodebySMS($to, $username, $code)
{
$text = Config::lang_f('pPassword_recovery_sms_body', $code);
$url = 'https://api.clickatell.com/http/sendmsg?api_id=' . Config::read('clickatell_api_id') . '&user=' . Config::read('clickatell_user') . '&password=' . Config::read('clickatell_password') . "&to=$to" . '&from=' . Config::read('clickatell_sender') . '&text=' . urlencode($text);
$result = file_get_contents($url);
return $result !== false;
}
if ($_SERVER['REQUEST_METHOD'] == "POST")
{
$tUsername = escape_string (safepost('fUsername'));
$table = table_by_key($context == 'users' ? 'mailbox' : 'admin');
$result = db_query("SELECT * FROM `$table` WHERE username='$tUsername'");
$eMessage = '';
if ($result['rows'] == 1)
{
$row = db_array($result['result']);
$code = getPasswordRecoveryCode($tUsername);
$email_other = trim($row['email_other']);
$phone = trim($row['phone']);
// An active session is required to propagate flash messages to redirected page
if ($email_other)
{
// send email
if (sendCodeByEmail($email_other, $tUsername, $code))
{
flash_info(Config::Lang('pPassword_recovery_email_sent') . ' ' . $email_other);
}
}
if ($phone)
{
// send phone
if (sendCodeBySMS($phone, $tUsername, $code))
{
flash_info(Config::Lang('pPassword_recovery_sms_sent') . ' ' . $phone);
}
}
if ($email_other || $phone)
{
// session_regenerate_id();
header("Location: password-change.php?username=" . $tUsername);
exit(0);
}
else
{
flash_error(Config::Lang('pPassword_recovery_no_alternative'));
}
}
else
{
flash_error(Config::Lang('pCreate_mailbox_username_text_error1'));
}
}
$smarty->assign ('language_selector', language_selector(), false);
$smarty->assign ('smarty_template', 'password-recover');
$smarty->display ('index.tpl');
/* vim: set expandtab softtabstop=4 tabstop=4 shiftwidth=4: */
?>
Loading…
Cancel
Save