# Copyright 2002-2008 Josh Clark and Global Moxie, LLC. This code cannot be
# redistributed without permission from globalmoxie.com.  For more
# information, consult your Big Medium license.
#
# $Id: ResetPassword.pm 3043 2008-03-31 14:00:38Z josh $

package BigMed::App::Web::ResetPassword;
use strict;
use warnings;
use utf8;
use Carp;
$Carp::Verbose = 1;
use base qw(BigMed::App::Web);
use BigMed::User;
use BigMed::Email;
use CGI::Session;
use BigMed::DiskUtil qw(bm_file_path bm_confirm_dir);
use BigMed::MD5 qw(md5_hex);

sub setup {
    my $app = shift;
    $app->start_mode('password-help');
    $app->run_modes(
        'AUTOLOAD'      => sub { $_[0]->rm_password_help() },
        'password-help' => 'rm_password_help',
        'username'      => 'rm_username',
        'email-pass'    => 'rm_email_pass',
        'r'             => 'rm_password_prompt',
        'reset'         => 'rm_reset',
    );
    return;
}

###########################################################
# RUN MODES
###########################################################

sub rm_password_help {
    my ( $app, %options ) = @_;

    #build the account-name fields
    my $email = $app->prompt_field_ref(
        data_class => 'BigMed::User',
        column     => 'name',
    );
    my $email_submit = $app->prompt_field_ref(
        id        => 'email_submit',
        prompt_as => 'submit',
        value     => $app->language('BM_SUBMIT_LABEL_Continue'),
    );
    my $email_fs = $app->prompt_fieldset_ref(
        id        => 'email_fs',
        fields    => [$email, $email_submit],
        query     => $options{query},
        field_msg => $options{field_msg},
    );
    $app->js_focus_field('name');

    #build the email/name-reminder fields
    my $username = $app->prompt_field_ref(
        data_class  => 'BigMed::User',
        column      => 'email',
        description => 'RESET_DESC_Email',
    );
    my $username_submit = $app->prompt_field_ref(
        id        => 'user_submit',
        prompt_as => 'submit',
        value     => $app->language('BM_SUBMIT_LABEL_Continue'),
    );
    my $username_fs = $app->prompt_fieldset_ref(
        id        => 'username_fs',
        fields    => [$username, $username_submit],
        query     => $options{query},
        field_msg => $options{field_msg},
    );

    my ( $title, $message ) = $app->title_and_message(
        field_msg => $options{field_msg},
        message   => $options{message} || 'RESET_DESC_Request Password Reset',
        title     => $options{head} || 'RESET_Request Password Reset',
    );

    my $password_url = $app->build_url(
        script => 'bm-reset.cgi',
        rm     => 'email-pass',
    );
    my $username_url = $app->build_url(
        script => 'bm-reset.cgi',
        rm     => 'username',
    );

    $app->js_make_toggle( 'bmUserToggler', 'bmUserHelp', 'slide' );
    return $app->html_template_screen(
        'screen_reset_request.tmpl',
        bmcp_title        => $title,
        message           => $message,
        password_fieldset => [$email_fs],
        username_fieldset => [$username_fs],
        password_url      => $password_url,
        username_url      => $username_url,
    );

}

sub rm_username {
    my $app   = shift;
    my %field = $app->parse_submission(
        {   data_class => 'BigMed::User',
            column     => 'email',
            required   => 1,
        },
    );
    return $app->rm_password_help(
        field_msg => $field{_ERROR},
        query     => $app->query
      )
      if $field{_ERROR};

    my $users = BigMed::User->select( { email => $field{email} } )
      or return $app->rm_password_help( query => $app->query );

    if ( !$users->count ) {
        $app->set_error(
            head => 'RESET_HEAD_No Matching Accounts',
            text =>
              ['RESET_TEXT_No Matching Accounts for email', $field{email}],
        );
        return $app->rm_password_help( query => $app->query );
    }

    my @names;
    my $user;
    while ( $user = $users->next ) {
        push @names, $app->unescape( $user->name );
    }
    return $app->rm_password_help( query => $app->query ) if !defined $user;

    my $names = join( "\n", @names );
    my $url =
      $app->build_url( script => 'bm-reset.cgi', rm => 'password-help' );
    my $message = $app->language( ['RESET_Username email', $names, $url] );

    BigMed::Email::send_email(
        to      => $field{email},
        from    => $app->env('ADMINEMAIL'),
        body    => $message,
        subject => $app->language('RESET_Username email subject'),
      )
      or return $app->rm_password_help( query => $app->query );

    return $app->rm_password_help(
        head    => 'RESET_HEAD_Message sent',
        message => ['RESET_TEXT_Message sent', $field{email}],
    );
}

