The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

App::ZofCMS::Plugin::UserLogin::ForgotPassword - addon plugin that adds functionality to let users reset passwords

SYNOPSIS

In your HTML::Template template:

    <tmpl_var name='plug_forgot_password'>

In your Main Config File or ZofCMS Template:

    plugins => [ qw/UserLogin::ForgotPassword/ ],

    plug_user_login_forgot_password => {
        # mandatory
        dsn                  => "DBI:mysql:database=test;host=localhost",

        # everything below is optional...
        # ...arguments' default values are shown
        user                 => '',
        pass                 => undef,
        opt                  => { RaiseError => 1, AutoCommit => 1 },
        users_table          => 'users',
        code_table           => 'users_forgot_password',
        q_code               => 'pulfp_code',
        max_abuse            => '5:10:60', # 5 min. intervals, max 10 attempts per 60 min.
        min_pass             => 6,
        code_expiry          => 24*60*60, # 1 day
        code_length          => 6,
        subject              => 'Password Reset',
        email_link           => undef, # this will be guessed
        from                 => undef,
        email_template       => undef, # use plugin's default template
        create_table         => undef,
        login_page           => '/',
        mime_lite_params     => undef,
        email                => undef, # use `email` column in users table
        button_send_link => q|<input type="submit" class="input_submit"|
            . q| value="Send password">|,
        button_change_pass => q|<input type="submit" class="input_submit"|
            . q| value="Change password">|,
        use_stage_indicators => 1,
        no_run               => undef,
    },

DESCRIPTION

The module is a plugin for App::ZofCMS that adds functionality to App::ZofCMS::Plugin::UserLogin plugin; that being the "forgot password?" operations. Namely, this involves showing the user the form to ask for their login, emailing the user special link which to follow (this is to establish ligitimate reset) and, finally, to provide a form where a user can enter their new password (and of course, the plugin will update the password in the users table). Wow, a mouthful of functionality! :)

This documentation assumes you've read App::ZofCMS, App::ZofCMS::Config and App::ZofCMS::Template. Whilst not necessary, being familiar with App::ZofCMS::Plugin::UserLogin might be helpful.

GENERAL OUTLINE OF THE WAY PLUGIN WORKS

Here's the big picture of what the plugin does: user visits a page, plugin shows the HTML form that asks the user to enter their login in order to request password reset.

Once the user does that, the plugin checks that the provided login indeed exists, checks that there's no abuse going on (flooding with reset requests), generates a special "code" that, as part of a full link-to-follow, is sent to the user inviting them to click it to proceed with the reset.

Once the user clicks the link in their email (and thus ends up back on your site), the plugin will invite them to enter (and reenter to confirm) their new password. Once the plugin ensures the password looks good, it will update user's password in the database.

All this can be enabled on your site with a few keystroke, thanks to this plugin :)

FIRST-LEVEL ZofCMS TEMPLATE AND MAIN CONFIG FILE KEYS

plugins

    plugins => [
        { 'UserLogin::ForgotPassword' => 2000 },
    ],

Mandatory. You need to include the plugin in the list of plugins to execute.

plug_user_login_forgot_password

    plug_user_login_forgot_password => {
        # mandatory
        dsn                  => "DBI:mysql:database=test;host=localhost",

        # everything below is optional...
        # ...arguments' default values are shown
        user                 => '',
        pass                 => undef,
        opt                  => { RaiseError => 1, AutoCommit => 1 },
        users_table          => 'users',
        code_table           => 'users_forgot_password',
        q_code               => 'pulfp_code',
        max_abuse            => '5:10:60', # 5 min. intervals, max 10 attempts per 60 min.
        min_pass             => 6,
        code_expiry          => 24*60*60, # 1 day
        code_length          => 6,
        subject              => 'Password Reset',
        email_link           => undef, # this will be guessed
        from                 => undef,
        email_template       => undef, # use plugin's default template
        create_table         => undef,
        login_page           => '/',
        mime_lite_params     => undef,
        email                => undef, # use `email` column in users table
        button_send_link => q|<input type="submit" class="input_submit"|
            . q| value="Send password">|,
        button_change_pass => q|<input type="submit" class="input_submit"|
            . q| value="Change password">|,
        use_stage_indicators => 1,
        no_run               => undef,
    },

    # or
    plug_user_login_forgot_password => sub {
        my ( $t, $q, $config ) = @_;
        ...
        return $hashref_to_assign_to_plug_user_login_forgot_password_key;
    },

