Send a new password by email
Create site16 by copying site15.
- /cms
- ...
- site15
- site16
In this chapter, we are going to program sending a new password by email to a registered user in an attached cryptogram.
To test the result online, enter http://www.frasq.org/cms/site16 in the address bar of your navigator.
Go to the identification page. Click on the link If you have forgotten your password under the input field for the password.
Enter foobar
as a name, check the confirmation box, type the verification code and press Send.
A new password in a cryptogram has been sent by email to the address nobody@frasq.org:
Your new password is in the attached cryptogram.
If you didn't ask to change it, just ignore this message.
NOTE: Your old password is still valid.
Identify yourself with the new password to activate it.
See you soon.
frasq.org - http://www.frasq.org
--------------------------------------------------------------
Start by creating the action password
by adding the file password.php in the folder actions with the following content:
- /cms/site16
- actions
- password.php
- actions
- function password($lang) {
- head('title', translate('password:title', $lang));
- head('description', false);
- head('keywords', false);
- head('robots', 'noindex, nofollow');
- $banner = build('banner', $lang);
- $remindme = build('remindme', $lang);
- $content = view('password', $lang, compact('remindme'));
- $output = layout('standard', compact('banner', 'content'));
- return $output;
- }
The password
action returns a page with a simple banner and a form built by the remindme
block.
To give access to the password
action, add an alias for each language in the file includes/aliases.inc:
- 'mot-de-passe' => 'password',
- 'password' => 'password',
Add the title for the page for sending a password in the file includes/strings.inc:
In English in the 'en' array:
- 'password:title' => 'Mot de passe',
In French in the 'fr' array:
- 'password:title' => 'Password',
Add the view for the page for sending a password in the folders views/en for the English version and views/fr for the version in French:
- /cms/site16
- views
- en
- password.phtml
- fr
- password.phtml
- en
- views
- <h3>New access key</h3>
- <?php echo $remindme; ?>
- <h3>Nouvelle clé d'accès</h3>
- <?php echo $remindme; ?>
The form for sending a password is in a block. Start by writing the view, first in one language:
- /cms/site16
- views
- en
- password.phtml
- remindme.phtml
- en
- views
- <div class="form">
- <form action="" method="post">
- <input type="hidden" name="remindme_token" value="<?php echo $token; ?>" />
- <div class="fields">
- <p class="label">What is your connection name?</p>
- <p class="input"><input type="text" name="remindme_login" id="remindme_login" size="40" maxlength="100" title="Identifier" onkeypress="return focusonenter(event, 'remindme_code')" value="<?php echo htmlspecialchars($login, ENT_COMPAT, 'UTF-8'); ?>" /></p>
- <p class="info">You may also enter your email address.</p>
- <p class="input"><input name="remindme_confirmed" id="remindme_confirmed" type="checkbox" title="Confirmation" <?php if ($confirmed) echo 'checked="checked"'; ?> /> I want to receive a new password</p>
- <?php if ($with_captcha): ?>
- <p class="input">
- <img src="<?php echo $base_path; ?>/captcha/remindme" alt="" title="Verification code" />
- :
- <input type="text" name="remindme_code" id="remindme_code" size="4" maxlength="4" title="4 letters" onkeypress="return submitonenter(event, 'remindme_submit')" value="" />
- </p>
- <?php endif; ?>
- <p class="submit"><button type="submit" name="remindme_submit" id="remindme_submit">Send</button></p>
- </div>
- </form>
The form has 3 input fields: identifier, confirmation and verification code. Apart from $token
, 2 variables are necessary: $login
and $confirmed
. The parameter $with_captcha
specifies if a captcha is displayed. All the field names are prefixed with remindme_
. The form has only one button called remindme_submit
.
The rest of the view deals with the error and the information messages.
The possible errors are $missing_code
, $bad_code
, $missing_login
, $bad_login
, $missing_confirmation
, $internal_error
and $contact_page
.
If $email_sent
is true
, the user is notified that the message has been sent and a link to the identification page $user_page
is shown.
- <?php
- if ($errors):
- extract($errors);
- ?>
- <div class="errors">
- <?php if ($missing_code): ?>
- <p>Enter the verification code displayed in the image.</p>
- <?php elseif ($bad_code): ?>
- <p>The verification code is incorrect.</p>
- <?php endif; ?>
- <?php if ($missing_login): ?>
- <p>You haven't typed your identifier.</p>
- <?php elseif ($bad_login): ?>
- <p>The identifier is not valid.</p>
- <?php endif; ?>
- <?php if ($missing_confirmation): ?>
- <p>Check the confirmation box.</p>
- <?php endif; ?>
- <?php if ($internal_error): ?>
- <p>An internal error has occurred. If you can describe the problem, please <a href="<?php echo $contact_page; ?>">contact us</a>.</p>
- <?php endif; ?>
- </div>
- <?php endif; ?>
- <?php
- if ($infos):
- extract($infos);
- ?>
- <div class="infos">
- <?php if ($email_sent): ?>
- <p>A new password has been sent to you by email.<br />
- To identify yourself, <a href="<?php echo $user_page; ?>">click here</a>.</p>
- <?php endif; ?>
- </div>
- <?php endif; ?>
- </div>
To validate the view, write a first version of the function remindme
which is limited to displaying the form:
- /cms/site16
- blocks
- remindme.php
- blocks
- require_once 'tokenid.php';
- function remindme($lang, $login=false) {
- $login=$confirmed=$code=$token=false;
- $missing_code=false;
- $bad_code=false;
- $bad_token=false;
- $missing_login=false;
- $bad_login=false;
- $missing_confirmation=false;
- $email_sent=false;
- $user_page=url('user', $lang);
- $internal_error=false;
- $contact_page=false;
- $with_captcha=true;
- $_SESSION['remindme_token'] = $token = token_id();
- $errors = compact('missing_login', 'bad_login', 'missing_confirmation', 'missing_code', 'bad_code', 'internal_error', 'contact_page');
- $infos = compact('email_sent', 'user_page');
- $output = view('remindme', $lang, compact('token', 'with_captcha', 'login', 'confirmed', 'errors', 'infos'));
- return $output;
- }
Enter http://localhost/cms/site16/en/user in the address bar of your navigator.
Edit the remindme
function. Assign values to all the input field variables. Set all the error variables to true
. Check the links to the contact page and to the identification page.
Add the view in French:
- /cms/site16
- views
- fr
- password.phtml
- remindme.phtml
- fr
- views
- <div class="form">
- <form action="" method="post">
- <input type="hidden" name="remindme_token" value="<?php echo $token; ?>" />
- <div class="fields">
- <p class="label">Quel est votre nom de connexion ?</p>
- <p class="input"><input type="text" name="remindme_login" id="remindme_login" size="40" maxlength="100" title="Identifiant" onkeypress="return focusonenter(event, 'remindme_code')" value="<?php echo htmlspecialchars($login, ENT_COMPAT, 'UTF-8'); ?>" /></p>
- <p class="info">Vous pouvez aussi entrer votre adresse d'email.</p>
- <p class="input"><input name="remindme_confirmed" id="remindme_confirmed" type="checkbox" title="Confirmation" <?php if ($confirmed) echo 'checked="checked"'; ?> /> Je veux recevoir un nouveau mot de passe</p>
- <?php if ($with_captcha): ?>
- <p class="input">
- <img src="<?php echo $base_path; ?>/captcha/remindme" alt="" title="Code de vérification" />
- :
- <input type="text" name="remindme_code" id="remindme_code" size="4" maxlength="4" title="4 lettres" onkeypress="return submitonenter(event, 'remindme_submit')" value="" />
- </p>
- <?php endif; ?>
- <p class="submit"><button type="submit" name="remindme_submit" id="remindme_submit">Envoyer</button></p>
- </div>
- </form>
- <?php
- if ($errors):
- extract($errors);
- ?>
- <div class="errors">
- <?php if ($missing_code): ?>
- <p>Entrez le code de vérification affiché dans l'image.</p>
- <?php elseif ($bad_code): ?>
- <p>Le code de vérification est incorrect.</p>
- <?php endif; ?>
- <?php if ($missing_login): ?>
- <p>Vous n'avez pas saisi votre identifiant.</p>
- <?php elseif ($bad_login): ?>
- <p>L'identifiant n'est pas valide.</p>
- <?php endif; ?>
- <?php if ($missing_confirmation): ?>
- <p>Cochez la case de confirmation.</p>
- <?php endif; ?>
- <?php if ($internal_error): ?>
- <p>Une erreur interne s'est produite. Si vous pouvez décrire le problème, merci de <a href="<?php echo $contact_page; ?>">nous contacter</a>.
- <?php endif; ?>
- </div>
- <?php endif; ?>
- <?php
- if ($infos):
- extract($infos);
- ?>
- <div class="infos">
- <?php if ($email_sent): ?>
- <p>Un nouveau mot de passe vous a été envoyé par email.<br />
- Pour vous identifier, <a href="<?php echo $user_page; ?>">cliquez ici</a>.</p>
- <?php endif; ?>
- </div>
- <?php endif; ?>
- </div>
Enter http://localhost/cms/site16/fr/mot-de-passe in the address bar of your navigator to validate the French version.
Once the views are tuned, complete remindme
with the following code:
- require_once 'readarg.php';
- require_once 'strflat.php';
- require_once 'validateusername.php';
- require_once 'validatemail.php';
- require_once 'isusernameallowed.php';
- require_once 'ismailallowed.php';
- require_once 'tokenid.php';
Loads the functions readarg
, strflat
, validate_user_name
, validate_mail
, is_user_name_allowed
, is_mail_allowed
and token_id
.
Add the files isusernameallowed.php and ismailallowed.php in the folder library with the following contents:
- /cms/site16
- library
- isusernameallowed.php
- ismailallowed.php
- library
- function is_user_name_allowed($name) {
- static $blacklist = array('frasq');
- return !in_array($name, $blacklist);
- }
is_user_name_allowed
returns false
if $name
is a name which is forbidden by the list $blacknamelist
, true
if $name
is allowed.
- function is_mail_allowed($mail) {
- static $blacklist = array('frasq@frasq.org', 'webmaster@frasq.org', 'keymaster@frasq.org');
- return !in_array($mail, $blacklist);
- }
is_mail_allowed
returns false
if $mail
is an email address which is forbidden by the list $blackmaillist
, true
if a mail to $mail
is allowed.
- function remindme($lang, $login=false) {
- $action='init';
- if (isset($_POST['remindme_submit'])) {
- $action='remindme';
- }
- $login=$confirmed=$code=$token=false;
- switch($action) {
- case 'remindme':
- if (isset($_POST['remindme_login'])) {
- $login=strtolower(strflat(readarg($_POST['remindme_login'], true)));
- }
- if (isset($_POST['remindme_confirmed'])) {
- $confirmed=$_POST['remindme_confirmed'] ? true : false;
- }
- if (isset($_POST['remindme_code'])) {
- $code=readarg($_POST['remindme_code'], true);
- }
- if (isset($_POST['remindme_token'])) {
- $token=readarg($_POST['remindme_token']);
- }
- break;
- default:
- break;
- }
Reads the form.
- switch($action) {
- case 'remindme':
- if (!isset($_SESSION['remindme_token']) or $token != $_SESSION['remindme_token']) {
- $bad_token=true;
- break;
- }
- if ($with_captcha) {
- if (!$code) {
- $missing_code=true;
- break;
- }
- $captcha=isset($_SESSION['captcha']) ? $_SESSION['captcha'] : false;
- if (!$captcha or $captcha != strtoupper($code)) {
- $bad_code=true;
- break;
- }
- }
- if (!$login) {
- $missing_login=true;
- }
- else if ((!validate_user_name($login) or !is_user_name_allowed($login)) and (!validate_mail($login) or !is_mail_allowed($login))) {
- $bad_login=true;
- }
- if (!$confirmed) {
- $missing_confirmation=true;
- }
- break;
- default:
- break;
- }
Controls all the input data, particularly if the connection name is allowed.
- switch($action) {
- case 'remindme':
- if ($bad_token or $missing_code or $bad_code or $missing_login or $bad_login or $missing_confirmation) {
- break;
- }
- require_once 'models/user.inc';
- $user_id = user_find($login);
- if (!$user_id) {
- $bad_login=true;
- require_once 'log.php';
- write_log('password.err', substr($login, 0, 40));
- break;
- }
- $user = user_get($user_id);
- if (!$user) {
- $internal_error=true;
- break;
- }
Checks if an error has been detected, loads the model user.inc and gets the user's number with user_find
.
If user_find
returns false
, the error is logged in password.err.
Otherwise, the code gets the email address for the account $user_id
with user_get
.
- require_once 'newpassword.php';
- $newpassword=newpassword();
- if (!user_set_newpassword($user_id, $newpassword)) {
- $internal_error=true;
- break;
- }
Loads the function newpassword
then assigns a new password to the user's account $user_id
.
- require_once 'emailcrypto.php';
- global $sitename, $webmaster;
- $to=$user['user_mail'];
- $subject = translate('email:new_password_subject', $lang);
- $msg = translate('email:new_password_text', $lang) . "\n\n" . translate('email:salutations', $lang);
- if (!emailcrypto($msg, $newpassword, $to, $subject, $webmaster)) {
- $internal_error=true;
- }
- else {
- $email_sent=$to;
- }
- $confirmed=false;
- break;
- default:
- break;
- }
Loads the emailcrypto
function.
Prepares the email message according to the user's language and calls emailcrypto
with the new password, the subject and the text of the message, the recepient and the sender's email addresses.
- if ($internal_error) {
- $contact_page=url('contact', $lang);
- }
- else if ($email_sent) {
- $user_page=url('user', $lang);
- }
Sets $contact_page
to the URL of the contact page if $internal_error
is true
or $user_page
to the URL of the identification if $email_sent
is true
.
- $_SESSION['remindme_token'] = $token = token_id();
- $errors = compact('missing_login', 'bad_login', 'missing_confirmation', 'missing_code', 'bad_code', 'internal_error', 'contact_page');
- $infos = compact('email_sent', 'user_page');
- $output = view('remindme', $lang, compact('token', 'with_captcha', 'login', 'confirmed', 'errors', 'infos'));
- return $output;
- }
The rest of the code prepares all the parameters of the view, including the token which is memorized in the session variable $_SESSION['remindme_token']
, generates it and returns content.
Define the subject and the text of the email by adding the parameters email:salutations
, email:new_password_subject
and email:new_password_text
in the file includes/strings.inc, in English and in French:
- 'email:salutations' => 'See you soon.',
- 'email:new_password_subject' => 'frasq.org : Your new password.',
- 'email:new_password_text' => "Your new password is in the attached cryptogram.\n\nIf you didn't ask to change it, just ignore this message.\nNOTE: Your old password is still valid.\n\nIdentify yourself with the new password to activate it.",
- 'email:salutations' => 'À bientôt.',
- 'email:new_password_subject' => 'frasq.org : Votre mot de passe.',
- 'email:new_password_text' => "Votre nouveau mot de passe est dans le cryptogramme en pièce jointe.\n\nSi vous n'avez pas demandé à en changer, veuillez ignorer ce message.\nNOTE : Votre ancien mot de passe est toujours valide.\n\nConnectez-vous avec le nouveau mot de passe pour l'activer.",
Add the files newpassword.php and emailcrypto.php in the folder library with the following contents:
- /cms/site16
- library
- newpassword.php
- emailcrypto.php
- library
- require_once 'strrand.php';
- function newpassword($len=6) {
- $charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
- return strrand($charset, $len);
- }
newpassword
returns 6 random characters in capital letters or digits.
The signature, the mailer and the return address of the email are global variables defined in the file includes/config.inc:
- global $sitename, $webmaster, $mailer;
- $sitename = 'frasq.org';
- $webmaster = 'nobody@frasq.org';
- $mailer = 'Frasq';
- global $signature;
- $signature='frasq.org - http://www.frasq.org';
- require_once 'strtag.php';
- function emailcrypto($text, $tag, $to, $subject, $sender) {
- global $signature, $mailer, $webmaster;
emailcrypto
sends the message $text
with an attached cryptogram of $tag
to $to
with the subject $subject
from $sender
.
- $img=strtag($tag);
- ob_start();
- imagepng($img);
- imagedestroy($img);
- $imgdata=ob_get_contents();
- ob_end_clean();
- $sep=md5(uniqid('sep'));
- $data=chunk_split(base64_encode($imgdata));
Builds a cryptogram from the string of characters $tag
with strtag
then converts the obtained image in PNG. The PNG is then encoded in base64.
- $headers = <<<_SEP_
- From: $sender
- Return-Path: $webmaster
- Content-Type: multipart/mixed; boundary="$sep"
- X-Mailer: $mailer
- _SEP_;
Generates the MIME header of the email with the fields From:
, Return-Path:
, Content-Type:
and X-Mailer:
with the parameter $sender
and the glocal variables $webmaster
and $mailer
.
The field Content-Type:
indicates that the message is in 2 parts, the text of email and the attached cryptogram, separated by $sep
.
- $body = '';
- if ($text) {
- $body .= <<<_SEP_
- --$sep
- Content-Type: text/plain; charset=utf-8
- $text
- $signature
- _SEP_;
- }
- $body .= <<<_SEP_
- --$sep
- Content-Type: image/png; name="crypto.png"
- Content-Transfer-Encoding: base64
- Content-Disposition: inline; filename="crypto.png"
- $data
- --$sep--
- _SEP_;
- return @mail($to, $subject, $body, $headers);
- }
Builds the body of the email, first the text followed by the attached image, then sends the email with the PHP function mail
.
Add a column to the user
table to memorize to new password:
mysql> ALTER TABLE user ADD newpassword VARCHAR( 32 ) NULL AFTER password;
Edit the file user.inc in the folder models/user.inc and the function user_set_newpassword
:
- function user_set_newpassword($user_id, $password) {
- if (!is_numeric($user_id)) {
- return false;
- }
- $sqlnewpassword=$password ? '\'' . md5($password) . '\'' : NULL;
- $tabuser=db_prefix_table('user');
- $sql="UPDATE $tabuser SET newpassword=$sqlnewpassword WHERE user_id=$user_id LIMIT 1";
- $r = db_update($sql);
- return $r;
- }
Modify the function user_get
to return the field user_password
:
- $sql="SELECT name AS user_name, password AS user_password, newpassword AS user_newpassword, mail AS user_mail, UNIX_TIMESTAMP(created) AS user_created, UNIX_TIMESTAMP(access) AS user_access, locale AS user_locale, active AS user_active, banned AS user_banned FROM $tabuser WHERE user_id=$user_id LIMIT 1";
Modify the function user_login
to take into account the possibilty of a new password:
- if ($user_newpassword) {
- if ($password == $user_newpassword) {
- // use the new one
- $sql="UPDATE $tabuser SET access=FROM_UNIXTIME($now), password='$user_newpassword', newpassword=NULL WHERE user_id=$user_id LIMIT 1";
- }
- else {
- // keep the old one
- $sql="UPDATE $tabuser SET access=FROM_UNIXTIME($now), newpassword=NULL WHERE user_id=$user_id LIMIT 1";
- }
- }
- else {
- // just keep track
- $sql="UPDATE $tabuser SET access=FROM_UNIXTIME($now) WHERE user_id=$user_id LIMIT 1";
- }
- db_update($sql);
If a new password has been defined and if the given password is the new password, user_login
replaces the old password by the new one, otherwise, the old password is kept.
The new password is always erased.
In all cases, user_login
writes down the date and the time of the connection.
Add a link to the page for sending a new password in the view of the identification page:
- <p class="info link">If you have forgotten your password, <a href="<?php echo $password_page; ?>">click here</a>.</p>
- <p class="info link">Si vous avez oublié votre mot de passe, <a href="<?php echo $password_page; ?>">cliquez ici</a>.</p>
Assign the URL of the page for sending a password to the variable $password_page
of the views in the login
function:
- $password_page=url('password', $lang);
- $output = view('login', $lang, compact('token', 'with_captcha', 'password_page', 'login', 'errors'));
Make sure the email address of the user foobar
is foobar@localnet.net.
Enter http://localhost/cms/site16 in the address bar of your navigator.
Go to the identification page. Click on the link If you have forgotten your password.
Enter foobar
as a name, check the confirmation box, type the verification code and press Send.
Read foobar
's email.
Try to identify yourself with the new password.
Disconnect. Ask again for a new password. Identify yourself with the old password which is still valid.
Comments