sub rm_email_pass {
    my $app   = shift;
    my %field = $app->parse_submission(
        {   data_class => 'BigMed::User',
            column     => 'name',
            required   => 1,
        },
    );
    return $app->rm_password_help(
        field_msg => $field{_ERROR},
        query     => $app->query
      )
      if $field{_ERROR};

    my $user = $app->fetch_account_for_name( $field{name} )
      or return $app->rm_password_help( query => $app->query );

    #create a session key for the reset
    my $session = $app->password_session;
    my $key     = $session->id();
    $session->param( 'acctname', md5_hex( lc $user->name ) );
    $session->expire('+1h');
    $session->flush;

    my $key_url = $app->build_url(
        script => 'bm-reset.cgi',
        rm     => 'r',
        args   => [$session->id],
    );
    my $name      = $app->unescape( $user->name );
    my $email_msg =
      $app->language( ['RESET_reset_email_message', $name, $key_url] );

    BigMed::Email::send_email(
        to      => $user->email,
        from    => $app->env('ADMINEMAIL'),
        subject => $app->language('RESET_reset_email_subject'),
        body    => $email_msg,
      )
      or return $app->rm_password_help( query => $app->query );

    return $app->basic_message(
        head => $app->language('RESET_HEAD_Instructions sent'),
        text => $app->language('RESET_TEXT_Instructions sent'),
    );
}

sub rm_password_prompt {
    my ( $app, %options ) = @_;

    #verify key and session
    my ($key) = $app->path_args;
    $key ||= $app->utf8_param('key');
    if ( !$key || length($key) != 32 || $key =~ /[^a-fA-F0-9]/ ) {
        $app->set_error(
            head => 'RESET_HEAD_Incomplete URL',
            text => 'RESET_TEXT_Incomplete URL',
        );
        $app->error_stop;
    }

    return $app->rm_password_help if !$app->valid_session($key);

    #build the fields
    my $user = $app->prompt_field_ref(
        data_class => 'BigMed::User',
        column     => 'name',
        required   => 1,
    );
    my $key_field = $app->prompt_field_ref(
        id        => 'key',
        prompt_as => 'hidden',
        value     => $key,
    );
    my $pass = $app->prompt_field_ref(
        data_class => 'BigMed::User',
        column     => 'password',
        label      => 'ACCT_LABEL_new_password',
        required   => 1,
    );
    my $confirm_pass = $app->prompt_field_ref(
        id          => 'password_confirm',
        prompt_as   => 'password',
        validate_as => 'password_confirm',
        required    => 1,
        label       => 'ACCT_LABEL_confirm_password',
    );
    my $submit = $app->prompt_field_ref(
        id        => 'reset_submit',
        prompt_as => 'submit',
        value     => $app->language('BM_SUBMIT_LABEL_Save'),
    );

    my $user_fs = $app->prompt_fieldset_ref(
        id        => 'user_fs',
        title     => 'RESET_TITLE_Confirm Account Name',
        pre_html  => $app->language('RESET_TEXT_Confirm Account Name'),
        fields    => [$user, $key_field],
        query     => $options{query},
        field_msg => $options{field_msg},
    );
    my $pass_fs = $app->prompt_fieldset_ref(
        id        => 'pass_fs',
        title     => 'ACCT_LABEL_new_password',
        fields    => [$pass, $confirm_pass, $submit],
        query     => $options{query},
        field_msg => $options{field_msg},
    );

    $app->js_focus_field('name');
    my ( $title, $message ) = $app->title_and_message(
        field_msg => $options{field_msg},
        message   => $options{message},
        title     => $options{head} || 'ACCT_LABEL_new_password',
    );

    my $form_url = $app->build_url(
        script => 'bm-reset.cgi',
        rm     => 'reset',
    );

    return $app->html_template_screen(
        'screen_web_basic.tmpl',
        bmcp_title => $title,
        message    => $message,
        fieldsets  => [$user_fs, $pass_fs],
        form_url   => $form_url,
    );

}