Mandatory. Takes either a hashref or a subref as a value. If subref is specified, its return value will be assigned to plug_user_login_forgot_password key as if it was already there. If sub returns an undef, then plugin will stop further processing. The @_ of the subref will contain $t, $q, and $config (in that order), where $t is ZofCMS Tempalate hashref, $q is query parameters hashref, and $config is the App::ZofCMS::Config object. The hashref has a whole ton of possible keys/values that control plugin's behavior; luckily, virtually all of them are optional with sensible defaults. Possible keys/values for the hashref are as follows:

dsn

    plug_user_login_forgot_password => {
        dsn => "DBI:mysql:database=test;host=localhost",
    ...

Mandatory. The dsn key will be passed to DBI's connect_cached() method, see documentation for DBI and DBD::your_database for the correct syntax for this one. The example above uses MySQL database called test which is located on localhost. Defaults to: "DBI:mysql:database=test;host=localhost", which is rather useless, so make sure to set your own :)

user

    plug_user_login_forgot_password => {
        user => '',
    ...

Optional. Specifies the user name (login) for the database. This can be an empty string if, for example, you are connecting using SQLite driver. Defaults to: '' (empty string)

pass

    plug_user_login_forgot_password => {
        pass => undef,
    ...

Optional. Same as user except specifies the password for the database. Defaults to: undef (no password)

opt

    plug_user_login_forgot_password => {
        opt => { RaiseError => 1, AutoCommit => 1 },
    ...

Optional. Will be passed directly to DBI's connect_cached() method as "options". Defaults to: { RaiseError => 1, AutoCommit => 1 }

users_table

    plug_user_login_forgot_password => {
        users_table => 'users',
    ...

Optional. Specifies the name of the SQL table that you're using for storing user records. This would be the App::ZofCMS::Plugin::UserLogin's table argument. If you're not using that plugin, your users table should have logins stored in login column, passwords in password columns. If you're not planning to specify the email argument (see below), your users table need to have email addresses specified in the email table column; these will be the email addresses to which the reset links will be emailed. Defaults to: users

code_table

    plug_user_login_forgot_password => {
        code_table => 'users_forgot_password',
    ...

    CREATE TABLE `users_forgot_password` (
        `login` TEXT,
        `time`  VARCHAR(10),
        `code`  TEXT
    );'

Optional. Specifies the name of SQL table into which to store reset codes. This table will be used when user submits password reset request, and the added entry will be deleted when user successfully enters new password. Above SQL code shows the needed structure of the table, but see create_table argument (below) for more on this. Defaults to: users_forgot_password

create_table

    plug_user_login_forgot_password => {
        create_table => undef,
    ...

Optional. Takes true or false values. When set to a true value, the plugin will automatically create the needed table where to store reset codes (see code_table above). Note: if the table already exists, plugin will crap out with an error - that's the intended behaviour, simply set create_table back to false value. Defaults to: undef

q_code

    plug_user_login_forgot_password => {
        q_code => 'pulfp_code',
    ...

Optional. Takes a scalar as a value that indicates the name of the query parameter that will be used by the plugin to reteive the "special" code. Plugin uses several query parameter names during its operation, but the code is sent via email and is directly visible to the user; the idea is that that might give you enough reason to wish control the name of that parameter. Defaults to: pulfp_code

max_abuse

    plug_user_login_forgot_password => {
        max_abuse => '5:10:60', # 5 min. intervals, max 10 attempts per 60 min.
    ...

    plug_user_login_forgot_password => {
        max_abuse => undef, # turn off abuse control
    ...

Optional. Defaults to: 5:10:60 (5 minute intervals, maximum 10 attempts per 60 minutes). Takes either undef or specially formatted "time code". This argument is responsible for abuse control (yey); abuse being the case when an idiot enters some user's login in the reset form and then hits browser's REFRESH a billion times, flooding said user. The values for this argument are:

undef

    plug_user_login_forgot_password => {
        max_abuse => undef, # turn off abuse control
    ...

If set to undef, abuse control will be disabled.

first time code number

    plug_user_login_forgot_password => {
        max_abuse => '5:10:60',
    ...

Unless set to undef, the argument's value must be three numbers separated by colons. The first number indicates, in minutes, the interval of time that must pass after a password reset request until another request can be sent using the same login (there's no per-IP protection, or anything like that). Default first number is 5.

second time code number

The second number indicates the maximum number of reset attempts (again, per-login) that can be done in third number interval of time. For example, if the second number is 10 and third is 60, a user can request password reset 10 times in 60 minutes and no more. Default second number is 10.

third time code number

The third number indicates, in minutes, the time interval used by the second number. Default third number is 60.

min_pass

    plug_user_login_forgot_password => {
        min_pass => 6,
    ...

Optional. Takes a positive integer as a value. Specifies the minimum length (number of characters) for the new password the user provides. Defaults to: 6

code_expiry

    plug_user_login_forgot_password => {
        code_expiry => 24*60*60, # 1 day
    ...

Optional. Takes, in seconds, the time after which to deem the reset code (request) as expired. In other words, if the user requests password reset, then ignores his email for code_expiry seconds, then the link in his email will no longer work, and he would have to request the reset all over again. Defaults to: 86400 (24 hours)

code_length

    plug_user_login_forgot_password => {
        code_length => 6,
    ...

Optional. Specifies the length of the randomly generated code that is used to identify legitimate user. Since this code is sent to the user via email, and is directly visible, specifying the code to be of too much length will look rather ugly. On the other hand, too short of a code can be easily guessed by a vandal. Defaults to: 6

subject

    plug_user_login_forgot_password => {
        subject => 'Password Reset',
    ...

Optional. Takes a string as a value, this will be used as the subject line of the email sent to the user (the one containing the link to click). Defaults to: Password Reset

from

    plug_user_login_forgot_password => {
        from => undef,
    ...

    plug_user_login_forgot_password => {
        from => 'Zoffix Znet <zoffix@cpan.org>',
    ...

Optional. Takes a scalar as a value that specifies the From field for your email. If not specified, the plugin will simply not set the From argument in MIME::Lite's new() method (which is what this plugin uses under the hood). See MIME::Lite's docs for more description. Defaults to: undef (not specified)

    plug_user_login_forgot_password => {
        email_link => undef, # guess the right page
    ...

    # note how the URI ends with the "invitation" to append the reset
    # ... code right to the end
    plug_user_login_forgot_password => {
        email_link => 'http://foobar.com/your_page?foo=bar&pulfp_code=',
    ...

Optional. Takes either undef or a string containing a link as a value. Specifies the link to the page with this plugin enabled, this link will be emailed to the user so that they could proceed to enter their new password. When set to undef, the plugin guesses the current page (using %ENV) and that's what it will use for the link. If you specify the string, make sure to end it with pulfp_code= (note the equals sign at the end), where pulfp_code is the value you have set for q_code argument. Defaults to: undef (makes the plugin guess the right link)

email_template

    plug_user_login_forgot_password => {
        email_template => undef, # use plugin's default template
    ...

    plug_user_login_forgot_password => {
        email_template => \'templates/file.tmpl', # read template from file
    ...

    plug_user_login_forgot_password => {
        email_template => '<p>Blah blah blah...', # use this string as template
    ...

Optional. Takes a scalar, a scalar ref, or undef as a value. Specifies HTML::Template template to use when generating the email with the reset link. When set to undef, plugin will use its default template (see OUTPUT section below). If you're using your own template, the link template variable will contain the link the user needs to follow (i.e., use <tmpl_var escape='html' name='link'>). Defaults to: undef (plugin's default, see OUTPUT section below)

login_page

    plug_user_login_forgot_password => {
        login_page => '/',
    ...

    plug_user_login_forgot_password => {
        login_page => '/my-login-page',
    ...

    plug_user_login_forgot_password => {
        login_page => 'http://lolwut.com/your-login-page',
    ...

Optional. As a value, takes either undef or a URI. Once the user is through will all the stuff plugin wants them to do, the plugin will tell them that the password has been changed, and that they can no go ahead and "log in". If login_page is specified, the "log in" text will be a link pointing to whatever you set in login_page; otherwise, the "log in" text will be just plain text. Defaults to: / (i.e. web root)

mime_lite_params

    plug_user_login_forgot_password => {
        mime_lite_params => undef,
    ...

    plug_user_login_forgot_password => {
        mime_lite_params => [
            'smtp',
            'meowmail',
            Auth   => [ 'FOO/bar', 'p4ss' ],
        ],
    ...

Optional. Takes an arrayref or undef as a value. If specified, the arrayref will be directly dereferenced into MIME::Lite->send(). Here you can set any special send arguments you need; see MIME::Lite docs for more info. Note: if the plugin refuses to send email, it could well be that you need to set some mime_lite_params; on my box, without anything set, the plugin behaves as if everything went through fine, but no email arrives. Defaults to: undef

email

    plug_user_login_forgot_password => {
        email => undef,
    ...

    plug_user_login_forgot_password => {
        email => 'foo@bar.com,meow.cans@catfood.com',
    ...

Optional. Takes either undef or email address(es) as a value. This argument tells the plugin where to send the email containing password reset link. If set to undef, plugin will look into users_table (see above) and will assume that email address is associated with the user's account and is stored in the email column of the users_table table. If you don't want that, set the email address directly here. Note: if you want to have multiple email addresses, simply separate them with commas. Defaults to: undef (take emails from users_table table)

    plug_user_login_forgot_password => {
        button_send_link => q|<input type="submit" class="input_submit"|
            . q| value="Send password">|,
    ...

Optional. Takes HTML code as a value. This code represents the submit button in the first form (the one that asks the user to enter their login). This, for example, allows you to use image buttons instead of regular ones. Also, feel free to use this as the insertion point for any extra HTML form you need in this form. Defaults to: <input type="submit" class="input_submit" value="Send password">

button_change_pass

    plug_user_login_forgot_password => {
        button_change_pass => q|<input type="submit" class="input_submit"|
            . q| value="Change password">|,
    ...

Optional. Takes HTML code as a value. This code represents the submit button in the second form (the one that asks the user to enter and reconfirm their new password). This, for example, allows you to use image buttons instead of regular ones. Also, feel free to use this as the insertion point for any extra HTML form you need in this form. Defaults to: <input type="submit" class="input_submit" value="Change password">

no_run

    plug_user_login_forgot_password => {
        no_run => undef,
    ...

    plug_user_login_forgot_password => {
        no_run => 1,
    ...

Optional. Takes either true or false values as a value. This argument is a simple control switch that you can use to tell the plugin not to execute. If set to a true value, plugin will not run. Defaults to: undef (for obvious reasons :))

use_stage_indicators

    plug_user_login_forgot_password => {
        use_stage_indicators => 1,
    ...

Optional. Takes either true or false values as a value. When set to a true value, plugin will set "stage indicators" (see namesake section below for details); otherwise, it won't set anything. Defaults to: 1

STAGE INDICATORS & PLUGIN'S OUTPUT VARIABLE

All of plugin's output is spit out into a single variable in your HTML::Template template:

    <tmpl_var name='plug_forgot_password'>

This raises the question of controlling the bells and whistles on your page with regard to what stage the plugin is undergoing (i.e. is it displaying that form that asks for a login or the one that is asking the user for a new password?). This is where stage indicators come into play.

Providing use_stage_indicators argument (see above) is set to a true value, the plugin will set the key with the name of appropriate stage indicator to a true value. That key resides in the {t} ZofCMS Template special key, so that you could use it in your HTML::Template templates. Possible stage indicators as well as explanations of when they are set are as follows:

plug_forgot_password_stage_initial

    <tmpl_if name='plug_forgot_password_stage_initial'>
        Forgot your pass, huh?
    </tmpl_if>

This indicator shows that the plugin is in its initial stage; i.e. the form asking the user to enter their login is shown.

plug_forgot_password_stage_ask_error_login

    <tmpl_if name='plug_forgot_password_stage_ask_error_login'>
        Yeah, that ain't gonna work if you don't tell me your login...
    </tmpl_if>

This indicator will be active if the user submits the form that is asking for his login, but does not specify his login.

plug_forgot_password_stage_ask_error_no_user

    <tmpl_if name='plug_forgot_password_stage_ask_error_no_user'>
        Are you sure you got the right address, bro?
    </tmpl_if>

This indicator shows that the plugin did not find user's login in the users_table table.

plug_forgot_password_stage_ask_error_abuse

    <tmpl_if name='plug_forgot_password_stage_ask_error_abuse'>
        Give it a rest, idiot!
    </tmpl_if>

This indicator shows that the plugin detected abuse (see max_abuse plugin's argument for details).

plug_forgot_password_stage_emailed

    <tmpl_if name='plug_forgot_password_stage_emailed'>
        Sent ya an email, dude!
    </tmpl_if>

This indicator turns on when the plugin successfully sent the user an email containing reset pass link.

plug_forgot_password_stage_code_invalid

    <tmpl_if name='plug_forgot_password_stage_code_invalid'>
        Your reset code has expired, buddy. Hurry up, next time!
    </tmpl_if>

This indicator is active when the plugin can't find the code the user is giving it. Under natural circumstances, this will only occur when the code has expired.

plug_forgot_password_stage_change_pass_ask

    <tmpl_if name='plug_forgot_password_stage_change_pass_ask'>
        What's the new pass you want, buddy?
    </tmpl_if>

This indicator turns on when the form asking the user for the new password is active.

plug_forgot_password_stage_code_bad_pass_length

    <tmpl_if name='plug_forgot_password_stage_code_bad_pass_length'>
        That pass's too short, dude.
    </tmpl_if>

This indicator signals that the user attempted to use too short of a new password (the length is controlled with the min_pass plugin's argument).

plug_forgot_password_stage_code_bad_pass_copy

    <tmpl_if name='plug_forgot_password_stage_code_bad_pass_copy'>
        It's really hard to type the same thing twice, ain't it?
    </tmpl_if>

This indicator turns on if the user did not retype the new password correctly.

plug_forgot_password_stage_change_pass_done

    <tmpl_if name='plug_forgot_password_stage_change_pass_done'>
        Well, looks like you're all done with reseting your pass and what not.
    </tmpl_if>

This indicator shows that the final stage of plugin's run has been reached; i.e. the user has successfully reset the password and can go on with their other business.

OUTPUT

The plugin generates a whole bunch of various output; what's below should cover all the bases:

Default Email Template

    <h2>Password Reset</h2>

    <p>Hello. Someone (possibly you) requested a password reset. If that
    was you, please follow this link to complete the action:
    <a href="<tmpl_var escape='html' name='link'>"><tmpl_var escape='html'
    name='link'></a></p>

    <p>If you did not request anything, simply ignore this email.</p>

You can change this using email_template argument. When using your own, use <tmpl_var escape='html' name='link'> to insert the link the user needs to follow.

"Ask Login" Form Template

    <form action="" method="POST" id="plug_forgot_password_form">
    <div>
        <p>Please enter your login into the form below and an email with
            further instructions will be sent to you.</p>

        <input type="hidden" name="page" value="<tmpl_var escape='html'
            name='page'>">
        <input type="hidden" name="pulfp_ask_link" value="1">
        <tmpl_if name='error'>
            <p class="error"><tmpl_var escape='html' name='error'></p>
        </tmpl_if>

        <label for="pulfp_login">Your login: </label
        ><input type="text"
            class="input_text"
            name="pulfp_login"
            id="pulfp_login">

        <input type="submit"
            class="input_submit"
            value="Send password">
    </div>
    </form>

This is the form that asks the user for their login in order to reset the password. Submit button is plugin's default code, you can control it with the button_send_link plugin's argument.

"New Password" Form Template

    <form action="" method="POST" id="plug_forgot_password_new_pass_form">
    <div>
        <p>Please enter your new password.</p>

        <input type="hidden" name="page" value="<tmpl_var escape='html'
            name='page'>">
        <input type="hidden" name="<tmpl_var escape='html'
            name='code_name'>"
            value="<tmpl_var escape='html' name='code_value'>">
        <input type="hidden" name="pulfp_has_change_pass" value="1">
        <tmpl_if name='error'>
            <p class="error"><tmpl_var escape='html' name='error'></p>
        </tmpl_if>

        <ul>
            <li>
                <label for="pulfp_pass">New password: </label
                ><input type="password"
                    class="input_password"
                    name="pulfp_pass"
                    id="pulfp_pass">
            </li>
            <li>
                <label for="pulfp_repass">Retype new password: </label
                ><input type="password"
                    class="input_password"
                    name="pulfp_repass"
                    id="pulfp_repass">
            </li>
        </ul>

        <input type="submit"
            class="input_submit"
            value="Change password">
    </div>
    </form>

This is the template for the form that asks the user for their new password, as well as the retype of it for confirmation purposes. The code for the submit button is what the plugin uses by default (see button_change_pass plugin's argument).

"Email Sent" Message

    <p class="reset_link_send_success">Please check your email
        for further instructions on how to reset your password.</p>

This message is shown when the user enters correct login and the plugin successfully sents the user their reset link email.

"Expired Reset Code" Message

    <p class="reset_code_expired">Your reset code has expired. Please try
        resetting your password again.</p>

This will be shown if the user follows a reset link that contains invalid (expired) reset code.

"Changes Successfull" Message

    <p class="reset_pass_success">Your password has been successfully
        changed. You can now use it to <a href="/">log in</a>.</p>

This will be shown when the plugin has done its business and the password has been reset. Note that the "log in" text will only be a link if login_page plugin's argument is set; otherwise it will be plain text.

REQUIRED MODUILES

The plugin requires the following modules/versions for healthy operation:

    App::ZofCMS::Plugin::Base  => 0.0105
    DBI                        => 1.607
    Digest::MD5                => 2.36_01
    HTML::Template             => 2.9
    MIME::Lite                 => 3.027

REPOSITORY

Fork this module on GitHub: https://github.com/zoffixznet/App-ZofCMS

BUGS

To report bugs or request features, please use https://github.com/zoffixznet/App-ZofCMS/issues

If you can't access GitHub, you can email your request to bug-App-ZofCMS at rt.cpan.org

AUTHOR

Zoffix Znet <zoffix at cpan.org> (http://zoffix.com/, http://haslayout.net/)

LICENSE

You can use and distribute this module under the same terms as Perl itself. See the LICENSE file included in this distribution for complete details.