sub rm_reset {
    my $app   = shift;
    my %field = $app->parse_submission(
        {   data_class => 'BigMed::User',
            column     => 'name',
            required   => 1,
        },
        {   data_class => 'BigMed::User',
            column     => 'password',
            required   => 1,
        },
        {   id       => 'password_confirm',
            parse_as => 'password',
            required => 1,
        },
        {   id       => 'key',
            parse_as => 'raw_text',
            required => 1,
        },
    );
    return $app->rm_password_prompt(
        query     => $app->query,
        field_msg => $field{_ERROR}
      )
      if $field{_ERROR};

    #validate account name
    my $session = $app->valid_session( $field{key} )
      or return $app->rm_password_help( query => $app->query );
    my $acct_check = md5_hex( lc $field{name} );
    if ( $acct_check ne $session->param('acctname') ) {
        $app->set_error(
            head => 'RESET_HEAD_Incorrect Account Name',
            text => 'RESET_TEXT_Incorrect Account Name',
        );
        return $app->rm_password_prompt( query => $app->query );
    }

    if ( $field{password} ne $field{password_confirm} ) {
        $app->set_error(
            head => 'BM_Please_Review_Your_Entry',
            text => 'ACCT_ERR_confirmation password must match',
        );
        return $app->rm_password_prompt( query => $app->query );
    }

    my $user = $app->fetch_account_for_name( $field{name} )
      or return $app->rm_password_help( query => $app->query );

    my $password = $user->encode_password( $field{password} );
    $user->set_password($password);
    $user->save or return $app->rm_password_help( query => $app->query );
    $session->delete();

    my $login_url = $app->build_url(
        script => 'bm-login.cgi',
        rm     => 'login',
    );
    return $app->basic_message(
        head => $app->language('RESET_HEAD_New Password Saved'),
        text => $app->language(['RESET_TEXT_New Password Saved', $login_url]),
    );
}

sub fetch_account_for_name {
    my ( $app, $name ) = @_;
    croak 'fetch_account_for_name requires name argument' if !defined $name;

    my $user = BigMed::User->fetch( { name => $name } );
    return if !defined $user;
    if ( !$user ) {
        return $app->set_error(
            head => 'RESET_HEAD_No Matching Accounts',
            text =>
              ['RESET_TEXT_No Matching Accounts for name', $name],
        );
    }
    return $user;
}

sub valid_session {
    my ( $app, $key ) = @_;
    my $session = $app->password_session($key);
    if ( $session->is_new ) {
        $session->delete();
        sleep 3;    #slow going to fish for keys
        my $url = $app->build_url(
            script => 'bm-reset.cgi',
            rm     => 'password-help',
        );
        return $app->set_error(
            head => 'RESET_HEAD_Link Expired',
            text => 'RESET_TEXT_Link Expired',
        );
    }
    return $session;
}

sub password_session {
    my $app = shift;
    my $sid = shift;

    my $sdir =
      $app->env('MOXIEDATA')
      ? bm_file_path( $app->env('MOXIEDATA'), 'user_data', 'sessions' )
      : "/tmp";
    bm_confirm_dir( $sdir, { data => 1, build_path => 1 } ) or return;

    return new CGI::Session( 'driver:File', $sid, { Directory => $sdir } );
}    #end start_session routine

1;

__END__

